diff --git a/BUILD.md b/BUILD.md index ba38f4b51d..df3f18cf51 100644 --- a/BUILD.md +++ b/BUILD.md @@ -1,3 +1,10 @@ +### OS Specific Build Guides + +* [BUILD_WIN.md](BUILD_WIN.md) - complete instructions for Windows. +* [BUILD_OSX.md](BUILD_OSX.md) - additional instructions for OS X. +* [BUILD_LINUX.md](BUILD_LINUX.md) - additional instructions for Linux. +* [BUILD_ANDROID.md](BUILD_ANDROID.md) - additional instructions for Android + ### Dependencies - [cmake](https://cmake.org/download/): 3.9 @@ -27,14 +34,7 @@ These are not placed in your normal build tree when doing an out of source build If you would like to use a specific install of a dependency instead of the version that would be grabbed as a CMake ExternalProject, you can pass -DUSE\_LOCAL\_$NAME=0 (where $NAME is the name of the subfolder in [cmake/externals](cmake/externals)) when you run CMake to tell it not to get that dependency as an external project. -### OS Specific Build Guides - -* [BUILD_OSX.md](BUILD_OSX.md) - additional instructions for OS X. -* [BUILD_LINUX.md](BUILD_LINUX.md) - additional instructions for Linux. -* [BUILD_WIN.md](BUILD_WIN.md) - additional instructions for Windows. -* [BUILD_ANDROID.md](BUILD_ANDROID.md) - additional instructions for Android - -### CMake +#### CMake Hifi uses CMake to generate build files and project files for your platform. @@ -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 @@ -80,6 +81,7 @@ In the examples below the variable $NAME would be replaced by the name of the de * $NAME_ROOT_DIR - set this variable in your ENV * HIFI_LIB_DIR - set this variable in your ENV to your High Fidelity lib folder, should contain a folder '$name' + ### Optional Components #### Devices diff --git a/BUILD_LINUX.md b/BUILD_LINUX.md index 0daef5ae05..1ee3d2b7c8 100644 --- a/BUILD_LINUX.md +++ b/BUILD_LINUX.md @@ -6,13 +6,20 @@ Please read the [general build guide](BUILD.md) for information on dependencies Should you choose not to install Qt5 via a package manager that handles dependencies for you, you may be missing some Qt5 dependencies. On Ubuntu, for example, the following additional packages are required: - libasound2 libxmu-dev libxi-dev freeglut3-dev libasound2-dev libjack0 libjack-dev libxrandr-dev libudev-dev libssl-dev + libasound2 libxmu-dev libxi-dev freeglut3-dev libasound2-dev libjack0 libjack-dev libxrandr-dev libudev-dev libssl-dev zlib1g-dev -## Ubuntu 16.04 specific build guide +## Ubuntu 16.04/18.04 specific build guide + +### Ubuntu 18.04 only +Add the universe repository: +_(This is not enabled by default on the server edition)_ +```bash +sudo add-apt-repository universe +sudo apt-get update +``` ### Prepare environment -hifiqt5.10.1 -Install qt: +Install Qt 5.10.1: ```bash wget http://debian.highfidelity.com/pool/h/hi/hifiqt5.10.1_5.10.1_amd64.deb sudo dpkg -i hifiqt5.10.1_5.10.1_amd64.deb @@ -20,19 +27,20 @@ sudo dpkg -i hifiqt5.10.1_5.10.1_amd64.deb Install build dependencies: ```bash -sudo apt-get install libasound2 libxmu-dev libxi-dev freeglut3-dev libasound2-dev libjack0 libjack-dev libxrandr-dev libudev-dev libssl-dev +sudo apt-get install libasound2 libxmu-dev libxi-dev freeglut3-dev libasound2-dev libjack0 libjack-dev libxrandr-dev libudev-dev libssl-dev zlib1g-dev ``` To compile interface in a server you must install: ```bash -sudo apt -y install libpulse0 libnss3 libnspr4 libfontconfig1 libxcursor1 libxcomposite1 libxtst6 libxslt1.1 +sudo apt-get -y install libpulse0 libnss3 libnspr4 libfontconfig1 libxcursor1 libxcomposite1 libxtst6 libxslt1.1 ``` Install build tools: ```bash -sudo apt install cmake +sudo apt-get install cmake ``` + ### Get code and checkout the tag you need Clone this repository: @@ -48,12 +56,7 @@ git tags Then checkout last tag with: ```bash -git checkout tags/RELEASE-6819 -``` - -Or go to the highfidelity download page (https://highfidelity.com/download) to get the release version. For example, if there is a BETA 6731 type: -```bash -git checkout tags/RELEASE-6731 +git checkout tags/v0.71.0 ``` ### Compiling @@ -66,15 +69,20 @@ cd hifi/build Prepare makefiles: ```bash -cmake -DQT_CMAKE_PREFIX_PATH=/usr/local/Qt5.10.1/5.10/gcc_64/lib/cmake .. +cmake -DQT_CMAKE_PREFIX_PATH=/usr/local/Qt5.10.1/5.10.1/gcc_64/lib/cmake .. ``` -Start compilation and get a cup of coffee: +Start compilation of the server and get a cup of coffee: ```bash -make domain-server assignment-client interface +make domain-server assignment-client ``` -In a server does not make sense to compile interface +To compile interface: +```bash +make interface +``` + +In a server, it does not make sense to compile interface ### Running the software @@ -93,4 +101,4 @@ Running interface: ./interface/interface ``` -Go to localhost in running interface. +Go to localhost in the running interface. diff --git a/CMakeLists.txt b/CMakeLists.txt index 54505717d4..2a01b18a57 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,6 +15,7 @@ include("cmake/compiler.cmake") if (BUILD_SCRIBE_ONLY) add_subdirectory(tools/scribe) + add_subdirectory(tools/shader_reflect) return() endif() diff --git a/android/app/build.gradle b/android/app/build.gradle index d5058a7f40..d3463411b8 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -24,6 +24,7 @@ android { '-DANDROID_STL=c++_shared', '-DQT_CMAKE_PREFIX_PATH=' + HIFI_ANDROID_PRECOMPILED + '/qt/lib/cmake', '-DNATIVE_SCRIBE=' + HIFI_ANDROID_PRECOMPILED + '/scribe' + EXEC_SUFFIX, + '-DNATIVE_SHREFLECT=' + HIFI_ANDROID_PRECOMPILED + '/shreflect' + EXEC_SUFFIX, '-DHIFI_ANDROID_PRECOMPILED=' + HIFI_ANDROID_PRECOMPILED, '-DRELEASE_NUMBER=' + RELEASE_NUMBER, '-DRELEASE_TYPE=' + RELEASE_TYPE, @@ -144,5 +145,7 @@ dependencies { compile 'com.squareup.retrofit2:converter-gson:2.4.0' implementation 'com.squareup.picasso:picasso:2.71828' + compile 'com.sothree.slidinguppanel:library:3.4.0' + implementation fileTree(include: ['*.jar'], dir: 'libs') } diff --git a/android/app/src/main/cpp/native.cpp b/android/app/src/main/cpp/native.cpp index 42124bf154..ce5af01f29 100644 --- a/android/app/src/main/cpp/native.cpp +++ b/android/app/src/main/cpp/native.cpp @@ -162,7 +162,19 @@ JNIEXPORT void Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeOnCrea jvmArgs.version = JNI_VERSION_1_6; // choose your JNI version jvmArgs.name = NULL; // you might want to give the java thread a name jvmArgs.group = NULL; // you might want to assign the java thread to a ThreadGroup - jvm->AttachCurrentThread(reinterpret_cast(&myNewEnv), &jvmArgs); + + int attachedHere = 0; // know if detaching at the end is necessary + jint res = jvm->GetEnv((void**)&myNewEnv, JNI_VERSION_1_6); // checks if current env needs attaching or it is already attached + if (JNI_OK != res) { + qDebug() << "[JCRASH] GetEnv env not attached yet, attaching now.."; + res = jvm->AttachCurrentThread(reinterpret_cast(&myNewEnv), &jvmArgs); + if (JNI_OK != res) { + qDebug() << "[JCRASH] Failed to AttachCurrentThread, ErrorCode = " << res; + return; + } else { + attachedHere = 1; + } + } QAndroidJniObject string = QAndroidJniObject::fromString(a); jboolean jBackToScene = (jboolean) backToScene; @@ -175,7 +187,9 @@ JNIEXPORT void Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeOnCrea myNewEnv->CallObjectMethod(hashmap, mapClassPut, QAndroidJniObject::fromString("url").object(), jArg.object()); } __interfaceActivity.callMethod("openAndroidActivity", "(Ljava/lang/String;ZLjava/util/HashMap;)V", string.object(), jBackToScene, hashmap); - jvm->DetachCurrentThread(); + if (attachedHere) { + jvm->DetachCurrentThread(); + } }); QObject::connect(&AndroidHelper::instance(), &AndroidHelper::hapticFeedbackRequested, [](int duration) { @@ -195,6 +209,11 @@ JNIEXPORT void Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeGotoUr DependencyManager::get()->loadSettings(jniUrl.toString()); } +JNIEXPORT void Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeGoToUser(JNIEnv* env, jobject obj, jstring username) { + QAndroidJniObject jniUsername("java/lang/String", "(Ljava/lang/String;)V", username); + DependencyManager::get()->goToUser(jniUsername.toString(), false); +} + JNIEXPORT void Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeOnPause(JNIEnv* env, jobject obj) { } @@ -271,6 +290,18 @@ Java_io_highfidelity_hifiinterface_fragment_LoginFragment_nativeLogin(JNIEnv *en Q_ARG(const QString&, username), Q_ARG(const QString&, password)); } +JNIEXPORT jboolean JNICALL +Java_io_highfidelity_hifiinterface_fragment_FriendsFragment_nativeIsLoggedIn(JNIEnv *env, jobject instance) { + auto accountManager = DependencyManager::get(); + return accountManager->isLoggedIn(); +} + +JNIEXPORT jstring JNICALL +Java_io_highfidelity_hifiinterface_fragment_FriendsFragment_nativeGetAccessToken(JNIEnv *env, jobject instance) { + auto accountManager = DependencyManager::get(); + return env->NewStringUTF(accountManager->getAccountInfo().getAccessToken().token.toLatin1().data()); +} + JNIEXPORT void JNICALL Java_io_highfidelity_hifiinterface_SplashActivity_registerLoadCompleteListener(JNIEnv *env, jobject instance) { diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/InterfaceActivity.java b/android/app/src/main/java/io/highfidelity/hifiinterface/InterfaceActivity.java index 8fd8b9d0e6..f161783d6a 100644 --- a/android/app/src/main/java/io/highfidelity/hifiinterface/InterfaceActivity.java +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/InterfaceActivity.java @@ -48,6 +48,7 @@ import com.google.vr.ndk.base.GvrApi;*/ public class InterfaceActivity extends QtActivity implements WebViewFragment.OnWebViewInteractionListener { public static final String DOMAIN_URL = "url"; + public static final String EXTRA_GOTO_USERNAME = "gotousername"; private static final String TAG = "Interface"; private static final int WEB_DRAWER_RIGHT_MARGIN = 262; private static final int WEB_DRAWER_BOTTOM_MARGIN = 150; @@ -59,6 +60,7 @@ public class InterfaceActivity extends QtActivity implements WebViewFragment.OnW private native long nativeOnCreate(InterfaceActivity instance, AssetManager assetManager); private native void nativeOnDestroy(); private native void nativeGotoUrl(String url); + private native void nativeGoToUser(String username); private native void nativeBeforeEnterBackground(); private native void nativeEnterBackground(); private native void nativeEnterForeground(); @@ -280,6 +282,9 @@ public class InterfaceActivity extends QtActivity implements WebViewFragment.OnW if (intent.hasExtra(DOMAIN_URL)) { webSlidingDrawer.setVisibility(View.GONE); nativeGotoUrl(intent.getStringExtra(DOMAIN_URL)); + } else if (intent.hasExtra(EXTRA_GOTO_USERNAME)) { + webSlidingDrawer.setVisibility(View.GONE); + nativeGoToUser(intent.getStringExtra(EXTRA_GOTO_USERNAME)); } } diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/MainActivity.java b/android/app/src/main/java/io/highfidelity/hifiinterface/MainActivity.java index 54161f60c6..db6f0fca24 100644 --- a/android/app/src/main/java/io/highfidelity/hifiinterface/MainActivity.java +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/MainActivity.java @@ -29,6 +29,7 @@ import android.widget.TextView; import com.squareup.picasso.Callback; import com.squareup.picasso.Picasso; +import io.highfidelity.hifiinterface.fragment.FriendsFragment; import io.highfidelity.hifiinterface.fragment.HomeFragment; import io.highfidelity.hifiinterface.fragment.LoginFragment; import io.highfidelity.hifiinterface.fragment.PolicyFragment; @@ -36,7 +37,8 @@ import io.highfidelity.hifiinterface.task.DownloadProfileImageTask; public class MainActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener, LoginFragment.OnLoginInteractionListener, - HomeFragment.OnHomeInteractionListener { + HomeFragment.OnHomeInteractionListener, + FriendsFragment.OnHomeInteractionListener { private static final int PROFILE_PICTURE_PLACEHOLDER = R.drawable.default_profile_avatar; public static final String DEFAULT_FRAGMENT = "Home"; @@ -56,6 +58,7 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On private View mLoginPanel; private View mProfilePanel; private TextView mLogoutOption; + private MenuItem mPeopleMenuItem; private boolean backToScene; @@ -75,6 +78,8 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On mDisplayName = mNavigationView.getHeaderView(0).findViewById(R.id.displayName); mProfilePicture = mNavigationView.getHeaderView(0).findViewById(R.id.profilePicture); + mPeopleMenuItem = mNavigationView.getMenu().findItem(R.id.action_people); + Toolbar toolbar = findViewById(R.id.toolbar); toolbar.setTitleTextAppearance(this, R.style.HomeActionBarTitleStyle); setSupportActionBar(toolbar); @@ -109,40 +114,69 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On loadLoginFragment(); break; case "Home": - loadHomeFragment(); + loadHomeFragment(true); break; case "Privacy Policy": loadPrivacyPolicyFragment(); break; + case "People": + loadPeopleFragment(); + break; default: Log.e(TAG, "Unknown fragment " + fragment); } } - private void loadHomeFragment() { + private void loadHomeFragment(boolean addToBackStack) { Fragment fragment = HomeFragment.newInstance(); - loadFragment(fragment, getString(R.string.home), false); + loadFragment(fragment, getString(R.string.home), getString(R.string.tagFragmentHome), addToBackStack); } private void loadLoginFragment() { Fragment fragment = LoginFragment.newInstance(); - loadFragment(fragment, getString(R.string.login), true); + loadFragment(fragment, getString(R.string.login), getString(R.string.tagFragmentLogin), true); } private void loadPrivacyPolicyFragment() { Fragment fragment = PolicyFragment.newInstance(); - loadFragment(fragment, getString(R.string.privacyPolicy), true); + loadFragment(fragment, getString(R.string.privacyPolicy), getString(R.string.tagFragmentPolicy), true); } - private void loadFragment(Fragment fragment, String title, boolean addToBackStack) { + private void loadPeopleFragment() { + Fragment fragment = FriendsFragment.newInstance(); + + loadFragment(fragment, getString(R.string.people), getString(R.string.tagFragmentPeople), true); + } + + private void loadFragment(Fragment fragment, String title, String tag, boolean addToBackStack) { FragmentManager fragmentManager = getFragmentManager(); + + // check if it's the same fragment + String currentFragmentName = fragmentManager.getBackStackEntryCount() > 0 + ? fragmentManager.getBackStackEntryAt(fragmentManager.getBackStackEntryCount() - 1).getName() + : ""; + if (currentFragmentName.equals(title)) { + mDrawerLayout.closeDrawer(mNavigationView); + return; // cancel as we are already in that fragment + } + + // go back until first transaction + int backStackEntryCount = fragmentManager.getBackStackEntryCount(); + for (int i = 0; i < backStackEntryCount - 1; i++) { + fragmentManager.popBackStackImmediate(); + } + + // this case is when we wanted to go home.. rollback already did that! + // But asking for a new Home fragment makes it easier to have an updated list so we let it to continue + FragmentTransaction ft = fragmentManager.beginTransaction(); - ft.replace(R.id.content_frame, fragment); + ft.replace(R.id.content_frame, fragment, tag); + if (addToBackStack) { - ft.addToBackStack(null); + ft.addToBackStack(title); } ft.commit(); setTitle(title); @@ -155,11 +189,13 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On mLoginPanel.setVisibility(View.GONE); mProfilePanel.setVisibility(View.VISIBLE); mLogoutOption.setVisibility(View.VISIBLE); + mPeopleMenuItem.setVisible(true); updateProfileHeader(); } else { mLoginPanel.setVisibility(View.VISIBLE); mProfilePanel.setVisibility(View.GONE); mLogoutOption.setVisibility(View.GONE); + mPeopleMenuItem.setVisible(false); mDisplayName.setText(""); } } @@ -200,7 +236,10 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On public boolean onNavigationItemSelected(@NonNull MenuItem item) { switch(item.getItemId()) { case R.id.action_home: - loadHomeFragment(); + loadHomeFragment(false); + return true; + case R.id.action_people: + loadPeopleFragment(); return true; } return false; @@ -219,6 +258,19 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On public void onLogoutClicked(View view) { nativeLogout(); updateLoginMenu(); + exitLoggedInFragment(); + + } + + private void exitLoggedInFragment() { + // If we are in a "logged in" fragment (like People), go back to home. This could be expanded to multiple fragments + FragmentManager fragmentManager = getFragmentManager(); + String currentFragmentName = fragmentManager.getBackStackEntryCount() > 0 + ? fragmentManager.getBackStackEntryAt(fragmentManager.getBackStackEntryCount() - 1).getName() + : ""; + if (currentFragmentName.equals(getString(R.string.people))) { + loadHomeFragment(false); + } } public void onSelectedDomain(String domainUrl) { @@ -237,9 +289,17 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On startActivity(intent); } + private void goToUser(String username) { + Intent intent = new Intent(this, InterfaceActivity.class); + intent.putExtra(InterfaceActivity.EXTRA_GOTO_USERNAME, username); + finish(); + intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); + startActivity(intent); + } + @Override public void onLoginCompleted() { - loadHomeFragment(); + loadHomeFragment(false); updateLoginMenu(); if (backToScene) { backToScene = false; @@ -266,6 +326,11 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On loadPrivacyPolicyFragment(); } + @Override + public void onVisitUserSelected(String username) { + goToUser(username); + } + private class RoundProfilePictureCallback implements Callback { @Override public void onSuccess() { @@ -284,15 +349,30 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On @Override public void onBackPressed() { - int index = getFragmentManager().getBackStackEntryCount() - 1; - if (index > -1) { + // if a fragment needs to internally manage back presses.. + FragmentManager fm = getFragmentManager(); + Log.d("[BACK]", "getBackStackEntryCount " + fm.getBackStackEntryCount()); + Fragment friendsFragment = fm.findFragmentByTag(getString(R.string.tagFragmentPeople)); + if (friendsFragment != null && friendsFragment instanceof FriendsFragment) { + if (((FriendsFragment) friendsFragment).onBackPressed()) { + return; + } + } + + int index = fm.getBackStackEntryCount() - 1; + + if (index > 0) { super.onBackPressed(); + index--; + if (index > -1) { + setTitle(fm.getBackStackEntryAt(index).getName()); + } if (backToScene) { backToScene = false; goToLastLocation(); } } else { - finishAffinity(); + finishAffinity(); } } diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/FriendsFragment.java b/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/FriendsFragment.java new file mode 100644 index 0000000000..2475c4d887 --- /dev/null +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/FriendsFragment.java @@ -0,0 +1,199 @@ +package io.highfidelity.hifiinterface.fragment; + + +import android.app.Fragment; +import android.content.Context; +import android.content.DialogInterface; +import android.os.Bundle; +import android.support.v4.widget.SwipeRefreshLayout; +import android.support.v7.app.AlertDialog; +import android.support.v7.widget.GridLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import com.sothree.slidinguppanel.SlidingUpPanelLayout; + +import io.highfidelity.hifiinterface.R; +import io.highfidelity.hifiinterface.provider.EndpointUsersProvider; +import io.highfidelity.hifiinterface.provider.UsersProvider; +import io.highfidelity.hifiinterface.view.UserListAdapter; + +public class FriendsFragment extends Fragment { + + public native boolean nativeIsLoggedIn(); + + public native String nativeGetAccessToken(); + + private RecyclerView mUsersView; + private View mUserActions; + private UserListAdapter mUsersAdapter; + private SlidingUpPanelLayout mSlidingUpPanelLayout; + private EndpointUsersProvider mUsersProvider; + private String mSelectedUsername; + + private OnHomeInteractionListener mListener; + private SwipeRefreshLayout mSwipeRefreshLayout; + + public FriendsFragment() { + // Required empty public constructor + } + + public static FriendsFragment newInstance() { + FriendsFragment fragment = new FriendsFragment(); + return fragment; + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + View rootView = inflater.inflate(R.layout.fragment_friends, container, false); + + String accessToken = nativeGetAccessToken(); + mUsersProvider = new EndpointUsersProvider(accessToken); + + Log.d("[USERS]", "token : [" + accessToken + "]"); + + mSwipeRefreshLayout = rootView.findViewById(R.id.swipeRefreshLayout); + + mUsersView = rootView.findViewById(R.id.rvUsers); + int numberOfColumns = 1; + GridLayoutManager gridLayoutMgr = new GridLayoutManager(getContext(), numberOfColumns); + mUsersView.setLayoutManager(gridLayoutMgr); + + mUsersAdapter = new UserListAdapter(getContext(), mUsersProvider); + mSwipeRefreshLayout.setRefreshing(true); + + mUserActions = rootView.findViewById(R.id.userActionsLayout); + + mSlidingUpPanelLayout = rootView.findViewById(R.id.sliding_layout); + mSlidingUpPanelLayout.setPanelHeight(0); + + rootView.findViewById(R.id.userActionDelete).setOnClickListener(view -> onRemoveConnectionClick()); + + rootView.findViewById(R.id.userActionVisit).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if (mListener != null && mSelectedUsername != null) { + mListener.onVisitUserSelected(mSelectedUsername); + } + } + }); + + mUsersAdapter.setClickListener(new UserListAdapter.ItemClickListener() { + @Override + public void onItemClick(View view, int position, UserListAdapter.User user) { + // 1. 'select' user + mSelectedUsername = user.name; + // .. + // 2. adapt options + // .. + rootView.findViewById(R.id.userActionVisit).setVisibility(user.online ? View.VISIBLE : View.GONE); + // 3. show + mSlidingUpPanelLayout.setPanelState(SlidingUpPanelLayout.PanelState.EXPANDED); + } + }); + + mUsersAdapter.setListener(new UserListAdapter.AdapterListener() { + @Override + public void onEmptyAdapter(boolean shouldStopRefreshing) { + if (shouldStopRefreshing) { + mSwipeRefreshLayout.setRefreshing(false); + } + } + + @Override + public void onNonEmptyAdapter(boolean shouldStopRefreshing) { + if (shouldStopRefreshing) { + mSwipeRefreshLayout.setRefreshing(false); + } + } + + @Override + public void onError(Exception e, String message) { + mSwipeRefreshLayout.setRefreshing(false); + } + }); + + mUsersView.setAdapter(mUsersAdapter); + + mUsersAdapter.startLoad(); + + mSlidingUpPanelLayout.setFadeOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + mSlidingUpPanelLayout.setPanelState(SlidingUpPanelLayout.PanelState.COLLAPSED); + mSelectedUsername = null; + } + }); + + mSwipeRefreshLayout.setOnRefreshListener(() -> mUsersAdapter.loadUsers()); + + return rootView; + } + + private void onRemoveConnectionClick() { + if (mSelectedUsername == null) { + return; + } + + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + builder.setMessage("Remove '" + mSelectedUsername + "' from People?"); + builder.setPositiveButton("Remove", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + mUsersProvider.removeConnection(mSelectedUsername, new UsersProvider.UserActionCallback() { + @Override + public void requestOk() { + mSlidingUpPanelLayout.setPanelState(SlidingUpPanelLayout.PanelState.COLLAPSED); + mSelectedUsername = null; + mUsersAdapter.loadUsers(); + } + + @Override + public void requestError(Exception e, String message) { + // CLD: Show error message? + } + }); + } + }); + builder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + // Cancelled, nothing to do + } + }); + builder.show(); + } + + /** + * Processes the back pressed event and returns true if it was managed by this Fragment + * @return + */ + public boolean onBackPressed() { + if (mSlidingUpPanelLayout.getPanelState().equals(SlidingUpPanelLayout.PanelState.EXPANDED)) { + mSlidingUpPanelLayout.setPanelState(SlidingUpPanelLayout.PanelState.COLLAPSED); + mSelectedUsername = null; + return true; + } else { + return false; + } + } + + @Override + public void onAttach(Context context) { + super.onAttach(context); + if (context instanceof OnHomeInteractionListener) { + mListener = (OnHomeInteractionListener) context; + } else { + throw new RuntimeException(context.toString() + + " must implement OnHomeInteractionListener"); + } + } + + public interface OnHomeInteractionListener { + void onVisitUserSelected(String username); + } +} diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/HomeFragment.java b/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/HomeFragment.java index 7bd373cf1d..86b8625cfe 100644 --- a/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/HomeFragment.java +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/HomeFragment.java @@ -76,18 +76,22 @@ public class HomeFragment extends Fragment { }); mDomainAdapter.setListener(new DomainAdapter.AdapterListener() { @Override - public void onEmptyAdapter() { + public void onEmptyAdapter(boolean shouldStopRefreshing) { searchNoResultsView.setText(R.string.search_no_results); searchNoResultsView.setVisibility(View.VISIBLE); mDomainsView.setVisibility(View.GONE); - mSwipeRefreshLayout.setRefreshing(false); + if (shouldStopRefreshing) { + mSwipeRefreshLayout.setRefreshing(false); + } } @Override - public void onNonEmptyAdapter() { + public void onNonEmptyAdapter(boolean shouldStopRefreshing) { searchNoResultsView.setVisibility(View.GONE); mDomainsView.setVisibility(View.VISIBLE); - mSwipeRefreshLayout.setRefreshing(false); + if (shouldStopRefreshing) { + mSwipeRefreshLayout.setRefreshing(false); + } } @Override @@ -96,11 +100,20 @@ public class HomeFragment extends Fragment { } }); mDomainsView.setAdapter(mDomainAdapter); + mDomainAdapter.startLoad(); mSearchView = rootView.findViewById(R.id.searchView); mSearchIconView = rootView.findViewById(R.id.search_mag_icon); mClearSearch = rootView.findViewById(R.id.search_clear); + getActivity().getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN); + + return rootView; + } + + @Override + public void onStart() { + super.onStart(); mSearchView.addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {} @@ -142,10 +155,6 @@ public class HomeFragment extends Fragment { mDomainAdapter.loadDomains(mSearchView.getText().toString(), true); } }); - - getActivity().getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN); - - return rootView; } @Override diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/provider/EndpointUsersProvider.java b/android/app/src/main/java/io/highfidelity/hifiinterface/provider/EndpointUsersProvider.java new file mode 100644 index 0000000000..7c32a8e8fb --- /dev/null +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/provider/EndpointUsersProvider.java @@ -0,0 +1,225 @@ +package io.highfidelity.hifiinterface.provider; + +import android.util.Log; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import io.highfidelity.hifiinterface.view.UserListAdapter; +import okhttp3.Interceptor; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Retrofit; +import retrofit2.converter.gson.GsonConverterFactory; +import retrofit2.http.Body; +import retrofit2.http.DELETE; +import retrofit2.http.GET; +import retrofit2.http.POST; +import retrofit2.http.Path; +import retrofit2.http.Query; + +/** + * Created by cduarte on 6/13/18. + */ + +public class EndpointUsersProvider implements UsersProvider { + + public static final String BASE_URL = "https://metaverse.highfidelity.com/"; + private final Retrofit mRetrofit; + private final EndpointUsersProviderService mEndpointUsersProviderService; + + public EndpointUsersProvider(String accessToken) { + mRetrofit = createAuthorizedRetrofit(accessToken); + mEndpointUsersProviderService = mRetrofit.create(EndpointUsersProviderService.class); + } + + private Retrofit createAuthorizedRetrofit(String accessToken) { + Retrofit mRetrofit; + OkHttpClient.Builder httpClient = new OkHttpClient.Builder(); + httpClient.addInterceptor(new Interceptor() { + @Override + public Response intercept(Chain chain) throws IOException { + Request original = chain.request(); + + Request request = original.newBuilder() + .header("Authorization", "Bearer " + accessToken) + .method(original.method(), original.body()) + .build(); + + return chain.proceed(request); + } + }); + + OkHttpClient client = httpClient.build(); + + mRetrofit = new Retrofit.Builder() + .baseUrl(BASE_URL) + .addConverterFactory(GsonConverterFactory.create()) + .client(client) + .build(); + return mRetrofit; + } + + @Override + public void retrieve(UsersCallback usersCallback) { + Call friendsCall = mEndpointUsersProviderService.getUsers( + CONNECTION_FILTER_CONNECTIONS, + 400, + null); + friendsCall.enqueue(new Callback() { + @Override + public void onResponse(Call call, retrofit2.Response response) { + if (!response.isSuccessful()) { + usersCallback.retrieveError(new Exception("Error calling Users API"), "Error calling Users API"); + return; + } + UsersResponse usersResponse = response.body(); + List adapterUsers = new ArrayList<>(usersResponse.total_entries); + for (User user : usersResponse.data.users) { + UserListAdapter.User adapterUser = new UserListAdapter.User(); + adapterUser.connection = user.connection; + adapterUser.imageUrl = user.images.thumbnail; + adapterUser.name = user.username; + adapterUser.online = user.online; + adapterUser.locationName = (user.location != null ? + (user.location.root != null ? user.location.root.name : + (user.location.domain != null ? user.location.domain.name : "")) + : ""); + adapterUsers.add(adapterUser); + } + usersCallback.retrieveOk(adapterUsers); + } + + @Override + public void onFailure(Call call, Throwable t) { + usersCallback.retrieveError(new Exception(t), "Error calling Users API"); + } + }); + } + + public class UserActionRetrofitCallback implements Callback { + + UserActionCallback callback; + + public UserActionRetrofitCallback(UserActionCallback callback) { + this.callback = callback; + } + + @Override + public void onResponse(Call call, retrofit2.Response response) { + if (!response.isSuccessful()) { + callback.requestError(new Exception("Error with " + + call.request().url().toString() + " " + + call.request().method() + " call " + response.message()), + response.message()); + return; + } + + if (response.body() == null || !"success".equals(response.body().status)) { + callback.requestError(new Exception("Error with " + + call.request().url().toString() + " " + + call.request().method() + " call " + response.message()), + response.message()); + return; + } + callback.requestOk(); + } + + @Override + public void onFailure(Call call, Throwable t) { + callback.requestError(new Exception(t), t.getMessage()); + } + } + + @Override + public void addFriend(String friendUserName, UserActionCallback callback) { + Call friendCall = mEndpointUsersProviderService.addFriend(new BodyAddFriend(friendUserName)); + friendCall.enqueue(new UserActionRetrofitCallback(callback)); + } + + @Override + public void removeFriend(String friendUserName, UserActionCallback callback) { + Call friendCall = mEndpointUsersProviderService.removeFriend(friendUserName); + friendCall.enqueue(new UserActionRetrofitCallback(callback)); + } + + @Override + public void removeConnection(String connectionUserName, UserActionCallback callback) { + Call connectionCall = mEndpointUsersProviderService.removeConnection(connectionUserName); + connectionCall.enqueue(new UserActionRetrofitCallback(callback)); + } + + public interface EndpointUsersProviderService { + @GET("api/v1/users") + Call getUsers(@Query("filter") String filter, + @Query("per_page") int perPage, + @Query("online") Boolean online); + + @DELETE("api/v1/user/connections/{connectionUserName}") + Call removeConnection(@Path("connectionUserName") String connectionUserName); + + @DELETE("api/v1/user/friends/{friendUserName}") + Call removeFriend(@Path("friendUserName") String friendUserName); + + @POST("api/v1/user/friends") + Call addFriend(@Body BodyAddFriend friendUserName); + + /* response + { + "status": "success" + } + */ + } + + class BodyAddFriend { + String username; + public BodyAddFriend(String username) { + this.username = username; + } + } + + class UsersResponse { + public UsersResponse() {} + String status; + int current_page; + int total_pages; + int per_page; + int total_entries; + Data data; + } + + class Data { + public Data() {} + List users; + } + + class User { + public User() {} + String username; + boolean online; + String connection; + Images images; + LocationData location; + } + + class Images { + public Images() {} + String hero; + String thumbnail; + String tiny; + } + + class LocationData { + public LocationData() {} + NameContainer root; + NameContainer domain; + } + class NameContainer { + String name; + } + +} diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/provider/UsersProvider.java b/android/app/src/main/java/io/highfidelity/hifiinterface/provider/UsersProvider.java new file mode 100644 index 0000000000..0088506407 --- /dev/null +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/provider/UsersProvider.java @@ -0,0 +1,35 @@ +package io.highfidelity.hifiinterface.provider; + +import java.util.List; + +import io.highfidelity.hifiinterface.view.UserListAdapter; + +/** + * Created by cduarte on 6/13/18. + */ + +public interface UsersProvider { + + public static String CONNECTION_TYPE_FRIEND = "friend"; + public static String CONNECTION_FILTER_CONNECTIONS = "connections"; + + void retrieve(UsersProvider.UsersCallback usersCallback); + + interface UsersCallback { + void retrieveOk(List users); + void retrieveError(Exception e, String message); + } + + + void addFriend(String friendUserName, UserActionCallback callback); + + void removeFriend(String friendUserName, UserActionCallback callback); + + void removeConnection(String connectionUserName, UserActionCallback callback); + + interface UserActionCallback { + void requestOk(); + void requestError(Exception e, String message); + } + +} diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/view/DomainAdapter.java b/android/app/src/main/java/io/highfidelity/hifiinterface/view/DomainAdapter.java index 71d634e9ea..78251ac4a4 100644 --- a/android/app/src/main/java/io/highfidelity/hifiinterface/view/DomainAdapter.java +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/view/DomainAdapter.java @@ -12,6 +12,7 @@ import android.widget.TextView; import com.squareup.picasso.Picasso; +import java.util.Arrays; import java.util.List; import io.highfidelity.hifiinterface.R; @@ -36,19 +37,41 @@ public class DomainAdapter extends RecyclerView.Adapter 0) { + mDomains = Arrays.copyOf(DOMAINS_TMP_CACHE, DOMAINS_TMP_CACHE.length); + notifyDataSetChanged(); + if (mAdapterListener != null) { + if (mDomains.length == 0) { + mAdapterListener.onEmptyAdapter(false); + } else { + mAdapterListener.onNonEmptyAdapter(false); + } + } + } + } + } + public void loadDomains(String filterText, boolean forceRefresh) { domainProvider.retrieve(filterText, new DomainProvider.DomainCallback() { @Override @@ -60,13 +83,18 @@ public class DomainAdapter extends RecyclerView.Adapter { + + private UsersProvider mProvider; + private LayoutInflater mInflater; + private Context mContext; + private List mUsers = new ArrayList<>(); + private ItemClickListener mClickListener; + private AdapterListener mAdapterListener; + + private static List USERS_TMP_CACHE; + + public UserListAdapter(Context c, UsersProvider usersProvider) { + mContext = c; + mInflater = LayoutInflater.from(mContext); + mProvider = usersProvider; + } + + public void setListener(AdapterListener adapterListener) { + mAdapterListener = adapterListener; + } + + public void startLoad() { + useTmpCachedUsers(); + loadUsers(); + } + + private void useTmpCachedUsers() { + synchronized (this) { + if (USERS_TMP_CACHE != null && USERS_TMP_CACHE.size() > 0) { + mUsers = new ArrayList<>(USERS_TMP_CACHE.size()); + mUsers.addAll(USERS_TMP_CACHE); + notifyDataSetChanged(); + if (mAdapterListener != null) { + if (mUsers.isEmpty()) { + mAdapterListener.onEmptyAdapter(false); + } else { + mAdapterListener.onNonEmptyAdapter(false); + } + } + } + } + } + + public void loadUsers() { + mProvider.retrieve(new UsersProvider.UsersCallback() { + @Override + public void retrieveOk(List users) { + mUsers = new ArrayList<>(users); + notifyDataSetChanged(); + + 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); + } + } + } + } + + @Override + public void retrieveError(Exception e, String message) { + Log.e("[USERS]", message, e); + if (mAdapterListener != null) { + mAdapterListener.onError(e, message); + } + } + }); + } + + @Override + public UserListAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View view = mInflater.inflate(R.layout.user_item, parent, false); + return new UserListAdapter.ViewHolder(view); + } + + @Override + public void onBindViewHolder(UserListAdapter.ViewHolder holder, int position) { + User aUser = mUsers.get(position); + holder.mUsername.setText(aUser.name); + + holder.mOnlineInfo.setVisibility(aUser.online? View.VISIBLE : View.GONE); + holder.mLocation.setText("- " + aUser.locationName); // Bring info from the API and use it here + + holder.mFriendStar.onBindSet(aUser.name, aUser.connection.equals(UsersProvider.CONNECTION_TYPE_FRIEND)); + Uri uri = Uri.parse(aUser.imageUrl); + Picasso.get().load(uri).into(holder.mImage, new RoundProfilePictureCallback(holder.mImage)); + } + + private class RoundProfilePictureCallback implements Callback { + private ImageView mProfilePicture; + public RoundProfilePictureCallback(ImageView imageView) { + mProfilePicture = imageView; + } + + @Override + public void onSuccess() { + Bitmap imageBitmap = ((BitmapDrawable) mProfilePicture.getDrawable()).getBitmap(); + RoundedBitmapDrawable imageDrawable = RoundedBitmapDrawableFactory.create(mProfilePicture.getContext().getResources(), imageBitmap); + imageDrawable.setCircular(true); + imageDrawable.setCornerRadius(Math.max(imageBitmap.getWidth(), imageBitmap.getHeight()) / 2.0f); + mProfilePicture.setImageDrawable(imageDrawable); + } + + @Override + public void onError(Exception e) { + mProfilePicture.setImageResource(R.drawable.default_profile_avatar); + } + } + + @Override + public int getItemCount() { + return mUsers.size(); + } + + public class ToggleWrapper { + + private ViewGroup mFrame; + private ImageView mImage; + private boolean mChecked = false; + private String mUsername; + private boolean waitingChangeConfirm = false; + + public ToggleWrapper(ViewGroup toggleFrame) { + mFrame = toggleFrame; + mImage = toggleFrame.findViewById(R.id.userFavImage); + mFrame.setOnClickListener(view -> toggle()); + } + + private void refreshUI() { + mImage.setColorFilter(ContextCompat.getColor(mImage.getContext(), + mChecked ? R.color.starSelectedTint : R.color.starUnselectedTint)); + } + + class RollbackUICallback implements UsersProvider.UserActionCallback { + + boolean previousStatus; + + RollbackUICallback(boolean previousStatus) { + this.previousStatus = previousStatus; + } + + @Override + public void requestOk() { + if (!waitingChangeConfirm) { + return; + } + mFrame.setClickable(true); + // nothing to do, new status was set + } + + @Override + public void requestError(Exception e, String message) { + if (!waitingChangeConfirm) { + return; + } + // new status was not set, rolling back + mChecked = previousStatus; + mFrame.setClickable(true); + refreshUI(); + } + + } + + protected void toggle() { + // TODO API CALL TO CHANGE + final boolean previousStatus = mChecked; + mChecked = !mChecked; + mFrame.setClickable(false); + refreshUI(); + waitingChangeConfirm = true; + if (mChecked) { + mProvider.addFriend(mUsername, new RollbackUICallback(previousStatus)); + } else { + mProvider.removeFriend(mUsername, new RollbackUICallback(previousStatus)); + } + } + + protected void onBindSet(String username, boolean checked) { + mChecked = checked; + mUsername = username; + waitingChangeConfirm = false; + mFrame.setClickable(true); + refreshUI(); + } + } + + public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener { + + TextView mUsername; + TextView mOnline; + View mOnlineInfo; + TextView mLocation; + ImageView mImage; + ToggleWrapper mFriendStar; + + public ViewHolder(View itemView) { + super(itemView); + mUsername = itemView.findViewById(R.id.userName); + mOnline = itemView.findViewById(R.id.userOnline); + mImage = itemView.findViewById(R.id.userImage); + mOnlineInfo = itemView.findViewById(R.id.userOnlineInfo); + mLocation = itemView.findViewById(R.id.userLocation); + mFriendStar = new ToggleWrapper(itemView.findViewById(R.id.userFav)); + itemView.setOnClickListener(this); + } + + @Override + public void onClick(View view) { + int position = getAdapterPosition(); + if (mClickListener != null) { + mClickListener.onItemClick(view, position, mUsers.get(position)); + } + } + } + + // allows clicks events to be caught + public void setClickListener(ItemClickListener itemClickListener) { + this.mClickListener = itemClickListener; + } + + public interface ItemClickListener { + void onItemClick(View view, int position, User user); + } + + public static class User { + public String name; + public String imageUrl; + public String connection; + public boolean online; + + public String locationName; + + public User() {} + } + + public interface AdapterListener { + void onEmptyAdapter(boolean shouldStopRefreshing); + void onNonEmptyAdapter(boolean shouldStopRefreshing); + void onError(Exception e, String message); + } + +} diff --git a/android/app/src/main/res/drawable/ic_delete_black_24dp.xml b/android/app/src/main/res/drawable/ic_delete_black_24dp.xml new file mode 100644 index 0000000000..39e64d6980 --- /dev/null +++ b/android/app/src/main/res/drawable/ic_delete_black_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/android/app/src/main/res/drawable/ic_star.xml b/android/app/src/main/res/drawable/ic_star.xml new file mode 100644 index 0000000000..abd1798942 --- /dev/null +++ b/android/app/src/main/res/drawable/ic_star.xml @@ -0,0 +1,4 @@ + + + diff --git a/android/app/src/main/res/drawable/ic_teleporticon.xml b/android/app/src/main/res/drawable/ic_teleporticon.xml new file mode 100644 index 0000000000..429e6b795d --- /dev/null +++ b/android/app/src/main/res/drawable/ic_teleporticon.xml @@ -0,0 +1,31 @@ + + + + + + + + + diff --git a/android/app/src/main/res/layout/fragment_friends.xml b/android/app/src/main/res/layout/fragment_friends.xml new file mode 100644 index 0000000000..c98878f68e --- /dev/null +++ b/android/app/src/main/res/layout/fragment_friends.xml @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + + + + diff --git a/android/app/src/main/res/layout/user_item.xml b/android/app/src/main/res/layout/user_item.xml new file mode 100644 index 0000000000..5ad1dcc5ee --- /dev/null +++ b/android/app/src/main/res/layout/user_item.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/menu/menu_navigation.xml b/android/app/src/main/res/menu/menu_navigation.xml index cf80c84177..3cce64f9f5 100644 --- a/android/app/src/main/res/menu/menu_navigation.xml +++ b/android/app/src/main/res/menu/menu_navigation.xml @@ -5,4 +5,8 @@ android:id="@+id/action_home" android:title="@string/home" /> + diff --git a/android/app/src/main/res/values/colors.xml b/android/app/src/main/res/values/colors.xml index 7e6cf52d36..e4bbb60544 100644 --- a/android/app/src/main/res/values/colors.xml +++ b/android/app/src/main/res/values/colors.xml @@ -18,4 +18,8 @@ #99000000 #292929 #23B2E7 + #62D5C6 + #FBD92A + #8A8A8A + #40000000 diff --git a/android/app/src/main/res/values/dimens.xml b/android/app/src/main/res/values/dimens.xml index bb5be1c070..d40132939b 100644 --- a/android/app/src/main/res/values/dimens.xml +++ b/android/app/src/main/res/values/dimens.xml @@ -37,4 +37,6 @@ 101dp 425dp + 8dp + diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml index 4f5f29e671..b158aba59d 100644 --- a/android/app/src/main/res/values/strings.xml +++ b/android/app/src/main/res/values/strings.xml @@ -1,6 +1,7 @@ Interface Home + People Open in browser Share link Shared a link @@ -21,5 +22,11 @@ No places exist with that name Privacy Policy Your Last Location + Online + + tagFragmentHome + tagFragmentLogin + tagFragmentPolicy + tagFragmentPeople diff --git a/android/build.gradle b/android/build.gradle index bc39c30472..a6de0d469c 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -28,6 +28,7 @@ allprojects { repositories { jcenter() google() + mavenCentral() } } @@ -164,18 +165,29 @@ def packages = [ def scribeLocalFile='scribe' + EXEC_SUFFIX - def scribeFile='scribe_linux_x86_64' def scribeChecksum='ca4b904f52f4f993c29175ba96798fa6' def scribeVersion='u_iTrJDaE95i2abTPXOpPZckGBIim53G' + +def shreflectLocalFile='shreflect' + EXEC_SUFFIX +def shreflectFile='shreflect_linux_x86_64' +def shreflectChecksum='d6094a8580066c0b6f4e80b5adfb1d98' +def shreflectVersion='jnrpudh6fptIg6T2.Z6fgKP2ultAdKmE' + if (Os.isFamily(Os.FAMILY_MAC)) { scribeFile = 'scribe_osx_x86_64' scribeChecksum='72db9d32d4e1e50add755570ac5eb749' scribeVersion='DAW0DmnjCRib4MD8x93bgc2Z2MpPojZC' + shreflectFile='shreflect_osx_x86_64' + shreflectChecksum='d613ef0703c21371fee93fd2e54b964f' + shreflectVersion='.rYNzjSFq6WtWDnE5KIKRIAGyJtr__ad' } else if (Os.isFamily(Os.FAMILY_WINDOWS)) { scribeFile = 'scribe_win32_x86_64.exe' scribeChecksum='678e43d290c90fda670c6fefe038a06d' scribeVersion='PuullrA_bPlO9kXZRt8rLe536X1UI.m7' + shreflectFile='shreflect_win32_x86_64.exe' + shreflectChecksum='6f4a77b8cceb3f1bbc655132c3665060' + shreflectVersion='iIyCyza1nelkbI7ihybF59bBlwrfAC3D' } def options = [ @@ -450,11 +462,27 @@ task fixScribePermissions(type: Exec, dependsOn: verifyScribe) { commandLine 'chmod', 'a+x', HIFI_ANDROID_PRECOMPILED + '/' + scribeLocalFile } -task setupScribe(dependsOn: verifyScribe) { } +task downloadShreflect(type: Download) { + src baseUrl + shreflectFile + '?versionId=' + shreflectVersion + dest new File(baseFolder, shreflectLocalFile) + onlyIfNewer true +} + +task verifyShreflect(type: Verify, dependsOn: downloadShreflect) { + src new File(baseFolder, shreflectLocalFile); + checksum shreflectChecksum +} + +task fixShreflectPermissions(type: Exec, dependsOn: verifyShreflect) { + commandLine 'chmod', 'a+x', HIFI_ANDROID_PRECOMPILED + '/' + shreflectLocalFile +} + +task setupScribe(dependsOn: [verifyScribe, verifyShreflect]) { } // On Windows, we don't need to set the executable bit, but on OSX and Unix we do if (!Os.isFamily(Os.FAMILY_WINDOWS)) { setupScribe.dependsOn fixScribePermissions + setupScribe.dependsOn fixShreflectPermissions } task extractGvrBinaries(dependsOn: extractDependencies) { diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index 73444d1198..2f03f15da7 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -19,20 +19,25 @@ #include #include +#include #include #include #include #include #include +#include #include #include #include #include #include #include +#include #include #include +#include #include +#include #include #include @@ -48,9 +53,10 @@ #include // TODO: consider moving to scriptengine.h #include "entities/AssignmentParentFinder.h" +#include "AssignmentDynamicFactory.h" #include "RecordingScriptingInterface.h" #include "AbstractAudioInterface.h" - +#include "AgentScriptingInterface.h" static const int RECEIVED_AUDIO_STREAM_CAPACITY_FRAMES = 10; @@ -60,6 +66,18 @@ Agent::Agent(ReceivedMessage& message) : _audioGate(AudioConstants::SAMPLE_RATE, AudioConstants::MONO), _avatarAudioTimer(this) { + DependencyManager::set(); + + DependencyManager::registerInheritance(); + DependencyManager::set(); + + DependencyManager::set(); + DependencyManager::set(); + DependencyManager::set(false); + + DependencyManager::set(); + DependencyManager::set(); + _entityEditSender.setPacketsPerSecond(DEFAULT_ENTITY_PPS_PER_SCRIPT); DependencyManager::get()->setPacketSender(&_entityEditSender); @@ -70,6 +88,7 @@ Agent::Agent(ReceivedMessage& message) : DependencyManager::set(); DependencyManager::set(); + DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); @@ -78,8 +97,6 @@ Agent::Agent(ReceivedMessage& message) : DependencyManager::set(); DependencyManager::set(); - DependencyManager::set(ScriptEngine::AGENT_SCRIPT); - DependencyManager::set(); DependencyManager::set(); @@ -97,7 +114,6 @@ Agent::Agent(ReceivedMessage& message) : this, "handleOctreePacket"); packetReceiver.registerListener(PacketType::SelectedAudioFormat, this, "handleSelectedAudioFormat"); - // 100Hz timer for audio const int TARGET_INTERVAL_MSEC = 10; // 10ms connect(&_avatarAudioTimer, &QTimer::timeout, this, &Agent::processAgentAvatarAudio); @@ -158,6 +174,8 @@ void Agent::handleAudioPacket(QSharedPointer message) { static const QString AGENT_LOGGING_NAME = "agent"; void Agent::run() { + // Create ScriptEngines on threaded-assignment thread then move to main thread. + DependencyManager::set(ScriptEngine::AGENT_SCRIPT)->moveToThread(qApp->thread()); // make sure we request our script once the agent connects to the domain auto nodeList = DependencyManager::get(); @@ -344,11 +362,11 @@ void Agent::scriptRequestFinished() { void Agent::executeScript() { _scriptEngine = scriptEngineFactory(ScriptEngine::AGENT_SCRIPT, _scriptContents, _payload); - DependencyManager::get()->setScriptEngine(_scriptEngine); - // setup an Avatar for the script to use auto scriptedAvatar = DependencyManager::get(); + scriptedAvatar->setID(getSessionUUID()); + connect(_scriptEngine.data(), SIGNAL(update(float)), scriptedAvatar.data(), SLOT(update(float)), Qt::ConnectionType::QueuedConnection); scriptedAvatar->setForceFaceTrackerConnected(true); @@ -366,7 +384,6 @@ void Agent::executeScript() { // give scripts access to the Users object _scriptEngine->registerGlobalObject("Users", DependencyManager::get().data()); - auto player = DependencyManager::get(); connect(player.data(), &recording::Deck::playbackStateChanged, [=] { if (player->isPlaying()) { @@ -438,7 +455,7 @@ void Agent::executeScript() { encodedBuffer = audio; } - AbstractAudioInterface::emitAudioPacket(encodedBuffer.data(), encodedBuffer.size(), audioSequenceNumber, false, + AbstractAudioInterface::emitAudioPacket(encodedBuffer.data(), encodedBuffer.size(), audioSequenceNumber, false, audioTransform, scriptedAvatar->getWorldPosition(), glm::vec3(0), packetType, _selectedCodecName); }); @@ -446,16 +463,11 @@ void Agent::executeScript() { auto avatarHashMap = DependencyManager::set(); _scriptEngine->registerGlobalObject("AvatarList", avatarHashMap.data()); - auto& packetReceiver = DependencyManager::get()->getPacketReceiver(); - packetReceiver.registerListener(PacketType::BulkAvatarData, avatarHashMap.data(), "processAvatarDataPacket"); - packetReceiver.registerListener(PacketType::KillAvatar, avatarHashMap.data(), "processKillAvatar"); - packetReceiver.registerListener(PacketType::AvatarIdentity, avatarHashMap.data(), "processAvatarIdentityPacket"); - // register ourselves to the script engine - _scriptEngine->registerGlobalObject("Agent", this); + _scriptEngine->registerGlobalObject("Agent", new AgentScriptingInterface(this)); - _scriptEngine->registerGlobalObject("SoundCache", DependencyManager::get().data()); - _scriptEngine->registerGlobalObject("AnimationCache", DependencyManager::get().data()); + _scriptEngine->registerGlobalObject("AnimationCache", DependencyManager::get().data()); + _scriptEngine->registerGlobalObject("SoundCache", DependencyManager::get().data()); QScriptValue webSocketServerConstructorValue = _scriptEngine->newFunction(WebSocketServerClass::constructor); _scriptEngine->globalObject().setProperty("WebSocketServer", webSocketServerConstructorValue); @@ -496,7 +508,6 @@ void Agent::executeScript() { Frame::clearFrameHandler(AVATAR_FRAME_TYPE); DependencyManager::destroy(); - setFinished(true); } @@ -515,7 +526,7 @@ void Agent::setIsListeningToAudioStream(bool isListeningToAudioStream) { auto nodeList = DependencyManager::get(); nodeList->eachMatchingNode( - [&](const SharedNodePointer& node)->bool { + [](const SharedNodePointer& node)->bool { return (node->getType() == NodeType::AudioMixer) && node->getActiveSocket(); }, [&](const SharedNodePointer& node) { @@ -597,6 +608,11 @@ void Agent::setIsAvatar(bool isAvatar) { } QMetaObject::invokeMethod(&_avatarAudioTimer, "stop"); + + _entityEditSender.setMyAvatar(nullptr); + } else { + auto scriptableAvatar = DependencyManager::get(); + _entityEditSender.setMyAvatar(scriptableAvatar.data()); } } @@ -825,10 +841,6 @@ void Agent::processAgentAvatarAudio() { void Agent::aboutToFinish() { setIsAvatar(false);// will stop timers for sending identity packets - if (_scriptEngine) { - _scriptEngine->stop(); - } - // our entity tree is going to go away so tell that to the EntityScriptingInterface DependencyManager::get()->setEntityTree(nullptr); @@ -841,15 +853,20 @@ void Agent::aboutToFinish() { // destroy all other created dependencies DependencyManager::destroy(); - DependencyManager::destroy(); DependencyManager::destroy(); + DependencyManager::destroy(); DependencyManager::destroy(); DependencyManager::destroy(); DependencyManager::destroy(); DependencyManager::destroy(); DependencyManager::destroy(); + DependencyManager::destroy(); + + DependencyManager::destroy(); + + DependencyManager::destroy(); QMetaObject::invokeMethod(&_avatarAudioTimer, "stop"); @@ -859,3 +876,11 @@ void Agent::aboutToFinish() { _encoder = nullptr; } } + +void Agent::stop() { + if (_scriptEngine) { + _scriptEngine->stop(); + } else { + setFinished(true); + } +} diff --git a/assignment-client/src/Agent.h b/assignment-client/src/Agent.h index 0fc3fbe1f9..2b5ff51b49 100644 --- a/assignment-client/src/Agent.h +++ b/assignment-client/src/Agent.h @@ -21,6 +21,7 @@ #include #include +#include #include #include #include @@ -33,19 +34,6 @@ #include "entities/EntityTreeHeadlessViewer.h" #include "avatars/ScriptableAvatar.h" -/**jsdoc - * @namespace Agent - * - * @hifi-assignment-client - * - * @property {boolean} isAvatar - * @property {boolean} isPlayingAvatarSound Read-only. - * @property {boolean} isListeningToAudioStream - * @property {boolean} isNoiseGateEnabled - * @property {number} lastReceivedAudioLoudness Read-only. - * @property {Uuid} sessionUUID Read-only. - */ - class Agent : public ThreadedAssignment { Q_OBJECT @@ -73,30 +61,15 @@ public: virtual void aboutToFinish() override; public slots: - /**jsdoc - * @function Agent.run - * @deprecated This function is being removed from the API. - */ void run() override; - /**jsdoc - * @function Agent.playAvatarSound - * @param {object} avatarSound - */ void playAvatarSound(SharedSoundPointer avatarSound); - - /**jsdoc - * @function Agent.setIsAvatar - * @param {boolean} isAvatar - */ - void setIsAvatar(bool isAvatar); - /**jsdoc - * @function Agent.isAvatar - * @returns {boolean} - */ + void setIsAvatar(bool isAvatar); bool isAvatar() const { return _isAvatar; } + Q_INVOKABLE virtual void stop() override; + private slots: void requestScript(); void scriptRequestFinished(); diff --git a/assignment-client/src/AgentScriptingInterface.cpp b/assignment-client/src/AgentScriptingInterface.cpp new file mode 100644 index 0000000000..3000c2b96f --- /dev/null +++ b/assignment-client/src/AgentScriptingInterface.cpp @@ -0,0 +1,17 @@ +// +// AgentScriptingInterface.cpp +// assignment-client/src +// +// Created by Thijs Wenker on 7/23/18. +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "AgentScriptingInterface.h" + +AgentScriptingInterface::AgentScriptingInterface(Agent* agent) : + QObject(agent), + _agent(agent) +{ } diff --git a/assignment-client/src/AgentScriptingInterface.h b/assignment-client/src/AgentScriptingInterface.h new file mode 100644 index 0000000000..9fa7688778 --- /dev/null +++ b/assignment-client/src/AgentScriptingInterface.h @@ -0,0 +1,80 @@ +// +// AgentScriptingInterface.h +// assignment-client/src +// +// Created by Thijs Wenker on 7/23/18. +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#pragma once + +#ifndef hifi_AgentScriptingInterface_h +#define hifi_AgentScriptingInterface_h + +#include + +#include "Agent.h" + +/**jsdoc + * @namespace Agent + * + * @hifi-assignment-client + * + * @property {boolean} isAvatar + * @property {boolean} isPlayingAvatarSound Read-only. + * @property {boolean} isListeningToAudioStream + * @property {boolean} isNoiseGateEnabled + * @property {number} lastReceivedAudioLoudness Read-only. + * @property {Uuid} sessionUUID Read-only. + */ +class AgentScriptingInterface : public QObject { + Q_OBJECT + Q_PROPERTY(bool isAvatar READ isAvatar WRITE setIsAvatar) + Q_PROPERTY(bool isPlayingAvatarSound READ isPlayingAvatarSound) + Q_PROPERTY(bool isListeningToAudioStream READ isListeningToAudioStream WRITE setIsListeningToAudioStream) + Q_PROPERTY(bool isNoiseGateEnabled READ isNoiseGateEnabled WRITE setIsNoiseGateEnabled) + Q_PROPERTY(float lastReceivedAudioLoudness READ getLastReceivedAudioLoudness) + Q_PROPERTY(QUuid sessionUUID READ getSessionUUID) + +public: + AgentScriptingInterface(Agent* agent); + + bool isPlayingAvatarSound() const { return _agent->isPlayingAvatarSound(); } + + bool isListeningToAudioStream() const { return _agent->isListeningToAudioStream(); } + void setIsListeningToAudioStream(bool isListeningToAudioStream) const { _agent->setIsListeningToAudioStream(isListeningToAudioStream); } + + bool isNoiseGateEnabled() const { return _agent->isNoiseGateEnabled(); } + void setIsNoiseGateEnabled(bool isNoiseGateEnabled) const { _agent->setIsNoiseGateEnabled(isNoiseGateEnabled); } + + float getLastReceivedAudioLoudness() const { return _agent->getLastReceivedAudioLoudness(); } + QUuid getSessionUUID() const { return _agent->getSessionUUID(); } + +public slots: + /**jsdoc + * @function Agent.setIsAvatar + * @param {boolean} isAvatar + */ + void setIsAvatar(bool isAvatar) const { _agent->setIsAvatar(isAvatar); } + + /**jsdoc + * @function Agent.isAvatar + * @returns {boolean} + */ + bool isAvatar() const { return _agent->isAvatar(); } + + /**jsdoc + * @function Agent.playAvatarSound + * @param {object} avatarSound + */ + void playAvatarSound(SharedSoundPointer avatarSound) const { _agent->playAvatarSound(avatarSound); } + +private: + Agent* _agent; + +}; + + +#endif // hifi_AgentScriptingInterface_h diff --git a/assignment-client/src/AssignmentClient.cpp b/assignment-client/src/AssignmentClient.cpp index 41e42aa0a1..426f3ce6fc 100644 --- a/assignment-client/src/AssignmentClient.cpp +++ b/assignment-client/src/AssignmentClient.cpp @@ -22,8 +22,6 @@ #include #include #include -#include -#include #include #include #include @@ -31,16 +29,12 @@ #include #include #include -#include -#include -#include + #include #include #include "AssignmentClientLogging.h" -#include "AssignmentDynamicFactory.h" #include "AssignmentFactory.h" -#include "avatars/ScriptableAvatar.h" const QString ASSIGNMENT_CLIENT_TARGET_NAME = "assignment-client"; const long long ASSIGNMENT_REQUEST_INTERVAL_MSECS = 1 * 1000; @@ -56,20 +50,11 @@ AssignmentClient::AssignmentClient(Assignment::Type requestAssignmentType, QStri DependencyManager::set(); DependencyManager::set(); - auto scriptableAvatar = DependencyManager::set(); auto addressManager = DependencyManager::set(); // create a NodeList as an unassigned client, must be after addressManager auto nodeList = DependencyManager::set(NodeType::Unassigned, listenPort); - auto animationCache = DependencyManager::set(); - auto entityScriptingInterface = DependencyManager::set(false); - - DependencyManager::registerInheritance(); - auto dynamicFactory = DependencyManager::set(); - DependencyManager::set(); - DependencyManager::set(); - nodeList->startThread(); // set the logging target to the the CHILD_TARGET_NAME LogHandler::getInstance().setTargetName(ASSIGNMENT_CLIENT_TARGET_NAME); diff --git a/assignment-client/src/AssignmentClientApp.cpp b/assignment-client/src/AssignmentClientApp.cpp index b37784cddc..acfbb8571c 100644 --- a/assignment-client/src/AssignmentClientApp.cpp +++ b/assignment-client/src/AssignmentClientApp.cpp @@ -11,6 +11,8 @@ #include "AssignmentClientApp.h" +#include + #include #include #include @@ -42,9 +44,8 @@ AssignmentClientApp::AssignmentClientApp(int argc, char* argv[]) : // parse command-line QCommandLineParser parser; parser.setApplicationDescription("High Fidelity Assignment Client"); - parser.addHelpOption(); - const QCommandLineOption helpOption = parser.addHelpOption(); + const QCommandLineOption versionOption = parser.addVersionOption(); QString typeDescription = "run single assignment client of given type\n# | Type\n============================"; for (Assignment::Type type = Assignment::FirstType; @@ -97,11 +98,16 @@ AssignmentClientApp::AssignmentClientApp(int argc, char* argv[]) : parser.addOption(parentPIDOption); if (!parser.parse(QCoreApplication::arguments())) { - qCritical() << parser.errorText() << endl; + std::cout << parser.errorText().toStdString() << std::endl; // Avoid Qt log spam parser.showHelp(); Q_UNREACHABLE(); } + if (parser.isSet(versionOption)) { + parser.showVersion(); + Q_UNREACHABLE(); + } + if (parser.isSet(helpOption)) { parser.showHelp(); Q_UNREACHABLE(); diff --git a/assignment-client/src/AssignmentClientMonitor.cpp b/assignment-client/src/AssignmentClientMonitor.cpp index 2847d4ebf1..330023dae0 100644 --- a/assignment-client/src/AssignmentClientMonitor.cpp +++ b/assignment-client/src/AssignmentClientMonitor.cpp @@ -25,6 +25,9 @@ #include "AssignmentClientChildData.h" #include "SharedUtil.h" #include +#ifdef _POSIX_SOURCE +#include +#endif const QString ASSIGNMENT_CLIENT_MONITOR_TARGET_NAME = "assignment-client-monitor"; const int WAIT_FOR_CHILD_MSECS = 1000; @@ -71,6 +74,7 @@ AssignmentClientMonitor::AssignmentClientMonitor(const unsigned int numAssignmen auto& packetReceiver = DependencyManager::get()->getPacketReceiver(); packetReceiver.registerListener(PacketType::AssignmentClientStatus, this, "handleChildStatusPacket"); + adjustOSResources(std::max(_numAssignmentClientForks, _maxAssignmentClientForks)); // use QProcess to fork off a process for each of the child assignment clients for (unsigned int i = 0; i < _numAssignmentClientForks; i++) { spawnChildClient(); @@ -372,3 +376,27 @@ bool AssignmentClientMonitor::handleHTTPRequest(HTTPConnection* connection, cons return true; } + +void AssignmentClientMonitor::adjustOSResources(unsigned int numForks) const +{ +#ifdef _POSIX_SOURCE + // QProcess on Unix uses six (I think) descriptors, some temporarily, for each child proc. + // Formula based on tests with a Ubuntu 16.04 VM. + unsigned requiredDescriptors = 30 + 6 * numForks; + struct rlimit descLimits; + if (getrlimit(RLIMIT_NOFILE, &descLimits) == 0) { + if (descLimits.rlim_cur < requiredDescriptors) { + descLimits.rlim_cur = requiredDescriptors; + if (setrlimit(RLIMIT_NOFILE, &descLimits) == 0) { + qDebug() << "Resetting descriptor limit to" << requiredDescriptors; + } else { + const char *const errorString = strerror(errno); + qDebug() << "Failed to reset descriptor limit to" << requiredDescriptors << ":" << errorString; + } + } + } else { + const char *const errorString = strerror(errno); + qDebug() << "Failed to read descriptor limit:" << errorString; + } +#endif +} diff --git a/assignment-client/src/AssignmentClientMonitor.h b/assignment-client/src/AssignmentClientMonitor.h index 8848d503ae..5e32c50e0d 100644 --- a/assignment-client/src/AssignmentClientMonitor.h +++ b/assignment-client/src/AssignmentClientMonitor.h @@ -55,6 +55,7 @@ public slots: private: void spawnChildClient(); void simultaneousWaitOnChildren(int waitMsecs); + void adjustOSResources(unsigned int numForks) const; QTimer _checkSparesTimer; // every few seconds see if it need fewer or more spare children diff --git a/assignment-client/src/assets/AssetServer.cpp b/assignment-client/src/assets/AssetServer.cpp index e79473783a..41aeaba468 100644 --- a/assignment-client/src/assets/AssetServer.cpp +++ b/assignment-client/src/assets/AssetServer.cpp @@ -945,22 +945,14 @@ void AssetServer::sendStatsPacket() { upstreamStats["2. Sent Packets"] = stat.second.sentPackets; upstreamStats["3. Recvd ACK"] = events[Events::ReceivedACK]; upstreamStats["4. Procd ACK"] = events[Events::ProcessedACK]; - upstreamStats["5. Recvd LACK"] = events[Events::ReceivedLightACK]; - upstreamStats["6. Recvd NAK"] = events[Events::ReceivedNAK]; - upstreamStats["7. Recvd TNAK"] = events[Events::ReceivedTimeoutNAK]; - upstreamStats["8. Sent ACK2"] = events[Events::SentACK2]; - upstreamStats["9. Retransmitted"] = events[Events::Retransmission]; + upstreamStats["5. Retransmitted"] = events[Events::Retransmission]; nodeStats["Upstream Stats"] = upstreamStats; QJsonObject downstreamStats; downstreamStats["1. Recvd (P/s)"] = stat.second.receiveRate; downstreamStats["2. Recvd Packets"] = stat.second.receivedPackets; downstreamStats["3. Sent ACK"] = events[Events::SentACK]; - downstreamStats["4. Sent LACK"] = events[Events::SentLightACK]; - downstreamStats["5. Sent NAK"] = events[Events::SentNAK]; - downstreamStats["6. Sent TNAK"] = events[Events::SentTimeoutNAK]; - downstreamStats["7. Recvd ACK2"] = events[Events::ReceivedACK2]; - downstreamStats["8. Duplicates"] = events[Events::Duplicate]; + downstreamStats["4. Duplicates"] = events[Events::Duplicate]; nodeStats["Downstream Stats"] = downstreamStats; QString uuid; diff --git a/assignment-client/src/audio/AudioMixer.cpp b/assignment-client/src/audio/AudioMixer.cpp index d56b22466e..0d42cc83be 100644 --- a/assignment-client/src/audio/AudioMixer.cpp +++ b/assignment-client/src/audio/AudioMixer.cpp @@ -366,7 +366,7 @@ AudioMixerClientData* AudioMixer::getOrCreateClientData(Node* node) { auto clientData = dynamic_cast(node->getLinkedData()); if (!clientData) { - node->setLinkedData(std::unique_ptr { new AudioMixerClientData(node->getUUID()) }); + node->setLinkedData(std::unique_ptr { new AudioMixerClientData(node->getUUID(), node->getLocalID()) }); clientData = dynamic_cast(node->getLinkedData()); connect(clientData, &AudioMixerClientData::injectorStreamFinished, this, &AudioMixer::removeHRTFsForFinishedInjector); } diff --git a/assignment-client/src/audio/AudioMixerClientData.cpp b/assignment-client/src/audio/AudioMixerClientData.cpp index bc08c6f24a..07cc5493b0 100644 --- a/assignment-client/src/audio/AudioMixerClientData.cpp +++ b/assignment-client/src/audio/AudioMixerClientData.cpp @@ -25,8 +25,8 @@ #include "AudioHelpers.h" #include "AudioMixer.h" -AudioMixerClientData::AudioMixerClientData(const QUuid& nodeID) : - NodeData(nodeID), +AudioMixerClientData::AudioMixerClientData(const QUuid& nodeID, Node::LocalID nodeLocalID) : + NodeData(nodeID, nodeLocalID), audioLimiter(AudioConstants::SAMPLE_RATE, AudioConstants::STEREO), _ignoreZone(*this), _outgoingMixedAudioSequenceNumber(0), diff --git a/assignment-client/src/audio/AudioMixerClientData.h b/assignment-client/src/audio/AudioMixerClientData.h index 514e1c9756..82bdc0e5c5 100644 --- a/assignment-client/src/audio/AudioMixerClientData.h +++ b/assignment-client/src/audio/AudioMixerClientData.h @@ -30,7 +30,7 @@ class AudioMixerClientData : public NodeData { Q_OBJECT public: - AudioMixerClientData(const QUuid& nodeID); + AudioMixerClientData(const QUuid& nodeID, Node::LocalID nodeLocalID); ~AudioMixerClientData(); using SharedStreamPointer = std::shared_ptr; diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index 772c8643b3..edbba20dc7 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -39,7 +39,8 @@ const QString AVATAR_MIXER_LOGGING_NAME = "avatar-mixer"; const int AVATAR_MIXER_BROADCAST_FRAMES_PER_SECOND = 45; AvatarMixer::AvatarMixer(ReceivedMessage& message) : - ThreadedAssignment(message) + ThreadedAssignment(message), + _slavePool(&_slaveSharedData) { // make sure we hear about node kills so we can tell the other nodes connect(DependencyManager::get().data(), &NodeList::nodeKilled, this, &AvatarMixer::handleAvatarKilled); @@ -54,6 +55,7 @@ AvatarMixer::AvatarMixer(ReceivedMessage& message) : packetReceiver.registerListener(PacketType::RadiusIgnoreRequest, this, "handleRadiusIgnoreRequestPacket"); packetReceiver.registerListener(PacketType::RequestsDomainListData, this, "handleRequestsDomainListDataPacket"); packetReceiver.registerListener(PacketType::AvatarIdentityRequest, this, "handleAvatarIdentityRequestPacket"); + packetReceiver.registerListener(PacketType::SetAvatarTraits, this, "queueIncomingPacket"); packetReceiver.registerListenerForTypes({ PacketType::ReplicatedAvatarIdentity, @@ -337,17 +339,7 @@ void AvatarMixer::manageIdentityData(const SharedNodePointer& node) { sendIdentity = true; qCDebug(avatars) << "Giving session display name" << sessionDisplayName << "to node with ID" << node->getUUID(); } - if (nodeData && nodeData->getAvatarSkeletonModelUrlMustChange()) { // never true for an empty _avatarWhitelist - nodeData->setAvatarSkeletonModelUrlMustChange(false); - AvatarData& avatar = nodeData->getAvatar(); - static const QUrl emptyURL(""); - QUrl url = avatar.cannonicalSkeletonModelURL(emptyURL); - if (!isAvatarInWhitelist(url)) { - qCDebug(avatars) << "Forbidden avatar" << nodeData->getNodeID() << avatar.getSkeletonModelURL() << "replaced with" << (_replacementAvatar.isEmpty() ? "default" : _replacementAvatar); - avatar.setSkeletonModelURL(_replacementAvatar); - sendIdentity = true; - } - } + if (sendIdentity && !node->isUpstream()) { sendIdentityPacket(nodeData, node); // Tell node whose name changed about its new session display name or avatar. // since this packet includes a change to either the skeleton model URL or the display name @@ -359,22 +351,6 @@ void AvatarMixer::manageIdentityData(const SharedNodePointer& node) { } } -bool AvatarMixer::isAvatarInWhitelist(const QUrl& url) { - // The avatar is in the whitelist if: - // 1. The avatar's URL's host matches one of the hosts of the URLs in the whitelist AND - // 2. The avatar's URL's path starts with the path of that same URL in the whitelist - for (const auto& whiteListedPrefix : _avatarWhitelist) { - auto whiteListURL = QUrl::fromUserInput(whiteListedPrefix); - // check if this script URL matches the whitelist domain and, optionally, is beneath the path - if (url.host().compare(whiteListURL.host(), Qt::CaseInsensitive) == 0 && - url.path().startsWith(whiteListURL.path(), Qt::CaseInsensitive)) { - return true; - } - } - - return false; -} - void AvatarMixer::throttle(std::chrono::microseconds duration, int frame) { // throttle using a modified proportional-integral controller const float FRAME_TIME = USECS_PER_SECOND / AVATAR_MIXER_BROADCAST_FRAMES_PER_SECOND; @@ -447,18 +423,21 @@ void AvatarMixer::handleAvatarKilled(SharedNodePointer avatarNode) { // send a kill packet for it to our other nodes nodeList->eachMatchingNode([&](const SharedNodePointer& node) { // we relay avatar kill packets to agents that are not upstream - // and downstream avatar mixers, if the node that was just killed was being replicated - return (node->getType() == NodeType::Agent && !node->isUpstream()) || - (avatarNode->isReplicated() && shouldReplicateTo(*avatarNode, *node)); + // and downstream avatar mixers, if the node that was just killed was being replicatedConnectedAgent + return node->getActiveSocket() && + ((node->getType() == NodeType::Agent && !node->isUpstream()) || + (avatarNode->isReplicated() && shouldReplicateTo(*avatarNode, *node))); }, [&](const SharedNodePointer& node) { if (node->getType() == NodeType::Agent) { if (!killPacket) { - killPacket = NLPacket::create(PacketType::KillAvatar, NUM_BYTES_RFC4122_UUID + sizeof(KillAvatarReason)); + killPacket = NLPacket::create(PacketType::KillAvatar, NUM_BYTES_RFC4122_UUID + sizeof(KillAvatarReason), true); killPacket->write(avatarNode->getUUID().toRfc4122()); killPacket->writePrimitive(KillAvatarReason::AvatarDisconnected); } - nodeList->sendUnreliablePacket(*killPacket, *node); + auto killPacketCopy = NLPacket::createCopy(*killPacket); + + nodeList->sendPacket(std::move(killPacketCopy), *node); } else { // send a replicated kill packet to the downstream avatar mixer if (!replicatedKillPacket) { @@ -491,7 +470,8 @@ void AvatarMixer::handleAvatarKilled(SharedNodePointer avatarNode) { QMetaObject::invokeMethod(node->getLinkedData(), "cleanupKilledNode", Qt::AutoConnection, - Q_ARG(const QUuid&, QUuid(avatarNode->getUUID()))); + Q_ARG(const QUuid&, QUuid(avatarNode->getUUID())), + Q_ARG(Node::LocalID, avatarNode->getLocalID())); } ); } @@ -561,7 +541,8 @@ void AvatarMixer::handleRequestsDomainListDataPacket(QSharedPointersetLastBroadcastTime(node->getUUID(), 0); + nodeData->setLastBroadcastTime(node->getUUID(), 0); + nodeData->resetSentTraitData(node->getLocalID()); } ); } @@ -584,8 +565,7 @@ void AvatarMixer::handleAvatarIdentityPacket(QSharedPointer mes // parse the identity packet and update the change timestamp if appropriate bool identityChanged = false; bool displayNameChanged = false; - bool skeletonModelUrlChanged = false; - avatar.processAvatarIdentity(message->getMessage(), identityChanged, displayNameChanged, skeletonModelUrlChanged); + avatar.processAvatarIdentity(message->getMessage(), identityChanged, displayNameChanged); if (identityChanged) { QMutexLocker nodeDataLocker(&nodeData->getMutex()); @@ -593,9 +573,6 @@ void AvatarMixer::handleAvatarIdentityPacket(QSharedPointer mes if (displayNameChanged) { nodeData->setAvatarSessionDisplayNameMustChange(true); } - if (skeletonModelUrlChanged && !_avatarWhitelist.isEmpty()) { - nodeData->setAvatarSkeletonModelUrlMustChange(true); - } } } } @@ -612,10 +589,10 @@ void AvatarMixer::handleAvatarIdentityRequestPacket(QSharedPointergetMessage()) ); if (!avatarID.isNull()) { auto nodeList = DependencyManager::get(); - auto node = nodeList->nodeWithUUID(avatarID); - if (node) { - QMutexLocker lock(&node->getMutex()); - AvatarMixerClientData* avatarClientData = dynamic_cast(node->getLinkedData()); + auto requestedNode = nodeList->nodeWithUUID(avatarID); + + if (requestedNode) { + AvatarMixerClientData* avatarClientData = static_cast(requestedNode->getLinkedData()); if (avatarClientData) { const AvatarData& avatarData = avatarClientData->getAvatar(); QByteArray serializedAvatar = avatarData.identityByteArray(); @@ -624,6 +601,11 @@ void AvatarMixer::handleAvatarIdentityRequestPacket(QSharedPointersendPacketList(std::move(identityPackets), *senderNode); ++_sumIdentityPackets; } + + AvatarMixerClientData* senderData = static_cast(senderNode->getLinkedData()); + if (senderData) { + senderData->resetSentTraitData(requestedNode->getLocalID()); + } } } } @@ -643,24 +625,31 @@ void AvatarMixer::handleNodeIgnoreRequestPacket(QSharedPointer auto start = usecTimestampNow(); auto nodeList = DependencyManager::get(); AvatarMixerClientData* nodeData = reinterpret_cast(senderNode->getLinkedData()); + bool addToIgnore; message->readPrimitive(&addToIgnore); while (message->getBytesLeftToRead()) { // parse out the UUID being ignored from the packet QUuid ignoredUUID = QUuid::fromRfc4122(message->readWithoutCopy(NUM_BYTES_RFC4122_UUID)); + auto ignoredNode = nodeList->nodeWithUUID(ignoredUUID); + if (ignoredNode) { + if (nodeData) { + // Reset the lastBroadcastTime for the ignored avatar to 0 + // so the AvatarMixer knows it'll have to send identity data about the ignored avatar + // to the ignorer if the ignorer unignores. + nodeData->setLastBroadcastTime(ignoredUUID, 0); + nodeData->resetSentTraitData(ignoredNode->getLocalID()); + } - if (nodeList->nodeWithUUID(ignoredUUID)) { - // Reset the lastBroadcastTime for the ignored avatar to 0 - // so the AvatarMixer knows it'll have to send identity data about the ignored avatar - // to the ignorer if the ignorer unignores. - nodeData->setLastBroadcastTime(ignoredUUID, 0); // Reset the lastBroadcastTime for the ignorer (FROM THE PERSPECTIVE OF THE IGNORED) to 0 // so the AvatarMixer knows it'll have to send identity data about the ignorer // to the ignored if the ignorer unignores. - auto ignoredNode = nodeList->nodeWithUUID(ignoredUUID); AvatarMixerClientData* ignoredNodeData = reinterpret_cast(ignoredNode->getLinkedData()); - ignoredNodeData->setLastBroadcastTime(senderNode->getUUID(), 0); + if (ignoredNodeData) { + ignoredNodeData->setLastBroadcastTime(senderNode->getUUID(), 0); + ignoredNodeData->resetSentTraitData(senderNode->getLocalID()); + } } if (addToIgnore) { @@ -894,7 +883,7 @@ AvatarMixerClientData* AvatarMixer::getOrCreateClientData(SharedNodePointer node auto clientData = dynamic_cast(node->getLinkedData()); if (!clientData) { - node->setLinkedData(std::unique_ptr { new AvatarMixerClientData(node->getUUID()) }); + node->setLinkedData(std::unique_ptr { new AvatarMixerClientData(node->getUUID(), node->getLocalID()) }); clientData = dynamic_cast(node->getLinkedData()); auto& avatar = clientData->getAvatar(); avatar.setDomainMinimumHeight(_domainMinimumHeight); @@ -982,20 +971,22 @@ void AvatarMixer::parseDomainServerSettings(const QJsonObject& domainSettings) { qCDebug(avatars) << "This domain requires a minimum avatar height of" << _domainMinimumHeight << "and a maximum avatar height of" << _domainMaximumHeight; - const QString AVATAR_WHITELIST_DEFAULT{ "" }; static const QString AVATAR_WHITELIST_OPTION = "avatar_whitelist"; - _avatarWhitelist = domainSettings[AVATARS_SETTINGS_KEY].toObject()[AVATAR_WHITELIST_OPTION].toString(AVATAR_WHITELIST_DEFAULT).split(',', QString::KeepEmptyParts); + _slaveSharedData.skeletonURLWhitelist = domainSettings[AVATARS_SETTINGS_KEY].toObject()[AVATAR_WHITELIST_OPTION] + .toString().split(',', QString::KeepEmptyParts); static const QString REPLACEMENT_AVATAR_OPTION = "replacement_avatar"; - _replacementAvatar = domainSettings[AVATARS_SETTINGS_KEY].toObject()[REPLACEMENT_AVATAR_OPTION].toString(REPLACEMENT_AVATAR_DEFAULT); + _slaveSharedData.skeletonReplacementURL = domainSettings[AVATARS_SETTINGS_KEY].toObject()[REPLACEMENT_AVATAR_OPTION] + .toString(); - if ((_avatarWhitelist.count() == 1) && _avatarWhitelist[0].isEmpty()) { - _avatarWhitelist.clear(); // KeepEmptyParts above will parse "," as ["", ""] (which is ok), but "" as [""] (which is not ok). + if (_slaveSharedData.skeletonURLWhitelist.count() == 1 && _slaveSharedData.skeletonURLWhitelist[0].isEmpty()) { + // KeepEmptyParts above will parse "," as ["", ""] (which is ok), but "" as [""] (which is not ok). + _slaveSharedData.skeletonURLWhitelist.clear(); } - if (_avatarWhitelist.isEmpty()) { + if (_slaveSharedData.skeletonURLWhitelist.isEmpty()) { qCDebug(avatars) << "All avatars are allowed."; } else { - qCDebug(avatars) << "Avatars other than" << _avatarWhitelist << "will be replaced by" << (_replacementAvatar.isEmpty() ? "default" : _replacementAvatar); + qCDebug(avatars) << "Avatars other than" << _slaveSharedData.skeletonURLWhitelist << "will be replaced by" << (_slaveSharedData.skeletonReplacementURL.isEmpty() ? "default" : _slaveSharedData.skeletonReplacementURL.toString()); } } diff --git a/assignment-client/src/avatars/AvatarMixer.h b/assignment-client/src/avatars/AvatarMixer.h index c27ca33729..8ae7fc9931 100644 --- a/assignment-client/src/avatars/AvatarMixer.h +++ b/assignment-client/src/avatars/AvatarMixer.h @@ -59,7 +59,6 @@ private slots: void handlePacketVersionMismatch(PacketType type, const HifiSockAddr& senderSockAddr, const QUuid& senderUUID); void start(); - private: AvatarMixerClientData* getOrCreateClientData(SharedNodePointer node); std::chrono::microseconds timeFrame(p_high_resolution_clock::time_point& timestamp); @@ -69,11 +68,6 @@ private: void sendIdentityPacket(AvatarMixerClientData* nodeData, const SharedNodePointer& destinationNode); void manageIdentityData(const SharedNodePointer& node); - bool isAvatarInWhitelist(const QUrl& url); - - const QString REPLACEMENT_AVATAR_DEFAULT{ "" }; - QStringList _avatarWhitelist { }; - QString _replacementAvatar { REPLACEMENT_AVATAR_DEFAULT }; void optionallyReplicatePacket(ReceivedMessage& message, const Node& node); @@ -83,7 +77,6 @@ private: float _trailingMixRatio { 0.0f }; float _throttlingRatio { 0.0f }; - int _sumListeners { 0 }; int _numStatFrames { 0 }; int _numTightLoopFrames { 0 }; @@ -126,9 +119,8 @@ private: RateCounter<> _loopRate; // this is the rate that the main thread tight loop runs - AvatarMixerSlavePool _slavePool; - + SlaveSharedData _slaveSharedData; }; #endif // hifi_AvatarMixer_h diff --git a/assignment-client/src/avatars/AvatarMixerClientData.cpp b/assignment-client/src/avatars/AvatarMixerClientData.cpp index e185fe9167..f524c071ec 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.cpp +++ b/assignment-client/src/avatars/AvatarMixerClientData.cpp @@ -16,8 +16,10 @@ #include #include -AvatarMixerClientData::AvatarMixerClientData(const QUuid& nodeID) : - NodeData(nodeID) +#include "AvatarMixerSlave.h" + +AvatarMixerClientData::AvatarMixerClientData(const QUuid& nodeID, Node::LocalID nodeLocalID) : + NodeData(nodeID, nodeLocalID) { // in case somebody calls getSessionUUID on the AvatarData instance, make sure it has the right ID _avatar->setID(nodeID); @@ -47,7 +49,7 @@ void AvatarMixerClientData::queuePacket(QSharedPointer message, _packetQueue.push(message); } -int AvatarMixerClientData::processPackets() { +int AvatarMixerClientData::processPackets(const SlaveSharedData& slaveSharedData) { int packetsProcessed = 0; SharedNodePointer node = _packetQueue.node; assert(_packetQueue.empty() || node); @@ -62,6 +64,9 @@ int AvatarMixerClientData::processPackets() { case PacketType::AvatarData: parseData(*packet); break; + case PacketType::SetAvatarTraits: + processSetTraitsMessage(*packet, slaveSharedData, *node); + break; default: Q_UNREACHABLE(); } @@ -87,6 +92,113 @@ int AvatarMixerClientData::parseData(ReceivedMessage& message) { // compute the offset to the data payload return _avatar->parseDataFromBuffer(message.readWithoutCopy(message.getBytesLeftToRead())); } + +void AvatarMixerClientData::processSetTraitsMessage(ReceivedMessage& message, + const SlaveSharedData& slaveSharedData, Node& sendingNode) { + // pull the trait version from the message + AvatarTraits::TraitVersion packetTraitVersion; + message.readPrimitive(&packetTraitVersion); + + bool anyTraitsChanged = false; + + while (message.getBytesLeftToRead() > 0) { + // for each trait in the packet, apply it if the trait version is newer than what we have + + AvatarTraits::TraitType traitType; + message.readPrimitive(&traitType); + + if (AvatarTraits::isSimpleTrait(traitType)) { + AvatarTraits::TraitWireSize traitSize; + message.readPrimitive(&traitSize); + + if (packetTraitVersion > _lastReceivedTraitVersions[traitType]) { + _avatar->processTrait(traitType, message.read(traitSize)); + _lastReceivedTraitVersions[traitType] = packetTraitVersion; + + if (traitType == AvatarTraits::SkeletonModelURL) { + // special handling for skeleton model URL, since we need to make sure it is in the whitelist + checkSkeletonURLAgainstWhitelist(slaveSharedData, sendingNode, packetTraitVersion); + } + + anyTraitsChanged = true; + } else { + message.seek(message.getPosition() + traitSize); + } + } else { + AvatarTraits::TraitInstanceID instanceID = QUuid::fromRfc4122(message.readWithoutCopy(NUM_BYTES_RFC4122_UUID)); + + AvatarTraits::TraitWireSize traitSize; + message.readPrimitive(&traitSize); + + auto& instanceVersionRef = _lastReceivedTraitVersions.getInstanceValueRef(traitType, instanceID); + + if (packetTraitVersion > instanceVersionRef) { + if (traitSize == AvatarTraits::DELETED_TRAIT_SIZE) { + _avatar->processDeletedTraitInstance(traitType, instanceID); + + // to track a deleted instance but keep version information + // the avatar mixer uses the negative value of the sent version + instanceVersionRef = -packetTraitVersion; + } else { + _avatar->processTraitInstance(traitType, instanceID, message.read(traitSize)); + instanceVersionRef = packetTraitVersion; + } + + anyTraitsChanged = true; + } else { + message.seek(message.getPosition() + traitSize); + } + } + } + + if (anyTraitsChanged) { + _lastReceivedTraitsChange = std::chrono::steady_clock::now(); + } +} + +void AvatarMixerClientData::checkSkeletonURLAgainstWhitelist(const SlaveSharedData &slaveSharedData, Node& sendingNode, + AvatarTraits::TraitVersion traitVersion) { + const auto& whitelist = slaveSharedData.skeletonURLWhitelist; + + if (!whitelist.isEmpty()) { + bool inWhitelist = false; + auto avatarURL = _avatar->getSkeletonModelURL(); + + // The avatar is in the whitelist if: + // 1. The avatar's URL's host matches one of the hosts of the URLs in the whitelist AND + // 2. The avatar's URL's path starts with the path of that same URL in the whitelist + for (const auto& whiteListedPrefix : whitelist) { + auto whiteListURL = QUrl::fromUserInput(whiteListedPrefix); + // check if this script URL matches the whitelist domain and, optionally, is beneath the path + if (avatarURL.host().compare(whiteListURL.host(), Qt::CaseInsensitive) == 0 && + avatarURL.path().startsWith(whiteListURL.path(), Qt::CaseInsensitive)) { + inWhitelist = true; + + break; + } + } + + if (!inWhitelist) { + // make sure we're not unecessarily overriding the default avatar with the default avatar + if (_avatar->getWireSafeSkeletonModelURL() != slaveSharedData.skeletonReplacementURL) { + // we need to change this avatar's skeleton URL, and send them a traits packet informing them of the change + qDebug() << "Overwriting avatar URL" << _avatar->getWireSafeSkeletonModelURL() + << "to replacement" << slaveSharedData.skeletonReplacementURL << "for" << sendingNode.getUUID(); + _avatar->setSkeletonModelURL(slaveSharedData.skeletonReplacementURL); + + auto packet = NLPacket::create(PacketType::SetAvatarTraits, -1, true); + + // the returned set traits packet uses the trait version from the incoming packet + // so the client knows they should not overwrite if they have since changed the trait + _avatar->packTrait(AvatarTraits::SkeletonModelURL, *packet, traitVersion); + + auto nodeList = DependencyManager::get(); + nodeList->sendPacket(std::move(packet), sendingNode); + } + } + } +} + uint64_t AvatarMixerClientData::getLastBroadcastTime(const QUuid& nodeUUID) const { // return the matching PacketSequenceNumber, or the default if we don't have it auto nodeMatch = _lastBroadcastTimes.find(nodeUUID); @@ -108,7 +220,7 @@ uint16_t AvatarMixerClientData::getLastBroadcastSequenceNumber(const QUuid& node void AvatarMixerClientData::ignoreOther(SharedNodePointer self, SharedNodePointer other) { if (!isRadiusIgnoring(other->getUUID())) { addToRadiusIgnoringSet(other->getUUID()); - auto killPacket = NLPacket::create(PacketType::KillAvatar, NUM_BYTES_RFC4122_UUID + sizeof(KillAvatarReason)); + auto killPacket = NLPacket::create(PacketType::KillAvatar, NUM_BYTES_RFC4122_UUID + sizeof(KillAvatarReason), true); killPacket->write(other->getUUID().toRfc4122()); if (self->isIgnoreRadiusEnabled()) { killPacket->writePrimitive(KillAvatarReason::TheirAvatarEnteredYourBubble); @@ -116,7 +228,10 @@ void AvatarMixerClientData::ignoreOther(SharedNodePointer self, SharedNodePointe killPacket->writePrimitive(KillAvatarReason::YourAvatarEnteredTheirBubble); } setLastBroadcastTime(other->getUUID(), 0); - DependencyManager::get()->sendUnreliablePacket(*killPacket, *self); + + resetSentTraitData(other->getLocalID()); + + DependencyManager::get()->sendPacket(std::move(killPacket), *self); } } @@ -126,6 +241,11 @@ void AvatarMixerClientData::removeFromRadiusIgnoringSet(SharedNodePointer self, } } +void AvatarMixerClientData::resetSentTraitData(Node::LocalID nodeLocalID) { + _lastSentTraitsTimestamps[nodeLocalID] = TraitsCheckTimestamp(); + _sentTraitVersions[nodeLocalID].reset(); +} + void AvatarMixerClientData::readViewFrustumPacket(const QByteArray& message) { _currentViewFrustums.clear(); @@ -164,3 +284,20 @@ void AvatarMixerClientData::loadJSONStats(QJsonObject& jsonObject) const { jsonObject["recent_other_av_in_view"] = _recentOtherAvatarsInView; jsonObject["recent_other_av_out_of_view"] = _recentOtherAvatarsOutOfView; } + +AvatarMixerClientData::TraitsCheckTimestamp AvatarMixerClientData::getLastOtherAvatarTraitsSendPoint(Node::LocalID otherAvatar) const { + auto it = _lastSentTraitsTimestamps.find(otherAvatar); + + if (it != _lastSentTraitsTimestamps.end()) { + return it->second; + } else { + return TraitsCheckTimestamp(); + } +} + +void AvatarMixerClientData::cleanupKilledNode(const QUuid& nodeUUID, Node::LocalID nodeLocalID) { + removeLastBroadcastSequenceNumber(nodeUUID); + removeLastBroadcastTime(nodeUUID); + _lastSentTraitsTimestamps.erase(nodeLocalID); + _sentTraitVersions.erase(nodeLocalID); +} diff --git a/assignment-client/src/avatars/AvatarMixerClientData.h b/assignment-client/src/avatars/AvatarMixerClientData.h index e038e81505..a892455fe3 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.h +++ b/assignment-client/src/avatars/AvatarMixerClientData.h @@ -22,6 +22,7 @@ #include #include +#include #include #include #include @@ -33,10 +34,12 @@ const QString OUTBOUND_AVATAR_DATA_STATS_KEY = "outbound_av_data_kbps"; const QString INBOUND_AVATAR_DATA_STATS_KEY = "inbound_av_data_kbps"; +struct SlaveSharedData; + class AvatarMixerClientData : public NodeData { Q_OBJECT public: - AvatarMixerClientData(const QUuid& nodeID = QUuid()); + AvatarMixerClientData(const QUuid& nodeID, Node::LocalID nodeLocalID); virtual ~AvatarMixerClientData() {} using HRCTime = p_high_resolution_clock::time_point; @@ -54,10 +57,7 @@ public: void setLastBroadcastTime(const QUuid& nodeUUID, uint64_t broadcastTime) { _lastBroadcastTimes[nodeUUID] = broadcastTime; } Q_INVOKABLE void removeLastBroadcastTime(const QUuid& nodeUUID) { _lastBroadcastTimes.erase(nodeUUID); } - Q_INVOKABLE void cleanupKilledNode(const QUuid& nodeUUID) { - removeLastBroadcastSequenceNumber(nodeUUID); - removeLastBroadcastTime(nodeUUID); - } + Q_INVOKABLE void cleanupKilledNode(const QUuid& nodeUUID, Node::LocalID nodeLocalID); uint16_t getLastReceivedSequenceNumber() const { return _lastReceivedSequenceNumber; } @@ -65,8 +65,6 @@ public: void flagIdentityChange() { _identityChangeTimestamp = usecTimestampNow(); } bool getAvatarSessionDisplayNameMustChange() const { return _avatarSessionDisplayNameMustChange; } void setAvatarSessionDisplayNameMustChange(bool set = true) { _avatarSessionDisplayNameMustChange = set; } - bool getAvatarSkeletonModelUrlMustChange() const { return _avatarSkeletonModelUrlMustChange; } - void setAvatarSkeletonModelUrlMustChange(bool set = true) { _avatarSkeletonModelUrlMustChange = set; } void resetNumAvatarsSentLastFrame() { _numAvatarsSentLastFrame = 0; } void incrementNumAvatarsSentLastFrame() { ++_numAvatarsSentLastFrame; } @@ -118,7 +116,26 @@ public: QVector& getLastOtherAvatarSentJoints(QUuid otherAvatar) { return _lastOtherAvatarSentJoints[otherAvatar]; } void queuePacket(QSharedPointer message, SharedNodePointer node); - int processPackets(); // returns number of packets processed + int processPackets(const SlaveSharedData& slaveSharedData); // returns number of packets processed + + void processSetTraitsMessage(ReceivedMessage& message, const SlaveSharedData& slaveSharedData, Node& sendingNode); + void checkSkeletonURLAgainstWhitelist(const SlaveSharedData& slaveSharedData, Node& sendingNode, + AvatarTraits::TraitVersion traitVersion); + + using TraitsCheckTimestamp = std::chrono::steady_clock::time_point; + + TraitsCheckTimestamp getLastReceivedTraitsChange() const { return _lastReceivedTraitsChange; } + + AvatarTraits::TraitVersions& getLastReceivedTraitVersions() { return _lastReceivedTraitVersions; } + const AvatarTraits::TraitVersions& getLastReceivedTraitVersions() const { return _lastReceivedTraitVersions; } + + TraitsCheckTimestamp getLastOtherAvatarTraitsSendPoint(Node::LocalID otherAvatar) const; + void setLastOtherAvatarTraitsSendPoint(Node::LocalID otherAvatar, TraitsCheckTimestamp sendPoint) + { _lastSentTraitsTimestamps[otherAvatar] = sendPoint; } + + AvatarTraits::TraitVersions& getLastSentTraitVersions(Node::LocalID otherAvatar) { return _sentTraitVersions[otherAvatar]; } + + void resetSentTraitData(Node::LocalID nodeID); private: struct PacketQueue : public std::queue> { @@ -156,6 +173,12 @@ private: int _recentOtherAvatarsOutOfView { 0 }; QString _baseDisplayName{}; // The santized key used in determinging unique sessionDisplayName, so that we can remove from dictionary. bool _requestsDomainListData { false }; + + AvatarTraits::TraitVersions _lastReceivedTraitVersions; + TraitsCheckTimestamp _lastReceivedTraitsChange; + + std::unordered_map _lastSentTraitsTimestamps; + std::unordered_map _sentTraitVersions; }; #endif // hifi_AvatarMixerClientData_h diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp index 563aac879f..c434d82116 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp @@ -59,7 +59,7 @@ void AvatarMixerSlave::processIncomingPackets(const SharedNodePointer& node) { auto nodeData = dynamic_cast(node->getLinkedData()); if (nodeData) { _stats.nodesProcessed++; - _stats.packetsProcessed += nodeData->processPackets(); + _stats.packetsProcessed += nodeData->processPackets(*_sharedData); } auto end = usecTimestampNow(); _stats.processIncomingPacketsElapsedTime += (end - start); @@ -79,6 +79,107 @@ int AvatarMixerSlave::sendIdentityPacket(const AvatarMixerClientData* nodeData, } } +qint64 AvatarMixerSlave::addChangedTraitsToBulkPacket(AvatarMixerClientData* listeningNodeData, + const AvatarMixerClientData* sendingNodeData, + NLPacketList& traitsPacketList) { + + auto otherNodeLocalID = sendingNodeData->getNodeLocalID(); + + // Perform a simple check with two server clock time points + // to see if there is any new traits data for this avatar that we need to send + auto timeOfLastTraitsSent = listeningNodeData->getLastOtherAvatarTraitsSendPoint(otherNodeLocalID); + auto timeOfLastTraitsChange = sendingNodeData->getLastReceivedTraitsChange(); + + qint64 bytesWritten = 0; + + if (timeOfLastTraitsChange > timeOfLastTraitsSent) { + // there is definitely new traits data to send + + // add the avatar ID to mark the beginning of traits for this avatar + bytesWritten += traitsPacketList.write(sendingNodeData->getNodeID().toRfc4122()); + + auto sendingAvatar = sendingNodeData->getAvatarSharedPointer(); + + // compare trait versions so we can see what exactly needs to go out + auto& lastSentVersions = listeningNodeData->getLastSentTraitVersions(otherNodeLocalID); + const auto& lastReceivedVersions = sendingNodeData->getLastReceivedTraitVersions(); + + auto simpleReceivedIt = lastReceivedVersions.simpleCBegin(); + while (simpleReceivedIt != lastReceivedVersions.simpleCEnd()) { + auto traitType = static_cast(std::distance(lastReceivedVersions.simpleCBegin(), + simpleReceivedIt)); + + auto lastReceivedVersion = *simpleReceivedIt; + auto& lastSentVersionRef = lastSentVersions[traitType]; + + if (lastReceivedVersions[traitType] > lastSentVersionRef) { + // there is an update to this trait, add it to the traits packet + bytesWritten += sendingAvatar->packTrait(traitType, traitsPacketList, lastReceivedVersion); + + // update the last sent version + lastSentVersionRef = lastReceivedVersion; + } + + ++simpleReceivedIt; + } + + // enumerate the received instanced trait versions + auto instancedReceivedIt = lastReceivedVersions.instancedCBegin(); + while (instancedReceivedIt != lastReceivedVersions.instancedCEnd()) { + auto traitType = instancedReceivedIt->traitType; + + // get or create the sent trait versions for this trait type + auto& sentIDValuePairs = lastSentVersions.getInstanceIDValuePairs(traitType); + + // enumerate each received instance + for (auto& receivedInstance : instancedReceivedIt->instances) { + auto instanceID = receivedInstance.id; + const auto receivedVersion = receivedInstance.value; + + // to track deletes and maintain version information for traits + // the mixer stores the negative value of the received version when a trait instance is deleted + bool isDeleted = receivedVersion < 0; + const auto absoluteReceivedVersion = std::abs(receivedVersion); + + // look for existing sent version for this instance + auto sentInstanceIt = std::find_if(sentIDValuePairs.begin(), sentIDValuePairs.end(), + [instanceID](auto& sentInstance) + { + return sentInstance.id == instanceID; + }); + + if (!isDeleted && (sentInstanceIt == sentIDValuePairs.end() || receivedVersion > sentInstanceIt->value)) { + // this instance version exists and has never been sent or is newer so we need to send it + bytesWritten += sendingAvatar->packTraitInstance(traitType, instanceID, traitsPacketList, receivedVersion); + + if (sentInstanceIt != sentIDValuePairs.end()) { + sentInstanceIt->value = receivedVersion; + } else { + sentIDValuePairs.emplace_back(instanceID, receivedVersion); + } + } else if (isDeleted && sentInstanceIt != sentIDValuePairs.end() && absoluteReceivedVersion > sentInstanceIt->value) { + // this instance version was deleted and we haven't sent the delete to this client yet + bytesWritten += AvatarTraits::packInstancedTraitDelete(traitType, instanceID, traitsPacketList, absoluteReceivedVersion); + + // update the last sent version for this trait instance to the absolute value of the deleted version + sentInstanceIt->value = absoluteReceivedVersion; + } + } + + ++instancedReceivedIt; + } + + // write a null trait type to mark the end of trait data for this avatar + bytesWritten += traitsPacketList.writePrimitive(AvatarTraits::NullTrait); + + // since we send all traits for this other avatar, update the time of last traits sent + // to match the time of last traits change + listeningNodeData->setLastOtherAvatarTraitsSendPoint(otherNodeLocalID, timeOfLastTraitsChange); + } + + return bytesWritten; +} + int AvatarMixerSlave::sendReplicatedIdentityPacket(const Node& agentNode, const AvatarMixerClientData* nodeData, const Node& destinationNode) { if (AvatarMixer::shouldReplicateTo(agentNode, destinationNode)) { QByteArray individualData = nodeData->getConstAvatarData()->identityByteArray(true); @@ -138,6 +239,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) // keep track of outbound data rate specifically for avatar data int numAvatarDataBytes = 0; int identityBytesSent = 0; + int traitBytesSent = 0; // max number of avatarBytes per frame auto maxAvatarBytesPerFrame = (_maxKbpsPerNode * BYTES_PER_KILOBIT) / AVATAR_MIXER_BROADCAST_FRAMES_PER_SECOND; @@ -227,6 +329,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) AvatarData::_avatarSortCoefficientSize, AvatarData::_avatarSortCoefficientCenter, AvatarData::_avatarSortCoefficientAge); + sortedAvatars.reserve(avatarsToSort.size()); // ignore or sort const AvatarSharedPointer& thisAvatar = nodeData->getAvatarSharedPointer(); @@ -326,9 +429,10 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) // loop through our sorted avatars and allocate our bandwidth to them accordingly int remainingAvatars = (int)sortedAvatars.size(); - while (!sortedAvatars.empty()) { - const auto avatarData = sortedAvatars.top().getAvatar(); - sortedAvatars.pop(); + auto traitsPacketList = NLPacketList::create(PacketType::BulkAvatarTraits, QByteArray(), true, true); + const auto& sortedAvatarVector = sortedAvatars.getSortedVector(); + for (const auto& sortedAvatar : sortedAvatarVector) { + const auto& avatarData = sortedAvatar.getAvatar(); remainingAvatars--; auto otherNode = avatarDataToNodes[avatarData]; @@ -392,11 +496,12 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) quint64 start = usecTimestampNow(); QByteArray bytes = otherAvatar->toByteArray(detail, lastEncodeForOther, lastSentJointsForOther, - hasFlagsOut, dropFaceTracking, distanceAdjust, viewerPosition, &lastSentJointsForOther); + hasFlagsOut, dropFaceTracking, distanceAdjust, viewerPosition, + &lastSentJointsForOther); quint64 end = usecTimestampNow(); _stats.toByteArrayElapsedTime += (end - start); - auto maxAvatarDataBytes = avatarPacketList->getMaxSegmentSize() - NUM_BYTES_RFC4122_UUID; + 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"; @@ -445,6 +550,11 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) quint64 endAvatarDataPacking = usecTimestampNow(); _stats.avatarDataPackingElapsedTime += (endAvatarDataPacking - startAvatarDataPacking); + + // use helper to add any changed traits to our packet list + traitBytesSent += addChangedTraitsToBulkPacket(nodeData, otherNodeData, *traitsPacketList); + + traitsPacketList->getDataSize(); } quint64 startPacketSending = usecTimestampNow(); @@ -461,6 +571,14 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) // record the bytes sent for other avatar data in the AvatarMixerClientData nodeData->recordSentAvatarData(numAvatarDataBytes); + // close the current traits packet list + traitsPacketList->closeCurrentPacket(); + + if (traitsPacketList->getNumPackets() >= 1) { + // send the traits packet list + nodeList->sendPacketList(std::move(traitsPacketList), *node); + } + // record the number of avatars held back this frame nodeData->recordNumOtherAvatarStarves(numAvatarsHeldBack); nodeData->recordNumOtherAvatarSkips(numAvatarsWithSkippedFrames); diff --git a/assignment-client/src/avatars/AvatarMixerSlave.h b/assignment-client/src/avatars/AvatarMixerSlave.h index 7be119c4b2..bcb70f8743 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.h +++ b/assignment-client/src/avatars/AvatarMixerSlave.h @@ -78,11 +78,16 @@ public: jobElapsedTime += rhs.jobElapsedTime; return *this; } +}; +struct SlaveSharedData { + QStringList skeletonURLWhitelist; + QUrl skeletonReplacementURL; }; class AvatarMixerSlave { public: + AvatarMixerSlave(SlaveSharedData* sharedData) : _sharedData(sharedData) {}; using ConstIter = NodeList::const_iterator; void configure(ConstIter begin, ConstIter end); @@ -99,6 +104,10 @@ private: int sendIdentityPacket(const AvatarMixerClientData* nodeData, const SharedNodePointer& destinationNode); int sendReplicatedIdentityPacket(const Node& agentNode, const AvatarMixerClientData* nodeData, const Node& destinationNode); + qint64 addChangedTraitsToBulkPacket(AvatarMixerClientData* listeningNodeData, + const AvatarMixerClientData* sendingNodeData, + NLPacketList& traitsPacketList); + void broadcastAvatarDataToAgent(const SharedNodePointer& node); void broadcastAvatarDataToDownstreamMixer(const SharedNodePointer& node); @@ -111,6 +120,7 @@ private: float _throttlingRatio { 0.0f }; AvatarMixerSlaveStats _stats; + SlaveSharedData* _sharedData; }; #endif // hifi_AvatarMixerSlave_h diff --git a/assignment-client/src/avatars/AvatarMixerSlavePool.cpp b/assignment-client/src/avatars/AvatarMixerSlavePool.cpp index 962bba21d2..cf842ac792 100644 --- a/assignment-client/src/avatars/AvatarMixerSlavePool.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlavePool.cpp @@ -168,7 +168,7 @@ void AvatarMixerSlavePool::resize(int numThreads) { if (numThreads > _numThreads) { // start new slaves for (int i = 0; i < numThreads - _numThreads; ++i) { - auto slave = new AvatarMixerSlaveThread(*this); + auto slave = new AvatarMixerSlaveThread(*this, _slaveSharedData); slave->start(); _slaves.emplace_back(slave); } diff --git a/assignment-client/src/avatars/AvatarMixerSlavePool.h b/assignment-client/src/avatars/AvatarMixerSlavePool.h index 15bd681b2c..71a9ace0d3 100644 --- a/assignment-client/src/avatars/AvatarMixerSlavePool.h +++ b/assignment-client/src/avatars/AvatarMixerSlavePool.h @@ -32,7 +32,8 @@ class AvatarMixerSlaveThread : public QThread, public AvatarMixerSlave { using Lock = std::unique_lock; public: - AvatarMixerSlaveThread(AvatarMixerSlavePool& pool) : _pool(pool) {} + AvatarMixerSlaveThread(AvatarMixerSlavePool& pool, SlaveSharedData* slaveSharedData) : + AvatarMixerSlave(slaveSharedData), _pool(pool) {}; void run() override final; @@ -59,7 +60,8 @@ class AvatarMixerSlavePool { public: using ConstIter = NodeList::const_iterator; - AvatarMixerSlavePool(int numThreads = QThread::idealThreadCount()) { setNumThreads(numThreads); } + AvatarMixerSlavePool(SlaveSharedData* slaveSharedData, int numThreads = QThread::idealThreadCount()) : + _slaveSharedData(slaveSharedData) { setNumThreads(numThreads); } ~AvatarMixerSlavePool() { resize(0); } // Jobs the slave pool can do... @@ -98,6 +100,8 @@ private: Queue _queue; ConstIter _begin; ConstIter _end; + + SlaveSharedData* _slaveSharedData; }; #endif // hifi_AvatarMixerSlavePool_h diff --git a/assignment-client/src/avatars/ScriptableAvatar.cpp b/assignment-client/src/avatars/ScriptableAvatar.cpp index e7210db83a..6f04cfa196 100644 --- a/assignment-client/src/avatars/ScriptableAvatar.cpp +++ b/assignment-client/src/avatars/ScriptableAvatar.cpp @@ -1,6 +1,6 @@ // // ScriptableAvatar.cpp -// +// assignment-client/src/avatars // // Created by Clement on 7/22/14. // Copyright 2014 High Fidelity, Inc. @@ -16,9 +16,13 @@ #include #include -#include #include +#include +#include +ScriptableAvatar::ScriptableAvatar() { + _clientTraitsHandler = std::unique_ptr(new ClientTraitsHandler(this)); +} QByteArray ScriptableAvatar::toByteArrayStateful(AvatarDataDetail dataDetail, bool dropFaceTracking) { _globalPosition = getWorldPosition(); @@ -61,6 +65,7 @@ AnimationDetails ScriptableAvatar::getAnimationDetails() { void ScriptableAvatar::setSkeletonModelURL(const QUrl& skeletonModelURL) { _bind.reset(); _animSkeleton.reset(); + AvatarData::setSkeletonModelURL(skeletonModelURL); } @@ -137,4 +142,6 @@ void ScriptableAvatar::update(float deltatime) { _animation.clear(); } } + + _clientTraitsHandler->sendChangedTraitsToMixer(); } diff --git a/assignment-client/src/avatars/ScriptableAvatar.h b/assignment-client/src/avatars/ScriptableAvatar.h index d34ad2d21e..89f9369133 100644 --- a/assignment-client/src/avatars/ScriptableAvatar.h +++ b/assignment-client/src/avatars/ScriptableAvatar.h @@ -1,6 +1,6 @@ // // ScriptableAvatar.h -// +// assignment-client/src/avatars // // Created by Clement on 7/22/14. // Copyright 2014 High Fidelity, Inc. @@ -123,7 +123,9 @@ class ScriptableAvatar : public AvatarData, public Dependency { Q_OBJECT public: - + + ScriptableAvatar(); + /**jsdoc * @function Avatar.startAnimation * @param {string} url diff --git a/assignment-client/src/entities/EntityServer.cpp b/assignment-client/src/entities/EntityServer.cpp index 8b86ba5eb2..089fb3e52f 100644 --- a/assignment-client/src/entities/EntityServer.cpp +++ b/assignment-client/src/entities/EntityServer.cpp @@ -24,6 +24,7 @@ #include #include +#include "../AssignmentDynamicFactory.h" #include "AssignmentParentFinder.h" #include "EntityNodeData.h" #include "EntityServerConsts.h" @@ -42,6 +43,9 @@ EntityServer::EntityServer(ReceivedMessage& message) : DependencyManager::set(); DependencyManager::set(); + DependencyManager::registerInheritance(); + DependencyManager::set(); + auto& packetReceiver = DependencyManager::get()->getPacketReceiver(); packetReceiver.registerListenerForTypes({ PacketType::EntityAdd, PacketType::EntityClone, @@ -71,6 +75,8 @@ EntityServer::~EntityServer() { void EntityServer::aboutToFinish() { DependencyManager::get()->cleanup(); + DependencyManager::destroy(); + OctreeServer::aboutToFinish(); } @@ -522,11 +528,8 @@ void EntityServer::startDynamicDomainVerification() { qCDebug(entities) << "Entity passed dynamic domain verification:" << entityID; } } else { - qCDebug(entities) << "Call to" << networkReply->url() << "failed with error" << networkReply->error() << "; deleting entity" << entityID + qCDebug(entities) << "Call to" << networkReply->url() << "failed with error" << networkReply->error() << "; NOT deleting entity" << entityID << "More info:" << jsonObject; - tree->withWriteLock([&] { - tree->deleteEntity(entityID, true); - }); } networkReply->deleteLater(); diff --git a/assignment-client/src/entities/EntityTreeSendThread.cpp b/assignment-client/src/entities/EntityTreeSendThread.cpp index f008ef9925..8b7c8771e8 100644 --- a/assignment-client/src/entities/EntityTreeSendThread.cpp +++ b/assignment-client/src/entities/EntityTreeSendThread.cpp @@ -17,7 +17,6 @@ #include "EntityServer.h" - EntityTreeSendThread::EntityTreeSendThread(OctreeServer* myServer, const SharedNodePointer& node) : OctreeSendThread(myServer, node) { @@ -100,7 +99,7 @@ void EntityTreeSendThread::preDistributionProcessing() { } } -void EntityTreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, OctreeQueryNode* nodeData, +bool EntityTreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, OctreeQueryNode* nodeData, bool viewFrustumChanged, bool isFullScene) { if (viewFrustumChanged || _traversal.finished()) { EntityTreeElementPointer root = std::dynamic_pointer_cast(_myServer->getOctree()->getRoot()); @@ -111,7 +110,7 @@ void EntityTreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, O int32_t lodLevelOffset = nodeData->getBoundaryLevelAdjust() + (viewFrustumChanged ? LOW_RES_MOVING_ADJUST : NO_BOUNDARY_ADJUST); newView.lodScaleFactor = powf(2.0f, lodLevelOffset); - + startNewTraversal(newView, root); // When the viewFrustum changed the sort order may be incorrect, so we re-sort @@ -156,7 +155,20 @@ void EntityTreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, O OctreeServer::trackTreeTraverseTime((float)(usecTimestampNow() - startTime)); } - OctreeSendThread::traverseTreeAndSendContents(node, nodeData, viewFrustumChanged, isFullScene); + bool sendComplete = OctreeSendThread::traverseTreeAndSendContents(node, nodeData, viewFrustumChanged, isFullScene); + + if (sendComplete && nodeData->wantReportInitialCompletion() && _traversal.finished()) { + // Dealt with all nearby entities. + nodeData->setReportInitialCompletion(false); + + // Send EntityQueryInitialResultsComplete reliable packet ... + auto initialCompletion = NLPacket::create(PacketType::EntityQueryInitialResultsComplete, + sizeof(OCTREE_PACKET_SEQUENCE), true); + initialCompletion->writePrimitive(OCTREE_PACKET_SEQUENCE(nodeData->getSequenceNumber())); + DependencyManager::get()->sendPacket(std::move(initialCompletion), *node); + } + + return sendComplete; } bool EntityTreeSendThread::addAncestorsToExtraFlaggedEntities(const QUuid& filteredEntityID, @@ -301,6 +313,7 @@ void EntityTreeSendThread::startNewTraversal(const DiffTraversal::View& view, En bool EntityTreeSendThread::traverseTreeAndBuildNextPacketPayload(EncodeBitstreamParams& params, const QJsonObject& jsonFilters) { if (_sendQueue.empty()) { + params.stopReason = EncodeBitstreamParams::FINISHED; OctreeServer::trackEncodeTime(OctreeServer::SKIP_TIME); return false; } diff --git a/assignment-client/src/entities/EntityTreeSendThread.h b/assignment-client/src/entities/EntityTreeSendThread.h index 1305d7bfc7..199769ca09 100644 --- a/assignment-client/src/entities/EntityTreeSendThread.h +++ b/assignment-client/src/entities/EntityTreeSendThread.h @@ -31,7 +31,7 @@ public: EntityTreeSendThread(OctreeServer* myServer, const SharedNodePointer& node); protected: - void traverseTreeAndSendContents(SharedNodePointer node, OctreeQueryNode* nodeData, + bool traverseTreeAndSendContents(SharedNodePointer node, OctreeQueryNode* nodeData, bool viewFrustumChanged, bool isFullScene) override; private slots: diff --git a/assignment-client/src/octree/OctreeSendThread.cpp b/assignment-client/src/octree/OctreeSendThread.cpp index e9aa44b970..ab357f4146 100644 --- a/assignment-client/src/octree/OctreeSendThread.cpp +++ b/assignment-client/src/octree/OctreeSendThread.cpp @@ -211,10 +211,9 @@ int OctreeSendThread::handlePacketSend(SharedNodePointer node, OctreeQueryNode* //_totalWastedBytes += 0; _trueBytesSent += numBytes; numPackets++; + NLPacket& sentPacket = nodeData->getPacket(); if (debug) { - NLPacket& sentPacket = nodeData->getPacket(); - sentPacket.seek(sizeof(OCTREE_PACKET_FLAGS)); OCTREE_PACKET_SEQUENCE sequence; @@ -231,9 +230,9 @@ int OctreeSendThread::handlePacketSend(SharedNodePointer node, OctreeQueryNode* // second packet OctreeServer::didCallWriteDatagram(this); - DependencyManager::get()->sendUnreliablePacket(nodeData->getPacket(), *node); + DependencyManager::get()->sendUnreliablePacket(sentPacket, *node); - numBytes = nodeData->getPacket().getDataSize(); + numBytes = sentPacket.getDataSize(); _totalBytes += numBytes; _totalPackets++; // we count wasted bytes here because we were unable to fit the stats packet @@ -243,8 +242,6 @@ int OctreeSendThread::handlePacketSend(SharedNodePointer node, OctreeQueryNode* numPackets++; if (debug) { - NLPacket& sentPacket = nodeData->getPacket(); - sentPacket.seek(sizeof(OCTREE_PACKET_FLAGS)); OCTREE_PACKET_SEQUENCE sequence; @@ -265,9 +262,10 @@ int OctreeSendThread::handlePacketSend(SharedNodePointer node, OctreeQueryNode* if (nodeData->isPacketWaiting() && !nodeData->isShuttingDown()) { // just send the octree packet OctreeServer::didCallWriteDatagram(this); - DependencyManager::get()->sendUnreliablePacket(nodeData->getPacket(), *node); + NLPacket& sentPacket = nodeData->getPacket(); + DependencyManager::get()->sendUnreliablePacket(sentPacket, *node); - int numBytes = nodeData->getPacket().getDataSize(); + int numBytes = sentPacket.getDataSize(); _totalBytes += numBytes; _totalPackets++; int thisWastedBytes = udt::MAX_PACKET_SIZE - numBytes; @@ -276,8 +274,6 @@ int OctreeSendThread::handlePacketSend(SharedNodePointer node, OctreeQueryNode* _trueBytesSent += numBytes; if (debug) { - NLPacket& sentPacket = nodeData->getPacket(); - sentPacket.seek(sizeof(OCTREE_PACKET_FLAGS)); OCTREE_PACKET_SEQUENCE sequence; @@ -434,7 +430,7 @@ int OctreeSendThread::packetDistributor(SharedNodePointer node, OctreeQueryNode* return _truePacketsSent; } -void OctreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, OctreeQueryNode* nodeData, bool viewFrustumChanged, bool isFullScene) { +bool OctreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, OctreeQueryNode* nodeData, bool viewFrustumChanged, bool isFullScene) { // calculate max number of packets that can be sent during this interval int clientMaxPacketsPerInterval = std::max(1, (nodeData->getMaxQueryPacketsPerSecond() / INTERVALS_PER_SECOND)); int maxPacketsPerInterval = std::min(clientMaxPacketsPerInterval, _myServer->getPacketsPerClientPerInterval()); @@ -517,4 +513,6 @@ void OctreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, Octre << " maxPacketsPerInterval = " << maxPacketsPerInterval << " clientMaxPacketsPerInterval = " << clientMaxPacketsPerInterval; } + + return params.stopReason == EncodeBitstreamParams::FINISHED; } diff --git a/assignment-client/src/octree/OctreeSendThread.h b/assignment-client/src/octree/OctreeSendThread.h index 91c0ec7adc..bdf0f03364 100644 --- a/assignment-client/src/octree/OctreeSendThread.h +++ b/assignment-client/src/octree/OctreeSendThread.h @@ -52,7 +52,7 @@ protected: /// Implements generic processing behavior for this thread. virtual bool process() override; - virtual void traverseTreeAndSendContents(SharedNodePointer node, OctreeQueryNode* nodeData, + virtual bool traverseTreeAndSendContents(SharedNodePointer node, OctreeQueryNode* nodeData, bool viewFrustumChanged, bool isFullScene); virtual bool traverseTreeAndBuildNextPacketPayload(EncodeBitstreamParams& params, const QJsonObject& jsonFilters) = 0; diff --git a/assignment-client/src/scripts/EntityScriptServer.cpp b/assignment-client/src/scripts/EntityScriptServer.cpp index ebe25b11bf..272985093c 100644 --- a/assignment-client/src/scripts/EntityScriptServer.cpp +++ b/assignment-client/src/scripts/EntityScriptServer.cpp @@ -24,14 +24,16 @@ #include #include #include +#include #include #include -#include +#include #include #include #include // for EntityScriptServerServices +#include "../AssignmentDynamicFactory.h" #include "EntityScriptServerLogging.h" #include "../entities/AssignmentParentFinder.h" @@ -55,7 +57,11 @@ int EntityScriptServer::_entitiesScriptEngineCount = 0; EntityScriptServer::EntityScriptServer(ReceivedMessage& message) : ThreadedAssignment(message) { qInstallMessageHandler(messageHandler); - DependencyManager::get()->setPacketSender(&_entityEditSender); + DependencyManager::registerInheritance(); + DependencyManager::set(); + + DependencyManager::set(false)->setPacketSender(&_entityEditSender); + DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); @@ -66,6 +72,7 @@ EntityScriptServer::EntityScriptServer(ReceivedMessage& message) : ThreadedAssig DependencyManager::set(); DependencyManager::set(); + DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); @@ -80,9 +87,6 @@ EntityScriptServer::EntityScriptServer(ReceivedMessage& message) : ThreadedAssig packetReceiver.registerListener(PacketType::SelectedAudioFormat, this, "handleSelectedAudioFormat"); auto avatarHashMap = DependencyManager::set(); - packetReceiver.registerListener(PacketType::BulkAvatarData, avatarHashMap.data(), "processAvatarDataPacket"); - packetReceiver.registerListener(PacketType::KillAvatar, avatarHashMap.data(), "processKillAvatar"); - packetReceiver.registerListener(PacketType::AvatarIdentity, avatarHashMap.data(), "processAvatarIdentityPacket"); packetReceiver.registerListener(PacketType::ReloadEntityServerScript, this, "handleReloadEntityServerScriptPacket"); packetReceiver.registerListener(PacketType::EntityScriptGetStatus, this, "handleEntityScriptGetStatusPacket"); @@ -438,7 +442,7 @@ void EntityScriptServer::resetEntitiesScriptEngine() { auto webSocketServerConstructorValue = newEngine->newFunction(WebSocketServerClass::constructor); newEngine->globalObject().setProperty("WebSocketServer", webSocketServerConstructorValue); - newEngine->registerGlobalObject("SoundCache", DependencyManager::get().data()); + newEngine->registerGlobalObject("SoundCache", DependencyManager::get().data()); // connect this script engines printedMessage signal to the global ScriptEngines these various messages auto scriptEngines = DependencyManager::get().data(); @@ -457,8 +461,11 @@ void EntityScriptServer::resetEntitiesScriptEngine() { auto newEngineSP = qSharedPointerCast(newEngine); DependencyManager::get()->setEntitiesScriptEngine(newEngineSP); - disconnect(_entitiesScriptEngine.data(), &ScriptEngine::entityScriptDetailsUpdated, - this, &EntityScriptServer::updateEntityPPS); + if (_entitiesScriptEngine) { + disconnect(_entitiesScriptEngine.data(), &ScriptEngine::entityScriptDetailsUpdated, + this, &EntityScriptServer::updateEntityPPS); + } + _entitiesScriptEngine.swap(newEngine); connect(_entitiesScriptEngine.data(), &ScriptEngine::entityScriptDetailsUpdated, this, &EntityScriptServer::updateEntityPPS); @@ -489,6 +496,21 @@ void EntityScriptServer::shutdownScriptEngine() { _shuttingDown = true; clear(); // always clear() on shutdown + + auto scriptEngines = DependencyManager::get(); + scriptEngines->shutdownScripting(); + + _entitiesScriptEngine.clear(); + + auto entityScriptingInterface = DependencyManager::get(); + // our entity tree is going to go away so tell that to the EntityScriptingInterface + entityScriptingInterface->setEntityTree(nullptr); + + // Should always be true as they are singletons. + if (entityScriptingInterface->getPacketSender() == &_entityEditSender) { + // The packet sender is about to go away. + entityScriptingInterface->setPacketSender(nullptr); + } } void EntityScriptServer::addingEntity(const EntityItemID& entityID) { @@ -561,24 +583,19 @@ void EntityScriptServer::handleOctreePacket(QSharedPointer mess void EntityScriptServer::aboutToFinish() { shutdownScriptEngine(); - auto entityScriptingInterface = DependencyManager::get(); - // our entity tree is going to go away so tell that to the EntityScriptingInterface - entityScriptingInterface->setEntityTree(nullptr); - - // Should always be true as they are singletons. - if (entityScriptingInterface->getPacketSender() == &_entityEditSender) { - // The packet sender is about to go away. - entityScriptingInterface->setPacketSender(nullptr); - } - + DependencyManager::destroy(); DependencyManager::destroy(); DependencyManager::get()->cleanup(); DependencyManager::destroy(); + DependencyManager::destroy(); + DependencyManager::destroy(); + // cleanup the AudioInjectorManager (and any still running injectors) DependencyManager::destroy(); + DependencyManager::destroy(); DependencyManager::destroy(); diff --git a/cmake/compiler.cmake b/cmake/compiler.cmake index de9f8a22fa..6ff6fce1d8 100644 --- a/cmake/compiler.cmake +++ b/cmake/compiler.cmake @@ -1,10 +1,12 @@ set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DDEBUG") +set(CMAKE_CXX_STANDARD 14) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +if (NOT "${CMAKE_SIZEOF_VOID_P}" EQUAL "8") + message( FATAL_ERROR "Only 64 bit builds supported." ) +endif() if (WIN32) - if (NOT "${CMAKE_SIZEOF_VOID_P}" EQUAL "8") - message( FATAL_ERROR "Only 64 bit builds supported." ) - endif() - add_definitions(-DNOMINMAX -D_CRT_SECURE_NO_WARNINGS) if (NOT WINDOW_SDK_PATH) @@ -52,32 +54,27 @@ endif() if (ANDROID) # assume that the toolchain selected for android has C++11 support - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14") +elseif(APPLE) + set(CMAKE_XCODE_ATTRIBUTE_CLANG_CXX_LANGUAGE_STANDARD "c++14") + set(CMAKE_XCODE_ATTRIBUTE_CLANG_CXX_LIBRARY "libc++") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --stdlib=libc++") + if (CMAKE_GENERATOR STREQUAL "Xcode") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g") + set(CMAKE_XCODE_ATTRIBUTE_GCC_GENERATE_DEBUGGING_SYMBOLS[variant=Release] "YES") + set(CMAKE_XCODE_ATTRIBUTE_DEBUG_INFORMATION_FORMAT[variant=Release] "dwarf-with-dsym") + set(CMAKE_XCODE_ATTRIBUTE_DEPLOYMENT_POSTPROCESSING[variant=Release] "YES") + endif() elseif ((NOT MSVC12) AND (NOT MSVC14)) include(CheckCXXCompilerFlag) - CHECK_CXX_COMPILER_FLAG("-std=c++11" COMPILER_SUPPORTS_CXX11) - CHECK_CXX_COMPILER_FLAG("-std=c++0x" COMPILER_SUPPORTS_CXX0X) - if (COMPILER_SUPPORTS_CXX11) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") - elseif(COMPILER_SUPPORTS_CXX0X) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x") + CHECK_CXX_COMPILER_FLAG("-std=c++14" COMPILER_SUPPORTS_CXX14) + if (COMPILER_SUPPORTS_CXX14) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14") else() message(FATAL_ERROR "The compiler ${CMAKE_CXX_COMPILER} has no C++11 support. Please use a different C++ compiler.") endif() endif () -if (CMAKE_GENERATOR STREQUAL "Xcode") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g") - set(CMAKE_XCODE_ATTRIBUTE_GCC_GENERATE_DEBUGGING_SYMBOLS[variant=Release] "YES") - set(CMAKE_XCODE_ATTRIBUTE_DEBUG_INFORMATION_FORMAT[variant=Release] "dwarf-with-dsym") - set(CMAKE_XCODE_ATTRIBUTE_DEPLOYMENT_POSTPROCESSING[variant=Release] "YES") -endif() - -if (APPLE) - set(CMAKE_XCODE_ATTRIBUTE_CLANG_CXX_LANGUAGE_STANDARD "c++11") - set(CMAKE_XCODE_ATTRIBUTE_CLANG_CXX_LIBRARY "libc++") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --stdlib=libc++") -endif () if (NOT ANDROID_LIB_DIR) set(ANDROID_LIB_DIR $ENV{ANDROID_LIB_DIR}) @@ -110,4 +107,4 @@ if (APPLE) # set that as the SDK to use set(CMAKE_OSX_SYSROOT ${_OSX_DESIRED_SDK_PATH}/MacOSX${OSX_SDK}.sdk) endif () -endif () \ No newline at end of file +endif () diff --git a/cmake/externals/json/CMakeLists.txt b/cmake/externals/json/CMakeLists.txt new file mode 100644 index 0000000000..91bdb09fc8 --- /dev/null +++ b/cmake/externals/json/CMakeLists.txt @@ -0,0 +1,22 @@ +set(EXTERNAL_NAME json) + +include(ExternalProject) +ExternalProject_Add( + ${EXTERNAL_NAME} + URL https://hifi-public.s3.amazonaws.com/dependencies/json_3.1.2.zip + URL_MD5 94dbf6ea25a7569ddc0ab6e20862cf16 + BINARY_DIR ${EXTERNAL_PROJECT_PREFIX}/build + CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH= ${EXTERNAL_ARGS} + CONFIGURE_COMMAND "" + BUILD_COMMAND "" + INSTALL_COMMAND "" + LOG_DOWNLOAD 1 +) + +# Hide this external target (for ide users) +set_target_properties(${EXTERNAL_NAME} PROPERTIES FOLDER "hidden/externals") + +ExternalProject_Get_Property(${EXTERNAL_NAME} SOURCE_DIR) + +string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER) +set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${SOURCE_DIR} CACHE PATH "List of json include directories") diff --git a/cmake/externals/serverless-content/CMakeLists.txt b/cmake/externals/serverless-content/CMakeLists.txt index 8505e0bab8..12e2b7a4c6 100644 --- a/cmake/externals/serverless-content/CMakeLists.txt +++ b/cmake/externals/serverless-content/CMakeLists.txt @@ -4,8 +4,8 @@ set(EXTERNAL_NAME serverless-content) ExternalProject_Add( ${EXTERNAL_NAME} - URL http://cdn.highfidelity.com/content-sets/serverless-tutorial-RC69.zip - URL_MD5 e2467b08de069da7e22ec8e032435592 + URL http://cdn.highfidelity.com/content-sets/serverless-tutorial-RC73.zip + URL_MD5 0c5edfb63cafb042311d3cf25261fbf2 CONFIGURE_COMMAND "" BUILD_COMMAND "" INSTALL_COMMAND "" diff --git a/cmake/macros/AutoScribeShader.cmake b/cmake/macros/AutoScribeShader.cmake index c4cd2d186a..1d1e5970d9 100755 --- a/cmake/macros/AutoScribeShader.cmake +++ b/cmake/macros/AutoScribeShader.cmake @@ -8,120 +8,224 @@ # See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html # -function(AUTOSCRIBE_SHADER SHADER_FILE) - # Grab include files - foreach(includeFile ${ARGN}) - list(APPEND SHADER_INCLUDE_FILES ${includeFile}) - endforeach() +macro(AUTOSCRIBE_SHADER) + message(STATUS "Processing shader ${SHADER_FILE}") + unset(SHADER_INCLUDE_FILES) + # Grab include files + foreach(includeFile ${ARGN}) + list(APPEND SHADER_INCLUDE_FILES ${includeFile}) + endforeach() - foreach(SHADER_INCLUDE ${SHADER_INCLUDE_FILES}) - get_filename_component(INCLUDE_DIR ${SHADER_INCLUDE} PATH) - list(APPEND SHADER_INCLUDES_PATHS ${INCLUDE_DIR}) - endforeach() + foreach(SHADER_INCLUDE ${SHADER_INCLUDE_FILES}) + get_filename_component(INCLUDE_DIR ${SHADER_INCLUDE} PATH) + list(APPEND SHADER_INCLUDES_PATHS ${INCLUDE_DIR}) + endforeach() - #Extract the unique include shader paths - set(INCLUDES ${HIFI_LIBRARIES_SHADER_INCLUDE_FILES}) - #message(${TARGET_NAME} Hifi for includes ${INCLUDES}) - foreach(EXTRA_SHADER_INCLUDE ${INCLUDES}) - list(APPEND SHADER_INCLUDES_PATHS ${EXTRA_SHADER_INCLUDE}) - endforeach() + list(REMOVE_DUPLICATES SHADER_INCLUDES_PATHS) + #Extract the unique include shader paths + set(INCLUDES ${HIFI_LIBRARIES_SHADER_INCLUDE_FILES}) + foreach(EXTRA_SHADER_INCLUDE ${INCLUDES}) + list(APPEND SHADER_INCLUDES_PATHS ${EXTRA_SHADER_INCLUDE}) + endforeach() - list(REMOVE_DUPLICATES SHADER_INCLUDES_PATHS) - #message(ready for includes ${SHADER_INCLUDES_PATHS}) + list(REMOVE_DUPLICATES SHADER_INCLUDES_PATHS) + #message(ready for includes ${SHADER_INCLUDES_PATHS}) - # make the scribe include arguments - set(SCRIBE_INCLUDES) - foreach(INCLUDE_PATH ${SHADER_INCLUDES_PATHS}) - set(SCRIBE_INCLUDES ${SCRIBE_INCLUDES} -I ${INCLUDE_PATH}/) - endforeach() + # make the scribe include arguments + set(SCRIBE_INCLUDES) + foreach(INCLUDE_PATH ${SHADER_INCLUDES_PATHS}) + set(SCRIBE_INCLUDES ${SCRIBE_INCLUDES} -I ${INCLUDE_PATH}/) + endforeach() - # Define the final name of the generated shader file - get_filename_component(SHADER_TARGET ${SHADER_FILE} NAME_WE) - get_filename_component(SHADER_EXT ${SHADER_FILE} EXT) - if(SHADER_EXT STREQUAL .slv) - set(SHADER_TYPE vert) - elseif(${SHADER_EXT} STREQUAL .slf) - set(SHADER_TYPE frag) - elseif(${SHADER_EXT} STREQUAL .slg) - set(SHADER_TYPE geom) - endif() - set(SHADER_TARGET ${SHADER_TARGET}_${SHADER_TYPE}) + # Define the final name of the generated shader file + get_filename_component(SHADER_NAME ${SHADER_FILE} NAME_WE) + get_filename_component(SHADER_EXT ${SHADER_FILE} EXT) + if(SHADER_EXT STREQUAL .slv) + set(SHADER_TYPE vert) + elseif(${SHADER_EXT} STREQUAL .slf) + set(SHADER_TYPE frag) + elseif(${SHADER_EXT} STREQUAL .slg) + set(SHADER_TYPE geom) + endif() + file(MAKE_DIRECTORY "${SHADERS_DIR}/${SHADER_LIB}") + set(SHADER_TARGET "${SHADERS_DIR}/${SHADER_LIB}/${SHADER_NAME}.${SHADER_TYPE}") + file(TO_CMAKE_PATH "${SHADER_TARGET}" COMPILED_SHADER) + set(REFLECTED_SHADER "${COMPILED_SHADER}.json") - set(SHADER_TARGET "${SHADERS_DIR}/${SHADER_TARGET}") - set(SHADER_TARGET_HEADER ${SHADER_TARGET}.h) - set(SHADER_TARGET_SOURCE ${SHADER_TARGET}.cpp) - set(SCRIBE_COMMAND scribe) + set(SCRIBE_ARGS -T ${SHADER_TYPE} -D GLPROFILE ${GLPROFILE} ${SCRIBE_INCLUDES} -o ${SHADER_TARGET} ${SHADER_FILE}) - # Target dependant Custom rule on the SHADER_FILE - if (APPLE) - set(GLPROFILE MAC_GL) - elseif (ANDROID) - set(GLPROFILE LINUX_GL) - set(SCRIBE_COMMAND ${NATIVE_SCRIBE}) - elseif (UNIX) - set(GLPROFILE LINUX_GL) - else () - set(GLPROFILE PC_GL) - endif() - set(SCRIBE_ARGS -c++ -T ${SHADER_TYPE} -D GLPROFILE ${GLPROFILE} ${SCRIBE_INCLUDES} -o ${SHADER_TARGET} ${SHADER_FILE}) - add_custom_command( - OUTPUT ${SHADER_TARGET_HEADER} ${SHADER_TARGET_SOURCE} - COMMAND ${SCRIBE_COMMAND} ${SCRIBE_ARGS} - DEPENDS ${SCRIBE_COMMAND} ${SHADER_INCLUDE_FILES} ${SHADER_FILE} - ) + # Generate the frag/vert file + add_custom_command( + OUTPUT ${SHADER_TARGET} + COMMAND ${SCRIBE_COMMAND} ${SCRIBE_ARGS} + DEPENDS ${SHADER_FILE} ${SCRIBE_COMMAND} ${SHADER_INCLUDE_FILES}) - #output the generated file name - set(AUTOSCRIBE_SHADER_RETURN ${SHADER_TARGET_HEADER} ${SHADER_TARGET_SOURCE} PARENT_SCOPE) + # Generate the json reflection + # FIXME move to spirv-cross for this task after we have spirv compatible shaders + add_custom_command( + OUTPUT ${REFLECTED_SHADER} + COMMAND ${SHREFLECT_COMMAND} ${COMPILED_SHADER} + DEPENDS ${SHREFLECT_DEPENDENCY} ${COMPILED_SHADER}) - file(GLOB INCLUDE_FILES ${SHADER_TARGET_HEADER}) + #output the generated file name + source_group("Compiled/${SHADER_LIB}" FILES ${COMPILED_SHADER}) + set_property(SOURCE ${COMPILED_SHADER} PROPERTY SKIP_AUTOMOC ON) + list(APPEND COMPILED_SHADERS ${COMPILED_SHADER}) -endfunction() + source_group("Reflected/${SHADER_LIB}" FILES ${REFLECTED_SHADER}) + list(APPEND REFLECTED_SHADERS ${REFLECTED_SHADER}) + string(CONCAT SHADER_QRC "${SHADER_QRC}" "${COMPILED_SHADER}\n") + string(CONCAT SHADER_QRC "${SHADER_QRC}" "${REFLECTED_SHADER}\n") + string(CONCAT SHADER_ENUMS "${SHADER_ENUMS}" "${SHADER_NAME} = ${SHADER_COUNT},\n") + + MATH(EXPR SHADER_COUNT "${SHADER_COUNT}+1") +endmacro() macro(AUTOSCRIBE_SHADER_LIB) - set(HIFI_LIBRARIES_SHADER_INCLUDE_FILES "") - file(RELATIVE_PATH RELATIVE_LIBRARY_DIR_PATH ${CMAKE_CURRENT_SOURCE_DIR} "${HIFI_LIBRARY_DIR}") - foreach(HIFI_LIBRARY ${ARGN}) - #if (NOT TARGET ${HIFI_LIBRARY}) - # file(GLOB_RECURSE HIFI_LIBRARIES_SHADER_INCLUDE_FILES ${RELATIVE_LIBRARY_DIR_PATH}/${HIFI_LIBRARY}/src/) - #endif () + if (NOT ("${TARGET_NAME}" STREQUAL "shaders")) + message(FATAL_ERROR "AUTOSCRIBE_SHADER_LIB can only be used by the shaders library") + endif() - #file(GLOB_RECURSE HIFI_LIBRARIES_SHADER_INCLUDE_FILES ${HIFI_LIBRARY_DIR}/${HIFI_LIBRARY}/src/*.slh) - list(APPEND HIFI_LIBRARIES_SHADER_INCLUDE_FILES ${HIFI_LIBRARY_DIR}/${HIFI_LIBRARY}/src) - endforeach() - #message("${TARGET_NAME} ${HIFI_LIBRARIES_SHADER_INCLUDE_FILES}") + list(APPEND HIFI_LIBRARIES_SHADER_INCLUDE_FILES "${CMAKE_SOURCE_DIR}/libraries/${SHADER_LIB}/src") + string(REGEX REPLACE "[-]" "_" SHADER_NAMESPACE ${SHADER_LIB}) + string(CONCAT SHADER_ENUMS "${SHADER_ENUMS}" "namespace ${SHADER_NAMESPACE} {\n") + set(SRC_FOLDER "${CMAKE_SOURCE_DIR}/libraries/${ARGN}/src") - file(GLOB_RECURSE SHADER_INCLUDE_FILES src/*.slh) - file(GLOB_RECURSE SHADER_SOURCE_FILES src/*.slv src/*.slf src/*.slg) + # Process the scribe headers + file(GLOB_RECURSE SHADER_INCLUDE_FILES ${SRC_FOLDER}/*.slh) + if(SHADER_INCLUDE_FILES) + source_group("${SHADER_LIB}/Headers" FILES ${SHADER_INCLUDE_FILES}) + list(APPEND ALL_SHADER_HEADERS ${SHADER_INCLUDE_FILES}) + list(APPEND ALL_SCRIBE_SHADERS ${SHADER_INCLUDE_FILES}) + endif() - #make the shader folder - set(SHADERS_DIR "${CMAKE_CURRENT_BINARY_DIR}/shaders/${TARGET_NAME}") - file(MAKE_DIRECTORY ${SHADERS_DIR}) + file(GLOB_RECURSE SHADER_VERTEX_FILES ${SRC_FOLDER}/*.slv) + if (SHADER_VERTEX_FILES) + source_group("${SHADER_LIB}/Vertex" FILES ${SHADER_VERTEX_FILES}) + list(APPEND ALL_SCRIBE_SHADERS ${SHADER_VERTEX_FILES}) + string(CONCAT SHADER_ENUMS "${SHADER_ENUMS}" "namespace vertex { enum {\n") + foreach(SHADER_FILE ${SHADER_VERTEX_FILES}) + AUTOSCRIBE_SHADER(${ALL_SHADER_HEADERS}) + endforeach() + string(CONCAT SHADER_ENUMS "${SHADER_ENUMS}" "}; } // vertex \n") + endif() - #message("${TARGET_NAME} ${SHADER_INCLUDE_FILES}") - set(AUTOSCRIBE_SHADER_SRC "") - foreach(SHADER_FILE ${SHADER_SOURCE_FILES}) - AUTOSCRIBE_SHADER(${SHADER_FILE} ${SHADER_INCLUDE_FILES}) - file(TO_CMAKE_PATH "${AUTOSCRIBE_SHADER_RETURN}" AUTOSCRIBE_GENERATED_FILE) - set_property(SOURCE ${AUTOSCRIBE_GENERATED_FILE} PROPERTY SKIP_AUTOMOC ON) - list(APPEND AUTOSCRIBE_SHADER_SRC ${AUTOSCRIBE_GENERATED_FILE}) - endforeach() - #message(${TARGET_NAME} ${AUTOSCRIBE_SHADER_SRC}) + file(GLOB_RECURSE SHADER_FRAGMENT_FILES ${SRC_FOLDER}/*.slf) + if (SHADER_FRAGMENT_FILES) + source_group("${SHADER_LIB}/Fragment" FILES ${SHADER_FRAGMENT_FILES}) + list(APPEND ALL_SCRIBE_SHADERS ${SHADER_FRAGMENT_FILES}) + string(CONCAT SHADER_ENUMS "${SHADER_ENUMS}" "namespace fragment { enum {\n") + foreach(SHADER_FILE ${SHADER_FRAGMENT_FILES}) + AUTOSCRIBE_SHADER(${ALL_SHADER_HEADERS}) + endforeach() + string(CONCAT SHADER_ENUMS "${SHADER_ENUMS}" "}; } // fragment \n") + endif() + + # FIXME add support for geometry, compute and tesselation shaders + #file(GLOB_RECURSE SHADER_GEOMETRY_FILES ${SRC_FOLDER}/*.slg) + #file(GLOB_RECURSE SHADER_COMPUTE_FILES ${SRC_FOLDER}/*.slc) - if (WIN32) - source_group("Shaders" FILES ${SHADER_INCLUDE_FILES}) - source_group("Shaders" FILES ${SHADER_SOURCE_FILES}) - source_group("Shaders\\generated" FILES ${AUTOSCRIBE_SHADER_SRC}) - endif() + # the programs + file(GLOB_RECURSE SHADER_PROGRAM_FILES ${SRC_FOLDER}/*.slp) + if (SHADER_PROGRAM_FILES) + source_group("${SHADER_LIB}/Program" FILES ${SHADER_PROGRAM_FILES}) + list(APPEND ALL_SCRIBE_SHADERS ${SHADER_PROGRAM_FILES}) + string(CONCAT SHADER_ENUMS "${SHADER_ENUMS}" "namespace program { enum {\n") + foreach(PROGRAM_FILE ${SHADER_PROGRAM_FILES}) + get_filename_component(PROGRAM_NAME ${PROGRAM_FILE} NAME_WE) + set(AUTOSCRIBE_PROGRAM_FRAGMENT ${PROGRAM_NAME}) + file(READ ${PROGRAM_FILE} PROGRAM_CONFIG) + set(AUTOSCRIBE_PROGRAM_VERTEX ${PROGRAM_NAME}) + set(AUTOSCRIBE_PROGRAM_FRAGMENT ${PROGRAM_NAME}) - list(APPEND AUTOSCRIBE_SHADER_LIB_SRC ${SHADER_INCLUDE_FILES}) - list(APPEND AUTOSCRIBE_SHADER_LIB_SRC ${SHADER_SOURCE_FILES}) - list(APPEND AUTOSCRIBE_SHADER_LIB_SRC ${AUTOSCRIBE_SHADER_SRC}) + if (NOT("${PROGRAM_CONFIG}" STREQUAL "")) + string(REGEX MATCH ".*VERTEX +([_\\:A-Z0-9a-z]+)" MVERT ${PROGRAM_CONFIG}) + if (CMAKE_MATCH_1) + set(AUTOSCRIBE_PROGRAM_VERTEX ${CMAKE_MATCH_1}) + endif() + string(REGEX MATCH ".*FRAGMENT +([_:A-Z0-9a-z]+)" MFRAG ${PROGRAM_CONFIG}) + if (CMAKE_MATCH_1) + set(AUTOSCRIBE_PROGRAM_FRAGMENT ${CMAKE_MATCH_1}) + endif() + endif() - # Link library shaders, if they exist - include_directories("${SHADERS_DIR}") + if (NOT (${AUTOSCRIBE_PROGRAM_VERTEX} MATCHES ".*::.*")) + set(AUTOSCRIBE_PROGRAM_VERTEX "vertex::${AUTOSCRIBE_PROGRAM_VERTEX}") + endif() + if (NOT (${AUTOSCRIBE_PROGRAM_FRAGMENT} MATCHES ".*::.*")) + set(AUTOSCRIBE_PROGRAM_FRAGMENT "fragment::${AUTOSCRIBE_PROGRAM_FRAGMENT}") + endif() - # Add search directory to find gpu/Shader.h - include_directories("${HIFI_LIBRARY_DIR}/gpu/src") + set(PROGRAM_ENTRY "${PROGRAM_NAME} = (${AUTOSCRIBE_PROGRAM_VERTEX} << 16) | ${AUTOSCRIBE_PROGRAM_FRAGMENT},\n") + string(CONCAT SHADER_ENUMS "${SHADER_ENUMS}" "${PROGRAM_ENTRY}") + string(CONCAT SHADER_PROGRAMS_ARRAY "${SHADER_PROGRAMS_ARRAY} ${SHADER_NAMESPACE}::program::${PROGRAM_NAME},\n") + endforeach() + string(CONCAT SHADER_ENUMS "${SHADER_ENUMS}" "}; } // program \n") + endif() + # Finish the shader enums + string(CONCAT SHADER_ENUMS "${SHADER_ENUMS}" "} // namespace ${SHADER_NAMESPACE}\n") + #file(RELATIVE_PATH RELATIVE_LIBRARY_DIR_PATH ${CMAKE_CURRENT_SOURCE_DIR} "${HIFI_LIBRARY_DIR}") + #foreach(HIFI_LIBRARY ${ARGN}) + #list(APPEND HIFI_LIBRARIES_SHADER_INCLUDE_FILES ${HIFI_LIBRARY_DIR}/${HIFI_LIBRARY}/src) + #endforeach() + #endif() +endmacro() + +macro(AUTOSCRIBE_SHADER_LIBS) + set(SCRIBE_COMMAND scribe) + set(SHREFLECT_COMMAND shreflect) + set(SHREFLECT_DEPENDENCY shreflect) + + # Target dependant Custom rule on the SHADER_FILE + if (ANDROID) + set(GLPROFILE LINUX_GL) + set(SCRIBE_COMMAND ${NATIVE_SCRIBE}) + set(SHREFLECT_COMMAND ${NATIVE_SHREFLECT}) + unset(SHREFLECT_DEPENDENCY) + else() + if (APPLE) + set(GLPROFILE MAC_GL) + elseif(UNIX) + set(GLPROFILE LINUX_GL) + else() + set(GLPROFILE PC_GL) + endif() + endif() + + # Start the shader IDs + set(SHADER_COUNT 1) + set(SHADERS_DIR "${CMAKE_CURRENT_BINARY_DIR}/shaders") + set(SHADER_ENUMS "") + file(MAKE_DIRECTORY ${SHADERS_DIR}) + + # + # Scribe generation & program defintiion + # + foreach(SHADER_LIB ${ARGN}) + AUTOSCRIBE_SHADER_LIB(${SHADER_LIB}) + endforeach() + + # Generate the library files + configure_file( + ShaderEnums.cpp.in + ${CMAKE_CURRENT_BINARY_DIR}/shaders/ShaderEnums.cpp) + configure_file( + ShaderEnums.h.in + ${CMAKE_CURRENT_BINARY_DIR}/shaders/ShaderEnums.h) + configure_file( + shaders.qrc.in + ${CMAKE_CURRENT_BINARY_DIR}/shaders.qrc) + + set(AUTOSCRIBE_SHADER_LIB_SRC "${CMAKE_CURRENT_BINARY_DIR}/shaders/ShaderEnums.h;${CMAKE_CURRENT_BINARY_DIR}/shaders/ShaderEnums.cpp") + set(QT_RESOURCES_FILE ${CMAKE_CURRENT_BINARY_DIR}/shaders.qrc) + + # Custom targets required to force generation of the shaders via scribe + add_custom_target(scribe_shaders SOURCES ${ALL_SCRIBE_SHADERS}) + add_custom_target(compiled_shaders SOURCES ${COMPILED_SHADERS}) + add_custom_target(reflected_shaders SOURCES ${REFLECTED_SHADERS}) + set_target_properties(scribe_shaders PROPERTIES FOLDER "Shaders") + set_target_properties(compiled_shaders PROPERTIES FOLDER "Shaders") + set_target_properties(reflected_shaders PROPERTIES FOLDER "Shaders") endmacro() diff --git a/cmake/macros/LinkHifiLibraries.cmake b/cmake/macros/LinkHifiLibraries.cmake index 395af01f8d..7a6a136799 100644 --- a/cmake/macros/LinkHifiLibraries.cmake +++ b/cmake/macros/LinkHifiLibraries.cmake @@ -19,12 +19,8 @@ function(LINK_HIFI_LIBRARIES) endforeach() foreach(HIFI_LIBRARY ${LIBRARIES_TO_LINK}) - include_directories("${HIFI_LIBRARY_DIR}/${HIFI_LIBRARY}/src") - include_directories("${CMAKE_BINARY_DIR}/libraries/${HIFI_LIBRARY}/shaders") - - #add_dependencies(${TARGET_NAME} ${HIFI_LIBRARY}) - + include_directories("${CMAKE_BINARY_DIR}/libraries/${HIFI_LIBRARY}") # link the actual library - it is static so don't bubble it up target_link_libraries(${TARGET_NAME} ${HIFI_LIBRARY}) endforeach() diff --git a/cmake/macros/SetPackagingParameters.cmake b/cmake/macros/SetPackagingParameters.cmake index c40691c632..0f8975e9b5 100644 --- a/cmake/macros/SetPackagingParameters.cmake +++ b/cmake/macros/SetPackagingParameters.cmake @@ -51,6 +51,10 @@ macro(SET_PACKAGING_PARAMETERS) set(USE_STABLE_GLOBAL_SERVICES 1) endif () + if (NOT BYPASS_SIGNING) + set(BYPASS_SIGNING 0) + endif () + elseif (RELEASE_TYPE STREQUAL "PR") set(DEPLOY_PACKAGE TRUE) set(PR_BUILD 1) diff --git a/cmake/macros/TargetJson.cmake b/cmake/macros/TargetJson.cmake new file mode 100644 index 0000000000..057024cd0a --- /dev/null +++ b/cmake/macros/TargetJson.cmake @@ -0,0 +1,13 @@ +# +# Created by Bradley Austin Davis on 2018/07/22 +# Copyright 2013-2018 High Fidelity, Inc. +# +# Distributed under the Apache License, Version 2.0. +# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +# +macro(TARGET_JSON) + add_dependency_external_projects(json) + find_package(JSON REQUIRED) + message("JSON_INCLUDE_DIRS ${JSON_INCLUDE_DIRS}") + target_include_directories(${TARGET_NAME} PUBLIC ${JSON_INCLUDE_DIRS}) +endmacro() \ No newline at end of file diff --git a/cmake/modules/FindJSON.cmake b/cmake/modules/FindJSON.cmake new file mode 100644 index 0000000000..d5011bd5dd --- /dev/null +++ b/cmake/modules/FindJSON.cmake @@ -0,0 +1,19 @@ +# +# Created by Bradley Austin Davis on 2018/07/22 +# Copyright 2013-2018 High Fidelity, Inc. +# +# Distributed under the Apache License, Version 2.0. +# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +# + +# setup hints for JSON search +include("${MACRO_DIR}/HifiLibrarySearchHints.cmake") +hifi_library_search_hints("json") + +# locate header +find_path(JSON_INCLUDE_DIRS "json/json.hpp" HINTS ${JSON_SEARCH_DIRS}) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(JSON DEFAULT_MSG JSON_INCLUDE_DIRS) + +mark_as_advanced(JSON_INCLUDE_DIRS JSON_SEARCH_DIRS) \ No newline at end of file diff --git a/cmake/templates/CPackProperties.cmake.in b/cmake/templates/CPackProperties.cmake.in index 68fa098508..1d7effd18f 100644 --- a/cmake/templates/CPackProperties.cmake.in +++ b/cmake/templates/CPackProperties.cmake.in @@ -50,3 +50,4 @@ set(SERVER_COMPONENT_CONDITIONAL "@SERVER_COMPONENT_CONDITIONAL@") set(CLIENT_COMPONENT_CONDITIONAL "@CLIENT_COMPONENT_CONDITIONAL@") set(INSTALLER_TYPE "@INSTALLER_TYPE@") set(APP_USER_MODEL_ID "@APP_USER_MODEL_ID@") +set(BYPASS_SIGNING "@BYPASS_SIGNING@") diff --git a/cmake/templates/NSIS.template.in b/cmake/templates/NSIS.template.in index c51e4ffd72..7f6884f478 100644 --- a/cmake/templates/NSIS.template.in +++ b/cmake/templates/NSIS.template.in @@ -130,7 +130,11 @@ ; The Inner invocation has written an uninstaller binary for us. ; We need to sign it if it's a production or PR build. !if @PRODUCTION_BUILD@ == 1 - !system '"@SIGNTOOL_EXECUTABLE@" sign /fd sha256 /f %HF_PFX_FILE% /p %HF_PFX_PASSPHRASE% /tr http://sha256timestamp.ws.symantec.com/sha256/timestamp /td SHA256 $%TEMP%\@UNINSTALLER_NAME@' = 0 + !if @BYPASS_SIGNING@ == 1 + !warning "BYPASS_SIGNING set - installer will not be signed" + !else + !system '"@SIGNTOOL_EXECUTABLE@" sign /fd sha256 /f %HF_PFX_FILE% /p %HF_PFX_PASSPHRASE% /tr http://sha256timestamp.ws.symantec.com/sha256/timestamp /td SHA256 $%TEMP%\@UNINSTALLER_NAME@' = 0 + !endif !endif ; Good. Now we can carry on writing the real installer. diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index 83dd633d22..5d1f725ab4 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -46,6 +46,14 @@ "default": "40102", "type": "int", "advanced": true + }, + { + "name": "enable_packet_verification", + "label": "Enable Packet Verification", + "help": "Enable secure checksums on communication that uses the High Fidelity protocol. Increases security with possibly a small performance penalty.", + "default": true, + "type": "checkbox", + "advanced": true } ] }, @@ -1223,7 +1231,7 @@ "name": "max_avatar_height", "type": "double", "label": "Maximum Avatar Height (meters)", - "help": "Limits the scale of avatars in your domain. Cannot be greater than 1755.", + "help": "Limits the height of avatars in your domain. Cannot be greater than 1755.", "placeholder": 5.2, "default": 5.2 }, diff --git a/domain-server/resources/web/settings/js/settings.js b/domain-server/resources/web/settings/js/settings.js index e67ea43158..3888277c00 100644 --- a/domain-server/resources/web/settings/js/settings.js +++ b/domain-server/resources/web/settings/js/settings.js @@ -58,7 +58,11 @@ $(document).ready(function(){ } Settings.handlePostSettings = function(formJSON) { - + + if (!verifyAvatarHeights()) { + return false; + } + // check if we've set the basic http password if (formJSON["security"]) { @@ -207,7 +211,7 @@ $(document).ready(function(){ swal({ title: '', type: 'error', - text: "There was a problem retreiving domain information from High Fidelity API.", + text: "There was a problem retrieving domain information from High Fidelity API.", confirmButtonText: 'Try again', showCancelButton: true, closeOnConfirm: false @@ -288,7 +292,7 @@ $(document).ready(function(){ swal({ title: 'Create new domain ID', type: 'input', - text: 'Enter a label this machine.

This will help you identify which domain ID belongs to which machine.

', + text: 'Enter a label for this machine.

This will help you identify which domain ID belongs to which machine.

', showCancelButton: true, confirmButtonText: "Create", closeOnConfirm: false, @@ -669,7 +673,7 @@ $(document).ready(function(){ var spinner = createDomainSpinner(); $('#' + Settings.PLACES_TABLE_ID).after($(spinner)); - var errorEl = createDomainLoadingError("There was an error retreiving your places."); + var errorEl = createDomainLoadingError("There was an error retrieving your places."); $("#" + Settings.PLACES_TABLE_ID).after(errorEl); // do we have a domain ID? @@ -1091,4 +1095,43 @@ $(document).ready(function(){ $('#settings_backup .panel-body').html(html); } + + function verifyAvatarHeights() { + var errorString = ''; + var minAllowedHeight = 0.009; + var maxAllowedHeight = 1755; + var alertCss = { backgroundColor: '#ffa0a0' }; + var minHeightElement = $('input[name="avatars.min_avatar_height"]'); + var maxHeightElement = $('input[name="avatars.max_avatar_height"]'); + + var minHeight = Number(minHeightElement.val()); + var maxHeight = Number(maxHeightElement.val()); + + if (maxHeight < minHeight) { + errorString = 'Maximum avatar height must not be less than minimum avatar height
'; + minHeightElement.css(alertCss); + maxHeightElement.css(alertCss); + }; + if (minHeight < minAllowedHeight) { + errorString += 'Minimum avatar height must not be less than ' + minAllowedHeight + '
'; + minHeightElement.css(alertCss); + } + if (maxHeight > maxAllowedHeight) { + errorString += 'Maximum avatar height must not be greater than ' + maxAllowedHeight + '
'; + maxHeightElement.css(alertCss); + } + + if (errorString.length > 0) { + swal({ + type: 'error', + title: '', + text: errorString, + html: true + }); + return false; + } else { + return true; + } + + } }); diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index 59cc8d10f6..e23d9e57a8 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -264,7 +264,8 @@ void DomainGatekeeper::updateNodePermissions() { QList nodesToKill; auto limitedNodeList = DependencyManager::get(); - limitedNodeList->eachNode([this, limitedNodeList, &nodesToKill](const SharedNodePointer& node){ + QWeakPointer limitedNodeListWeak = limitedNodeList; + limitedNodeList->eachNode([this, limitedNodeListWeak, &nodesToKill](const SharedNodePointer& node){ // the id and the username in NodePermissions will often be the same, but id is set before // authentication and verifiedUsername is only set once they user's key has been confirmed. QString verifiedUsername = node->getPermissions().getVerifiedUserName(); @@ -296,7 +297,8 @@ void DomainGatekeeper::updateNodePermissions() { machineFingerprint = nodeData->getMachineFingerprint(); auto sendingAddress = nodeData->getSendingSockAddr().getAddress(); - isLocalUser = (sendingAddress == limitedNodeList->getLocalSockAddr().getAddress() || + auto nodeList = limitedNodeListWeak.lock(); + isLocalUser = ((nodeList && sendingAddress == nodeList->getLocalSockAddr().getAddress()) || sendingAddress == QHostAddress::LocalHost); } @@ -458,22 +460,18 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect // in case this is a node that's failing to connect // double check we don't have the same node whose sockets match exactly already in the list - limitedNodeList->eachNodeBreakable([&](const SharedNodePointer& node){ + limitedNodeList->eachNodeBreakable([nodeConnection, username, &existingNodeID](const SharedNodePointer& node){ if (node->getPublicSocket() == nodeConnection.publicSockAddr && node->getLocalSocket() == nodeConnection.localSockAddr) { - // we have a node that already has these exact sockets - this can occur if a node - // is failing to connect to the domain - - // we'll re-use the existing node ID - // as long as the user hasn't changed their username (by logging in or logging out) - auto existingNodeData = static_cast(node->getLinkedData()); - - if (existingNodeData->getUsername() == username) { - qDebug() << "Deleting existing connection from same sockaddr: " << node->getUUID(); - existingNodeID = node->getUUID(); - return false; - } + // we have a node that already has these exact sockets + // this can occur if a node is failing to connect to the domain + + // remove the old node before adding the new node + qDebug() << "Deleting existing connection from same sockaddr: " << node->getUUID(); + existingNodeID = node->getUUID(); + return false; } + return true; }); @@ -1009,7 +1007,7 @@ void DomainGatekeeper::refreshGroupsCache() { getDomainOwnerFriendsList(); auto nodeList = DependencyManager::get(); - nodeList->eachNode([&](const SharedNodePointer& node) { + nodeList->eachNode([this](const SharedNodePointer& node) { if (!node->getPermissions().isAssignment) { // this node is an agent const QString& verifiedUserName = node->getPermissions().getVerifiedUserName(); diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 86a9a58058..57c4692794 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -13,6 +13,7 @@ #include #include +#include #include #include @@ -69,6 +70,14 @@ const QString ICE_SERVER_DEFAULT_HOSTNAME = "ice.highfidelity.com"; const QString ICE_SERVER_DEFAULT_HOSTNAME = "dev-ice.highfidelity.com"; #endif +QString DomainServer::_iceServerAddr { ICE_SERVER_DEFAULT_HOSTNAME }; +int DomainServer::_iceServerPort { ICE_SERVER_DEFAULT_PORT }; +bool DomainServer::_overrideDomainID { false }; +QUuid DomainServer::_overridingDomainID; +bool DomainServer::_getTempName { false }; +QString DomainServer::_userConfigFilename; +int DomainServer::_parentPID { -1 }; + bool DomainServer::forwardMetaverseAPIRequest(HTTPConnection* connection, const QString& metaversePath, const QString& requestSubobjectKey, @@ -148,24 +157,13 @@ bool DomainServer::forwardMetaverseAPIRequest(HTTPConnection* connection, DomainServer::DomainServer(int argc, char* argv[]) : QCoreApplication(argc, argv), _gatekeeper(this), - _httpManager(QHostAddress::AnyIPv4, DOMAIN_SERVER_HTTP_PORT, QString("%1/resources/web/").arg(QCoreApplication::applicationDirPath()), this), - _allAssignments(), - _unfulfilledAssignments(), - _isUsingDTLS(false), - _oauthProviderURL(), - _oauthClientID(), - _hostname(), - _ephemeralACScripts(), - _webAuthenticationStateSet(), - _cookieSessionHash(), - _automaticNetworkingSetting(), - _settingsManager(), - _iceServerAddr(ICE_SERVER_DEFAULT_HOSTNAME), - _iceServerPort(ICE_SERVER_DEFAULT_PORT) + _httpManager(QHostAddress::AnyIPv4, DOMAIN_SERVER_HTTP_PORT, QString("%1/resources/web/").arg(QCoreApplication::applicationDirPath()), this) { - PathUtils::removeTemporaryApplicationDirs(); + if (_parentPID != -1) { + watchParentProcess(_parentPID); + } - parseCommandLine(); + PathUtils::removeTemporaryApplicationDirs(); DependencyManager::set(); DependencyManager::set(); @@ -185,9 +183,16 @@ DomainServer::DomainServer(int argc, char* argv[]) : // (need this since domain-server can restart itself and maintain static variables) DependencyManager::set(); - auto args = arguments(); - - _settingsManager.setupConfigMap(args); + // load the user config + QString userConfigFilename; + if (!_userConfigFilename.isEmpty()) { + userConfigFilename = _userConfigFilename; + } else { + // we weren't passed a user config path + static const QString USER_CONFIG_FILE_NAME = "config.json"; + userConfigFilename = PathUtils::getAppDataFilePath(USER_CONFIG_FILE_NAME); + } + _settingsManager.setupConfigMap(userConfigFilename); // setup a shutdown event listener to handle SIGTERM or WM_CLOSE for us #ifdef _WIN32 @@ -246,8 +251,7 @@ DomainServer::DomainServer(int argc, char* argv[]) : } // check for the temporary name parameter - const QString GET_TEMPORARY_NAME_SWITCH = "--get-temp-name"; - if (args.contains(GET_TEMPORARY_NAME_SWITCH)) { + if (_getTempName) { getTemporaryName(); } @@ -316,28 +320,45 @@ DomainServer::DomainServer(int argc, char* argv[]) : connect(_contentManager.get(), &DomainContentBackupManager::recoveryCompleted, this, &DomainServer::restart); } -void DomainServer::parseCommandLine() { +void DomainServer::parseCommandLine(int argc, char* argv[]) { QCommandLineParser parser; parser.setApplicationDescription("High Fidelity Domain Server"); - parser.addHelpOption(); + const QCommandLineOption versionOption = parser.addVersionOption(); + const QCommandLineOption helpOption = parser.addHelpOption(); const QCommandLineOption iceServerAddressOption("i", "ice-server address", "IP:PORT or HOSTNAME:PORT"); parser.addOption(iceServerAddressOption); - const QCommandLineOption domainIDOption("d", "domain-server uuid"); + const QCommandLineOption domainIDOption("d", "domain-server uuid", "uuid"); parser.addOption(domainIDOption); const QCommandLineOption getTempNameOption("get-temp-name", "Request a temporary domain-name"); parser.addOption(getTempNameOption); - const QCommandLineOption masterConfigOption("master-config", "Deprecated config-file option"); - parser.addOption(masterConfigOption); + const QCommandLineOption userConfigOption("user-config", "Pass user config file pass", "path"); + parser.addOption(userConfigOption); const QCommandLineOption parentPIDOption(PARENT_PID_OPTION, "PID of the parent process", "parent-pid"); parser.addOption(parentPIDOption); - if (!parser.parse(QCoreApplication::arguments())) { - qWarning() << parser.errorText() << endl; + + QStringList arguments; + for (int i = 0; i < argc; ++i) { + arguments << argv[i]; + } + if (!parser.parse(arguments)) { + std::cout << parser.errorText().toStdString() << std::endl; // Avoid Qt log spam + QCoreApplication mockApp(argc, argv); // required for call to showHelp() + parser.showHelp(); + Q_UNREACHABLE(); + } + + if (parser.isSet(versionOption)) { + parser.showVersion(); + Q_UNREACHABLE(); + } + if (parser.isSet(helpOption)) { + QCoreApplication mockApp(argc, argv); // required for call to showHelp() parser.showHelp(); Q_UNREACHABLE(); } @@ -354,7 +375,7 @@ void DomainServer::parseCommandLine() { if (_iceServerAddr.isEmpty()) { qWarning() << "Could not parse an IP address and port combination from" << hostnamePortString; - QMetaObject::invokeMethod(this, "quit", Qt::QueuedConnection); + ::exit(0); } } @@ -364,14 +385,21 @@ void DomainServer::parseCommandLine() { qDebug() << "domain-server ID is" << _overridingDomainID; } + if (parser.isSet(getTempNameOption)) { + _getTempName = true; + } + + if (parser.isSet(userConfigOption)) { + _userConfigFilename = parser.value(userConfigOption); + } if (parser.isSet(parentPIDOption)) { bool ok = false; int parentPID = parser.value(parentPIDOption).toInt(&ok); if (ok) { - qDebug() << "Parent process PID is" << parentPID; - watchParentProcess(parentPID); + _parentPID = parentPID; + qDebug() << "Parent process PID is" << _parentPID; } } } @@ -630,6 +658,7 @@ bool DomainServer::isPacketVerified(const udt::Packet& packet) { void DomainServer::setupNodeListAndAssignments() { const QString CUSTOM_LOCAL_PORT_OPTION = "metaverse.local_port"; + static const QString ENABLE_PACKET_AUTHENTICATION = "metaverse.enable_packet_verification"; QVariant localPortValue = _settingsManager.valueOrDefaultValueForKeyPath(CUSTOM_LOCAL_PORT_OPTION); int domainServerPort = localPortValue.toInt(); @@ -696,6 +725,9 @@ void DomainServer::setupNodeListAndAssignments() { } } + bool isAuthEnabled = _settingsManager.valueOrDefaultValueForKeyPath(ENABLE_PACKET_AUTHENTICATION).toBool(); + nodeList->setAuthenticatePackets(isAuthEnabled); + connect(nodeList.data(), &LimitedNodeList::nodeAdded, this, &DomainServer::nodeAdded); connect(nodeList.data(), &LimitedNodeList::nodeKilled, this, &DomainServer::nodeKilled); @@ -1046,7 +1078,7 @@ bool DomainServer::isInInterestSet(const SharedNodePointer& nodeA, const SharedN unsigned int DomainServer::countConnectedUsers() { unsigned int result = 0; auto nodeList = DependencyManager::get(); - nodeList->eachNode([&](const SharedNodePointer& node){ + nodeList->eachNode([&result](const SharedNodePointer& node){ // only count unassigned agents (i.e., users) if (node->getType() == NodeType::Agent) { auto nodeData = static_cast(node->getLinkedData()); @@ -1133,7 +1165,7 @@ void DomainServer::sendDomainListToNode(const SharedNodePointer& node, const Hif extendedHeaderStream << node->getUUID(); extendedHeaderStream << node->getLocalID(); extendedHeaderStream << node->getPermissions(); - + extendedHeaderStream << limitedNodeList->getAuthenticatePackets(); auto domainListPackets = NLPacketList::create(PacketType::DomainList, extendedHeader); // always send the node their own UUID back @@ -1149,7 +1181,7 @@ void DomainServer::sendDomainListToNode(const SharedNodePointer& node, const Hif // DTLSServerSession* dtlsSession = _isUsingDTLS ? _dtlsSessions[senderSockAddr] : NULL; if (nodeData->isAuthenticated()) { // if this authenticated node has any interest types, send back those nodes as well - limitedNodeList->eachNode([&](const SharedNodePointer& otherNode) { + limitedNodeList->eachNode([this, node, &domainListPackets, &domainListStream](const SharedNodePointer& otherNode) { if (otherNode->getUUID() != node->getUUID() && isInInterestSet(node, otherNode)) { // since we're about to add a node to the packet we start a segment domainListPackets->startSegment(); @@ -1198,6 +1230,7 @@ QUuid DomainServer::connectionSecretForNodes(const SharedNodePointer& nodeA, con void DomainServer::broadcastNewNode(const SharedNodePointer& addedNode) { auto limitedNodeList = DependencyManager::get(); + QWeakPointer limitedNodeListWeak = limitedNodeList; auto addNodePacket = NLPacket::create(PacketType::DomainServerAddedNode); @@ -1209,7 +1242,7 @@ void DomainServer::broadcastNewNode(const SharedNodePointer& addedNode) { int connectionSecretIndex = addNodePacket->pos(); limitedNodeList->eachMatchingNode( - [&](const SharedNodePointer& node)->bool { + [this, addedNode](const SharedNodePointer& node)->bool { if (node->getLinkedData() && node->getActiveSocket() && node != addedNode) { // is the added Node in this node's interest list? return isInInterestSet(node, addedNode); @@ -1217,16 +1250,19 @@ void DomainServer::broadcastNewNode(const SharedNodePointer& addedNode) { return false; } }, - [&](const SharedNodePointer& node) { - addNodePacket->seek(connectionSecretIndex); - - QByteArray rfcConnectionSecret = connectionSecretForNodes(node, addedNode).toRfc4122(); - - // replace the bytes at the end of the packet for the connection secret between these nodes - addNodePacket->write(rfcConnectionSecret); - + [this, &addNodePacket, connectionSecretIndex, addedNode, limitedNodeListWeak](const SharedNodePointer& node) { // send off this packet to the node - limitedNodeList->sendUnreliablePacket(*addNodePacket, *node); + auto limitedNodeList = limitedNodeListWeak.lock(); + if (limitedNodeList) { + addNodePacket->seek(connectionSecretIndex); + + QByteArray rfcConnectionSecret = connectionSecretForNodes(node, addedNode).toRfc4122(); + + // replace the bytes at the end of the packet for the connection secret between these nodes + addNodePacket->write(rfcConnectionSecret); + + limitedNodeList->sendUnreliablePacket(*addNodePacket, *node); + } } ); } @@ -2403,8 +2439,8 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url } } else if (connection->requestOperation() == QNetworkAccessManager::DeleteOperation) { - const QString ALL_NODE_DELETE_REGEX_STRING = QString("\\%1\\/?$").arg(URI_NODES); - const QString NODE_DELETE_REGEX_STRING = QString("\\%1\\/(%2)\\/$").arg(URI_NODES).arg(UUID_REGEX_STRING); + const QString ALL_NODE_DELETE_REGEX_STRING = QString("%1/?$").arg(URI_NODES); + const QString NODE_DELETE_REGEX_STRING = QString("%1/(%2)$").arg(URI_NODES).arg(UUID_REGEX_STRING); QRegExp allNodesDeleteRegex(ALL_NODE_DELETE_REGEX_STRING); QRegExp nodeDeleteRegex(NODE_DELETE_REGEX_STRING); @@ -2832,7 +2868,7 @@ void DomainServer::updateReplicationNodes(ReplicationServerDirection direction) auto serversSettings = replicationSettings.value(serversKey).toList(); std::vector knownReplicationNodes; - nodeList->eachNode([&](const SharedNodePointer& otherNode) { + nodeList->eachNode([direction, &knownReplicationNodes](const SharedNodePointer& otherNode) { if ((direction == Upstream && NodeType::isUpstream(otherNode->getType())) || (direction == Downstream && NodeType::isDownstream(otherNode->getType()))) { knownReplicationNodes.push_back(otherNode->getPublicSocket()); @@ -2870,7 +2906,7 @@ void DomainServer::updateReplicationNodes(ReplicationServerDirection direction) // collect them in a vector to separately remove them with handleKillNode (since eachNode has a read lock and // we cannot recursively take the write lock required by handleKillNode) std::vector nodesToKill; - nodeList->eachNode([&](const SharedNodePointer& otherNode) { + nodeList->eachNode([this, direction, replicationNodesInSettings, replicationDirection, &nodesToKill](const SharedNodePointer& otherNode) { if ((direction == Upstream && NodeType::isUpstream(otherNode->getType())) || (direction == Downstream && NodeType::isDownstream(otherNode->getType()))) { bool nodeInSettings = find(replicationNodesInSettings.cbegin(), replicationNodesInSettings.cend(), diff --git a/domain-server/src/DomainServer.h b/domain-server/src/DomainServer.h index c69267f379..e2bddc1aa5 100644 --- a/domain-server/src/DomainServer.h +++ b/domain-server/src/DomainServer.h @@ -59,6 +59,8 @@ public: DomainServer(int argc, char* argv[]); ~DomainServer(); + static void parseCommandLine(int argc, char* argv[]); + enum DomainType { NonMetaverse, MetaverseDomain, @@ -138,7 +140,6 @@ signals: private: QUuid getID(); - void parseCommandLine(); QString getContentBackupDir(); QString getEntitiesDirPath(); @@ -228,7 +229,7 @@ private: QQueue _unfulfilledAssignments; TransactionHash _pendingAssignmentCredits; - bool _isUsingDTLS; + bool _isUsingDTLS { false }; QUrl _oauthProviderURL; QString _oauthClientID; @@ -265,10 +266,13 @@ private: friend class DomainGatekeeper; friend class DomainMetadata; - QString _iceServerAddr; - int _iceServerPort; - bool _overrideDomainID { false }; // should we override the domain-id from settings? - QUuid _overridingDomainID { QUuid() }; // what should we override it with? + static QString _iceServerAddr; + static int _iceServerPort; + static bool _overrideDomainID; // should we override the domain-id from settings? + static QUuid _overridingDomainID; // what should we override it with? + static bool _getTempName; + static QString _userConfigFilename; + static int _parentPID; bool _sendICEServerAddressToMetaverseAPIInProgress { false }; bool _sendICEServerAddressToMetaverseAPIRedo { false }; diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp index 2bcaa8899e..780fad15f2 100644 --- a/domain-server/src/DomainServerSettingsManager.cpp +++ b/domain-server/src/DomainServerSettingsManager.cpp @@ -191,13 +191,12 @@ void DomainServerSettingsManager::processSettingsRequestPacket(QSharedPointersendPacketList(std::move(packetList), message->getSenderSockAddr()); } -void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList) { +void DomainServerSettingsManager::setupConfigMap(const QString& userConfigFilename) { // since we're called from the DomainServerSettingsManager constructor, we don't take a write lock here // even though we change the underlying config map - _argumentList = argumentList; - - _configMap.loadConfig(_argumentList); + _configMap.setUserConfigFilename(userConfigFilename); + _configMap.loadConfig(); static const auto VERSION_SETTINGS_KEYPATH = "version"; QVariant* versionVariant = _configMap.valueForKeyPath(VERSION_SETTINGS_KEYPATH); @@ -1736,7 +1735,7 @@ void DomainServerSettingsManager::persistToFile() { // failed to write, reload whatever the current config state is // with a write lock since we're about to overwrite the config map QWriteLocker locker(&_settingsLock); - _configMap.loadConfig(_argumentList); + _configMap.loadConfig(); } } diff --git a/domain-server/src/DomainServerSettingsManager.h b/domain-server/src/DomainServerSettingsManager.h index bcd33c2bb0..2020561205 100644 --- a/domain-server/src/DomainServerSettingsManager.h +++ b/domain-server/src/DomainServerSettingsManager.h @@ -49,7 +49,7 @@ public: DomainServerSettingsManager(); bool handleAuthenticatedHTTPRequest(HTTPConnection* connection, const QUrl& url); - void setupConfigMap(const QStringList& argumentList); + void setupConfigMap(const QString& userConfigFilename); // each of the three methods in this group takes a read lock of _settingsLock // and cannot be called when the a write lock is held by the same thread @@ -144,8 +144,6 @@ private slots: void processUsernameFromIDRequestPacket(QSharedPointer message, SharedNodePointer sendingNode); private: - QStringList _argumentList; - QJsonArray filteredDescriptionArray(bool isContentSettings); void updateSetting(const QString& key, const QJsonValue& newValue, QVariantMap& settingMap, const QJsonObject& settingDescription); diff --git a/domain-server/src/main.cpp b/domain-server/src/main.cpp index d7856bf867..7aea9cc3d4 100644 --- a/domain-server/src/main.cpp +++ b/domain-server/src/main.cpp @@ -24,6 +24,8 @@ int main(int argc, char* argv[]) { setupHifiApplication(BuildInfo::DOMAIN_SERVER_NAME); + DomainServer::parseCommandLine(argc, argv); + Setting::init(); int currentExitCode = 0; diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index ea30909a08..990d84a774 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -214,6 +214,7 @@ link_hifi_libraries( controllers plugins image trackers ui-plugins display-plugins input-plugins ${PLATFORM_GL_BACKEND} + shaders ) # include the binary directory of render-utils for shader includes diff --git a/interface/resources/avatar/animations/idle.fbx b/interface/resources/avatar/animations/idle.fbx index 67e2be8735..801237036f 100644 Binary files a/interface/resources/avatar/animations/idle.fbx and b/interface/resources/avatar/animations/idle.fbx differ diff --git a/interface/resources/avatar/animations/jog_fwd.fbx b/interface/resources/avatar/animations/jog_fwd.fbx new file mode 100644 index 0000000000..3c3b8118ab Binary files /dev/null and b/interface/resources/avatar/animations/jog_fwd.fbx differ diff --git a/interface/resources/avatar/animations/jog_left.fbx b/interface/resources/avatar/animations/jog_left.fbx new file mode 100644 index 0000000000..88600005b9 Binary files /dev/null and b/interface/resources/avatar/animations/jog_left.fbx differ diff --git a/interface/resources/avatar/animations/jump_land.fbx b/interface/resources/avatar/animations/jump_land.fbx index 03b1d2c5f6..c0034bd091 100644 Binary files a/interface/resources/avatar/animations/jump_land.fbx and b/interface/resources/avatar/animations/jump_land.fbx differ diff --git a/interface/resources/avatar/animations/jump_takeoff.fbx b/interface/resources/avatar/animations/jump_takeoff.fbx index 2d72a1371f..4119b2417f 100644 Binary files a/interface/resources/avatar/animations/jump_takeoff.fbx and b/interface/resources/avatar/animations/jump_takeoff.fbx differ diff --git a/interface/resources/avatar/animations/settle_to_idle.fbx b/interface/resources/avatar/animations/settle_to_idle.fbx new file mode 100644 index 0000000000..a5ccbfb490 Binary files /dev/null and b/interface/resources/avatar/animations/settle_to_idle.fbx differ diff --git a/interface/resources/avatar/animations/side_step_left.fbx b/interface/resources/avatar/animations/side_step_left.fbx index 29fcea041f..4bface0c75 100644 Binary files a/interface/resources/avatar/animations/side_step_left.fbx and b/interface/resources/avatar/animations/side_step_left.fbx differ diff --git a/interface/resources/avatar/animations/side_step_left_fast.fbx b/interface/resources/avatar/animations/side_step_left_fast.fbx new file mode 100644 index 0000000000..af71a46bb5 Binary files /dev/null and b/interface/resources/avatar/animations/side_step_left_fast.fbx differ diff --git a/interface/resources/avatar/animations/side_step_left_fast02.fbx b/interface/resources/avatar/animations/side_step_left_fast02.fbx new file mode 100644 index 0000000000..9064b35d37 Binary files /dev/null and b/interface/resources/avatar/animations/side_step_left_fast02.fbx differ diff --git a/interface/resources/avatar/animations/side_step_short_left.fbx b/interface/resources/avatar/animations/side_step_short_left.fbx index d5047181e4..f236f30c31 100644 Binary files a/interface/resources/avatar/animations/side_step_short_left.fbx and b/interface/resources/avatar/animations/side_step_short_left.fbx differ diff --git a/interface/resources/avatar/animations/side_strafe_left.fbx b/interface/resources/avatar/animations/side_strafe_left.fbx new file mode 100644 index 0000000000..61c3ba7b32 Binary files /dev/null and b/interface/resources/avatar/animations/side_strafe_left.fbx differ diff --git a/interface/resources/avatar/animations/side_strafe_left02.fbx b/interface/resources/avatar/animations/side_strafe_left02.fbx new file mode 100644 index 0000000000..e9bc801997 Binary files /dev/null and b/interface/resources/avatar/animations/side_strafe_left02.fbx differ diff --git a/interface/resources/avatar/animations/talk.fbx b/interface/resources/avatar/animations/talk.fbx index 3fd470befd..ba8f88589e 100644 Binary files a/interface/resources/avatar/animations/talk.fbx and b/interface/resources/avatar/animations/talk.fbx differ diff --git a/interface/resources/avatar/animations/turn_left.fbx b/interface/resources/avatar/animations/turn_left.fbx index de6d68b6c0..3d32d832e6 100644 Binary files a/interface/resources/avatar/animations/turn_left.fbx and b/interface/resources/avatar/animations/turn_left.fbx differ diff --git a/interface/resources/avatar/animations/walk_bwd.fbx b/interface/resources/avatar/animations/walk_bwd.fbx index d699631111..158ba52840 100644 Binary files a/interface/resources/avatar/animations/walk_bwd.fbx and b/interface/resources/avatar/animations/walk_bwd.fbx differ diff --git a/interface/resources/avatar/animations/walk_bwd_fast.fbx b/interface/resources/avatar/animations/walk_bwd_fast.fbx new file mode 100644 index 0000000000..f2007b03b1 Binary files /dev/null and b/interface/resources/avatar/animations/walk_bwd_fast.fbx differ diff --git a/interface/resources/avatar/animations/walk_fwd.fbx b/interface/resources/avatar/animations/walk_fwd.fbx index bc7dd00bc5..7d8ab94494 100644 Binary files a/interface/resources/avatar/animations/walk_fwd.fbx and b/interface/resources/avatar/animations/walk_fwd.fbx differ diff --git a/interface/resources/avatar/animations/walk_fwd_fast.fbx b/interface/resources/avatar/animations/walk_fwd_fast.fbx new file mode 100644 index 0000000000..1ba94a798f Binary files /dev/null and b/interface/resources/avatar/animations/walk_fwd_fast.fbx differ diff --git a/interface/resources/avatar/animations/walk_fwd_fast02.fbx b/interface/resources/avatar/animations/walk_fwd_fast02.fbx new file mode 100644 index 0000000000..470b1d57f3 Binary files /dev/null and b/interface/resources/avatar/animations/walk_fwd_fast02.fbx differ diff --git a/interface/resources/avatar/animations/walk_fwd_fast_armout.fbx b/interface/resources/avatar/animations/walk_fwd_fast_armout.fbx new file mode 100644 index 0000000000..6aa07d0a45 Binary files /dev/null and b/interface/resources/avatar/animations/walk_fwd_fast_armout.fbx differ diff --git a/interface/resources/avatar/animations/walk_left.fbx b/interface/resources/avatar/animations/walk_left.fbx new file mode 100644 index 0000000000..bfe10aa18f Binary files /dev/null and b/interface/resources/avatar/animations/walk_left.fbx differ diff --git a/interface/resources/avatar/animations/walk_left_fast.fbx b/interface/resources/avatar/animations/walk_left_fast.fbx new file mode 100644 index 0000000000..ced6c08bac Binary files /dev/null and b/interface/resources/avatar/animations/walk_left_fast.fbx differ diff --git a/interface/resources/avatar/avatar-animation.json b/interface/resources/avatar/avatar-animation.json index 44d294f767..6ebbbf3000 100644 --- a/interface/resources/avatar/avatar-animation.json +++ b/interface/resources/avatar/avatar-animation.json @@ -1,5 +1,5 @@ { - "version": "1.0", + "version": "1.1", "root": { "id": "userAnimStateMachine", "type": "stateMachine", @@ -38,1156 +38,1504 @@ "children": [ { "id": "userAnimNone", - "type": "overlay", + "type": "poleVectorConstraint", "data": { - "alpha": 1.0, - "alphaVar": "ikOverlayAlpha", - "boneSet": "fullBody" + "enabled": false, + "referenceVector": [0, 0, 1], + "baseJointName": "RightUpLeg", + "midJointName": "RightLeg", + "tipJointName": "RightFoot", + "enabledVar": "rightFootPoleVectorEnabled", + "poleVectorVar": "rightFootPoleVector" }, "children": [ { - "id": "ik", - "type": "inverseKinematics", + "id": "rightFootIK", + "type": "twoBoneIK", "data": { - "solutionSource": "relaxToUnderPoses", - "solutionSourceVar": "solutionSource", - "targets": [ - { - "jointName": "Hips", - "positionVar": "hipsPosition", - "rotationVar": "hipsRotation", - "typeVar": "hipsType", - "weightVar": "hipsWeight", - "weight": 1.0, - "flexCoefficients": [1] - }, - { - "jointName": "RightHand", - "positionVar": "rightHandPosition", - "rotationVar": "rightHandRotation", - "typeVar": "rightHandType", - "weightVar": "rightHandWeight", - "weight": 1.0, - "flexCoefficients": [1, 0.5, 0.5, 0.2, 0.01, 0.005, 0.001, 0.0, 0.0], - "poleVectorEnabledVar": "rightHandPoleVectorEnabled", - "poleReferenceVectorVar": "rightHandPoleReferenceVector", - "poleVectorVar": "rightHandPoleVector" - }, - { - "jointName": "LeftHand", - "positionVar": "leftHandPosition", - "rotationVar": "leftHandRotation", - "typeVar": "leftHandType", - "weightVar": "leftHandWeight", - "weight": 1.0, - "flexCoefficients": [1, 0.5, 0.5, 0.2, 0.01, 0.005, 0.001, 0.0, 0.0], - "poleVectorEnabledVar": "leftHandPoleVectorEnabled", - "poleReferenceVectorVar": "leftHandPoleReferenceVector", - "poleVectorVar": "leftHandPoleVector" - }, - { - "jointName": "RightFoot", - "positionVar": "rightFootPosition", - "rotationVar": "rightFootRotation", - "typeVar": "rightFootType", - "weightVar": "rightFootWeight", - "weight": 1.0, - "flexCoefficients": [1, 0.45, 0.45], - "poleVectorEnabledVar": "rightFootPoleVectorEnabled", - "poleReferenceVectorVar": "rightFootPoleReferenceVector", - "poleVectorVar": "rightFootPoleVector" - }, - { - "jointName": "LeftFoot", - "positionVar": "leftFootPosition", - "rotationVar": "leftFootRotation", - "typeVar": "leftFootType", - "weightVar": "leftFootWeight", - "weight": 1.0, - "flexCoefficients": [1, 0.45, 0.45], - "poleVectorEnabledVar": "leftFootPoleVectorEnabled", - "poleReferenceVectorVar": "leftFootPoleReferenceVector", - "poleVectorVar": "leftFootPoleVector" - }, - { - "jointName": "Spine2", - "positionVar": "spine2Position", - "rotationVar": "spine2Rotation", - "typeVar": "spine2Type", - "weightVar": "spine2Weight", - "weight": 2.0, - "flexCoefficients": [1.0, 0.5, 0.25] - }, - { - "jointName": "Head", - "positionVar": "headPosition", - "rotationVar": "headRotation", - "typeVar": "headType", - "weightVar": "headWeight", - "weight": 4.0, - "flexCoefficients": [1, 0.5, 0.25, 0.2, 0.1] - } - ] - }, - "children": [] - }, - { - "id": "defaultPoseOverlay", - "type": "overlay", - "data": { - "alpha": 0.0, - "alphaVar": "defaultPoseOverlayAlpha", - "boneSet": "fullBody", - "boneSetVar": "defaultPoseOverlayBoneSet" + "alpha": 1.0, + "enabled": false, + "interpDuration": 15, + "baseJointName": "RightUpLeg", + "midJointName": "RightLeg", + "tipJointName": "RightFoot", + "midHingeAxis": [-1, 0, 0], + "alphaVar": "rightFootIKAlpha", + "enabledVar": "rightFootIKEnabled", + "endEffectorRotationVarVar": "rightFootIKRotationVar", + "endEffectorPositionVarVar": "rightFootIKPositionVar" }, "children": [ { - "id": "defaultPose", - "type": "defaultPose", + "id": "leftFootPoleVector", + "type": "poleVectorConstraint", "data": { - }, - "children": [] - }, - { - "id": "rightHandOverlay", - "type": "overlay", - "data": { - "alpha": 0.0, - "boneSet": "rightHand", - "alphaVar": "rightHandOverlayAlpha" + "enabled": false, + "referenceVector": [0, 0, 1], + "baseJointName": "LeftUpLeg", + "midJointName": "LeftLeg", + "tipJointName": "LeftFoot", + "enabledVar": "leftFootPoleVectorEnabled", + "poleVectorVar": "leftFootPoleVector" }, "children": [ { - "id": "rightHandStateMachine", - "type": "stateMachine", + "id": "leftFootIK", + "type": "twoBoneIK", "data": { - "currentState": "rightHandGrasp", - "states": [ - { - "id": "rightHandGrasp", - "interpTarget": 3, - "interpDuration": 3, - "transitions": [ - { "var": "isRightIndexPoint", "state": "rightIndexPoint" }, - { "var": "isRightThumbRaise", "state": "rightThumbRaise" }, - { "var": "isRightIndexPointAndThumbRaise", "state": "rightIndexPointAndThumbRaise" } - ] - }, - { - "id": "rightIndexPoint", - "interpTarget": 15, - "interpDuration": 3, - "transitions": [ - { "var": "isRightHandGrasp", "state": "rightHandGrasp" }, - { "var": "isRightThumbRaise", "state": "rightThumbRaise" }, - { "var": "isRightIndexPointAndThumbRaise", "state": "rightIndexPointAndThumbRaise" } - ] - }, - { - "id": "rightThumbRaise", - "interpTarget": 15, - "interpDuration": 3, - "transitions": [ - { "var": "isRightHandGrasp", "state": "rightHandGrasp" }, - { "var": "isRightIndexPoint", "state": "rightIndexPoint" }, - { "var": "isRightIndexPointAndThumbRaise", "state": "rightIndexPointAndThumbRaise" } - ] - }, - { - "id": "rightIndexPointAndThumbRaise", - "interpTarget": 15, - "interpDuration": 3, - "transitions": [ - { "var": "isRightHandGrasp", "state": "rightHandGrasp" }, - { "var": "isRightIndexPoint", "state": "rightIndexPoint" }, - { "var": "isRightThumbRaise", "state": "rightThumbRaise" } - ] - } - ] + "alpha": 1.0, + "enabled": false, + "interpDuration": 15, + "baseJointName": "LeftUpLeg", + "midJointName": "LeftLeg", + "tipJointName": "LeftFoot", + "midHingeAxis": [-1, 0, 0], + "alphaVar": "leftFootIKAlpha", + "enabledVar": "leftFootIKEnabled", + "endEffectorRotationVarVar": "leftFootIKRotationVar", + "endEffectorPositionVarVar": "leftFootIKPositionVar" }, "children": [ { - "id": "rightHandGrasp", - "type": "blendLinear", + "id": "ikOverlay", + "type": "overlay", "data": { - "alpha": 0.0, - "alphaVar": "rightHandGraspAlpha" + "alpha": 1.0, + "alphaVar": "ikOverlayAlpha", + "boneSet": "fullBody" }, "children": [ { - "id": "rightHandGraspOpen", - "type": "clip", + "id": "ik", + "type": "inverseKinematics", "data": { - "url": "animations/hydra_pose_open_right.fbx", - "startFrame": 0.0, - "endFrame": 0.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "rightHandGraspClosed", - "type": "clip", - "data": { - "url": "animations/hydra_pose_closed_right.fbx", - "startFrame": 0.0, - "endFrame": 0.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - } - ] - }, - { - "id": "rightIndexPoint", - "type": "blendLinear", - "data": { - "alpha": 0.0, - "alphaVar": "rightHandGraspAlpha" - }, - "children": [ - { - "id": "rightIndexPointOpen", - "type": "clip", - "data": { - "url": "animations/touch_point_open_right.fbx", - "startFrame": 15.0, - "endFrame": 15.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "rightIndexPointClosed", - "type": "clip", - "data": { - "url": "animations/touch_point_closed_right.fbx", - "startFrame": 15.0, - "endFrame": 15.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - } - ] - }, - { - "id": "rightThumbRaise", - "type": "blendLinear", - "data": { - "alpha": 0.0, - "alphaVar": "rightHandGraspAlpha" - }, - "children": [ - { - "id": "rightThumbRaiseOpen", - "type": "clip", - "data": { - "url": "animations/touch_thumb_open_right.fbx", - "startFrame": 15.0, - "endFrame": 15.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "rightThumbRaiseClosed", - "type": "clip", - "data": { - "url": "animations/touch_thumb_closed_right.fbx", - "startFrame": 15.0, - "endFrame": 15.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - } - ] - }, - { - "id": "rightIndexPointAndThumbRaise", - "type": "blendLinear", - "data": { - "alpha": 0.0, - "alphaVar": "rightHandGraspAlpha" - }, - "children": [ - { - "id": "rightIndexPointAndThumbRaiseOpen", - "type": "clip", - "data": { - "url": "animations/touch_thumb_point_open_right.fbx", - "startFrame": 15.0, - "endFrame": 15.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "rightIndexPointAndThumbRaiseClosed", - "type": "clip", - "data": { - "url": "animations/touch_thumb_point_closed_right.fbx", - "startFrame": 15.0, - "endFrame": 15.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - } - ] - } - ] - }, - { - "id": "leftHandOverlay", - "type": "overlay", - "data": { - "alpha": 0.0, - "boneSet": "leftHand", - "alphaVar": "leftHandOverlayAlpha" - }, - "children": [ - { - "id": "leftHandStateMachine", - "type": "stateMachine", - "data": { - "currentState": "leftHandGrasp", - "states": [ - { - "id": "leftHandGrasp", - "interpTarget": 3, - "interpDuration": 3, - "transitions": [ - { "var": "isLeftIndexPoint", "state": "leftIndexPoint" }, - { "var": "isLeftThumbRaise", "state": "leftThumbRaise" }, - { "var": "isLeftIndexPointAndThumbRaise", "state": "leftIndexPointAndThumbRaise" } - ] - }, - { - "id": "leftIndexPoint", - "interpTarget": 15, - "interpDuration": 3, - "transitions": [ - { "var": "isLeftHandGrasp", "state": "leftHandGrasp" }, - { "var": "isLeftThumbRaise", "state": "leftThumbRaise" }, - { "var": "isLeftIndexPointAndThumbRaise", "state": "leftIndexPointAndThumbRaise" } - ] - }, - { - "id": "leftThumbRaise", - "interpTarget": 15, - "interpDuration": 3, - "transitions": [ - { "var": "isLeftHandGrasp", "state": "leftHandGrasp" }, - { "var": "isLeftIndexPoint", "state": "leftIndexPoint" }, - { "var": "isLeftIndexPointAndThumbRaise", "state": "leftIndexPointAndThumbRaise" } - ] - }, - { - "id": "leftIndexPointAndThumbRaise", - "interpTarget": 15, - "interpDuration": 3, - "transitions": [ - { "var": "isLeftHandGrasp", "state": "leftHandGrasp" }, - { "var": "isLeftIndexPoint", "state": "leftIndexPoint" }, - { "var": "isLeftThumbRaise", "state": "leftThumbRaise" } - ] - } - ] - }, - "children": [ - { - "id": "leftHandGrasp", - "type": "blendLinear", - "data": { - "alpha": 0.0, - "alphaVar": "leftHandGraspAlpha" - }, - "children": [ - { - "id": "leftHandGraspOpen", - "type": "clip", - "data": { - "url": "animations/hydra_pose_open_left.fbx", - "startFrame": 0.0, - "endFrame": 0.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "leftHandGraspClosed", - "type": "clip", - "data": { - "url": "animations/hydra_pose_closed_left.fbx", - "startFrame": 10.0, - "endFrame": 10.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - } - ] - }, - { - "id": "leftIndexPoint", - "type": "blendLinear", - "data": { - "alpha": 0.0, - "alphaVar": "leftHandGraspAlpha" - }, - "children": [ - { - "id": "leftIndexPointOpen", - "type": "clip", - "data": { - "url": "animations/touch_point_open_left.fbx", - "startFrame": 15.0, - "endFrame": 15.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "leftIndexPointClosed", - "type": "clip", - "data": { - "url": "animations/touch_point_closed_left.fbx", - "startFrame": 15.0, - "endFrame": 15.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - } - ] - }, - { - "id": "leftThumbRaise", - "type": "blendLinear", - "data": { - "alpha": 0.0, - "alphaVar": "leftHandGraspAlpha" - }, - "children": [ - { - "id": "leftThumbRaiseOpen", - "type": "clip", - "data": { - "url": "animations/touch_thumb_open_left.fbx", - "startFrame": 15.0, - "endFrame": 15.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "leftThumbRaiseClosed", - "type": "clip", - "data": { - "url": "animations/touch_thumb_closed_left.fbx", - "startFrame": 15.0, - "endFrame": 15.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - } - ] - }, - { - "id": "leftIndexPointAndThumbRaise", - "type": "blendLinear", - "data": { - "alpha": 0.0, - "alphaVar": "leftHandGraspAlpha" - }, - "children": [ - { - "id": "leftIndexPointAndThumbRaiseOpen", - "type": "clip", - "data": { - "url": "animations/touch_thumb_point_open_left.fbx", - "startFrame": 15.0, - "endFrame": 15.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "leftIndexPointAndThumbRaiseClosed", - "type": "clip", - "data": { - "url": "animations/touch_thumb_point_closed_left.fbx", - "startFrame": 15.0, - "endFrame": 15.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - } - ] - } - ] - }, - { - "id": "mainStateMachine", - "type": "stateMachine", - "data": { - "currentState": "idle", - "states": [ - { - "id": "idle", - "interpTarget": 10, - "interpDuration": 10, - "transitions": [ - { "var": "isMovingForward", "state": "idleToWalkFwd" }, - { "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": "isInAirStand", "state": "inAirStand" }, - { "var": "isInAirRun", "state": "inAirRun" } - ] - }, - { - "id": "idleToWalkFwd", - "interpTarget": 10, - "interpDuration": 3, - "transitions": [ - { "var": "idleToWalkFwdOnDone", "state": "walkFwd" }, - { "var": "isNotMoving", "state": "idle" }, - { "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": "isInAirStand", "state": "inAirStand" }, - { "var": "isInAirRun", "state": "inAirRun" } - ] - }, - { - "id": "walkFwd", - "interpTarget": 16, - "interpDuration": 6, - "transitions": [ - { "var": "isNotMoving", "state": "idle" }, - { "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": "isInAirStand", "state": "inAirStand" }, - { "var": "isInAirRun", "state": "inAirRun" } - ] - }, - { - "id": "walkBwd", - "interpTarget": 8, - "interpDuration": 2, - "transitions": [ - { "var": "isNotMoving", "state": "idle" }, - { "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": "isInAirStand", "state": "inAirStand" }, - { "var": "isInAirRun", "state": "inAirRun" } - ] - }, - { - "id": "strafeRight", - "interpTarget": 20, - "interpDuration": 1, - "transitions": [ - { "var": "isNotMoving", "state": "idle" }, - { "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": "isInAirStand", "state": "inAirStand" }, - { "var": "isInAirRun", "state": "inAirRun" } - ] - }, - { - "id": "strafeLeft", - "interpTarget": 20, - "interpDuration": 1, - "transitions": [ - { "var": "isNotMoving", "state": "idle" }, - { "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": "isInAirStand", "state": "inAirStand" }, - { "var": "isInAirRun", "state": "inAirRun" } - ] - }, - { - "id": "turnRight", - "interpTarget": 6, - "interpDuration": 6, - "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" } - ] - }, - { - "id": "turnLeft", - "interpTarget": 6, - "interpDuration": 6, - "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" } - ] - }, - { - "id": "fly", - "interpTarget": 6, - "interpDuration": 6, - "transitions": [ - { "var": "isNotFlying", "state": "idle" } - ] - }, - { - "id": "takeoffStand", - "interpTarget": 0, - "interpDuration": 6, - "transitions": [ - { "var": "isNotTakeoff", "state": "inAirStand" } - ] - }, - { - "id": "takeoffRun", - "interpTarget": 0, - "interpDuration": 6, - "transitions": [ - { "var": "isNotTakeoff", "state": "inAirRun" } - ] - }, - { - "id": "inAirStand", - "interpTarget": 0, - "interpDuration": 6, - "interpType": "snapshotPrev", - "transitions": [ - { "var": "isNotInAir", "state": "landStandImpact" } - ] - }, - { - "id": "inAirRun", - "interpTarget": 0, - "interpDuration": 6, - "interpType": "snapshotPrev", - "transitions": [ - { "var": "isNotInAir", "state": "landRun" } - ] - }, - { - "id": "landStandImpact", - "interpTarget": 6, - "interpDuration": 4, - "transitions": [ - { "var": "isFlying", "state": "fly" }, - { "var": "isTakeoffStand", "state": "takeoffStand" }, - { "var": "isTakeoffRun", "state": "takeoffRun" }, - { "var": "landStandImpactOnDone", "state": "landStand" } - ] - }, - { - "id": "landStand", - "interpTarget": 0, - "interpDuration": 1, - "transitions": [ - { "var": "isMovingForward", "state": "idleToWalkFwd" }, - { "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": "isInAirStand", "state": "inAirStand" }, - { "var": "isInAirRun", "state": "inAirRun" }, - { "var": "landStandOnDone", "state": "idle" } - ] - }, - { - "id": "landRun", - "interpTarget": 1, - "interpDuration": 7, - "transitions": [ - { "var": "isFlying", "state": "fly" }, - { "var": "isTakeoffStand", "state": "takeoffStand" }, - { "var": "isTakeoffRun", "state": "takeoffRun" }, - { "var": "landRunOnDone", "state": "walkFwd" } - ] - } - ] - }, - "children": [ - { - "id": "idle", - "type": "stateMachine", - "data": { - "currentState": "idleStand", - "states": [ + "solutionSource": "relaxToUnderPoses", + "solutionSourceVar": "solutionSource", + "targets": [ { - "id": "idleStand", - "interpTarget": 6, - "interpDuration": 6, - "transitions": [ - { "var": "isTalking", "state": "idleTalk" } - ] + "jointName": "Hips", + "positionVar": "hipsPosition", + "rotationVar": "hipsRotation", + "typeVar": "hipsType", + "weightVar": "hipsWeight", + "weight": 1.0, + "flexCoefficients": [1] }, { - "id": "idleTalk", - "interpTarget": 6, - "interpDuration": 6, - "transitions": [ - { "var": "notIsTalking", "state": "idleStand" } - ] + "jointName": "RightHand", + "positionVar": "rightHandPosition", + "rotationVar": "rightHandRotation", + "typeVar": "rightHandType", + "weightVar": "rightHandWeight", + "weight": 1.0, + "flexCoefficients": [1, 0.5, 0.5, 0.2, 0.01, 0.005, 0.001, 0.0, 0.0], + "poleVectorEnabledVar": "rightHandPoleVectorEnabled", + "poleReferenceVectorVar": "rightHandPoleReferenceVector", + "poleVectorVar": "rightHandPoleVector" + }, + { + "jointName": "LeftHand", + "positionVar": "leftHandPosition", + "rotationVar": "leftHandRotation", + "typeVar": "leftHandType", + "weightVar": "leftHandWeight", + "weight": 1.0, + "flexCoefficients": [1, 0.5, 0.5, 0.2, 0.01, 0.005, 0.001, 0.0, 0.0], + "poleVectorEnabledVar": "leftHandPoleVectorEnabled", + "poleReferenceVectorVar": "leftHandPoleReferenceVector", + "poleVectorVar": "leftHandPoleVector" + }, + { + "jointName": "Spine2", + "positionVar": "spine2Position", + "rotationVar": "spine2Rotation", + "typeVar": "spine2Type", + "weightVar": "spine2Weight", + "weight": 2.0, + "flexCoefficients": [1.0, 0.5, 0.25] + }, + { + "jointName": "Head", + "positionVar": "headPosition", + "rotationVar": "headRotation", + "typeVar": "headType", + "weightVar": "headWeight", + "weight": 4.0, + "flexCoefficients": [1, 0.5, 0.25, 0.2, 0.1] } ] }, - "children": [ - { - "id": "idleStand", - "type": "clip", - "data": { - "url": "animations/idle.fbx", - "startFrame": 0.0, - "endFrame": 300.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "idleTalk", - "type": "clip", - "data": { - "url": "animations/talk.fbx", - "startFrame": 0.0, - "endFrame": 800.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - } - ] + "children": [] }, { - "id": "walkFwd", - "type": "blendLinearMove", + "id": "defaultPoseOverlay", + "type": "overlay", "data": { "alpha": 0.0, - "desiredSpeed": 1.4, - "characteristicSpeeds": [0.5, 1.4, 4.5], - "alphaVar": "moveForwardAlpha", - "desiredSpeedVar": "moveForwardSpeed" + "alphaVar": "defaultPoseOverlayAlpha", + "boneSet": "fullBody", + "boneSetVar": "defaultPoseOverlayBoneSet" }, "children": [ { - "id": "walkFwdShort", - "type": "clip", + "id": "defaultPose", + "type": "defaultPose", "data": { - "url": "animations/walk_short_fwd.fbx", - "startFrame": 0.0, - "endFrame": 39.0, - "timeScale": 1.0, - "loopFlag": true }, "children": [] }, { - "id": "walkFwdNormal", - "type": "clip", + "id": "rightHandOverlay", + "type": "overlay", "data": { - "url": "animations/walk_fwd.fbx", - "startFrame": 0.0, - "endFrame": 35.0, - "timeScale": 1.0, - "loopFlag": true + "alpha": 0.0, + "boneSet": "rightHand", + "alphaVar": "rightHandOverlayAlpha" }, - "children": [] - }, - { - "id": "walkFwdRun", - "type": "clip", - "data": { - "url": "animations/run_fwd.fbx", - "startFrame": 0.0, - "endFrame": 21.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] + "children": [ + { + "id": "rightHandStateMachine", + "type": "stateMachine", + "data": { + "currentState": "rightHandGrasp", + "states": [ + { + "id": "rightHandGrasp", + "interpTarget": 3, + "interpDuration": 3, + "transitions": [ + { "var": "isRightIndexPoint", "state": "rightIndexPoint" }, + { "var": "isRightThumbRaise", "state": "rightThumbRaise" }, + { "var": "isRightIndexPointAndThumbRaise", "state": "rightIndexPointAndThumbRaise" } + ] + }, + { + "id": "rightIndexPoint", + "interpTarget": 15, + "interpDuration": 3, + "transitions": [ + { "var": "isRightHandGrasp", "state": "rightHandGrasp" }, + { "var": "isRightThumbRaise", "state": "rightThumbRaise" }, + { "var": "isRightIndexPointAndThumbRaise", "state": "rightIndexPointAndThumbRaise" } + ] + }, + { + "id": "rightThumbRaise", + "interpTarget": 15, + "interpDuration": 3, + "transitions": [ + { "var": "isRightHandGrasp", "state": "rightHandGrasp" }, + { "var": "isRightIndexPoint", "state": "rightIndexPoint" }, + { "var": "isRightIndexPointAndThumbRaise", "state": "rightIndexPointAndThumbRaise" } + ] + }, + { + "id": "rightIndexPointAndThumbRaise", + "interpTarget": 15, + "interpDuration": 3, + "transitions": [ + { "var": "isRightHandGrasp", "state": "rightHandGrasp" }, + { "var": "isRightIndexPoint", "state": "rightIndexPoint" }, + { "var": "isRightThumbRaise", "state": "rightThumbRaise" } + ] + } + ] + }, + "children": [ + { + "id": "rightHandGrasp", + "type": "blendLinear", + "data": { + "alpha": 0.0, + "alphaVar": "rightHandGraspAlpha" + }, + "children": [ + { + "id": "rightHandGraspOpen", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/hydra_pose_open_right.fbx", + "startFrame": 0.0, + "endFrame": 0.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "rightHandGraspClosed", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/hydra_pose_closed_right.fbx", + "startFrame": 0.0, + "endFrame": 0.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + }, + { + "id": "rightIndexPoint", + "type": "blendLinear", + "data": { + "alpha": 0.0, + "alphaVar": "rightHandGraspAlpha" + }, + "children": [ + { + "id": "rightIndexPointOpen", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/touch_point_open_right.fbx", + "startFrame": 15.0, + "endFrame": 15.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "rightIndexPointClosed", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/touch_point_closed_right.fbx", + "startFrame": 15.0, + "endFrame": 15.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + }, + { + "id": "rightThumbRaise", + "type": "blendLinear", + "data": { + "alpha": 0.0, + "alphaVar": "rightHandGraspAlpha" + }, + "children": [ + { + "id": "rightThumbRaiseOpen", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/touch_thumb_open_right.fbx", + "startFrame": 15.0, + "endFrame": 15.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "rightThumbRaiseClosed", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/touch_thumb_closed_right.fbx", + "startFrame": 15.0, + "endFrame": 15.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + }, + { + "id": "rightIndexPointAndThumbRaise", + "type": "blendLinear", + "data": { + "alpha": 0.0, + "alphaVar": "rightHandGraspAlpha" + }, + "children": [ + { + "id": "rightIndexPointAndThumbRaiseOpen", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/touch_thumb_point_open_right.fbx", + "startFrame": 15.0, + "endFrame": 15.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "rightIndexPointAndThumbRaiseClosed", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/touch_thumb_point_closed_right.fbx", + "startFrame": 15.0, + "endFrame": 15.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + } + ] + }, + { + "id": "leftHandOverlay", + "type": "overlay", + "data": { + "alpha": 0.0, + "boneSet": "leftHand", + "alphaVar": "leftHandOverlayAlpha" + }, + "children": [ + { + "id": "leftHandStateMachine", + "type": "stateMachine", + "data": { + "currentState": "leftHandGrasp", + "states": [ + { + "id": "leftHandGrasp", + "interpTarget": 3, + "interpDuration": 3, + "transitions": [ + { "var": "isLeftIndexPoint", "state": "leftIndexPoint" }, + { "var": "isLeftThumbRaise", "state": "leftThumbRaise" }, + { "var": "isLeftIndexPointAndThumbRaise", "state": "leftIndexPointAndThumbRaise" } + ] + }, + { + "id": "leftIndexPoint", + "interpTarget": 15, + "interpDuration": 3, + "transitions": [ + { "var": "isLeftHandGrasp", "state": "leftHandGrasp" }, + { "var": "isLeftThumbRaise", "state": "leftThumbRaise" }, + { "var": "isLeftIndexPointAndThumbRaise", "state": "leftIndexPointAndThumbRaise" } + ] + }, + { + "id": "leftThumbRaise", + "interpTarget": 15, + "interpDuration": 3, + "transitions": [ + { "var": "isLeftHandGrasp", "state": "leftHandGrasp" }, + { "var": "isLeftIndexPoint", "state": "leftIndexPoint" }, + { "var": "isLeftIndexPointAndThumbRaise", "state": "leftIndexPointAndThumbRaise" } + ] + }, + { + "id": "leftIndexPointAndThumbRaise", + "interpTarget": 15, + "interpDuration": 3, + "transitions": [ + { "var": "isLeftHandGrasp", "state": "leftHandGrasp" }, + { "var": "isLeftIndexPoint", "state": "leftIndexPoint" }, + { "var": "isLeftThumbRaise", "state": "leftThumbRaise" } + ] + } + ] + }, + "children": [ + { + "id": "leftHandGrasp", + "type": "blendLinear", + "data": { + "alpha": 0.0, + "alphaVar": "leftHandGraspAlpha" + }, + "children": [ + { + "id": "leftHandGraspOpen", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/hydra_pose_open_left.fbx", + "startFrame": 0.0, + "endFrame": 0.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "leftHandGraspClosed", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/hydra_pose_closed_left.fbx", + "startFrame": 10.0, + "endFrame": 10.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + }, + { + "id": "leftIndexPoint", + "type": "blendLinear", + "data": { + "alpha": 0.0, + "alphaVar": "leftHandGraspAlpha" + }, + "children": [ + { + "id": "leftIndexPointOpen", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/touch_point_open_left.fbx", + "startFrame": 15.0, + "endFrame": 15.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "leftIndexPointClosed", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/touch_point_closed_left.fbx", + "startFrame": 15.0, + "endFrame": 15.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + }, + { + "id": "leftThumbRaise", + "type": "blendLinear", + "data": { + "alpha": 0.0, + "alphaVar": "leftHandGraspAlpha" + }, + "children": [ + { + "id": "leftThumbRaiseOpen", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/touch_thumb_open_left.fbx", + "startFrame": 15.0, + "endFrame": 15.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "leftThumbRaiseClosed", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/touch_thumb_closed_left.fbx", + "startFrame": 15.0, + "endFrame": 15.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + }, + { + "id": "leftIndexPointAndThumbRaise", + "type": "blendLinear", + "data": { + "alpha": 0.0, + "alphaVar": "leftHandGraspAlpha" + }, + "children": [ + { + "id": "leftIndexPointAndThumbRaiseOpen", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/touch_thumb_point_open_left.fbx", + "startFrame": 15.0, + "endFrame": 15.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "leftIndexPointAndThumbRaiseClosed", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/touch_thumb_point_closed_left.fbx", + "startFrame": 15.0, + "endFrame": 15.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + } + ] + }, + { + "id": "mainStateMachine", + "type": "stateMachine", + "data": { + "outputJoints": ["LeftFoot", "RightFoot"], + "currentState": "idle", + "states": [ + { + "id": "idle", + "interpTarget": 0, + "interpDuration": 4, + "interpType": "snapshotPrev", + "transitions": [ + { "var": "isMovingForward", "state": "idleToWalkFwd" }, + { "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": "isInAirStand", "state": "inAirStand" }, + { "var": "isInAirRun", "state": "inAirRun" }, + { "var": "isMovingRightHmd", "state": "strafeRightHmd" }, + { "var": "isMovingLeftHmd", "state": "strafeLeftHmd" } + ] + }, + { + "id": "idleToWalkFwd", + "interpTarget": 10, + "interpDuration": 4, + "interpType": "snapshotPrev", + "transitions": [ + { "var": "idleToWalkFwdOnDone", "state": "walkFwd" }, + { "var": "isNotMoving", "state": "idle" }, + { "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": "isInAirStand", "state": "inAirStand" }, + { "var": "isInAirRun", "state": "inAirRun" }, + { "var": "isMovingRightHmd", "state": "strafeRightHmd" }, + { "var": "isMovingLeftHmd", "state": "strafeLeftHmd" } + ] + }, + { + "id": "idleSettle", + "interpTarget": 10, + "interpDuration": 10, + "interpType": "snapshotPrev", + "transitions": [ + {"var": "idleSettleOnDone", "state": "idle" }, + {"var": "isMovingForward", "state": "idleToWalkFwd" }, + { "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": "isInAirStand", "state": "inAirStand" }, + { "var": "isInAirRun", "state": "inAirRun" } + ] + }, + { + "id": "walkFwd", + "interpTarget": 16, + "interpDuration": 6, + "interpType": "snapshotPrev", + "transitions": [ + { "var": "isNotMoving", "state": "idleSettle" }, + { "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": "isInAirStand", "state": "inAirStand" }, + { "var": "isInAirRun", "state": "inAirRun" }, + { "var": "isMovingRightHmd", "state": "strafeRightHmd" }, + { "var": "isMovingLeftHmd", "state": "strafeLeftHmd" } + ] + }, + { + "id": "walkBwd", + "interpTarget": 8, + "interpDuration": 6, + "interpType": "snapshotPrev", + "transitions": [ + { "var": "isNotMoving", "state": "idleSettle" }, + { "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": "isInAirStand", "state": "inAirStand" }, + { "var": "isInAirRun", "state": "inAirRun" }, + { "var": "isMovingRightHmd", "state": "strafeRightHmd" }, + { "var": "isMovingLeftHmd", "state": "strafeLeftHmd" } + ] + }, + { + "id": "strafeRight", + "interpTarget": 5, + "interpDuration": 8, + "interpType": "snapshotPrev", + "transitions": [ + { "var": "isNotMoving", "state": "idleSettle" }, + { "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": "isInAirStand", "state": "inAirStand" }, + { "var": "isInAirRun", "state": "inAirRun" }, + { "var": "isMovingRightHmd", "state": "strafeRightHmd" }, + { "var": "isMovingLeftHmd", "state": "strafeLeftHmd" } + ] + }, + { + "id": "strafeLeft", + "interpTarget": 5, + "interpDuration": 8, + "interpType": "snapshotPrev", + "transitions": [ + { "var": "isNotMoving", "state": "idleSettle" }, + { "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": "isInAirStand", "state": "inAirStand" }, + { "var": "isInAirRun", "state": "inAirRun" }, + { "var": "isMovingRightHmd", "state": "strafeRightHmd" }, + { "var": "isMovingLeftHmd", "state": "strafeLeftHmd" } + ] + }, + { + "id": "strafeRightHmd", + "interpTarget": 5, + "interpDuration": 8, + "interpType": "snapshotPrev", + "transitions": [ + { "var": "isNotMoving", "state": "idleSettle" }, + { "var": "isMovingForward", "state": "walkFwd" }, + { "var": "isMovingBackward", "state": "walkBwd" }, + { "var": "isMovingLeftHmd", "state": "strafeLeftHmd" }, + { "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": "isInAirStand", "state": "inAirStand" }, + { "var": "isInAirRun", "state": "inAirRun" } + ] + }, + { + "id": "strafeLeftHmd", + "interpTarget": 5, + "interpDuration": 8, + "interpType": "snapshotPrev", + "transitions": [ + { "var": "isNotMoving", "state": "idleSettle" }, + { "var": "isMovingForward", "state": "walkFwd" }, + { "var": "isMovingBackward", "state": "walkBwd" }, + { "var": "isMovingRightHmd", "state": "strafeRightHmd" }, + { "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": "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" } + ] + }, + { + "id": "fly", + "interpTarget": 6, + "interpDuration": 6, + "transitions": [ + { "var": "isNotFlying", "state": "idleSettle" } + ] + }, + { + "id": "takeoffStand", + "interpTarget": 0, + "interpDuration": 6, + "transitions": [ + { "var": "isNotTakeoff", "state": "inAirStand" } + ] + }, + { + "id": "takeoffRun", + "interpTarget": 0, + "interpDuration": 6, + "transitions": [ + { "var": "isNotTakeoff", "state": "inAirRun" } + ] + }, + { + "id": "inAirStand", + "interpTarget": 0, + "interpDuration": 6, + "interpType": "snapshotPrev", + "transitions": [ + { "var": "isNotInAir", "state": "landStandImpact" } + ] + }, + { + "id": "inAirRun", + "interpTarget": 0, + "interpDuration": 6, + "interpType": "snapshotPrev", + "transitions": [ + { "var": "isNotInAir", "state": "landRun" } + ] + }, + { + "id": "landStandImpact", + "interpTarget": 6, + "interpDuration": 4, + "transitions": [ + { "var": "isFlying", "state": "fly" }, + { "var": "isTakeoffStand", "state": "takeoffStand" }, + { "var": "isTakeoffRun", "state": "takeoffRun" }, + { "var": "landStandImpactOnDone", "state": "landStand" } + ] + }, + { + "id": "landStand", + "interpTarget": 0, + "interpDuration": 1, + "transitions": [ + { "var": "isMovingForward", "state": "idleToWalkFwd" }, + { "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": "isInAirStand", "state": "inAirStand" }, + { "var": "isInAirRun", "state": "inAirRun" }, + { "var": "landStandOnDone", "state": "idle" }, + { "var": "isMovingRightHmd", "state": "strafeRightHmd" }, + { "var": "isMovingLeftHmd", "state": "strafeLeftHmd" } + ] + }, + { + "id": "landRun", + "interpTarget": 1, + "interpDuration": 7, + "transitions": [ + { "var": "isFlying", "state": "fly" }, + { "var": "isTakeoffStand", "state": "takeoffStand" }, + { "var": "isTakeoffRun", "state": "takeoffRun" }, + { "var": "landRunOnDone", "state": "walkFwd" } + ] + } + ] + }, + "children": [ + { + "id": "idle", + "type": "stateMachine", + "data": { + "currentState": "idleStand", + "states": [ + { + "id": "idleStand", + "interpTarget": 6, + "interpDuration": 6, + "transitions": [ + { "var": "isTalking", "state": "idleTalk" } + ] + }, + { + "id": "idleTalk", + "interpTarget": 6, + "interpDuration": 6, + "transitions": [ + { "var": "notIsTalking", "state": "idleStand" } + ] + } + ] + }, + "children": [ + { + "id": "idleStand", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/idle.fbx", + "startFrame": 0.0, + "endFrame": 300.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "idleTalk", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/talk.fbx", + "startFrame": 0.0, + "endFrame": 800.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + }, + { + "id": "walkFwd", + "type": "blendLinearMove", + "data": { + "alpha": 0.0, + "desiredSpeed": 1.4, + "characteristicSpeeds": [0.5, 1.5, 2.5, 3.2, 4.5], + "alphaVar": "moveForwardAlpha", + "desiredSpeedVar": "moveForwardSpeed" + }, + "children": [ + { + "id": "walkFwdShort_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/walk_short_fwd.fbx", + "startFrame": 0.0, + "endFrame": 39.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "walkFwdNormal_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/walk_fwd.fbx", + "startFrame": 0.0, + "endFrame": 35.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "walkFwdFast_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/walk_fwd_fast.fbx", + "startFrame": 0.0, + "endFrame": 25.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "walkFwdJog_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jog_fwd.fbx", + "startFrame": 0.0, + "endFrame": 25.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "walkFwdRun_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/run_fwd.fbx", + "startFrame": 0.0, + "endFrame": 21.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + }, + { + "id": "idleToWalkFwd", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/idle_to_walk.fbx", + "startFrame": 1.0, + "endFrame": 13.0, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + }, + { + "id": "idleSettle", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/settle_to_idle.fbx", + "startFrame": 1.0, + "endFrame": 48.0, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + }, + { + "id": "walkBwd", + "type": "blendLinearMove", + "data": { + "alpha": 0.0, + "desiredSpeed": 1.4, + "characteristicSpeeds": [0.6, 1.7], + "alphaVar": "moveBackwardAlpha", + "desiredSpeedVar": "moveBackwardSpeed" + }, + "children": [ + { + "id": "walkBwdShort", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/walk_short_bwd.fbx", + "startFrame": 0.0, + "endFrame": 38.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "walkBwdNormal", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/walk_bwd_fast.fbx", + "startFrame": 0.0, + "endFrame": 27.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + }, + { + "id": "turnLeft", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/turn_left.fbx", + "startFrame": 0.0, + "endFrame": 32.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "turnRight", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/turn_left.fbx", + "startFrame": 0.0, + "endFrame": 32.0, + "timeScale": 1.0, + "loopFlag": true, + "mirrorFlag": true + }, + "children": [] + }, + { + "id": "strafeLeft", + "type": "blendLinearMove", + "data": { + "alpha": 0.0, + "desiredSpeed": 1.4, + "characteristicSpeeds": [0, 0.5, 1.5, 2.6, 3.0], + "alphaVar": "moveLateralAlpha", + "desiredSpeedVar": "moveLateralSpeed" + }, + "children": [ + { + "id": "strafeLeftShort_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/side_step_short_left.fbx", + "startFrame": 0.0, + "endFrame": 29.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "strafeLeft_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/side_step_left.fbx", + "startFrame": 0.0, + "endFrame": 20.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "strafeLeftAnim_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/walk_left.fbx", + "startFrame": 0.0, + "endFrame": 33.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "strafeLeftFast_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/walk_left_fast.fbx", + "startFrame": 0.0, + "endFrame": 21.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "strafeLeftJog_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jog_left.fbx", + "startFrame": 0.0, + "endFrame": 24.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + }, + { + "id": "strafeRight", + "type": "blendLinearMove", + "data": { + "alpha": 0.0, + "desiredSpeed": 1.4, + "characteristicSpeeds": [0, 0.5, 1.5, 2.6, 3.0], + "alphaVar": "moveLateralAlpha", + "desiredSpeedVar": "moveLateralSpeed" + }, + "children": [ { + "id": "stepRightShort_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/side_step_short_left.fbx", + "startFrame": 0.0, + "endFrame": 29.0, + "timeScale": 1.0, + "loopFlag": true, + "mirrorFlag": true + }, + "children": [] + }, + { + "id": "stepRight_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/side_step_left.fbx", + "startFrame": 0.0, + "endFrame": 20.0, + "timeScale": 1.0, + "loopFlag": true, + "mirrorFlag": true + }, + "children": [] + }, + { + "id": "strafeRight_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/walk_left.fbx", + "startFrame": 0.0, + "endFrame": 33.0, + "timeScale": 1.0, + "loopFlag": true, + "mirrorFlag": true + }, + "children": [] + }, + { + "id": "strafeRightFast_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/walk_left_fast.fbx", + "startFrame": 0.0, + "endFrame": 21.0, + "timeScale": 1.0, + "loopFlag": true, + "mirrorFlag": true + }, + "children": [] + }, + { + "id": "strafeRightJog_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jog_left.fbx", + "startFrame": 0.0, + "endFrame": 24.0, + "timeScale": 1.0, + "loopFlag": true, + "mirrorFlag": true + }, + "children": [] + } + ] + }, + { + "id": "strafeLeftHmd", + "type": "blendLinearMove", + "data": { + "alpha": 0.0, + "desiredSpeed": 1.4, + "characteristicSpeeds": [0, 0.5, 2.5], + "alphaVar": "moveLateralAlpha", + "desiredSpeedVar": "moveLateralSpeed" + }, + "children": [ + { + "id": "stepLeftShort_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/side_step_short_left.fbx", + "startFrame": 0.0, + "endFrame": 29.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "stepLeft_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/side_step_left.fbx", + "startFrame": 0.0, + "endFrame": 20.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "strafeLeftAnim_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/side_step_left_fast.fbx", + "startFrame": 0.0, + "endFrame": 16.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + }, + { + "id": "strafeRightHmd", + "type": "blendLinearMove", + "data": { + "alpha": 0.0, + "desiredSpeed": 1.4, + "characteristicSpeeds": [0, 0.5, 2.5], + "alphaVar": "moveLateralAlpha", + "desiredSpeedVar": "moveLateralSpeed" + }, + "children": [ + { + "id": "stepRightShort_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/side_step_short_left.fbx", + "startFrame": 0.0, + "endFrame": 29.0, + "timeScale": 1.0, + "loopFlag": true, + "mirrorFlag": true + }, + "children": [] + }, + { + "id": "stepRight_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/side_step_left.fbx", + "startFrame": 0.0, + "endFrame": 20.0, + "timeScale": 1.0, + "loopFlag": true, + "mirrorFlag": true + }, + "children": [] + }, + { + "id": "strafeRightAnim_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/side_step_left_fast.fbx", + "startFrame": 0.0, + "endFrame": 16.0, + "timeScale": 1.0, + "loopFlag": true, + "mirrorFlag": true + }, + "children": [] + } + ] + }, + { + "id": "fly", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/fly.fbx", + "startFrame": 1.0, + "endFrame": 80.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "takeoffStand", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jump_standing_takeoff.fbx", + "startFrame": 17.0, + "endFrame": 25.0, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + }, + { + "id": "takeoffRun", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jump_takeoff.fbx", + "startFrame": 1.0, + "endFrame": 2.5, + "timeScale": 0.01, + "loopFlag": false + }, + "children": [] + }, + { + "id": "inAirStand", + "type": "blendLinear", + "data": { + "alpha": 0.0, + "alphaVar": "inAirAlpha" + }, + "children": [ + { + "id": "inAirStandPreApex", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jump_standing_apex.fbx", + "startFrame": 0.0, + "endFrame": 0.0, + "timeScale": 0.0, + "loopFlag": false + }, + "children": [] + }, + { + "id": "inAirStandApex", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jump_standing_apex.fbx", + "startFrame": 1.0, + "endFrame": 1.0, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + }, + { + "id": "inAirStandPostApex", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jump_standing_apex.fbx", + "startFrame": 2.0, + "endFrame": 2.0, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + } + ] + }, + { + "id": "inAirRun", + "type": "blendLinear", + "data": { + "alpha": 0.0, + "alphaVar": "inAirAlpha" + }, + "children": [ + { + "id": "inAirRunPreApex", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jump_in_air.fbx", + "startFrame": 0.0, + "endFrame": 0.0, + "timeScale": 0.0, + "loopFlag": false + }, + "children": [] + }, + { + "id": "inAirRunApex", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jump_in_air.fbx", + "startFrame": 6.0, + "endFrame": 6.0, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + }, + { + "id": "inAirRunPostApex", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jump_in_air.fbx", + "startFrame": 11.0, + "endFrame": 11.0, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + } + ] + }, + { + "id": "landStandImpact", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jump_standing_land.fbx", + "startFrame": 1.0, + "endFrame": 6.0, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + }, + { + "id": "landStand", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jump_standing_land.fbx", + "startFrame": 6.0, + "endFrame": 28.0, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + }, + { + "id": "landRun", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/jump_land.fbx", + "startFrame": 1.0, + "endFrame": 6.0, + "timeScale": 0.65, + "loopFlag": false + }, + "children": [] + } + ] + } + ] + } + ] } ] - }, - { - "id": "idleToWalkFwd", - "type": "clip", - "data": { - "url": "animations/idle_to_walk.fbx", - "startFrame": 1.0, - "endFrame": 13.0, - "timeScale": 1.0, - "loopFlag": false - }, - "children": [] - }, - { - "id": "walkBwd", - "type": "blendLinearMove", - "data": { - "alpha": 0.0, - "desiredSpeed": 1.4, - "characteristicSpeeds": [0.6, 1.45], - "alphaVar": "moveBackwardAlpha", - "desiredSpeedVar": "moveBackwardSpeed" - }, - "children": [ - { - "id": "walkBwdShort", - "type": "clip", - "data": { - "url": "animations/walk_short_bwd.fbx", - "startFrame": 0.0, - "endFrame": 38.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "walkBwdNormal", - "type": "clip", - "data": { - "url": "animations/walk_bwd.fbx", - "startFrame": 0.0, - "endFrame": 36.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - } - ] - }, - { - "id": "turnLeft", - "type": "clip", - "data": { - "url": "animations/turn_left.fbx", - "startFrame": 0.0, - "endFrame": 28.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "turnRight", - "type": "clip", - "data": { - "url": "animations/turn_left.fbx", - "startFrame": 0.0, - "endFrame": 30.0, - "timeScale": 1.0, - "loopFlag": true, - "mirrorFlag": true - }, - "children": [] - }, - { - "id": "strafeLeft", - "type": "blendLinearMove", - "data": { - "alpha": 0.0, - "desiredSpeed": 1.4, - "characteristicSpeeds": [0.2, 0.65], - "alphaVar": "moveLateralAlpha", - "desiredSpeedVar": "moveLateralSpeed" - }, - "children": [ - { - "id": "strafeLeftShort", - "type": "clip", - "data": { - "url": "animations/side_step_short_left.fbx", - "startFrame": 0.0, - "endFrame": 28.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "strafeLeftNormal", - "type": "clip", - "data": { - "url": "animations/side_step_left.fbx", - "startFrame": 0.0, - "endFrame": 30.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - } - ] - }, - { - "id": "strafeRight", - "type": "blendLinearMove", - "data": { - "alpha": 0.0, - "desiredSpeed": 1.4, - "characteristicSpeeds": [0.2, 0.65], - "alphaVar": "moveLateralAlpha", - "desiredSpeedVar": "moveLateralSpeed" - }, - "children": [ - { - "id": "strafeRightShort", - "type": "clip", - "data": { - "url": "animations/side_step_short_right.fbx", - "startFrame": 0.0, - "endFrame": 28.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "strafeRightNormal", - "type": "clip", - "data": { - "url": "animations/side_step_right.fbx", - "startFrame": 0.0, - "endFrame": 30.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - } - ] - }, - { - "id": "fly", - "type": "clip", - "data": { - "url": "animations/fly.fbx", - "startFrame": 1.0, - "endFrame": 80.0, - "timeScale": 1.0, - "loopFlag": true - }, - "children": [] - }, - { - "id": "takeoffStand", - "type": "clip", - "data": { - "url": "animations/jump_standing_takeoff.fbx", - "startFrame": 17.0, - "endFrame": 25.0, - "timeScale": 1.0, - "loopFlag": false - }, - "children": [] - }, - { - "id": "takeoffRun", - "type": "clip", - "data": { - "url": "animations/jump_takeoff.fbx", - "startFrame": 1.0, - "endFrame": 2.5, - "timeScale": 0.01, - "loopFlag": false - }, - "children": [] - }, - { - "id": "inAirStand", - "type": "blendLinear", - "data": { - "alpha": 0.0, - "alphaVar": "inAirAlpha" - }, - "children": [ - { - "id": "inAirStandPreApex", - "type": "clip", - "data": { - "url": "animations/jump_standing_apex.fbx", - "startFrame": 0.0, - "endFrame": 0.0, - "timeScale": 0.0, - "loopFlag": false - }, - "children": [] - }, - { - "id": "inAirStandApex", - "type": "clip", - "data": { - "url": "animations/jump_standing_apex.fbx", - "startFrame": 1.0, - "endFrame": 1.0, - "timeScale": 1.0, - "loopFlag": false - }, - "children": [] - }, - { - "id": "inAirStandPostApex", - "type": "clip", - "data": { - "url": "animations/jump_standing_apex.fbx", - "startFrame": 2.0, - "endFrame": 2.0, - "timeScale": 1.0, - "loopFlag": false - }, - "children": [] - } - ] - }, - { - "id": "inAirRun", - "type": "blendLinear", - "data": { - "alpha": 0.0, - "alphaVar": "inAirAlpha" - }, - "children": [ - { - "id": "inAirRunPreApex", - "type": "clip", - "data": { - "url": "animations/jump_in_air.fbx", - "startFrame": 0.0, - "endFrame": 0.0, - "timeScale": 0.0, - "loopFlag": false - }, - "children": [] - }, - { - "id": "inAirRunApex", - "type": "clip", - "data": { - "url": "animations/jump_in_air.fbx", - "startFrame": 6.0, - "endFrame": 6.0, - "timeScale": 1.0, - "loopFlag": false - }, - "children": [] - }, - { - "id": "inAirRunPostApex", - "type": "clip", - "data": { - "url": "animations/jump_in_air.fbx", - "startFrame": 11.0, - "endFrame": 11.0, - "timeScale": 1.0, - "loopFlag": false - }, - "children": [] - } - ] - }, - { - "id": "landStandImpact", - "type": "clip", - "data": { - "url": "animations/jump_standing_land.fbx", - "startFrame": 1.0, - "endFrame": 6.0, - "timeScale": 1.0, - "loopFlag": false - }, - "children": [] - }, - { - "id": "landStand", - "type": "clip", - "data": { - "url": "animations/jump_standing_land.fbx", - "startFrame": 6.0, - "endFrame": 28.0, - "timeScale": 1.0, - "loopFlag": false - }, - "children": [] - }, - { - "id": "landRun", - "type": "clip", - "data": { - "url": "animations/jump_land.fbx", - "startFrame": 1.0, - "endFrame": 6.0, - "timeScale": 0.65, - "loopFlag": false - }, - "children": [] } ] } @@ -1203,7 +1551,7 @@ "id": "userAnimA", "type": "clip", "data": { - "url": "animations/idle.fbx", + "url": "qrc:///avatar/animations/idle.fbx", "startFrame": 0.0, "endFrame": 90.0, "timeScale": 1.0, @@ -1215,7 +1563,7 @@ "id": "userAnimB", "type": "clip", "data": { - "url": "animations/idle.fbx", + "url": "qrc:///avatar/animations/idle.fbx", "startFrame": 0.0, "endFrame": 90.0, "timeScale": 1.0, diff --git a/interface/resources/avatar/bookmarks/avatarbookmarks.json b/interface/resources/avatar/bookmarks/avatarbookmarks.json index 2ef59d53a3..9976036f8e 100644 --- a/interface/resources/avatar/bookmarks/avatarbookmarks.json +++ b/interface/resources/avatar/bookmarks/avatarbookmarks.json @@ -380,15 +380,21 @@ { "properties": { "acceleration": { + "blue": 0, + "green": 0, + "red": 0, "x": 0, "y": 0, "z": 0 }, "actionData": "", - "age": 14.011327743530273, - "ageAsText": "0 hours 0 minutes 14 seconds", + "age": 321.8835144042969, + "ageAsText": "0 hours 5 minutes 21 seconds", "angularDamping": 0.39346998929977417, "angularVelocity": { + "blue": 0, + "green": 0, + "red": 0, "x": 0, "y": 0, "z": 0 @@ -406,24 +412,36 @@ }, "boundingBox": { "brn": { - "x": -0.20154684782028198, - "y": 0.03644842654466629, - "z": -0.2641940414905548 + "blue": -0.03950843587517738, + "green": 0.20785385370254517, + "red": -0.04381325840950012, + "x": -0.04381325840950012, + "y": 0.20785385370254517, + "z": -0.03950843587517738 }, "center": { - "x": -0.030000001192092896, - "y": 0.12999820709228516, - "z": -0.07000023126602173 + "blue": 0, + "green": 0.23000000417232513, + "red": 0, + "x": 0, + "y": 0.23000000417232513, + "z": 0 }, "dimensions": { - "x": 0.3430936932563782, - "y": 0.18709957599639893, - "z": 0.38838762044906616 + "blue": 0.07901687175035477, + "green": 0.044292300939559937, + "red": 0.08762651681900024, + "x": 0.08762651681900024, + "y": 0.044292300939559937, + "z": 0.07901687175035477 }, "tfl": { - "x": 0.1415468454360962, - "y": 0.22354799509048462, - "z": 0.12419357895851135 + "blue": 0.03950843587517738, + "green": 0.2521461546421051, + "red": 0.04381325840950012, + "x": 0.04381325840950012, + "y": 0.2521461546421051, + "z": 0.03950843587517738 } }, "canCastShadow": true, @@ -441,189 +459,14 @@ "collisionless": false, "collisionsWillMove": false, "compoundShapeURL": "", - "created": "2018-06-06T17:25:42Z", - "damping": 0.39346998929977417, - "density": 1000, - "description": "", - "dimensions": { - "x": 0.33466479182243347, - "y": 0.16981728374958038, - "z": 0.38838762044906616 - }, - "dynamic": false, - "editionNumber": 19, - "entityInstanceNumber": 0, - "friction": 0.5, - "gravity": { - "x": 0, - "y": 0, - "z": 0 - }, - "href": "", - "id": "{6b0a2b08-e8e3-4d43-95cc-dfc4f7a4b0c9}", - "ignoreForCollisions": false, - "itemArtist": "jyoum", - "itemCategories": "Wearables", - "itemDescription": "A stylish and classic piece of headwear for your avatar.", - "itemLicense": "", - "itemName": "Fedora", - "jointRotations": [ - ], - "jointRotationsSet": [ - ], - "jointTranslations": [ - ], - "jointTranslationsSet": [ - ], - "lastEdited": 1528306032827319, - "lastEditedBy": "{4c770def-4abb-40c6-91a1-88da5247b2db}", - "lifetime": -1, - "limitedRun": 4294967295, - "localPosition": { - "x": -0.030000008642673492, - "y": 0.12999820709228516, - "z": -0.07000023126602173 - }, - "localRotation": { - "w": 0.9996573328971863, - "x": 0, - "y": 0, - "z": 0.026176949962973595 - }, - "locked": false, - "marketplaceID": "11c4208d-15d7-4449-9758-a08da6dbd3dc", - "modelURL": "http://mpassets.highfidelity.com/11c4208d-15d7-4449-9758-a08da6dbd3dc-v1/Fedora.fbx", - "name": "", - "naturalDimensions": { - "x": 0.2765824794769287, - "y": 0.14034485816955566, - "z": 0.320981502532959 - }, - "naturalPosition": { - "x": 0.000143393874168396, - "y": 1.7460365295410156, - "z": 0.022502630949020386 - }, - "originalTextures": "{\n \"file5\": \"http://mpassets.highfidelity.com/11c4208d-15d7-4449-9758-a08da6dbd3dc-v1/Fedora.fbx/Texture/Fedora_Hat1_Base_Color.png\",\n \"file7\": \"http://mpassets.highfidelity.com/11c4208d-15d7-4449-9758-a08da6dbd3dc-v1/Fedora.fbx/Texture/Fedora_Hat1_Roughness.png\"\n}\n", - "owningAvatarID": "{4c770def-4abb-40c6-91a1-88da5247b2db}", - "parentID": "{4c770def-4abb-40c6-91a1-88da5247b2db}", - "parentJointIndex": 64, - "position": { - "x": -0.030000008642673492, - "y": 0.12999820709228516, - "z": -0.07000023126602173 - }, - "queryAACube": { - "scale": 1.6202316284179688, - "x": -0.5601736903190613, - "y": -10.668098449707031, - "z": -0.8933582305908203 - }, - "registrationPoint": { - "x": 0.5, - "y": 0.5, - "z": 0.5 - }, - "relayParentJoints": false, - "renderInfo": { - "drawCalls": 1, - "hasTransparent": false, - "texturesCount": 2, - "texturesSize": 327680, - "verticesCount": 719 - }, - "restitution": 0.5, - "rotation": { - "w": 0.9996573328971863, - "x": 0, - "y": 0, - "z": 0.026176949962973595 - }, - "script": "", - "scriptTimestamp": 0, - "serverScripts": "", - "shapeType": "box", - "staticCertificateVersion": 0, - "textures": "", - "type": "Model", - "userData": "{\"Attachment\":{\"action\":\"attach\",\"joint\":\"HeadTop_End\",\"attached\":false,\"options\":{\"translation\":{\"x\":0,\"y\":0,\"z\":0},\"scale\":1}},\"grabbableKey\":{\"cloneable\":false,\"grabbable\":true}}", - "velocity": { - "x": 0, - "y": 0, - "z": 0 - }, - "visible": true - } - }, - { - "properties": { - "acceleration": { - "x": 0, - "y": 0, - "z": 0 - }, - "actionData": "", - "age": 14.011027336120605, - "ageAsText": "0 hours 0 minutes 14 seconds", - "angularDamping": 0.39346998929977417, - "angularVelocity": { - "x": 0, - "y": 0, - "z": 0 - }, - "animation": { - "allowTranslation": true, - "currentFrame": 0, - "firstFrame": 0, - "fps": 30, - "hold": false, - "lastFrame": 100000, - "loop": true, - "running": false, - "url": "" - }, - "boundingBox": { - "brn": { - "x": -0.04381517320871353, - "y": 0.20789726078510284, - "z": -0.0394962802529335 - }, - "center": { - "x": -1.9073486328125e-06, - "y": 0.2300434112548828, - "z": 1.2159347534179688e-05 - }, - "dimensions": { - "x": 0.08762653172016144, - "y": 0.04429228603839874, - "z": 0.07901687920093536 - }, - "tfl": { - "x": 0.043811358511447906, - "y": 0.2521895468235016, - "z": 0.03952059894800186 - } - }, - "canCastShadow": true, - "certificateID": "", - "clientOnly": true, - "cloneAvatarEntity": false, - "cloneDynamic": false, - "cloneLifetime": 300, - "cloneLimit": 0, - "cloneOriginID": "{00000000-0000-0000-0000-000000000000}", - "cloneable": false, - "collidesWith": "", - "collisionMask": 0, - "collisionSoundURL": "", - "collisionless": false, - "collisionsWillMove": false, - "compoundShapeURL": "", - "created": "2018-06-06T17:25:42Z", + "created": "2018-07-26T23:56:46Z", "damping": 0.39346998929977417, "density": 1000, "description": "", "dimensions": { + "blue": 0.07229919731616974, + "green": 0.06644226610660553, + "red": 0.03022606298327446, "x": 0.03022606298327446, "y": 0.06644226610660553, "z": 0.07229919731616974 @@ -633,12 +476,15 @@ "entityInstanceNumber": 0, "friction": 0.5, "gravity": { + "blue": 0, + "green": 0, + "red": 0, "x": 0, "y": 0, "z": 0 }, "href": "", - "id": "{d018c6ea-b2f4-441e-85e1-d17373ae6f34}", + "id": "{03053239-bb37-4c51-a013-a1772baaeed5}", "ignoreForCollisions": false, "itemArtist": "jyoum", "itemCategories": "Wearables", @@ -653,51 +499,66 @@ ], "jointTranslationsSet": [ ], - "lastEdited": 1528306032505220, - "lastEditedBy": "{b46f9c9e-4cd3-4964-96d6-cf3954abb908}", + "lastEdited": 1532649569894305, + "lastEditedBy": "{042ac463-7879-40f0-8126-e2e56c4345ca}", "lifetime": -1, "limitedRun": 4294967295, "localPosition": { - "x": -1.9073486328125e-06, - "y": 0.2300434112548828, - "z": 1.2159347534179688e-05 + "blue": 0, + "green": 0.23000000417232513, + "red": 0, + "x": 0, + "y": 0.23000000417232513, + "z": 0 }, "localRotation": { - "w": 0.5910987257957458, - "x": -0.48726412653923035, - "y": -0.4088631868362427, - "z": 0.49599069356918335 + "w": 0.5910986065864563, + "x": -0.48726415634155273, + "y": -0.4088630974292755, + "z": 0.49599072337150574 }, "locked": false, "marketplaceID": "0685794d-fddb-4bad-a608-6d7789ceda90", "modelURL": "http://mpassets.highfidelity.com/0685794d-fddb-4bad-a608-6d7789ceda90-v1/ScifiWatch.fbx", "name": "Scifi Watch by Jimi", "naturalDimensions": { + "blue": 0.055614765733480453, + "green": 0.0511094331741333, + "red": 0.023250818252563477, "x": 0.023250818252563477, "y": 0.0511094331741333, "z": 0.055614765733480453 }, "naturalPosition": { + "blue": -0.06031447649002075, + "green": 1.4500460624694824, + "red": 0.6493338942527771, "x": 0.6493338942527771, "y": 1.4500460624694824, "z": -0.06031447649002075 }, "originalTextures": "{\n \"file4\": \"http://mpassets.highfidelity.com/0685794d-fddb-4bad-a608-6d7789ceda90-v1/ScifiWatch.fbx/ScifiWatch/texture/lambert1_Base_Color.png\",\n \"file5\": \"http://mpassets.highfidelity.com/0685794d-fddb-4bad-a608-6d7789ceda90-v1/ScifiWatch.fbx/ScifiWatch/texture/lambert1_Normal_OpenGL.png\",\n \"file6\": \"http://mpassets.highfidelity.com/0685794d-fddb-4bad-a608-6d7789ceda90-v1/ScifiWatch.fbx/ScifiWatch/texture/lambert1_Metallic.png\",\n \"file7\": \"http://mpassets.highfidelity.com/0685794d-fddb-4bad-a608-6d7789ceda90-v1/ScifiWatch.fbx/ScifiWatch/texture/lambert1_Roughness.png\",\n \"file8\": \"http://mpassets.highfidelity.com/0685794d-fddb-4bad-a608-6d7789ceda90-v1/ScifiWatch.fbx/ScifiWatch/texture/lambert1_Emissive.png\"\n}\n", - "owningAvatarID": "{4c770def-4abb-40c6-91a1-88da5247b2db}", - "parentID": "{4c770def-4abb-40c6-91a1-88da5247b2db}", + "owningAvatarID": "{042ac463-7879-40f0-8126-e2e56c4345ca}", + "parentID": "{042ac463-7879-40f0-8126-e2e56c4345ca}", "parentJointIndex": 16, "position": { - "x": -1.9073486328125e-06, - "y": 0.2300434112548828, - "z": 1.2159347534179688e-05 + "blue": 0, + "green": 0.23000000417232513, + "red": 0, + "x": 0, + "y": 0.23000000417232513, + "z": 0 }, "queryAACube": { "scale": 0.3082179129123688, - "x": -0.19203892350196838, - "y": -10.429610252380371, - "z": -0.4076632857322693 + "x": 495.7716979980469, + "y": 498.345703125, + "z": 498.52044677734375 }, "registrationPoint": { + "blue": 0.5, + "green": 0.5, + "red": 0.5, "x": 0.5, "y": 0.5, "z": 0.5 @@ -712,10 +573,10 @@ }, "restitution": 0.5, "rotation": { - "w": 0.5910987257957458, - "x": -0.48726412653923035, - "y": -0.4088631868362427, - "z": 0.49599069356918335 + "w": 0.5910986065864563, + "x": -0.48726415634155273, + "y": -0.4088630974292755, + "z": 0.49599072337150574 }, "script": "", "scriptTimestamp": 0, @@ -726,6 +587,229 @@ "type": "Model", "userData": "{\"Attachment\":{\"action\":\"attach\",\"joint\":\"[LR]ForeArm\",\"attached\":false,\"options\":{\"translation\":{\"x\":0,\"y\":0,\"z\":0},\"scale\":1}},\"grabbableKey\":{\"cloneable\":false,\"grabbable\":true}}", "velocity": { + "blue": 0, + "green": 0, + "red": 0, + "x": 0, + "y": 0, + "z": 0 + }, + "visible": true + } + }, + { + "properties": { + "acceleration": { + "blue": 0, + "green": 0, + "red": 0, + "x": 0, + "y": 0, + "z": 0 + }, + "actionData": "", + "age": 308.8044128417969, + "ageAsText": "0 hours 5 minutes 8 seconds", + "angularDamping": 0.39346998929977417, + "angularVelocity": { + "blue": 0, + "green": 0, + "red": 0, + "x": 0, + "y": 0, + "z": 0 + }, + "animation": { + "allowTranslation": true, + "currentFrame": 0, + "firstFrame": 0, + "fps": 30, + "hold": false, + "lastFrame": 100000, + "loop": true, + "running": false, + "url": "" + }, + "boundingBox": { + "brn": { + "blue": -0.2340194433927536, + "green": -0.07067721337080002, + "red": -0.17002610862255096, + "x": -0.17002610862255096, + "y": -0.07067721337080002, + "z": -0.2340194433927536 + }, + "center": { + "blue": -0.039825439453125, + "green": 0.02001953125, + "red": 0.0001678466796875, + "x": 0.0001678466796875, + "y": 0.02001953125, + "z": -0.039825439453125 + }, + "dimensions": { + "blue": 0.3883880078792572, + "green": 0.18139348924160004, + "red": 0.34038791060447693, + "x": 0.34038791060447693, + "y": 0.18139348924160004, + "z": 0.3883880078792572 + }, + "tfl": { + "blue": 0.1543685644865036, + "green": 0.11071627587080002, + "red": 0.17036180198192596, + "x": 0.17036180198192596, + "y": 0.11071627587080002, + "z": 0.1543685644865036 + } + }, + "canCastShadow": true, + "certificateID": "", + "clientOnly": true, + "cloneAvatarEntity": false, + "cloneDynamic": false, + "cloneLifetime": 300, + "cloneLimit": 0, + "cloneOriginID": "{00000000-0000-0000-0000-000000000000}", + "cloneable": false, + "collidesWith": "", + "collisionMask": 0, + "collisionSoundURL": "", + "collisionless": false, + "collisionsWillMove": false, + "compoundShapeURL": "", + "created": "2018-07-26T23:56:46Z", + "damping": 0.39346998929977417, + "density": 1000, + "description": "", + "dimensions": { + "blue": 0.38838762044906616, + "green": 0.16981728374958038, + "red": 0.33466479182243347, + "x": 0.33466479182243347, + "y": 0.16981728374958038, + "z": 0.38838762044906616 + }, + "dynamic": false, + "editionNumber": 18, + "entityInstanceNumber": 0, + "friction": 0.5, + "gravity": { + "blue": 0, + "green": 0, + "red": 0, + "x": 0, + "y": 0, + "z": 0 + }, + "href": "", + "id": "{1bf231ce-3913-4c53-be3c-b1f4094dac51}", + "ignoreForCollisions": false, + "itemArtist": "jyoum", + "itemCategories": "Wearables", + "itemDescription": "A stylish and classic piece of headwear for your avatar.", + "itemLicense": "", + "itemName": "Fedora", + "jointRotations": [ + ], + "jointRotationsSet": [ + ], + "jointTranslations": [ + ], + "jointTranslationsSet": [ + ], + "lastEdited": 1532649698129709, + "lastEditedBy": "{042ac463-7879-40f0-8126-e2e56c4345ca}", + "lifetime": -1, + "limitedRun": 4294967295, + "localPosition": { + "blue": -0.039825439453125, + "green": 0.02001953125, + "red": 0.0001678466796875, + "x": 0.0001678466796875, + "y": 0.02001953125, + "z": -0.039825439453125 + }, + "localRotation": { + "w": 0.9998477101325989, + "x": -9.898545982878204e-09, + "y": 5.670873406415922e-07, + "z": 0.017452405765652657 + }, + "locked": false, + "marketplaceID": "11c4208d-15d7-4449-9758-a08da6dbd3dc", + "modelURL": "http://mpassets.highfidelity.com/11c4208d-15d7-4449-9758-a08da6dbd3dc-v1/Fedora.fbx", + "name": "", + "naturalDimensions": { + "blue": 0.320981502532959, + "green": 0.14034485816955566, + "red": 0.2765824794769287, + "x": 0.2765824794769287, + "y": 0.14034485816955566, + "z": 0.320981502532959 + }, + "naturalPosition": { + "blue": 0.022502630949020386, + "green": 1.7460365295410156, + "red": 0.000143393874168396, + "x": 0.000143393874168396, + "y": 1.7460365295410156, + "z": 0.022502630949020386 + }, + "originalTextures": "{\n \"file5\": \"http://mpassets.highfidelity.com/11c4208d-15d7-4449-9758-a08da6dbd3dc-v1/Fedora.fbx/Texture/Fedora_Hat1_Base_Color.png\",\n \"file7\": \"http://mpassets.highfidelity.com/11c4208d-15d7-4449-9758-a08da6dbd3dc-v1/Fedora.fbx/Texture/Fedora_Hat1_Roughness.png\"\n}\n", + "owningAvatarID": "{042ac463-7879-40f0-8126-e2e56c4345ca}", + "parentID": "{042ac463-7879-40f0-8126-e2e56c4345ca}", + "parentJointIndex": 66, + "position": { + "blue": -0.039825439453125, + "green": 0.02001953125, + "red": 0.0001678466796875, + "x": 0.0001678466796875, + "y": 0.02001953125, + "z": -0.039825439453125 + }, + "queryAACube": { + "scale": 1.6202316284179688, + "x": 495.21051025390625, + "y": 498.5577697753906, + "z": 497.6370849609375 + }, + "registrationPoint": { + "blue": 0.5, + "green": 0.5, + "red": 0.5, + "x": 0.5, + "y": 0.5, + "z": 0.5 + }, + "relayParentJoints": false, + "renderInfo": { + "drawCalls": 1, + "hasTransparent": false, + "texturesCount": 2, + "texturesSize": 327680, + "verticesCount": 719 + }, + "restitution": 0.5, + "rotation": { + "w": 0.9998477101325989, + "x": -9.898545982878204e-09, + "y": 5.670873406415922e-07, + "z": 0.017452405765652657 + }, + "script": "", + "scriptTimestamp": 0, + "serverScripts": "", + "shapeType": "box", + "staticCertificateVersion": 0, + "textures": "", + "type": "Model", + "userData": "{\"Attachment\":{\"action\":\"attach\",\"joint\":\"HeadTop_End\",\"attached\":false,\"options\":{\"translation\":{\"x\":0,\"y\":0,\"z\":0},\"scale\":1}},\"grabbableKey\":{\"cloneable\":false,\"grabbable\":true}}", + "velocity": { + "blue": 0, + "green": 0, + "red": 0, "x": 0, "y": 0, "z": 0 diff --git a/interface/resources/avatar/old-avatar-animation.json b/interface/resources/avatar/old-avatar-animation.json new file mode 100644 index 0000000000..44d294f767 --- /dev/null +++ b/interface/resources/avatar/old-avatar-animation.json @@ -0,0 +1,1228 @@ +{ + "version": "1.0", + "root": { + "id": "userAnimStateMachine", + "type": "stateMachine", + "data": { + "currentState": "userAnimNone", + "states": [ + { + "id": "userAnimNone", + "interpTarget": 6, + "interpDuration": 6, + "transitions": [ + { "var": "userAnimA", "state": "userAnimA" }, + { "var": "userAnimB", "state": "userAnimB" } + ] + }, + { + "id": "userAnimA", + "interpTarget": 6, + "interpDuration": 6, + "transitions": [ + { "var": "userAnimNone", "state": "userAnimNone" }, + { "var": "userAnimB", "state": "userAnimB" } + ] + }, + { + "id": "userAnimB", + "interpTarget": 6, + "interpDuration": 6, + "transitions": [ + { "var": "userAnimNone", "state": "userAnimNone" }, + { "var": "userAnimA", "state": "userAnimA" } + ] + } + ] + }, + "children": [ + { + "id": "userAnimNone", + "type": "overlay", + "data": { + "alpha": 1.0, + "alphaVar": "ikOverlayAlpha", + "boneSet": "fullBody" + }, + "children": [ + { + "id": "ik", + "type": "inverseKinematics", + "data": { + "solutionSource": "relaxToUnderPoses", + "solutionSourceVar": "solutionSource", + "targets": [ + { + "jointName": "Hips", + "positionVar": "hipsPosition", + "rotationVar": "hipsRotation", + "typeVar": "hipsType", + "weightVar": "hipsWeight", + "weight": 1.0, + "flexCoefficients": [1] + }, + { + "jointName": "RightHand", + "positionVar": "rightHandPosition", + "rotationVar": "rightHandRotation", + "typeVar": "rightHandType", + "weightVar": "rightHandWeight", + "weight": 1.0, + "flexCoefficients": [1, 0.5, 0.5, 0.2, 0.01, 0.005, 0.001, 0.0, 0.0], + "poleVectorEnabledVar": "rightHandPoleVectorEnabled", + "poleReferenceVectorVar": "rightHandPoleReferenceVector", + "poleVectorVar": "rightHandPoleVector" + }, + { + "jointName": "LeftHand", + "positionVar": "leftHandPosition", + "rotationVar": "leftHandRotation", + "typeVar": "leftHandType", + "weightVar": "leftHandWeight", + "weight": 1.0, + "flexCoefficients": [1, 0.5, 0.5, 0.2, 0.01, 0.005, 0.001, 0.0, 0.0], + "poleVectorEnabledVar": "leftHandPoleVectorEnabled", + "poleReferenceVectorVar": "leftHandPoleReferenceVector", + "poleVectorVar": "leftHandPoleVector" + }, + { + "jointName": "RightFoot", + "positionVar": "rightFootPosition", + "rotationVar": "rightFootRotation", + "typeVar": "rightFootType", + "weightVar": "rightFootWeight", + "weight": 1.0, + "flexCoefficients": [1, 0.45, 0.45], + "poleVectorEnabledVar": "rightFootPoleVectorEnabled", + "poleReferenceVectorVar": "rightFootPoleReferenceVector", + "poleVectorVar": "rightFootPoleVector" + }, + { + "jointName": "LeftFoot", + "positionVar": "leftFootPosition", + "rotationVar": "leftFootRotation", + "typeVar": "leftFootType", + "weightVar": "leftFootWeight", + "weight": 1.0, + "flexCoefficients": [1, 0.45, 0.45], + "poleVectorEnabledVar": "leftFootPoleVectorEnabled", + "poleReferenceVectorVar": "leftFootPoleReferenceVector", + "poleVectorVar": "leftFootPoleVector" + }, + { + "jointName": "Spine2", + "positionVar": "spine2Position", + "rotationVar": "spine2Rotation", + "typeVar": "spine2Type", + "weightVar": "spine2Weight", + "weight": 2.0, + "flexCoefficients": [1.0, 0.5, 0.25] + }, + { + "jointName": "Head", + "positionVar": "headPosition", + "rotationVar": "headRotation", + "typeVar": "headType", + "weightVar": "headWeight", + "weight": 4.0, + "flexCoefficients": [1, 0.5, 0.25, 0.2, 0.1] + } + ] + }, + "children": [] + }, + { + "id": "defaultPoseOverlay", + "type": "overlay", + "data": { + "alpha": 0.0, + "alphaVar": "defaultPoseOverlayAlpha", + "boneSet": "fullBody", + "boneSetVar": "defaultPoseOverlayBoneSet" + }, + "children": [ + { + "id": "defaultPose", + "type": "defaultPose", + "data": { + }, + "children": [] + }, + { + "id": "rightHandOverlay", + "type": "overlay", + "data": { + "alpha": 0.0, + "boneSet": "rightHand", + "alphaVar": "rightHandOverlayAlpha" + }, + "children": [ + { + "id": "rightHandStateMachine", + "type": "stateMachine", + "data": { + "currentState": "rightHandGrasp", + "states": [ + { + "id": "rightHandGrasp", + "interpTarget": 3, + "interpDuration": 3, + "transitions": [ + { "var": "isRightIndexPoint", "state": "rightIndexPoint" }, + { "var": "isRightThumbRaise", "state": "rightThumbRaise" }, + { "var": "isRightIndexPointAndThumbRaise", "state": "rightIndexPointAndThumbRaise" } + ] + }, + { + "id": "rightIndexPoint", + "interpTarget": 15, + "interpDuration": 3, + "transitions": [ + { "var": "isRightHandGrasp", "state": "rightHandGrasp" }, + { "var": "isRightThumbRaise", "state": "rightThumbRaise" }, + { "var": "isRightIndexPointAndThumbRaise", "state": "rightIndexPointAndThumbRaise" } + ] + }, + { + "id": "rightThumbRaise", + "interpTarget": 15, + "interpDuration": 3, + "transitions": [ + { "var": "isRightHandGrasp", "state": "rightHandGrasp" }, + { "var": "isRightIndexPoint", "state": "rightIndexPoint" }, + { "var": "isRightIndexPointAndThumbRaise", "state": "rightIndexPointAndThumbRaise" } + ] + }, + { + "id": "rightIndexPointAndThumbRaise", + "interpTarget": 15, + "interpDuration": 3, + "transitions": [ + { "var": "isRightHandGrasp", "state": "rightHandGrasp" }, + { "var": "isRightIndexPoint", "state": "rightIndexPoint" }, + { "var": "isRightThumbRaise", "state": "rightThumbRaise" } + ] + } + ] + }, + "children": [ + { + "id": "rightHandGrasp", + "type": "blendLinear", + "data": { + "alpha": 0.0, + "alphaVar": "rightHandGraspAlpha" + }, + "children": [ + { + "id": "rightHandGraspOpen", + "type": "clip", + "data": { + "url": "animations/hydra_pose_open_right.fbx", + "startFrame": 0.0, + "endFrame": 0.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "rightHandGraspClosed", + "type": "clip", + "data": { + "url": "animations/hydra_pose_closed_right.fbx", + "startFrame": 0.0, + "endFrame": 0.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + }, + { + "id": "rightIndexPoint", + "type": "blendLinear", + "data": { + "alpha": 0.0, + "alphaVar": "rightHandGraspAlpha" + }, + "children": [ + { + "id": "rightIndexPointOpen", + "type": "clip", + "data": { + "url": "animations/touch_point_open_right.fbx", + "startFrame": 15.0, + "endFrame": 15.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "rightIndexPointClosed", + "type": "clip", + "data": { + "url": "animations/touch_point_closed_right.fbx", + "startFrame": 15.0, + "endFrame": 15.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + }, + { + "id": "rightThumbRaise", + "type": "blendLinear", + "data": { + "alpha": 0.0, + "alphaVar": "rightHandGraspAlpha" + }, + "children": [ + { + "id": "rightThumbRaiseOpen", + "type": "clip", + "data": { + "url": "animations/touch_thumb_open_right.fbx", + "startFrame": 15.0, + "endFrame": 15.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "rightThumbRaiseClosed", + "type": "clip", + "data": { + "url": "animations/touch_thumb_closed_right.fbx", + "startFrame": 15.0, + "endFrame": 15.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + }, + { + "id": "rightIndexPointAndThumbRaise", + "type": "blendLinear", + "data": { + "alpha": 0.0, + "alphaVar": "rightHandGraspAlpha" + }, + "children": [ + { + "id": "rightIndexPointAndThumbRaiseOpen", + "type": "clip", + "data": { + "url": "animations/touch_thumb_point_open_right.fbx", + "startFrame": 15.0, + "endFrame": 15.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "rightIndexPointAndThumbRaiseClosed", + "type": "clip", + "data": { + "url": "animations/touch_thumb_point_closed_right.fbx", + "startFrame": 15.0, + "endFrame": 15.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + } + ] + }, + { + "id": "leftHandOverlay", + "type": "overlay", + "data": { + "alpha": 0.0, + "boneSet": "leftHand", + "alphaVar": "leftHandOverlayAlpha" + }, + "children": [ + { + "id": "leftHandStateMachine", + "type": "stateMachine", + "data": { + "currentState": "leftHandGrasp", + "states": [ + { + "id": "leftHandGrasp", + "interpTarget": 3, + "interpDuration": 3, + "transitions": [ + { "var": "isLeftIndexPoint", "state": "leftIndexPoint" }, + { "var": "isLeftThumbRaise", "state": "leftThumbRaise" }, + { "var": "isLeftIndexPointAndThumbRaise", "state": "leftIndexPointAndThumbRaise" } + ] + }, + { + "id": "leftIndexPoint", + "interpTarget": 15, + "interpDuration": 3, + "transitions": [ + { "var": "isLeftHandGrasp", "state": "leftHandGrasp" }, + { "var": "isLeftThumbRaise", "state": "leftThumbRaise" }, + { "var": "isLeftIndexPointAndThumbRaise", "state": "leftIndexPointAndThumbRaise" } + ] + }, + { + "id": "leftThumbRaise", + "interpTarget": 15, + "interpDuration": 3, + "transitions": [ + { "var": "isLeftHandGrasp", "state": "leftHandGrasp" }, + { "var": "isLeftIndexPoint", "state": "leftIndexPoint" }, + { "var": "isLeftIndexPointAndThumbRaise", "state": "leftIndexPointAndThumbRaise" } + ] + }, + { + "id": "leftIndexPointAndThumbRaise", + "interpTarget": 15, + "interpDuration": 3, + "transitions": [ + { "var": "isLeftHandGrasp", "state": "leftHandGrasp" }, + { "var": "isLeftIndexPoint", "state": "leftIndexPoint" }, + { "var": "isLeftThumbRaise", "state": "leftThumbRaise" } + ] + } + ] + }, + "children": [ + { + "id": "leftHandGrasp", + "type": "blendLinear", + "data": { + "alpha": 0.0, + "alphaVar": "leftHandGraspAlpha" + }, + "children": [ + { + "id": "leftHandGraspOpen", + "type": "clip", + "data": { + "url": "animations/hydra_pose_open_left.fbx", + "startFrame": 0.0, + "endFrame": 0.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "leftHandGraspClosed", + "type": "clip", + "data": { + "url": "animations/hydra_pose_closed_left.fbx", + "startFrame": 10.0, + "endFrame": 10.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + }, + { + "id": "leftIndexPoint", + "type": "blendLinear", + "data": { + "alpha": 0.0, + "alphaVar": "leftHandGraspAlpha" + }, + "children": [ + { + "id": "leftIndexPointOpen", + "type": "clip", + "data": { + "url": "animations/touch_point_open_left.fbx", + "startFrame": 15.0, + "endFrame": 15.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "leftIndexPointClosed", + "type": "clip", + "data": { + "url": "animations/touch_point_closed_left.fbx", + "startFrame": 15.0, + "endFrame": 15.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + }, + { + "id": "leftThumbRaise", + "type": "blendLinear", + "data": { + "alpha": 0.0, + "alphaVar": "leftHandGraspAlpha" + }, + "children": [ + { + "id": "leftThumbRaiseOpen", + "type": "clip", + "data": { + "url": "animations/touch_thumb_open_left.fbx", + "startFrame": 15.0, + "endFrame": 15.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "leftThumbRaiseClosed", + "type": "clip", + "data": { + "url": "animations/touch_thumb_closed_left.fbx", + "startFrame": 15.0, + "endFrame": 15.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + }, + { + "id": "leftIndexPointAndThumbRaise", + "type": "blendLinear", + "data": { + "alpha": 0.0, + "alphaVar": "leftHandGraspAlpha" + }, + "children": [ + { + "id": "leftIndexPointAndThumbRaiseOpen", + "type": "clip", + "data": { + "url": "animations/touch_thumb_point_open_left.fbx", + "startFrame": 15.0, + "endFrame": 15.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "leftIndexPointAndThumbRaiseClosed", + "type": "clip", + "data": { + "url": "animations/touch_thumb_point_closed_left.fbx", + "startFrame": 15.0, + "endFrame": 15.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + } + ] + }, + { + "id": "mainStateMachine", + "type": "stateMachine", + "data": { + "currentState": "idle", + "states": [ + { + "id": "idle", + "interpTarget": 10, + "interpDuration": 10, + "transitions": [ + { "var": "isMovingForward", "state": "idleToWalkFwd" }, + { "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": "isInAirStand", "state": "inAirStand" }, + { "var": "isInAirRun", "state": "inAirRun" } + ] + }, + { + "id": "idleToWalkFwd", + "interpTarget": 10, + "interpDuration": 3, + "transitions": [ + { "var": "idleToWalkFwdOnDone", "state": "walkFwd" }, + { "var": "isNotMoving", "state": "idle" }, + { "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": "isInAirStand", "state": "inAirStand" }, + { "var": "isInAirRun", "state": "inAirRun" } + ] + }, + { + "id": "walkFwd", + "interpTarget": 16, + "interpDuration": 6, + "transitions": [ + { "var": "isNotMoving", "state": "idle" }, + { "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": "isInAirStand", "state": "inAirStand" }, + { "var": "isInAirRun", "state": "inAirRun" } + ] + }, + { + "id": "walkBwd", + "interpTarget": 8, + "interpDuration": 2, + "transitions": [ + { "var": "isNotMoving", "state": "idle" }, + { "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": "isInAirStand", "state": "inAirStand" }, + { "var": "isInAirRun", "state": "inAirRun" } + ] + }, + { + "id": "strafeRight", + "interpTarget": 20, + "interpDuration": 1, + "transitions": [ + { "var": "isNotMoving", "state": "idle" }, + { "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": "isInAirStand", "state": "inAirStand" }, + { "var": "isInAirRun", "state": "inAirRun" } + ] + }, + { + "id": "strafeLeft", + "interpTarget": 20, + "interpDuration": 1, + "transitions": [ + { "var": "isNotMoving", "state": "idle" }, + { "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": "isInAirStand", "state": "inAirStand" }, + { "var": "isInAirRun", "state": "inAirRun" } + ] + }, + { + "id": "turnRight", + "interpTarget": 6, + "interpDuration": 6, + "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" } + ] + }, + { + "id": "turnLeft", + "interpTarget": 6, + "interpDuration": 6, + "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" } + ] + }, + { + "id": "fly", + "interpTarget": 6, + "interpDuration": 6, + "transitions": [ + { "var": "isNotFlying", "state": "idle" } + ] + }, + { + "id": "takeoffStand", + "interpTarget": 0, + "interpDuration": 6, + "transitions": [ + { "var": "isNotTakeoff", "state": "inAirStand" } + ] + }, + { + "id": "takeoffRun", + "interpTarget": 0, + "interpDuration": 6, + "transitions": [ + { "var": "isNotTakeoff", "state": "inAirRun" } + ] + }, + { + "id": "inAirStand", + "interpTarget": 0, + "interpDuration": 6, + "interpType": "snapshotPrev", + "transitions": [ + { "var": "isNotInAir", "state": "landStandImpact" } + ] + }, + { + "id": "inAirRun", + "interpTarget": 0, + "interpDuration": 6, + "interpType": "snapshotPrev", + "transitions": [ + { "var": "isNotInAir", "state": "landRun" } + ] + }, + { + "id": "landStandImpact", + "interpTarget": 6, + "interpDuration": 4, + "transitions": [ + { "var": "isFlying", "state": "fly" }, + { "var": "isTakeoffStand", "state": "takeoffStand" }, + { "var": "isTakeoffRun", "state": "takeoffRun" }, + { "var": "landStandImpactOnDone", "state": "landStand" } + ] + }, + { + "id": "landStand", + "interpTarget": 0, + "interpDuration": 1, + "transitions": [ + { "var": "isMovingForward", "state": "idleToWalkFwd" }, + { "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": "isInAirStand", "state": "inAirStand" }, + { "var": "isInAirRun", "state": "inAirRun" }, + { "var": "landStandOnDone", "state": "idle" } + ] + }, + { + "id": "landRun", + "interpTarget": 1, + "interpDuration": 7, + "transitions": [ + { "var": "isFlying", "state": "fly" }, + { "var": "isTakeoffStand", "state": "takeoffStand" }, + { "var": "isTakeoffRun", "state": "takeoffRun" }, + { "var": "landRunOnDone", "state": "walkFwd" } + ] + } + ] + }, + "children": [ + { + "id": "idle", + "type": "stateMachine", + "data": { + "currentState": "idleStand", + "states": [ + { + "id": "idleStand", + "interpTarget": 6, + "interpDuration": 6, + "transitions": [ + { "var": "isTalking", "state": "idleTalk" } + ] + }, + { + "id": "idleTalk", + "interpTarget": 6, + "interpDuration": 6, + "transitions": [ + { "var": "notIsTalking", "state": "idleStand" } + ] + } + ] + }, + "children": [ + { + "id": "idleStand", + "type": "clip", + "data": { + "url": "animations/idle.fbx", + "startFrame": 0.0, + "endFrame": 300.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "idleTalk", + "type": "clip", + "data": { + "url": "animations/talk.fbx", + "startFrame": 0.0, + "endFrame": 800.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + }, + { + "id": "walkFwd", + "type": "blendLinearMove", + "data": { + "alpha": 0.0, + "desiredSpeed": 1.4, + "characteristicSpeeds": [0.5, 1.4, 4.5], + "alphaVar": "moveForwardAlpha", + "desiredSpeedVar": "moveForwardSpeed" + }, + "children": [ + { + "id": "walkFwdShort", + "type": "clip", + "data": { + "url": "animations/walk_short_fwd.fbx", + "startFrame": 0.0, + "endFrame": 39.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "walkFwdNormal", + "type": "clip", + "data": { + "url": "animations/walk_fwd.fbx", + "startFrame": 0.0, + "endFrame": 35.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "walkFwdRun", + "type": "clip", + "data": { + "url": "animations/run_fwd.fbx", + "startFrame": 0.0, + "endFrame": 21.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + }, + { + "id": "idleToWalkFwd", + "type": "clip", + "data": { + "url": "animations/idle_to_walk.fbx", + "startFrame": 1.0, + "endFrame": 13.0, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + }, + { + "id": "walkBwd", + "type": "blendLinearMove", + "data": { + "alpha": 0.0, + "desiredSpeed": 1.4, + "characteristicSpeeds": [0.6, 1.45], + "alphaVar": "moveBackwardAlpha", + "desiredSpeedVar": "moveBackwardSpeed" + }, + "children": [ + { + "id": "walkBwdShort", + "type": "clip", + "data": { + "url": "animations/walk_short_bwd.fbx", + "startFrame": 0.0, + "endFrame": 38.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "walkBwdNormal", + "type": "clip", + "data": { + "url": "animations/walk_bwd.fbx", + "startFrame": 0.0, + "endFrame": 36.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + }, + { + "id": "turnLeft", + "type": "clip", + "data": { + "url": "animations/turn_left.fbx", + "startFrame": 0.0, + "endFrame": 28.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "turnRight", + "type": "clip", + "data": { + "url": "animations/turn_left.fbx", + "startFrame": 0.0, + "endFrame": 30.0, + "timeScale": 1.0, + "loopFlag": true, + "mirrorFlag": true + }, + "children": [] + }, + { + "id": "strafeLeft", + "type": "blendLinearMove", + "data": { + "alpha": 0.0, + "desiredSpeed": 1.4, + "characteristicSpeeds": [0.2, 0.65], + "alphaVar": "moveLateralAlpha", + "desiredSpeedVar": "moveLateralSpeed" + }, + "children": [ + { + "id": "strafeLeftShort", + "type": "clip", + "data": { + "url": "animations/side_step_short_left.fbx", + "startFrame": 0.0, + "endFrame": 28.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "strafeLeftNormal", + "type": "clip", + "data": { + "url": "animations/side_step_left.fbx", + "startFrame": 0.0, + "endFrame": 30.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + }, + { + "id": "strafeRight", + "type": "blendLinearMove", + "data": { + "alpha": 0.0, + "desiredSpeed": 1.4, + "characteristicSpeeds": [0.2, 0.65], + "alphaVar": "moveLateralAlpha", + "desiredSpeedVar": "moveLateralSpeed" + }, + "children": [ + { + "id": "strafeRightShort", + "type": "clip", + "data": { + "url": "animations/side_step_short_right.fbx", + "startFrame": 0.0, + "endFrame": 28.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "strafeRightNormal", + "type": "clip", + "data": { + "url": "animations/side_step_right.fbx", + "startFrame": 0.0, + "endFrame": 30.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + }, + { + "id": "fly", + "type": "clip", + "data": { + "url": "animations/fly.fbx", + "startFrame": 1.0, + "endFrame": 80.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "takeoffStand", + "type": "clip", + "data": { + "url": "animations/jump_standing_takeoff.fbx", + "startFrame": 17.0, + "endFrame": 25.0, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + }, + { + "id": "takeoffRun", + "type": "clip", + "data": { + "url": "animations/jump_takeoff.fbx", + "startFrame": 1.0, + "endFrame": 2.5, + "timeScale": 0.01, + "loopFlag": false + }, + "children": [] + }, + { + "id": "inAirStand", + "type": "blendLinear", + "data": { + "alpha": 0.0, + "alphaVar": "inAirAlpha" + }, + "children": [ + { + "id": "inAirStandPreApex", + "type": "clip", + "data": { + "url": "animations/jump_standing_apex.fbx", + "startFrame": 0.0, + "endFrame": 0.0, + "timeScale": 0.0, + "loopFlag": false + }, + "children": [] + }, + { + "id": "inAirStandApex", + "type": "clip", + "data": { + "url": "animations/jump_standing_apex.fbx", + "startFrame": 1.0, + "endFrame": 1.0, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + }, + { + "id": "inAirStandPostApex", + "type": "clip", + "data": { + "url": "animations/jump_standing_apex.fbx", + "startFrame": 2.0, + "endFrame": 2.0, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + } + ] + }, + { + "id": "inAirRun", + "type": "blendLinear", + "data": { + "alpha": 0.0, + "alphaVar": "inAirAlpha" + }, + "children": [ + { + "id": "inAirRunPreApex", + "type": "clip", + "data": { + "url": "animations/jump_in_air.fbx", + "startFrame": 0.0, + "endFrame": 0.0, + "timeScale": 0.0, + "loopFlag": false + }, + "children": [] + }, + { + "id": "inAirRunApex", + "type": "clip", + "data": { + "url": "animations/jump_in_air.fbx", + "startFrame": 6.0, + "endFrame": 6.0, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + }, + { + "id": "inAirRunPostApex", + "type": "clip", + "data": { + "url": "animations/jump_in_air.fbx", + "startFrame": 11.0, + "endFrame": 11.0, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + } + ] + }, + { + "id": "landStandImpact", + "type": "clip", + "data": { + "url": "animations/jump_standing_land.fbx", + "startFrame": 1.0, + "endFrame": 6.0, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + }, + { + "id": "landStand", + "type": "clip", + "data": { + "url": "animations/jump_standing_land.fbx", + "startFrame": 6.0, + "endFrame": 28.0, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + }, + { + "id": "landRun", + "type": "clip", + "data": { + "url": "animations/jump_land.fbx", + "startFrame": 1.0, + "endFrame": 6.0, + "timeScale": 0.65, + "loopFlag": false + }, + "children": [] + } + ] + } + ] + } + ] + } + ] + } + ] + }, + { + "id": "userAnimA", + "type": "clip", + "data": { + "url": "animations/idle.fbx", + "startFrame": 0.0, + "endFrame": 90.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "userAnimB", + "type": "clip", + "data": { + "url": "animations/idle.fbx", + "startFrame": 0.0, + "endFrame": 90.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + } +} diff --git a/interface/resources/icons/tablet-icons/EmoteAppIcon.svg b/interface/resources/icons/tablet-icons/EmoteAppIcon.svg deleted file mode 100644 index 340f0fcd2f..0000000000 --- a/interface/resources/icons/tablet-icons/EmoteAppIcon.svg +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - diff --git a/interface/resources/icons/tablet-icons/emote-a.svg b/interface/resources/icons/tablet-icons/emote-a.svg new file mode 100644 index 0000000000..981bb77566 --- /dev/null +++ b/interface/resources/icons/tablet-icons/emote-a.svg @@ -0,0 +1,30 @@ + + + + + + + + + + + diff --git a/interface/resources/icons/tablet-icons/emote-i.svg b/interface/resources/icons/tablet-icons/emote-i.svg new file mode 100644 index 0000000000..57a957052e --- /dev/null +++ b/interface/resources/icons/tablet-icons/emote-i.svg @@ -0,0 +1,33 @@ + + + + + + + + + + + + diff --git a/interface/resources/images/avatarapp/BodyMart.PNG b/interface/resources/images/avatarapp/BodyMart.PNG index c51ca880cb..669a02c8fe 100644 Binary files a/interface/resources/images/avatarapp/BodyMart.PNG and b/interface/resources/images/avatarapp/BodyMart.PNG differ diff --git a/interface/resources/images/buttonBezel.png b/interface/resources/images/buttonBezel.png deleted file mode 100644 index fe55855462..0000000000 Binary files a/interface/resources/images/buttonBezel.png and /dev/null differ diff --git a/interface/resources/images/buttonBezel_highlight.png b/interface/resources/images/buttonBezel_highlight.png deleted file mode 100644 index ab0a99e4c5..0000000000 Binary files a/interface/resources/images/buttonBezel_highlight.png and /dev/null differ diff --git a/interface/resources/qml/+android/Stats.qml b/interface/resources/qml/+android/Stats.qml index fe827f6ece..0dcb07e730 100644 --- a/interface/resources/qml/+android/Stats.qml +++ b/interface/resources/qml/+android/Stats.qml @@ -264,20 +264,27 @@ Item { StatText { text: "GPU: " + root.gpuFrameTime.toFixed(1) + " ms" } + StatText { + text: "Drawcalls: " + root.drawcalls + } StatText { text: "Triangles: " + root.triangles + " / Material Switches: " + root.materialSwitches } StatText { + visible: root.expanded; text: "GPU Free Memory: " + root.gpuFreeMemory + " MB"; } StatText { + visible: root.expanded; text: "GPU Textures: "; } StatText { + visible: root.expanded; text: " Count: " + root.gpuTextures; } StatText { + visible: root.expanded; text: " Pressure State: " + root.gpuTextureMemoryPressureState; } StatText { @@ -287,27 +294,35 @@ Item { text: " " + root.gpuTextureResourceMemory + " / " + root.gpuTextureResourcePopulatedMemory + " / " + root.texturePendingTransfers + " MB"; } StatText { + visible: root.expanded; text: " Resident Memory: " + root.gpuTextureResidentMemory + " MB"; } StatText { + visible: root.expanded; text: " Framebuffer Memory: " + root.gpuTextureFramebufferMemory + " MB"; } StatText { + visible: root.expanded; text: " External Memory: " + root.gpuTextureExternalMemory + " MB"; } StatText { + visible: root.expanded; text: "GPU Buffers: " } StatText { + visible: root.expanded; text: " Count: " + root.gpuBuffers; } StatText { + visible: root.expanded; text: " Memory: " + root.gpuBufferMemory + " MB"; } StatText { + visible: root.expanded; text: "GL Swapchain Memory: " + root.glContextSwapchainMemory + " MB"; } StatText { + visible: root.expanded; text: "QML Texture Memory: " + root.qmlTextureMemory + " MB"; } StatText { diff --git a/interface/resources/qml/InteractiveWindow.qml b/interface/resources/qml/InteractiveWindow.qml index 800026710d..e8ddbf823d 100644 --- a/interface/resources/qml/InteractiveWindow.qml +++ b/interface/resources/qml/InteractiveWindow.qml @@ -146,7 +146,8 @@ Windows.Window { Qt.WindowCloseButtonHint | Qt.WindowMaximizeButtonHint | Qt.WindowMinimizeButtonHint; - if ((flags & Desktop.ALWAYS_ON_TOP) === Desktop.ALWAYS_ON_TOP) { + // only use the always on top feature for non Windows OS + if (Qt.platform.os !== "windows" && (flags & Desktop.ALWAYS_ON_TOP)) { nativeWindowFlags |= Qt.WindowStaysOnTopHint; } nativeWindow.flags = nativeWindowFlags; diff --git a/interface/resources/qml/LoginDialog/LinkAccountBody.qml b/interface/resources/qml/LoginDialog/LinkAccountBody.qml index 814778a4b1..4c6e5f6fce 100644 --- a/interface/resources/qml/LoginDialog/LinkAccountBody.qml +++ b/interface/resources/qml/LoginDialog/LinkAccountBody.qml @@ -120,6 +120,7 @@ Item { TextField { id: usernameField + text: Settings.getValue("wallet/savedUsername", ""); width: parent.width focus: true label: "Username or Email" diff --git a/interface/resources/qml/QmlWebWindow.qml b/interface/resources/qml/QmlWebWindow.qml index d73a574081..8c4d6145ec 100644 --- a/interface/resources/qml/QmlWebWindow.qml +++ b/interface/resources/qml/QmlWebWindow.qml @@ -62,6 +62,7 @@ Windows.ScrollingWindow { url: "about:blank" anchors.fill: parent focus: true + profile: HFWebEngineProfile; property string userScriptUrl: "" diff --git a/interface/resources/qml/Stats.qml b/interface/resources/qml/Stats.qml index bff13cea54..d5cfcff1e2 100644 --- a/interface/resources/qml/Stats.qml +++ b/interface/resources/qml/Stats.qml @@ -60,6 +60,9 @@ Item { StatText { text: "Game Rate: " + root.gameLoopRate } + StatText { + text: "Physics Object Count: " + root.physicsObjectCount + } StatText { visible: root.expanded text: root.gameUpdateStats @@ -116,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 + } } } @@ -173,6 +192,21 @@ 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, " + @@ -270,52 +304,69 @@ Item { StatText { text: "GPU frame size: " + root.gpuFrameSize.x + " x " + root.gpuFrameSize.y } + StatText { + text: "Drawcalls: " + root.drawcalls + } StatText { text: "Triangles: " + root.triangles + " / Material Switches: " + root.materialSwitches } StatText { + visible: root.expanded; text: "GPU Free Memory: " + root.gpuFreeMemory + " MB"; } StatText { + visible: root.expanded; text: "GPU Textures: "; } StatText { + visible: root.expanded; text: " Count: " + root.gpuTextures; } StatText { + visible: root.expanded; text: " Pressure State: " + root.gpuTextureMemoryPressureState; } StatText { + visible: root.expanded; property bool showIdeal: (root.gpuTextureResourceIdealMemory != root.gpuTextureResourceMemory); text: " Resource Allocated " + (showIdeal ? "(Ideal)" : "") + " / Populated / Pending: "; } StatText { + visible: root.expanded; property bool showIdeal: (root.gpuTextureResourceIdealMemory != root.gpuTextureResourceMemory); text: " " + root.gpuTextureResourceMemory + (showIdeal ? ("(" + root.gpuTextureResourceIdealMemory + ")") : "") + " / " + root.gpuTextureResourcePopulatedMemory + " / " + root.texturePendingTransfers + " MB"; } StatText { + visible: root.expanded; text: " Resident Memory: " + root.gpuTextureResidentMemory + " MB"; } StatText { + visible: root.expanded; text: " Framebuffer Memory: " + root.gpuTextureFramebufferMemory + " MB"; } StatText { + visible: root.expanded; text: " External Memory: " + root.gpuTextureExternalMemory + " MB"; } StatText { + visible: root.expanded; text: "GPU Buffers: " } StatText { + visible: root.expanded; text: " Count: " + root.gpuBuffers; } StatText { + visible: root.expanded; text: " Memory: " + root.gpuBufferMemory + " MB"; } StatText { + visible: root.expanded; text: "GL Swapchain Memory: " + root.glContextSwapchainMemory + " MB"; } StatText { + visible: root.expanded; text: "QML Texture Memory: " + root.qmlTextureMemory + " MB"; } StatText { diff --git a/interface/resources/qml/controls-uit/Button.qml b/interface/resources/qml/controls-uit/Button.qml index 1509abdae3..f1a6e4bb4a 100644 --- a/interface/resources/qml/controls-uit/Button.qml +++ b/interface/resources/qml/controls-uit/Button.qml @@ -20,8 +20,10 @@ Original.Button { property int color: 0 property int colorScheme: hifi.colorSchemes.light property int fontSize: hifi.fontSizes.buttonLabel + property int radius: hifi.buttons.radius property alias implicitTextWidth: buttonText.implicitWidth property string buttonGlyph: ""; + property int fontCapitalization: Font.AllUppercase width: hifi.dimensions.buttonWidth height: hifi.dimensions.controlLineHeight @@ -45,7 +47,7 @@ Original.Button { } background: Rectangle { - radius: hifi.buttons.radius + radius: control.radius border.width: (control.color === hifi.buttons.none || (control.color === hifi.buttons.noneBorderless && control.hovered) || @@ -107,7 +109,7 @@ Original.Button { RalewayBold { id: buttonText; anchors.centerIn: parent; - font.capitalization: Font.AllUppercase + font.capitalization: control.fontCapitalization color: enabled ? hifi.buttons.textColor[control.color] : hifi.buttons.disabledTextColor[control.colorScheme] size: control.fontSize diff --git a/interface/resources/qml/controls-uit/ComboBox.qml b/interface/resources/qml/controls-uit/ComboBox.qml index be8c9f6740..245b565a62 100644 --- a/interface/resources/qml/controls-uit/ComboBox.qml +++ b/interface/resources/qml/controls-uit/ComboBox.qml @@ -103,7 +103,7 @@ FocusScope { verticalCenter: parent.verticalCenter } size: hifi.fontSizes.textFieldInput - text: comboBox.currentText + text: comboBox.displayText ? comboBox.displayText : comboBox.currentText elide: Text.ElideRight color: comboBox.hovered || comboBox.popup.visible ? hifi.colors.baseGray : (isLightColorScheme ? hifi.colors.lightGray : hifi.colors.lightGrayText ) } diff --git a/interface/resources/qml/controls-uit/SpinBox.qml b/interface/resources/qml/controls-uit/SpinBox.qml index 5a4ba70080..3d3ea7a75e 100644 --- a/interface/resources/qml/controls-uit/SpinBox.qml +++ b/interface/resources/qml/controls-uit/SpinBox.qml @@ -21,6 +21,7 @@ SpinBox { id: hifi } + inputMethodHints: Qt.ImhFormattedNumbersOnly property int colorScheme: hifi.colorSchemes.light readonly property bool isLightColorScheme: colorScheme === hifi.colorSchemes.light property string label: "" @@ -91,7 +92,6 @@ SpinBox { } valueFromText: function(text, locale) { - spinBox.value = 0; // Force valueChanged signal to be emitted so that validator fires. return Number.fromLocaleString(locale, text)*factor; } @@ -104,6 +104,8 @@ SpinBox { selectedTextColor: hifi.colors.black selectionColor: hifi.colors.primaryHighlight text: spinBox.textFromValue(spinBox.value, spinBox.locale) + suffix + inputMethodHints: spinBox.inputMethodHints + validator: spinBox.validator verticalAlignment: Qt.AlignVCenter leftPadding: spinBoxLabelInside.visible ? 30 : hifi.dimensions.textPadding //rightPadding: hifi.dimensions.spinnerSize @@ -124,6 +126,11 @@ SpinBox { color: spinBox.up.pressed || spinBox.up.hovered ? (isLightColorScheme ? hifi.colors.black : hifi.colors.white) : hifi.colors.gray } } + up.onPressedChanged: { + if(value) { + spinBox.forceActiveFocus(); + } + } down.indicator: Item { x: spinBox.width - implicitWidth - 5 @@ -138,6 +145,11 @@ SpinBox { color: spinBox.down.pressed || spinBox.down.hovered ? (isLightColorScheme ? hifi.colors.black : hifi.colors.white) : hifi.colors.gray } } + down.onPressedChanged: { + if(value) { + spinBox.forceActiveFocus(); + } + } HifiControls.Label { id: spinBoxLabel diff --git a/interface/resources/qml/controls/TabletWebView.qml b/interface/resources/qml/controls/TabletWebView.qml index bc958aa876..db695dbfb2 100644 --- a/interface/resources/qml/controls/TabletWebView.qml +++ b/interface/resources/qml/controls/TabletWebView.qml @@ -52,12 +52,18 @@ Item { id: back enabledColor: hifi.colors.darkGray disabledColor: hifi.colors.lightGrayText - enabled: historyIndex > 0 + enabled: true text: "BACK" MouseArea { anchors.fill: parent - onClicked: goBack() + onClicked: { + if (historyIndex > 0) { + goBack(); + } else { + closeWebEngine(); + } + } } } diff --git a/interface/resources/qml/dialogs/TabletFileDialog.qml b/interface/resources/qml/dialogs/TabletFileDialog.qml index 4de0460796..6848c230e3 100644 --- a/interface/resources/qml/dialogs/TabletFileDialog.qml +++ b/interface/resources/qml/dialogs/TabletFileDialog.qml @@ -471,7 +471,6 @@ TabletModalWindow { bottomMargin: hifi.dimensions.contentSpacing.y + currentSelection.controlHeight - currentSelection.height } headerVisible: !selectDirectory - onClicked: navigateToRow(row); onDoubleClicked: navigateToRow(row); focus: true Keys.onReturnPressed: navigateToCurrentRow(); diff --git a/interface/resources/qml/hifi/AssetServer.qml b/interface/resources/qml/hifi/AssetServer.qml index 526ea6aad0..1a7f5bac40 100644 --- a/interface/resources/qml/hifi/AssetServer.qml +++ b/interface/resources/qml/hifi/AssetServer.qml @@ -186,6 +186,8 @@ Windows.ScrollingWindow { return; } + var grabbable = MenuInterface.isOptionChecked("Create Entities As Grabbable (except Zones, Particles, and Lights)"); + if (defaultURL.endsWith(".jpg") || defaultURL.endsWith(".png")) { var name = assetProxyModel.data(treeView.selection.currentIndex); var modelURL = "https://hifi-content.s3.amazonaws.com/DomainContent/production/default-image-model.fbx"; @@ -195,7 +197,7 @@ Windows.ScrollingWindow { var collisionless = true; var position = Vec3.sum(MyAvatar.position, Vec3.multiply(2, Quat.getForward(MyAvatar.orientation))); var gravity = Vec3.multiply(Vec3.fromPolar(Math.PI / 2, 0), 0); - Entities.addModelEntity(name, modelURL, textures, shapeType, dynamic, collisionless, position, gravity); + Entities.addModelEntity(name, modelURL, textures, shapeType, dynamic, collisionless, grabbable, position, gravity); } else { var SHAPE_TYPE_NONE = 0; var SHAPE_TYPE_SIMPLE_HULL = 1; @@ -281,7 +283,7 @@ Windows.ScrollingWindow { print("Asset browser - adding asset " + url + " (" + name + ") to world."); // Entities.addEntity doesn't work from QML, so we use this. - Entities.addModelEntity(name, url, "", shapeType, dynamic, collisionless, addPosition, gravity); + Entities.addModelEntity(name, url, "", shapeType, dynamic, collisionless, grabbable, addPosition, gravity); } } }); diff --git a/interface/resources/qml/hifi/AvatarApp.qml b/interface/resources/qml/hifi/AvatarApp.qml index 0a2dcb951b..d8eae311d8 100644 --- a/interface/resources/qml/hifi/AvatarApp.qml +++ b/interface/resources/qml/hifi/AvatarApp.qml @@ -57,7 +57,7 @@ Rectangle { try { var marketResponse = JSON.parse(xmlhttp.responseText.trim()) - if(marketResponse.status === 'success') { + if (marketResponse.status === 'success') { avatar.modelName = marketResponse.data.title; } } @@ -72,14 +72,14 @@ Rectangle { function getAvatarModelName() { - if(currentAvatar === null) { + if (currentAvatar === null) { return ''; } - if(currentAvatar.modelName !== undefined) { + if (currentAvatar.modelName !== undefined) { return currentAvatar.modelName; } else { var marketId = allAvatars.extractMarketId(currentAvatar.avatarUrl); - if(marketId !== '') { + if (marketId !== '') { fetchAvatarModelName(marketId, currentAvatar); } } @@ -103,51 +103,51 @@ Rectangle { property url externalAvatarThumbnailUrl: '../../images/avatarapp/guy-in-circle.svg' function fromScript(message) { - if(message.method === 'initialize') { + if (message.method === 'initialize') { jointNames = message.data.jointNames; emitSendToScript({'method' : getAvatarsMethod}); - } else if(message.method === 'wearableUpdated') { + } else if (message.method === 'wearableUpdated') { adjustWearables.refreshWearable(message.entityID, message.wearableIndex, message.properties, message.updateUI); - } else if(message.method === 'wearablesUpdated') { + } else if (message.method === 'wearablesUpdated') { var wearablesModel = currentAvatar.wearables; wearablesModel.clear(); message.wearables.forEach(function(wearable) { wearablesModel.append(wearable); }); adjustWearables.refresh(currentAvatar); - } else if(message.method === 'scaleChanged') { + } else if (message.method === 'scaleChanged') { currentAvatar.avatarScale = message.value; updateCurrentAvatarInBookmarks(currentAvatar); - } else if(message.method === 'externalAvatarApplied') { + } else if (message.method === 'externalAvatarApplied') { currentAvatar.avatarUrl = message.avatarURL; currentAvatar.thumbnailUrl = allAvatars.makeThumbnailUrl(message.avatarURL); currentAvatar.entry.avatarUrl = currentAvatar.avatarUrl; currentAvatar.modelName = undefined; updateCurrentAvatarInBookmarks(currentAvatar); - } else if(message.method === 'settingChanged') { + } else if (message.method === 'settingChanged') { currentAvatarSettings[message.name] = message.value; - } else if(message.method === 'changeSettings') { + } else if (message.method === 'changeSettings') { currentAvatarSettings = message.settings; - } else if(message.method === 'bookmarkLoaded') { + } else if (message.method === 'bookmarkLoaded') { setCurrentAvatar(message.data.currentAvatar, message.data.name); var avatarIndex = allAvatars.findAvatarIndex(currentAvatar.name); allAvatars.move(avatarIndex, 0, 1); view.setPage(0); - } else if(message.method === 'bookmarkAdded') { + } else if (message.method === 'bookmarkAdded') { var avatar = allAvatars.findAvatar(message.bookmarkName); - if(avatar !== undefined) { + if (avatar !== undefined) { var avatarObject = allAvatars.makeAvatarObject(message.bookmark, message.bookmarkName); for(var prop in avatarObject) { avatar[prop] = avatarObject[prop]; } - if(currentAvatar.name === message.bookmarkName) { + if (currentAvatar.name === message.bookmarkName) { currentAvatar = currentAvatarModel.makeAvatarEntry(avatarObject); } } else { allAvatars.addAvatarEntry(message.bookmark, message.bookmarkName); } updateCurrentAvatarInBookmarks(currentAvatar); - } else if(message.method === 'bookmarkDeleted') { + } else if (message.method === 'bookmarkDeleted') { pageOfAvatars.isUpdating = true; var index = pageOfAvatars.findAvatarIndex(message.name); @@ -159,15 +159,16 @@ Rectangle { var itemsOnPage = pageOfAvatars.count; var newItemIndex = view.currentPage * view.itemsPerPage + itemsOnPage; - if(newItemIndex <= (allAvatars.count - 1)) { + if (newItemIndex <= (allAvatars.count - 1)) { pageOfAvatars.append(allAvatars.get(newItemIndex)); } else { - if(!pageOfAvatars.hasGetAvatars()) + if (!pageOfAvatars.hasGetAvatars()) { pageOfAvatars.appendGetAvatars(); + } } pageOfAvatars.isUpdating = false; - } else if(message.method === getAvatarsMethod) { + } else if (message.method === getAvatarsMethod) { var getAvatarsData = message.data; allAvatars.populate(getAvatarsData.bookmarks); setCurrentAvatar(getAvatarsData.currentAvatar, ''); @@ -175,16 +176,16 @@ Rectangle { currentAvatarSettings = getAvatarsData.currentAvatarSettings; updateCurrentAvatarInBookmarks(currentAvatar); - } else if(message.method === 'updateAvatarInBookmarks') { + } else if (message.method === 'updateAvatarInBookmarks') { updateCurrentAvatarInBookmarks(currentAvatar); - } else if(message.method === 'selectAvatarEntity') { + } else if (message.method === 'selectAvatarEntity') { adjustWearables.selectWearableByID(message.entityID); } } function updateCurrentAvatarInBookmarks(avatar) { var bookmarkAvatarIndex = allAvatars.findAvatarIndexByValue(avatar); - if(bookmarkAvatarIndex === -1) { + if (bookmarkAvatarIndex === -1) { avatar.name = ''; view.setPage(0); } else { @@ -244,7 +245,7 @@ Rectangle { var avatarSettings = { dominantHand : settings.dominantHandIsLeft ? 'left' : 'right', collisionsEnabled : settings.avatarCollisionsOn, - animGraphUrl : settings.avatarAnimationJSON, + animGraphOverrideUrl : settings.avatarAnimationOverrideJSON, collisionSoundUrl : settings.avatarCollisionSoundUrl }; @@ -285,6 +286,9 @@ Rectangle { onWearableSelected: { emitSendToScript({'method' : 'selectWearable', 'entityID' : id}); } + onAddWearable: { + emitSendToScript({'method' : 'addWearable', 'avatarName' : avatarName, 'url' : url}); + } z: 3 } @@ -476,17 +480,13 @@ Rectangle { anchors.verticalCenter: avatarNameLabel.verticalCenter glyphText: "." glyphSize: 22 - - MouseArea { - anchors.fill: parent - onClicked: { - popup.showSpecifyAvatarUrl(function() { - var url = popup.inputText.text; - emitSendToScript({'method' : 'applyExternalAvatar', 'avatarURL' : url}) - }, function(link) { - Qt.openUrlExternally(link); - }); - } + onClicked: { + popup.showSpecifyAvatarUrl(currentAvatar.avatarUrl, function() { + var url = popup.inputText.text; + emitSendToScript({'method' : 'applyExternalAvatar', 'avatarURL' : url}) + }, function(link) { + Qt.openUrlExternally(link); + }); } } @@ -495,35 +495,8 @@ Rectangle { anchors.verticalCenter: wearablesLabel.verticalCenter glyphText: "\ue02e" - visible: avatarWearablesCount !== 0 - - MouseArea { - anchors.fill: parent - onClicked: { - adjustWearables.open(currentAvatar); - } - } - } - - // TextStyle3 - RalewayRegular { - size: 22; - anchors.right: parent.right - anchors.verticalCenter: wearablesLabel.verticalCenter - font.underline: true - text: "Add" - color: 'black' - visible: avatarWearablesCount === 0 - - MouseArea { - anchors.fill: parent - onClicked: { - popup.showGetWearables(function() { - emitSendToScript({'method' : 'navigate', 'url' : 'hifi://AvatarIsland/11.5848,-8.10862,-2.80195'}) - }, function(link) { - emitSendToScript({'method' : 'navigate', 'url' : link}) - }); - } + onClicked: { + adjustWearables.open(currentAvatar); } } } @@ -625,8 +598,9 @@ Rectangle { pageOfAvatars.append(avatarItem); } - if(pageOfAvatars.count !== itemsPerPage) + if (pageOfAvatars.count !== itemsPerPage) { pageOfAvatars.appendGetAvatars(); + } currentPage = pageIndex; pageOfAvatars.isUpdating = false; @@ -647,7 +621,7 @@ Rectangle { } function removeGetAvatars() { - if(hasGetAvatars()) { + if (hasGetAvatars()) { remove(count - 1) } } @@ -682,6 +656,14 @@ Rectangle { PropertyChanges { target: container; y: -5 } PropertyChanges { target: favoriteAvatarImage; dropShadowRadius: 10 } PropertyChanges { target: favoriteAvatarImage; dropShadowVerticalOffset: 6 } + }, + State { + name: "getMoreAvatarsHovered" + when: getMoreAvatarsMouseArea.containsMouse; + PropertyChanges { target: getMoreAvatarsMouseArea; anchors.bottomMargin: -5 } + PropertyChanges { target: container; y: -5 } + PropertyChanges { target: getMoreAvatarsImage; dropShadowRadius: 10 } + PropertyChanges { target: getMoreAvatarsImage; dropShadowVerticalOffset: 6 } } ] @@ -707,13 +689,13 @@ Rectangle { hoverEnabled: enabled onClicked: { - if(isInManageState) { + if (isInManageState) { var currentItem = delegateRoot.GridView.view.model.get(index); popup.showDeleteFavorite(currentItem.name, function() { view.deleteAvatar(currentItem); }); } else { - if(delegateRoot.GridView.view.currentIndex !== index) { + if (delegateRoot.GridView.view.currentIndex !== index) { var currentItem = delegateRoot.GridView.view.model.get(index); popup.showLoadFavorite(currentItem.name, function() { view.selectAvatar(currentItem); @@ -741,6 +723,7 @@ Rectangle { } ShadowRectangle { + id: getMoreAvatarsImage width: 92 height: 92 radius: 5 @@ -756,7 +739,9 @@ Rectangle { } MouseArea { + id: getMoreAvatarsMouseArea anchors.fill: parent + hoverEnabled: true onClicked: { popup.showBuyAvatars(function() { diff --git a/interface/resources/qml/hifi/Feed.qml b/interface/resources/qml/hifi/Feed.qml index 785b586dd2..346481fe1f 100644 --- a/interface/resources/qml/hifi/Feed.qml +++ b/interface/resources/qml/hifi/Feed.qml @@ -41,6 +41,8 @@ Column { property var goFunction: null; property var http: null; + property bool autoScrollTimerEnabled: false; + HifiConstants { id: hifi } Component.onCompleted: suggestions.getFirstPage(); HifiModels.PSFListModel { @@ -88,7 +90,9 @@ Column { online_users: data.details.connections || data.details.concurrency || 0, // Server currently doesn't give isStacked (undefined). Could give bool. drillDownToPlace: data.is_stacked || (data.action === 'concurrency'), - isStacked: !!data.is_stacked + isStacked: !!data.is_stacked, + + time_before_autoscroll_ms: data.hold_time || 3000 }; } @@ -102,9 +106,12 @@ Column { id: scroll; model: suggestions; orientation: ListView.Horizontal; - highlightFollowsCurrentItem: false - highlightMoveDuration: -1; - highlightMoveVelocity: -1; + highlightFollowsCurrentItem: true; + preferredHighlightBegin: 0; + preferredHighlightEnd: cardWidth; + highlightRangeMode: ListView.StrictlyEnforceRange; + highlightMoveDuration: 800; + highlightMoveVelocity: 1; currentIndex: -1; spacing: 12; @@ -134,8 +141,49 @@ Column { textSizeSmall: root.textSizeSmall; stackShadowNarrowing: root.stackShadowNarrowing; shadowHeight: root.stackedCardShadowHeight; - hoverThunk: function () { hovered = true } - unhoverThunk: function () { hovered = false } + hoverThunk: function () { + hovered = true; + if(root.autoScrollTimerEnabled) { + autoScrollTimer.stop(); + } + } + unhoverThunk: function () { + hovered = false; + if(root.autoScrollTimerEnabled) { + autoScrollTimer.start(); + } + } + } + + onCountChanged: { + if (scroll.currentIndex === -1 && scroll.count > 0 && root.autoScrollTimerEnabled) { + scroll.currentIndex = 0; + autoScrollTimer.interval = suggestions.get(scroll.currentIndex).time_before_autoscroll_ms; + autoScrollTimer.start(); + } + } + + onCurrentIndexChanged: { + if (root.autoScrollTimerEnabled) { + autoScrollTimer.interval = suggestions.get(scroll.currentIndex).time_before_autoscroll_ms; + autoScrollTimer.start(); + } + } + } + + Timer { + id: autoScrollTimer; + interval: 3000; + running: false; + repeat: false; + onTriggered: { + if (scroll.currentIndex !== -1) { + if (scroll.currentIndex === scroll.count - 1) { + scroll.currentIndex = 0; + } else { + scroll.currentIndex++; + } + } } } } diff --git a/interface/resources/qml/hifi/NameCard.qml b/interface/resources/qml/hifi/NameCard.qml index c7c2174e9f..dfa6555150 100644 --- a/interface/resources/qml/hifi/NameCard.qml +++ b/interface/resources/qml/hifi/NameCard.qml @@ -526,11 +526,11 @@ Item { anchors.left: nameCardVUMeter.left; // Properties visible: (isMyCard || (selected && pal.activeTab == "nearbyTab")) && isPresent; - value: Users.getAvatarGain(uuid) minimumValue: -60.0 maximumValue: 20.0 stepSize: 5 updateValueWhileDragging: true + value: Users.getAvatarGain(uuid) onValueChanged: { updateGainFromQML(uuid, value, false); } @@ -587,9 +587,11 @@ Item { } function updateGainFromQML(avatarUuid, sliderValue, isReleased) { - Users.setAvatarGain(avatarUuid, sliderValue); - if (isReleased) { - UserActivityLogger.palAction("avatar_gain_changed", avatarUuid); + if (Users.getAvatarGain(avatarUuid) != sliderValue) { + Users.setAvatarGain(avatarUuid, sliderValue); + if (isReleased) { + UserActivityLogger.palAction("avatar_gain_changed", avatarUuid); + } } } diff --git a/interface/resources/qml/hifi/Pal.qml b/interface/resources/qml/hifi/Pal.qml index 6884d2e1f6..35a0078d32 100644 --- a/interface/resources/qml/hifi/Pal.qml +++ b/interface/resources/qml/hifi/Pal.qml @@ -145,6 +145,22 @@ Rectangle { } pal.sendToScript({method: 'refreshNearby', params: params}); } + function refreshConnections() { + var flickable = connectionsUserModel.flickable; + connectionsRefreshScrollTimer.oldY = flickable.contentY; + flickable.contentY = 0; + connectionsUserModel.getFirstPage('delayRefresh', function () { + connectionsRefreshScrollTimer.start(); + }); + } + Timer { + id: connectionsRefreshScrollTimer; + interval: 500; + property real oldY: 0; + onTriggered: { + connectionsUserModel.flickable.contentY = oldY; + } + } Rectangle { id: palTabContainer; @@ -276,7 +292,10 @@ Rectangle { id: reloadConnections; width: reloadConnections.height; glyph: hifi.glyphs.reload; - onClicked: connectionsUserModel.getFirstPage('delayRefresh'); + onClicked: { + pal.sendToScript({method: 'refreshConnections'}); + refreshConnections(); + } } } // "CONNECTIONS" text @@ -761,6 +780,12 @@ Rectangle { headerVisible: true; sortIndicatorColumn: settings.connectionsSortIndicatorColumn; sortIndicatorOrder: settings.connectionsSortIndicatorOrder; + onSortIndicatorColumnChanged: { + settings.connectionsSortIndicatorColumn = sortIndicatorColumn; + } + onSortIndicatorOrderChanged: { + settings.connectionsSortIndicatorOrder = sortIndicatorOrder; + } TableViewColumn { id: connectionsUserNameHeader; @@ -1027,12 +1052,13 @@ Rectangle { enabled: myData.userName !== "Unknown user" && !userInfoViewer.visible; hoverEnabled: true; onClicked: { + // TODO: Change language from "Happening Now" to something else (or remove entirely) popupComboDialog("Set your availability:", availabilityComboBox.availabilityStrings, ["Your username will be visible in everyone's 'Nearby' list. Anyone will be able to jump to your location from within the 'Nearby' list.", - "Your location will be visible in the 'Connections' list only for those with whom you are connected or friends. They'll be able to jump to your location if the domain allows.", - "Your location will be visible in the 'Connections' list only for those with whom you are friends. They'll be able to jump to your location if the domain allows. You will only receive 'Happening Now' notifications in 'Go To' from friends.", - "You will appear offline in the 'Connections' list, and you will not receive 'Happening Now' notifications in 'Go To'."], + "Your location will be visible in the 'Connections' list only for those with whom you are connected or friends. They'll be able to jump to your location if the domain allows, and you will see 'Snaps' Blasts from them in 'Go To'.", + "Your location will be visible in the 'Connections' list only for those with whom you are friends. They'll be able to jump to your location if the domain allows, and you will see 'Snaps' Blasts from them in 'Go To'", + "You will appear offline in the 'Connections' list, and you will not receive Snaps Blasts from connections or friends in 'Go To'."], ["all", "connections", "friends", "none"]); } onEntered: availabilityComboBox.color = hifi.colors.lightGrayText; @@ -1209,6 +1235,9 @@ Rectangle { case 'clearLocalQMLData': ignored = {}; break; + case 'refreshConnections': + refreshConnections(); + break; case 'avatarDisconnected': var sessionID = message.params[0]; delete ignored[sessionID]; diff --git a/interface/resources/qml/hifi/avatarapp/AdjustWearables.qml b/interface/resources/qml/hifi/avatarapp/AdjustWearables.qml index a501185853..e80dab60c2 100644 --- a/interface/resources/qml/hifi/avatarapp/AdjustWearables.qml +++ b/interface/resources/qml/hifi/avatarapp/AdjustWearables.qml @@ -1,5 +1,6 @@ import Hifi 1.0 as Hifi import QtQuick 2.5 +import QtQuick.Layouts 1.3 import "../../styles-uit" import "../../controls-uit" as HifiControlsUit import "../../controls" as HifiControls @@ -17,6 +18,7 @@ Rectangle { signal adjustWearablesOpened(var avatarName); signal adjustWearablesClosed(bool status, var avatarName); + signal addWearable(string avatarName, string url); property bool modified: false; Component.onCompleted: { @@ -30,6 +32,7 @@ Rectangle { function open(avatar) { adjustWearablesOpened(avatar.name); + modified = false; visible = true; avatarName = avatar.name; wearablesModel = avatar.wearables; @@ -38,43 +41,47 @@ Rectangle { function refresh(avatar) { wearablesCombobox.model.clear(); - for(var i = 0; i < avatar.wearables.count; ++i) { + wearablesCombobox.currentIndex = -1; + + for (var i = 0; i < avatar.wearables.count; ++i) { var wearable = avatar.wearables.get(i).properties; - for(var j = (wearable.modelURL.length - 1); j >= 0; --j) { - if(wearable.modelURL[j] === '/') { - wearable.text = wearable.modelURL.substring(j + 1) + ' [%jointIndex%]'.replace('%jointIndex%', jointNames[wearable.parentJointIndex]); + for (var j = (wearable.modelURL.length - 1); j >= 0; --j) { + if (wearable.modelURL[j] === '/') { + wearable.text = wearable.modelURL.substring(j + 1); break; } } wearablesCombobox.model.append(wearable); } - wearablesCombobox.currentIndex = 0; + if (wearablesCombobox.model.count !== 0) { + wearablesCombobox.currentIndex = 0; + } } function refreshWearable(wearableID, wearableIndex, properties, updateUI) { - if(wearableIndex === -1) { + if (wearableIndex === -1) { wearableIndex = wearablesCombobox.model.findIndexById(wearableID); } var wearable = wearablesCombobox.model.get(wearableIndex); - if(!wearable) { + if (!wearable) { return; } var wearableModelItemProperties = wearablesModel.get(wearableIndex).properties; - for(var prop in properties) { + for (var prop in properties) { wearable[prop] = properties[prop]; wearableModelItemProperties[prop] = wearable[prop]; - if(updateUI) { - if(prop === 'localPosition') { - position.set(wearable[prop]); - } else if(prop === 'localRotationAngles') { - rotation.set(wearable[prop]); - } else if(prop === 'dimensions') { + if (updateUI) { + if (prop === 'localPosition') { + positionVector.set(wearable[prop]); + } else if (prop === 'localRotationAngles') { + rotationVector.set(wearable[prop]); + } else if (prop === 'dimensions') { scalespinner.set(wearable[prop].x / wearable.naturalDimensions.x); } } @@ -84,13 +91,13 @@ Rectangle { } function getCurrentWearable() { - return wearablesCombobox.model.get(wearablesCombobox.currentIndex) + return wearablesCombobox.currentIndex !== -1 ? wearablesCombobox.model.get(wearablesCombobox.currentIndex) : null; } function selectWearableByID(entityID) { - for(var i = 0; i < wearablesCombobox.model.count; ++i) { + for (var i = 0; i < wearablesCombobox.model.count; ++i) { var wearable = wearablesCombobox.model.get(i); - if(wearable.id === entityID) { + if (wearable.id === entityID) { wearablesCombobox.currentIndex = i; break; } @@ -115,72 +122,216 @@ Rectangle { Column { anchors.top: parent.top - anchors.topMargin: 15 + anchors.topMargin: 12 anchors.horizontalCenter: parent.horizontalCenter spacing: 20 - width: parent.width - 30 * 2 + width: parent.width - 22 * 2 - HifiControlsUit.ComboBox { - id: wearablesCombobox - anchors.left: parent.left - anchors.right: parent.right - comboBox.textRole: "text" + Column { + width: parent.width - model: ListModel { - function findIndexById(id) { + Rectangle { + color: hifi.colors.orangeHighlight + anchors.left: parent.left + anchors.right: parent.right + height: 70 + visible: HMD.active - for(var i = 0; i < count; ++i) { - var wearable = get(i); - if(wearable.id === id) { - return i; - } - } - - return -1; + RalewayRegular { + anchors.fill: parent + anchors.leftMargin: 18 + anchors.rightMargin: 18 + size: 20; + lineHeightMode: Text.FixedHeight + lineHeight: 23; + text: "Tip: You can use hand controllers to grab and adjust your wearables" + wrapMode: Text.WordWrap } } - comboBox.onCurrentIndexChanged: { - var currentWearable = getCurrentWearable(); + Rectangle { + anchors.left: parent.left + anchors.right: parent.right + height: 12 // spacing + visible: HMD.active + } - if(currentWearable) { - position.set(currentWearable.localPosition); - rotation.set(currentWearable.localRotationAngles); - scalespinner.set(currentWearable.dimensions.x / currentWearable.naturalDimensions.x) + RowLayout { + anchors.left: parent.left + anchors.right: parent.right - wearableSelected(currentWearable.id); + RalewayBold { + size: 15; + lineHeightMode: Text.FixedHeight + lineHeight: 18; + text: "Wearable" + anchors.verticalCenter: parent.verticalCenter + } + + spacing: 10 + + RalewayBold { + size: 15; + lineHeightMode: Text.FixedHeight + lineHeight: 18; + text: "Get more" + linkColor: hifi.colors.blueHighlight + anchors.verticalCenter: parent.verticalCenter + onLinkActivated: { + popup.showGetWearables(function() { + emitSendToScript({'method' : 'navigate', 'url' : 'hifi://AvatarIsland/11.5848,-8.10862,-2.80195'}) + }, function(link) { + emitSendToScript({'method' : 'navigate', 'url' : link}) + }); + } + } + + Rectangle { + Layout.fillWidth: true + Layout.fillHeight: true + + RalewayBold { + size: 15; + lineHeightMode: Text.FixedHeight + lineHeight: 18; + text: "Add custom" + linkColor: hifi.colors.blueHighlight + anchors.verticalCenter: parent.verticalCenter + anchors.right: parent.right + onLinkActivated: { + popup.showSpecifyWearableUrl(function(url) { + console.debug('popup.showSpecifyWearableUrl: ', url); + addWearable(root.avatarName, url); + modified = true; + }); + } + } + } + } + + HifiControlsUit.ComboBox { + id: wearablesCombobox + anchors.left: parent.left + anchors.right: parent.right + enabled: getCurrentWearable() !== null + comboBox.textRole: "text" + + model: ListModel { + function findIndexById(id) { + + for (var i = 0; i < count; ++i) { + var wearable = get(i); + if (wearable.id === id) { + return i; + } + } + + return -1; + } + } + + comboBox.onCurrentIndexChanged: { + var currentWearable = getCurrentWearable(); + var position = currentWearable ? currentWearable.localPosition : { x : 0, y : 0, z : 0 }; + var rotation = currentWearable ? currentWearable.localRotationAngles : { x : 0, y : 0, z : 0 }; + var scale = currentWearable ? currentWearable.dimensions.x / currentWearable.naturalDimensions.x : 1.0; + var joint = currentWearable ? currentWearable.parentJointIndex : -1; + var soft = currentWearable ? currentWearable.relayParentJoints : false; + + positionVector.set(position); + rotationVector.set(rotation); + scalespinner.set(scale); + jointsCombobox.set(joint); + isSoft.set(soft); + + if (currentWearable) { + wearableSelected(currentWearable.id); + } + } + } + } + + Column { + width: parent.width + + RalewayBold { + size: 15; + lineHeightMode: Text.FixedHeight + lineHeight: 18; + text: "Joint" + } + + HifiControlsUit.ComboBox { + id: jointsCombobox + anchors.left: parent.left + anchors.right: parent.right + enabled: getCurrentWearable() !== null && !isSoft.checked + comboBox.displayText: isSoft.checked ? 'Hips' : comboBox.currentText + + model: jointNames + property bool notify: false + + function set(jointIndex) { + notify = false; + currentIndex = jointIndex; + notify = true; + } + + function notifyJointChanged() { + modified = true; + var properties = { + parentJointIndex: currentIndex, + localPosition: { + x: positionVector.xvalue, + y: positionVector.yvalue, + z: positionVector.zvalue + }, + localRotationAngles: { + x: rotationVector.xvalue, + y: rotationVector.yvalue, + z: rotationVector.zvalue, + } + }; + + wearableUpdated(getCurrentWearable().id, wearablesCombobox.currentIndex, properties); + } + + onCurrentIndexChanged: { + if (notify) notifyJointChanged(); } } } Column { width: parent.width - spacing: 5 Row { spacing: 20 // TextStyle5 - FiraSansSemiBold { + RalewayBold { id: positionLabel - size: 22; + size: 15; + lineHeightMode: Text.FixedHeight + lineHeight: 18; text: "Position" } // TextStyle7 - FiraSansRegular { - size: 18; + RalewayBold { + size: 15; lineHeightMode: Text.FixedHeight - lineHeight: 16.9; + lineHeight: 18; text: "m" anchors.verticalCenter: positionLabel.verticalCenter } } Vector3 { - id: position + id: positionVector backgroundColor: "lightgray" + enabled: getCurrentWearable() !== null function set(localPosition) { notify = false; @@ -201,9 +352,9 @@ Rectangle { property bool notify: false; - onXvalueChanged: if(notify) notifyPositionChanged(); - onYvalueChanged: if(notify) notifyPositionChanged(); - onZvalueChanged: if(notify) notifyPositionChanged(); + onXvalueChanged: if (notify) notifyPositionChanged(); + onYvalueChanged: if (notify) notifyPositionChanged(); + onZvalueChanged: if (notify) notifyPositionChanged(); decimals: 2 realFrom: -10 @@ -214,31 +365,33 @@ Rectangle { Column { width: parent.width - spacing: 5 Row { spacing: 20 // TextStyle5 - FiraSansSemiBold { + RalewayBold { id: rotationLabel - size: 22; + size: 15; + lineHeightMode: Text.FixedHeight + lineHeight: 18; text: "Rotation" } // TextStyle7 - FiraSansRegular { - size: 18; + RalewayBold { + size: 15; lineHeightMode: Text.FixedHeight - lineHeight: 16.9; + lineHeight: 18; text: "deg" anchors.verticalCenter: rotationLabel.verticalCenter } } Vector3 { - id: rotation + id: rotationVector backgroundColor: "lightgray" + enabled: getCurrentWearable() !== null function set(localRotationAngles) { notify = false; @@ -259,9 +412,9 @@ Rectangle { property bool notify: false; - onXvalueChanged: if(notify) notifyRotationChanged(); - onYvalueChanged: if(notify) notifyRotationChanged(); - onZvalueChanged: if(notify) notifyRotationChanged(); + onXvalueChanged: if (notify) notifyRotationChanged(); + onYvalueChanged: if (notify) notifyRotationChanged(); + onZvalueChanged: if (notify) notifyRotationChanged(); decimals: 0 realFrom: -180 @@ -270,33 +423,66 @@ Rectangle { } } - Column { + Item { width: parent.width - spacing: 5 + height: childrenRect.height - // TextStyle5 - FiraSansSemiBold { - size: 22; - text: "Scale" + HifiControlsUit.CheckBox { + id: isSoft + enabled: getCurrentWearable() !== null + text: "Is soft" + labelFontSize: 15 + labelFontWeight: Font.Bold + color: Qt.black + y: scalespinner.y + + function set(value) { + notify = false; + checked = value + notify = true; + } + + function notifyIsSoftChanged() { + modified = true; + var properties = { + relayParentJoints: checked + }; + + wearableUpdated(getCurrentWearable().id, wearablesCombobox.currentIndex, properties); + } + + property bool notify: false; + + onCheckedChanged: if (notify) notifyIsSoftChanged(); } - Item { - width: parent.width - height: childrenRect.height + Column { + id: scalesColumn + anchors.right: parent.right + + // TextStyle5 + RalewayBold { + id: scaleLabel + size: 15; + lineHeightMode: Text.FixedHeight + lineHeight: 18; + text: "Scale" + } HifiControlsUit.SpinBox { id: scalespinner + enabled: getCurrentWearable() !== null decimals: 2 realStepSize: 0.1 realFrom: 0.1 realTo: 3.0 realValue: 1.0 backgroundColor: "lightgray" - width: position.spinboxWidth + width: positionVector.spinboxWidth colorScheme: hifi.colorSchemes.light property bool notify: false; - onValueChanged: if(notify) notifyScaleChanged(); + onRealValueChanged: if (notify) notifyScaleChanged(); function set(value) { notify = false; @@ -320,26 +506,34 @@ Rectangle { wearableUpdated(currentWearable.id, wearablesCombobox.currentIndex, properties); } } - - HifiControlsUit.Button { - fontSize: 18 - height: 40 - anchors.right: parent.right - color: hifi.buttons.red; - colorScheme: hifi.colorSchemes.dark; - text: "TAKE IT OFF" - onClicked: wearableDeleted(root.avatarName, getCurrentWearable().id); - enabled: wearablesCombobox.model.count !== 0 - anchors.verticalCenter: scalespinner.verticalCenter - } } + } + Column { + width: parent.width + + HifiControlsUit.Button { + fontSize: 18 + height: 40 + width: scalespinner.width + anchors.right: parent.right + color: hifi.buttons.red; + colorScheme: hifi.colorSchemes.light; + text: "TAKE IT OFF" + onClicked: { + modified = true; + wearableDeleted(root.avatarName, getCurrentWearable().id); + } + enabled: wearablesCombobox.model.count !== 0 + } } } DialogButtons { + yesButton.enabled: modified + anchors.bottom: parent.bottom - anchors.bottomMargin: 30 + anchors.bottomMargin: 57 anchors.left: parent.left anchors.leftMargin: 30 anchors.right: parent.right diff --git a/interface/resources/qml/hifi/avatarapp/AvatarsModel.qml b/interface/resources/qml/hifi/avatarapp/AvatarsModel.qml index 931a041747..bfd66f1a30 100644 --- a/interface/resources/qml/hifi/avatarapp/AvatarsModel.qml +++ b/interface/resources/qml/hifi/avatarapp/AvatarsModel.qml @@ -24,8 +24,9 @@ ListModel { function makeThumbnailUrl(avatarUrl) { var marketId = extractMarketId(avatarUrl); - if(marketId === '') + if (marketId === '') { return ''; + } var avatarThumbnailUrl = "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/previews/%marketId%/large/hifi-mp-%marketId%.jpg" .split('%marketId%').join(marketId); @@ -42,7 +43,6 @@ ListModel { 'thumbnailUrl' : avatarThumbnailUrl, 'avatarUrl' : avatar.avatarUrl, 'wearables' : avatar.avatarEntites ? avatar.avatarEntites : [], - 'attachments' : avatar.attachments ? avatar.attachments : [], 'entry' : avatar, 'getMoreAvatars' : false }; @@ -58,7 +58,7 @@ ListModel { function populate(bookmarks) { clear(); - for(var avatarName in bookmarks) { + for (var avatarName in bookmarks) { var avatar = bookmarks[avatarName]; var avatarEntry = makeAvatarObject(avatar, avatarName); @@ -67,19 +67,19 @@ ListModel { } function arraysAreEqual(a1, a2, comparer) { - if(Array.isArray(a1) && Array.isArray(a2)) { - if(a1.length !== a2.length) { + if (Array.isArray(a1) && Array.isArray(a2)) { + if (a1.length !== a2.length) { return false; } - for(var i = 0; i < a1.length; ++i) { - if(!comparer(a1[i], a2[i])) { + for (var i = 0; i < a1.length; ++i) { + if (!comparer(a1[i], a2[i])) { return false; } } - } else if(Array.isArray(a1)) { + } else if (Array.isArray(a1)) { return a1.length === 0; - } else if(Array.isArray(a2)) { + } else if (Array.isArray(a2)) { return a2.length === 0; } @@ -87,26 +87,26 @@ ListModel { } function modelsAreEqual(m1, m2, comparer) { - if(m1.count !== m2.count) { + if (m1.count !== m2.count) { return false; } - for(var i = 0; i < m1.count; ++i) { + for (var i = 0; i < m1.count; ++i) { var e1 = m1.get(i); var allDifferent = true; // it turns out order of wearables can randomly change so make position-independent comparison here - for(var j = 0; j < m2.count; ++j) { + for (var j = 0; j < m2.count; ++j) { var e2 = m2.get(j); - if(comparer(e1, e2)) { + if (comparer(e1, e2)) { allDifferent = false; break; } } - if(allDifferent) { + if (allDifferent) { return false; } } @@ -115,18 +115,20 @@ ListModel { } function compareNumericObjects(o1, o2) { - if(o1 === undefined && o2 !== undefined) + if (o1 === undefined && o2 !== undefined) { return false; - if(o1 !== undefined && o2 === undefined) + } + if (o1 !== undefined && o2 === undefined) { return false; + } - for(var prop in o1) { - if(o1.hasOwnProperty(prop) && o2.hasOwnProperty(prop)) { + for (var prop in o1) { + if (o1.hasOwnProperty(prop) && o2.hasOwnProperty(prop)) { var v1 = o1[prop]; var v2 = o2[prop]; - if(v1 !== v2 && Math.round(v1 * 500) != Math.round(v2 * 500)) { + if (v1 !== v2 && Math.round(v1 * 500) != Math.round(v2 * 500)) { return false; } } @@ -136,7 +138,7 @@ ListModel { } function compareObjects(o1, o2, props, arrayProp) { - for(var i = 0; i < props.length; ++i) { + for (var i = 0; i < props.length; ++i) { var prop = props[i]; var propertyName = prop.propertyName; var comparer = prop.comparer; @@ -144,12 +146,12 @@ ListModel { var o1Value = arrayProp ? o1[arrayProp][propertyName] : o1[propertyName]; var o2Value = arrayProp ? o2[arrayProp][propertyName] : o2[propertyName]; - if(comparer) { - if(comparer(o1Value, o2Value) === false) { + if (comparer) { + if (comparer(o1Value, o2Value) === false) { return false; } } else { - if(JSON.stringify(o1Value) !== JSON.stringify(o2Value)) { + if (JSON.stringify(o1Value) !== JSON.stringify(o2Value)) { return false; } } @@ -164,42 +166,34 @@ ListModel { {'propertyName' : 'marketplaceID'}, {'propertyName' : 'itemName'}, {'propertyName' : 'script'}, + {'propertyName' : 'relayParentJoints'}, {'propertyName' : 'localPosition', 'comparer' : compareNumericObjects}, {'propertyName' : 'localRotationAngles', 'comparer' : compareNumericObjects}, {'propertyName' : 'dimensions', 'comparer' : compareNumericObjects}], 'properties') } - function compareAttachments(a1, a2) { - return compareObjects(a1, a2, [{'propertyName' : 'position', 'comparer' : compareNumericObjects}, - {'propertyName' : 'orientation'}, - {'propertyName' : 'parentJointIndex'}, - {'propertyName' : 'modelurl'}]) - } - function findAvatarIndexByValue(avatar) { var index = -1; // 2DO: find better way of determining selected avatar in bookmarks - for(var i = 0; i < allAvatars.count; ++i) { + for (var i = 0; i < allAvatars.count; ++i) { var thesame = true; var bookmarkedAvatar = allAvatars.get(i); - if(bookmarkedAvatar.avatarUrl !== avatar.avatarUrl) - continue; - - if(bookmarkedAvatar.avatarScale !== avatar.avatarScale) - continue; - - if(!modelsAreEqual(bookmarkedAvatar.attachments, avatar.attachments, compareAttachments)) { + if (bookmarkedAvatar.avatarUrl !== avatar.avatarUrl) { continue; } - if(!modelsAreEqual(bookmarkedAvatar.wearables, avatar.wearables, compareWearables)) { + if (bookmarkedAvatar.avatarScale !== avatar.avatarScale) { continue; } - if(thesame) { + if (!modelsAreEqual(bookmarkedAvatar.wearables, avatar.wearables, compareWearables)) { + continue; + } + + if (thesame) { index = i; break; } @@ -209,8 +203,8 @@ ListModel { } function findAvatarIndex(avatarName) { - for(var i = 0; i < count; ++i) { - if(get(i).name === avatarName) { + for (var i = 0; i < count; ++i) { + if (get(i).name === avatarName) { return i; } } @@ -219,8 +213,9 @@ ListModel { function findAvatar(avatarName) { var avatarIndex = findAvatarIndex(avatarName); - if(avatarIndex === -1) + if (avatarIndex === -1) { return undefined; + } return get(avatarIndex); } diff --git a/interface/resources/qml/hifi/avatarapp/CreateFavoriteDialog.qml b/interface/resources/qml/hifi/avatarapp/CreateFavoriteDialog.qml index ab995e79e8..1387c0791a 100644 --- a/interface/resources/qml/hifi/avatarapp/CreateFavoriteDialog.qml +++ b/interface/resources/qml/hifi/avatarapp/CreateFavoriteDialog.qml @@ -125,9 +125,9 @@ Rectangle { size: 15 color: 'red' visible: { - for(var i = 0; i < avatars.count; ++i) { + for (var i = 0; i < avatars.count; ++i) { var avatarName = avatars.get(i).name; - if(avatarName === favoriteName.text) { + if (avatarName === favoriteName.text) { return true; } } @@ -165,7 +165,7 @@ Rectangle { } onYesClicked: function() { - if(onSaveClicked) { + if (onSaveClicked) { onSaveClicked(); } else { root.close(); @@ -173,7 +173,7 @@ Rectangle { } onNoClicked: function() { - if(onCancelClicked) { + if (onCancelClicked) { onCancelClicked(); } else { root.close(); diff --git a/interface/resources/qml/hifi/avatarapp/DialogButtons.qml b/interface/resources/qml/hifi/avatarapp/DialogButtons.qml index 46c17bb4dc..d6eb0139ed 100644 --- a/interface/resources/qml/hifi/avatarapp/DialogButtons.qml +++ b/interface/resources/qml/hifi/avatarapp/DialogButtons.qml @@ -33,7 +33,7 @@ Row { onClicked: { console.debug('whitebutton.clicked', onNoClicked); - if(onNoClicked) { + if (onNoClicked) { onNoClicked(); } } diff --git a/interface/resources/qml/hifi/avatarapp/MessageBox.qml b/interface/resources/qml/hifi/avatarapp/MessageBox.qml index f2df0b5199..f111303214 100644 --- a/interface/resources/qml/hifi/avatarapp/MessageBox.qml +++ b/interface/resources/qml/hifi/avatarapp/MessageBox.qml @@ -14,6 +14,7 @@ Rectangle { property string titleText: '' property string bodyText: '' property alias inputText: input; + property alias dialogButtons: buttons property string imageSource: null onImageSourceChanged: { @@ -36,6 +37,7 @@ Rectangle { function close() { visible = false; + dialogButtons.yesButton.fontCapitalization = Font.AllUppercase; onButton1Clicked = null; onButton2Clicked = null; button1text = ''; @@ -126,7 +128,7 @@ Rectangle { wrapMode: Text.WordWrap; onLinkActivated: { - if(onLinkClicked) + if (onLinkClicked) onLinkClicked(link); } } @@ -164,7 +166,7 @@ Rectangle { noText: root.button1text onYesClicked: function() { - if(onButton2Clicked) { + if (onButton2Clicked) { onButton2Clicked(); } else { root.close(); @@ -172,7 +174,7 @@ Rectangle { } onNoClicked: function() { - if(onButton1Clicked) { + if (onButton1Clicked) { onButton1Clicked(); } else { root.close(); diff --git a/interface/resources/qml/hifi/avatarapp/MessageBoxes.qml b/interface/resources/qml/hifi/avatarapp/MessageBoxes.qml index 125b30fa95..b7782c697d 100644 --- a/interface/resources/qml/hifi/avatarapp/MessageBoxes.qml +++ b/interface/resources/qml/hifi/avatarapp/MessageBoxes.qml @@ -3,7 +3,7 @@ import QtQuick 2.5 MessageBox { id: popup - function showSpecifyAvatarUrl(callback, linkCallback) { + function showSpecifyAvatarUrl(url, callback, linkCallback) { popup.onButton2Clicked = callback; popup.titleText = 'Specify Avatar URL' popup.bodyText = 'This will not overwrite your existing favorite if you are wearing one.
' + @@ -12,19 +12,44 @@ MessageBox { '' popup.inputText.visible = true; popup.inputText.placeholderText = 'Enter Avatar Url'; + popup.inputText.text = url; + popup.inputText.selectAll(); popup.button1text = 'CANCEL'; popup.button2text = 'CONFIRM'; popup.onButton2Clicked = function() { - if(callback) + if (callback) { callback(); + } popup.close(); } popup.onLinkClicked = function(link) { - if(linkCallback) + if (linkCallback) { linkCallback(link); + } + } + + popup.open(); + popup.inputText.forceActiveFocus(); + } + + function showSpecifyWearableUrl(callback) { + popup.button2text = 'CONFIRM' + popup.button1text = 'CANCEL' + popup.titleText = 'Specify Wearable URL' + popup.bodyText = 'If you want to add a custom wearable, you can specify the URL of the wearable file here.' + + popup.inputText.visible = true; + popup.inputText.placeholderText = 'Enter Wearable URL'; + + popup.onButton2Clicked = function() { + if (callback) { + callback(popup.inputText.text); + } + + popup.close(); } popup.open(); @@ -35,25 +60,28 @@ MessageBox { function showGetWearables(callback, linkCallback) { popup.button2text = 'AvatarIsland' + popup.dialogButtons.yesButton.fontCapitalization = Font.MixedCase; popup.button1text = 'CANCEL' popup.titleText = 'Get Wearables' - popup.bodyText = 'Buy wearables from Marketplace' + '
' + - 'Wear wearables from My Purchases' + '
' + - 'You can visit the domain “AvatarIsland” to get wearables' + popup.bodyText = 'Buy wearables from Marketplace.' + '
' + + 'Wear wearable from My Purchases.' + '
' + '
' + + 'Visit “AvatarIsland” to get wearables' popup.imageSource = getWearablesUrl; popup.onButton2Clicked = function() { popup.close(); - if(callback) + if (callback) { callback(); + } } popup.onLinkClicked = function(link) { popup.close(); - if(linkCallback) + if (linkCallback) { linkCallback(link); + } } popup.open(); @@ -69,8 +97,9 @@ MessageBox { popup.onButton2Clicked = function() { popup.close(); - if(callback) + if (callback) { callback(); + } } popup.open(); } @@ -84,8 +113,9 @@ MessageBox { popup.onButton2Clicked = function() { popup.close(); - if(callback) + if (callback) { callback(); + } } popup.open(); } @@ -94,26 +124,29 @@ MessageBox { function showBuyAvatars(callback, linkCallback) { popup.button2text = 'BodyMart' + popup.dialogButtons.yesButton.fontCapitalization = Font.MixedCase; popup.button1text = 'CANCEL' popup.titleText = 'Get Avatars' - popup.bodyText = 'Buy avatars from Marketplace' + '
' + - 'Wear avatars from My Purchases' + '
' + - 'You can visit the domain “BodyMart” to get avatars' + popup.bodyText = 'Buy avatars from Marketplace.' + '
' + + 'Wear avatars in My Purchases.' + '
' + '
' + + 'Visit “BodyMart” to get free avatars.' popup.imageSource = getAvatarsUrl; popup.onButton2Clicked = function() { popup.close(); - if(callback) + if (callback) { callback(); + } } popup.onLinkClicked = function(link) { popup.close(); - if(linkCallback) + if (linkCallback) { linkCallback(link); + } } popup.open(); diff --git a/interface/resources/qml/hifi/avatarapp/Settings.qml b/interface/resources/qml/hifi/avatarapp/Settings.qml index f996bdfd03..71bfbb084d 100644 --- a/interface/resources/qml/hifi/avatarapp/Settings.qml +++ b/interface/resources/qml/hifi/avatarapp/Settings.qml @@ -20,7 +20,8 @@ Rectangle { property real scaleValue: scaleSlider.value / 10 property alias dominantHandIsLeft: leftHandRadioButton.checked property alias avatarCollisionsOn: collisionsEnabledRadiobutton.checked - property alias avatarAnimationJSON: avatarAnimationUrlInputText.text + property alias avatarAnimationOverrideJSON: avatarAnimationUrlInputText.text + property alias avatarAnimationJSON: avatarAnimationUrlInputText.placeholderText property alias avatarCollisionSoundUrl: avatarCollisionSoundUrlInputText.text property real avatarScaleBackup; @@ -32,19 +33,20 @@ Rectangle { scaleSlider.value = Math.round(avatarScale * 10); scaleSlider.notify = true;; - if(settings.dominantHand === 'left') { + if (settings.dominantHand === 'left') { leftHandRadioButton.checked = true; } else { rightHandRadioButton.checked = true; } - if(settings.collisionsEnabled) { + if (settings.collisionsEnabled) { collisionsEnabledRadiobutton.checked = true; } else { collisionsDisabledRadioButton.checked = true; } avatarAnimationJSON = settings.animGraphUrl; + avatarAnimationOverrideJSON = settings.animGraphOverrideUrl; avatarCollisionSoundUrl = settings.collisionSoundUrl; visible = true; @@ -111,7 +113,7 @@ Rectangle { onValueChanged: { console.debug('value changed: ', value); - if(notify) { + if (notify) { console.debug('notifying.. '); root.scaleChanged(value / 10); } diff --git a/interface/resources/qml/hifi/avatarapp/SquareLabel.qml b/interface/resources/qml/hifi/avatarapp/SquareLabel.qml index 3c5463e1dd..e2c456ec04 100644 --- a/interface/resources/qml/hifi/avatarapp/SquareLabel.qml +++ b/interface/resources/qml/hifi/avatarapp/SquareLabel.qml @@ -1,25 +1,42 @@ import "../../styles-uit" +import "../../controls-uit" as HifiControlsUit import QtQuick 2.9 import QtGraphicalEffects 1.0 -ShadowRectangle { +Item { + id: root width: 44 height: 28 - AvatarAppStyle { - id: style + signal clicked(); + + HifiControlsUit.Button { + id: button + + HifiConstants { + id: hifi + } + + anchors.fill: parent + color: hifi.buttons.blue; + colorScheme: hifi.colorSchemes.light; + radius: 3 + onClicked: root.clicked(); } - gradient: Gradient { - GradientStop { position: 0.0; color: style.colors.blueHighlight } - GradientStop { position: 1.0; color: style.colors.blueAccent } + DropShadow { + id: shadow + anchors.fill: button + radius: 6 + horizontalOffset: 0 + verticalOffset: 3 + color: Qt.rgba(0, 0, 0, 0.25) + source: button } property alias glyphText: glyph.text property alias glyphRotation: glyph.rotation property alias glyphSize: glyph.size - radius: 3 - HiFiGlyphs { id: glyph color: 'white' diff --git a/interface/resources/qml/hifi/avatarapp/TransparencyMask.qml b/interface/resources/qml/hifi/avatarapp/TransparencyMask.qml index 4884d1e1ad..6c50a6093a 100644 --- a/interface/resources/qml/hifi/avatarapp/TransparencyMask.qml +++ b/interface/resources/qml/hifi/avatarapp/TransparencyMask.qml @@ -32,7 +32,7 @@ Item { highp vec4 maskColor = texture2D(mask, vec2(qt_TexCoord0.x, qt_TexCoord0.y)); highp vec4 sourceColor = texture2D(source, vec2(qt_TexCoord0.x, qt_TexCoord0.y)); - if(maskColor.a > 0.0) + if (maskColor.a > 0.0) gl_FragColor = sourceColor; else gl_FragColor = maskColor; diff --git a/interface/resources/qml/hifi/avatarapp/Vector3.qml b/interface/resources/qml/hifi/avatarapp/Vector3.qml index 33e82fe0ee..d77665f992 100644 --- a/interface/resources/qml/hifi/avatarapp/Vector3.qml +++ b/interface/resources/qml/hifi/avatarapp/Vector3.qml @@ -20,6 +20,7 @@ Row { spacing: spinboxSpace + property bool enabled: false; property alias xvalue: xspinner.realValue property alias yvalue: yspinner.realValue property alias zvalue: zspinner.realValue @@ -35,6 +36,7 @@ Row { realFrom: root.realFrom realTo: root.realTo realStepSize: root.realStepSize + enabled: root.enabled } HifiControlsUit.SpinBox { @@ -48,6 +50,7 @@ Row { realFrom: root.realFrom realTo: root.realTo realStepSize: root.realStepSize + enabled: root.enabled } HifiControlsUit.SpinBox { @@ -61,5 +64,6 @@ Row { realFrom: root.realFrom realTo: root.realTo realStepSize: root.realStepSize + enabled: root.enabled } } diff --git a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml index cac62d3976..4d47479589 100644 --- a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml +++ b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml @@ -876,7 +876,7 @@ Rectangle { horizontalAlignment: Text.AlignLeft; verticalAlignment: Text.AlignVCenter; onLinkActivated: { - sendToScript({method: 'checkout_goToPurchases'}); + sendToScript({method: 'checkout_goToPurchases', filterText: root.itemName}); } } diff --git a/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml b/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml index b2338d08de..032d9b0199 100644 --- a/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml +++ b/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml @@ -335,7 +335,8 @@ Item { upgradeUrl: root.upgradeUrl, itemHref: root.itemHref, itemType: root.itemType, - isInstalled: root.isInstalled + isInstalled: root.isInstalled, + wornEntityID: root.wornEntityID }); } } diff --git a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml index 3569ce6767..3b8e2c0f4d 100644 --- a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml +++ b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml @@ -707,6 +707,12 @@ Rectangle { } } } else if (msg.method === "updateItemClicked") { + // These three cases are very similar to the conditionals below, under + // "if msg.method === "giftAsset". They differ in their popup's wording + // and the actions to take when continuing. + // I could see an argument for DRYing up this code, but I think the + // actions are different enough now and potentially moving forward such that I'm + // OK with "somewhat repeating myself". if (msg.itemType === "app" && msg.isInstalled) { lightboxPopup.titleText = "Uninstall App"; lightboxPopup.bodyText = "The app that you are trying to update is installed.

" + @@ -721,6 +727,35 @@ Rectangle { sendToScript(msg); }; lightboxPopup.visible = true; + } else if (msg.itemType === "wearable" && msg.wornEntityID !== '') { + lightboxPopup.titleText = "Remove Wearable"; + lightboxPopup.bodyText = "You are currently wearing the wearable that you are trying to update.

" + + "If you proceed, this wearable will be removed."; + lightboxPopup.button1text = "CANCEL"; + lightboxPopup.button1method = function() { + lightboxPopup.visible = false; + } + lightboxPopup.button2text = "CONFIRM"; + lightboxPopup.button2method = function() { + Entities.deleteEntity(msg.wornEntityID); + purchasesModel.setProperty(index, 'wornEntityID', ''); + sendToScript(msg); + }; + lightboxPopup.visible = true; + } else if (msg.itemType === "avatar" && MyAvatar.skeletonModelURL === msg.itemHref) { + lightboxPopup.titleText = "Change Avatar to Default"; + lightboxPopup.bodyText = "You are currently wearing the avatar that you are trying to update.

" + + "If you proceed, your avatar will be changed to the default avatar."; + lightboxPopup.button1text = "CANCEL"; + lightboxPopup.button1method = function() { + lightboxPopup.visible = false; + } + lightboxPopup.button2text = "CONFIRM"; + lightboxPopup.button2method = function() { + MyAvatar.useFullAvatarURL(''); + sendToScript(msg); + }; + lightboxPopup.visible = true; } else { sendToScript(msg); } diff --git a/interface/resources/qml/hifi/commerce/wallet/Help.qml b/interface/resources/qml/hifi/commerce/wallet/Help.qml index b453509712..6d8fc3c33f 100644 --- a/interface/resources/qml/hifi/commerce/wallet/Help.qml +++ b/interface/resources/qml/hifi/commerce/wallet/Help.qml @@ -63,47 +63,6 @@ Item { question: "How can I get HFC?"; answer: "High Fidelity commerce is in open beta right now. Want more HFC? \ Get it by going to

BankOfHighFidelity. and meeting with the banker!"; - } - ListElement { - isExpanded: false; - question: "What are private keys and where are they stored?"; - answer: - "A private key is a secret piece of text that is used to prove ownership, unlock confidential information, and sign transactions. \ -In High Fidelity, your private key is used to securely access the contents of your Wallet and Purchases. \ -After wallet setup, a hifikey file is stored on your computer in High Fidelity Interface's AppData directory. \ -Your hifikey file contains your private key and is protected by your wallet passphrase. \ -

It is very important to back up your hifikey file! \ -Tap here to open the folder where your HifiKeys are stored on your main display." - } - ListElement { - isExpanded: false; - question: "How do I back up my private keys?"; - answer: "You can back up your hifikey file (which contains your private key and is encrypted using your wallet passphrase) by copying it to a USB flash drive, or to a service like Dropbox or Google Drive. \ -Restore your hifikey file by replacing the file in Interface's AppData directory with your backup copy. \ -Others with access to your back up should not be able to spend your HFC without your passphrase. \ -Tap here to open the folder where your HifiKeys are stored on your main display."; - } - ListElement { - isExpanded: false; - question: "What happens if I lose my private keys?"; - answer: "We cannot stress enough that you should keep a backup! For security reasons, High Fidelity does not keep a copy, and cannot restore it for you. \ -If you lose your private key, you will no longer have access to the contents of your Wallet or My Purchases. \ -Here are some things to try:
    \ -
  • If you have backed up your hifikey file before, search your backup location
  • \ -
  • Search your AppData directory in the last machine you used to set up the Wallet
  • \ -
  • If you are a developer and have installed multiple builds of High Fidelity, your hifikey file might be in another folder
  • \ -


As a last resort, you can set up your Wallet again and generate a new hifikey file. \ -Unfortunately, this means you will start with 0 HFC and your purchased items will not be transferred over."; - } - ListElement { - isExpanded: false; - question: "What if I forget my wallet passphrase?"; - answer: "Your wallet passphrase is used to encrypt your private keys. Please write it down and store it securely! \ -

If you forget your passphrase, you will no longer be able to decrypt the hifikey file that the passphrase protects. \ -You will also no longer have access to the contents of your Wallet or My Purchases. \ -For security reasons, High Fidelity does not keep a copy of your passphrase, and can't restore it for you. \ -

If you still cannot remember your wallet passphrase, you can set up your Wallet again and generate a new hifikey file. \ -Unfortunately, this means you will start with 0 HFC and your purchased items will not be transferred over."; } ListElement { isExpanded: false; @@ -114,11 +73,9 @@ In your Wallet's Send Money tab, choose from your list of connections, or choose ListElement { isExpanded: false; question: "What is a Security Pic?" - answer: "Your Security Pic is an encrypted image that you select during Wallet Setup. \ -It acts as an extra layer of Wallet security. \ -When you see your Security Pic, you know that your actions and data are securely making use of your private keys.\ -

Don't enter your passphrase anywhere that doesn't display your Security Pic! \ -If you don't see your Security Pic on a page that requests your Wallet passphrase, someone untrustworthy may be trying to access your Wallet."; + answer: "Your Security Pic acts as an extra layer of Wallet security. \ +When you see your Security Pic, you know that your actions and data are securely making use of your account. \ +

Tap here to change your Security Pic."; } ListElement { isExpanded: false; @@ -260,6 +217,8 @@ At the moment, there is currently no way to convert HFC to other currencies. Sta } } else if (link === "#support") { Qt.openUrlExternally("mailto:support@highfidelity.com"); + } else if (link === "#securitypic") { + sendSignalToWallet({method: 'walletSecurity_changeSecurityImage'}); } } } diff --git a/interface/resources/qml/hifi/commerce/wallet/Security.qml b/interface/resources/qml/hifi/commerce/wallet/Security.qml index 216d621bf8..14ac696ef7 100644 --- a/interface/resources/qml/hifi/commerce/wallet/Security.qml +++ b/interface/resources/qml/hifi/commerce/wallet/Security.qml @@ -88,76 +88,9 @@ Item { color: hifi.colors.faintGray; } - Item { - id: changePassphraseContainer; - anchors.top: securityTextSeparator.bottom; - anchors.topMargin: 8; - anchors.left: parent.left; - anchors.leftMargin: 40; - anchors.right: parent.right; - anchors.rightMargin: 55; - height: 75; - - HiFiGlyphs { - id: changePassphraseImage; - text: hifi.glyphs.passphrase; - // Size - size: 80; - // Anchors - anchors.top: parent.top; - anchors.bottom: parent.bottom; - anchors.left: parent.left; - // Style - color: hifi.colors.white; - } - - RalewaySemiBold { - text: "Passphrase"; - // Anchors - anchors.top: parent.top; - anchors.bottom: parent.bottom; - anchors.left: changePassphraseImage.right; - anchors.leftMargin: 30; - width: 50; - // Text size - size: 18; - // Style - color: hifi.colors.white; - } - - // "Change Passphrase" button - HifiControlsUit.Button { - id: changePassphraseButton; - color: hifi.buttons.blue; - colorScheme: hifi.colorSchemes.dark; - anchors.right: parent.right; - anchors.verticalCenter: parent.verticalCenter; - width: 140; - height: 40; - text: "Change"; - onClicked: { - sendSignalToWallet({method: 'walletSecurity_changePassphrase'}); - } - } - } - - Rectangle { - id: changePassphraseSeparator; - // Size - width: parent.width; - height: 1; - // Anchors - anchors.left: parent.left; - anchors.right: parent.right; - anchors.top: changePassphraseContainer.bottom; - anchors.topMargin: 8; - // Style - color: hifi.colors.faintGray; - } - Item { id: changeSecurityImageContainer; - anchors.top: changePassphraseSeparator.bottom; + anchors.top: securityTextSeparator.bottom; anchors.topMargin: 8; anchors.left: parent.left; anchors.leftMargin: 40; @@ -208,139 +141,77 @@ Item { } } - Rectangle { - id: privateKeysSeparator; - // Size - width: parent.width; - height: 1; - // Anchors - anchors.left: parent.left; - anchors.right: parent.right; + Item { + id: autoLogoutContainer; anchors.top: changeSecurityImageContainer.bottom; anchors.topMargin: 8; - // Style - color: hifi.colors.faintGray; - } - - Item { - id: yourPrivateKeysContainer; - anchors.top: privateKeysSeparator.bottom; anchors.left: parent.left; anchors.leftMargin: 40; anchors.right: parent.right; anchors.rightMargin: 55; - anchors.bottom: parent.bottom; + height: 75; - onVisibleChanged: { - if (visible) { - Commerce.getKeyFilePathIfExists(); - } + HiFiGlyphs { + id: autoLogoutImage; + text: hifi.glyphs.walletKey; + // Size + size: 80; + // Anchors + anchors.top: parent.top; + anchors.topMargin: 20; + anchors.left: parent.left; + // Style + color: hifi.colors.white; } - HiFiGlyphs { - id: yourPrivateKeysImage; - text: hifi.glyphs.walletKey; - // Size - size: 80; + HifiControlsUit.CheckBox { + id: autoLogoutCheckbox; + checked: Settings.getValue("wallet/autoLogout", false); + text: "Automatically Log Out when Exiting Interface" // Anchors - anchors.top: parent.top; - anchors.topMargin: 20; - anchors.left: parent.left; - // Style + anchors.verticalCenter: autoLogoutImage.verticalCenter; + anchors.left: autoLogoutImage.right; + anchors.leftMargin: 20; + anchors.right: autoLogoutHelp.left; + anchors.rightMargin: 12; + boxSize: 28; + labelFontSize: 18; color: hifi.colors.white; + onCheckedChanged: { + Settings.setValue("wallet/autoLogout", checked); + if (checked) { + Settings.setValue("wallet/savedUsername", Account.username); + } else { + Settings.setValue("wallet/savedUsername", ""); + } + } } RalewaySemiBold { - id: yourPrivateKeysText; - text: "Private Keys"; - size: 18; + id: autoLogoutHelp; + text: '[?]'; // Anchors - anchors.top: parent.top; - anchors.topMargin: 32; - anchors.left: yourPrivateKeysImage.right; - anchors.leftMargin: 30; + anchors.verticalCenter: autoLogoutImage.verticalCenter; anchors.right: parent.right; + width: 30; height: 30; - // Style - color: hifi.colors.white; - } - - // Text below "private keys" - RalewayRegular { - id: explanitoryText; - text: "Your money and purchases are secured with private keys that only you have access to."; // Text size size: 18; - // Anchors - anchors.top: yourPrivateKeysText.bottom; - anchors.topMargin: 10; - anchors.left: yourPrivateKeysText.left; - anchors.right: yourPrivateKeysText.right; - height: paintedHeight; // Style - color: hifi.colors.white; - wrapMode: Text.WordWrap; - // Alignment - horizontalAlignment: Text.AlignLeft; - verticalAlignment: Text.AlignVCenter; - } + color: hifi.colors.blueHighlight; - Rectangle { - id: removeHmdContainer; - z: 998; - visible: false; - - gradient: Gradient { - GradientStop { - position: 0.2; - color: hifi.colors.baseGrayHighlight; - } - GradientStop { - position: 1.0; - color: hifi.colors.baseGrayShadow; - } - } - anchors.fill: backupInstructionsButton; - radius: 5; MouseArea { anchors.fill: parent; - propagateComposedEvents: false; hoverEnabled: true; - } - - RalewayBold { - anchors.fill: parent; - text: "INSTRUCTIONS OPEN ON DESKTOP"; - size: 15; - color: hifi.colors.white; - verticalAlignment: Text.AlignVCenter; - horizontalAlignment: Text.AlignHCenter; - } - - Timer { - id: removeHmdContainerTimer; - interval: 5000; - onTriggered: removeHmdContainer.visible = false + onEntered: { + parent.color = hifi.colors.blueAccent; + } + onExited: { + parent.color = hifi.colors.blueHighlight; + } + onClicked: { + sendSignalToWallet({method: 'walletSecurity_autoLogoutHelp'}); } - } - - HifiControlsUit.Button { - id: backupInstructionsButton; - text: "View Backup Instructions"; - color: hifi.buttons.blue; - colorScheme: hifi.colorSchemes.dark; - anchors.left: explanitoryText.left; - anchors.right: explanitoryText.right; - anchors.top: explanitoryText.bottom; - anchors.topMargin: 16; - height: 40; - - onClicked: { - var keyPath = "file:///" + root.keyFilePath.substring(0, root.keyFilePath.lastIndexOf('/')); - Qt.openUrlExternally(keyPath + "/backup_instructions.html"); - Qt.openUrlExternally(keyPath); - removeHmdContainer.visible = true; - removeHmdContainerTimer.start(); } } } diff --git a/interface/resources/qml/hifi/commerce/wallet/Wallet.qml b/interface/resources/qml/hifi/commerce/wallet/Wallet.qml index 603d7fb676..65d98af234 100644 --- a/interface/resources/qml/hifi/commerce/wallet/Wallet.qml +++ b/interface/resources/qml/hifi/commerce/wallet/Wallet.qml @@ -382,6 +382,17 @@ Rectangle { } else if (msg.method === 'walletSecurity_changeSecurityImage') { securityImageChange.initModel(); root.activeView = "securityImageChange"; + } else if (msg.method === 'walletSecurity_autoLogoutHelp') { + lightboxPopup.titleText = "Automatically Log Out"; + lightboxPopup.bodyText = "By default, after you log in to High Fidelity, you will stay logged in to your High Fidelity " + + "account even after you close and re-open Interface. This means anyone who opens Interface on your computer " + + "could make purchases with your Wallet.\n\n" + + "If you do not want to stay logged in across Interface sessions, check this box."; + lightboxPopup.button1text = "CLOSE"; + lightboxPopup.button1method = function() { + lightboxPopup.visible = false; + } + lightboxPopup.visible = true; } } } @@ -399,6 +410,9 @@ Rectangle { onSendSignalToWallet: { if (msg.method === 'walletReset' || msg.method === 'passphraseReset') { sendToScript(msg); + } else if (msg.method === 'walletSecurity_changeSecurityImage') { + securityImageChange.initModel(); + root.activeView = "securityImageChange"; } } } @@ -803,12 +817,24 @@ Rectangle { } function walletResetSetup() { + /* Bypass all this and do it automatically root.activeView = "walletSetup"; var timestamp = new Date(); walletSetup.startingTimestamp = timestamp; walletSetup.setupAttemptID = generateUUID(); UserActivityLogger.commerceWalletSetupStarted(timestamp, walletSetup.setupAttemptID, walletSetup.setupFlowVersion, walletSetup.referrer ? walletSetup.referrer : "wallet app", (AddressManager.placename || AddressManager.hostname || '') + (AddressManager.pathname ? AddressManager.pathname.match(/\/[^\/]+/)[0] : '')); + */ + + var randomNumber = Math.floor(Math.random() * 34) + 1; + var securityImagePath = "images/" + addLeadingZero(randomNumber) + ".jpg"; + Commerce.getWalletAuthenticatedStatus(); // before writing security image, ensures that salt/account password is set. + Commerce.chooseSecurityImage(securityImagePath); + Commerce.generateKeyPair(); + } + + function addLeadingZero(n) { + return n < 10 ? '0' + n : '' + n; } function followReferrer(msg) { diff --git a/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml b/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml index a0c6057b3b..50208793fe 100644 --- a/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml +++ b/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml @@ -142,7 +142,7 @@ Item { Timer { id: refreshTimer; - interval: 4000; + interval: 6000; onTriggered: { if (transactionHistory.atYBeginning) { console.log("Refreshing 1st Page of Recent Activity..."); @@ -211,6 +211,7 @@ Item { HifiModels.PSFListModel { id: transactionHistoryModel; + property int lastPendingCount: 0; listModelName: "transaction history"; // For debugging. Alternatively, we could specify endpoint for that purpose, even though it's not used directly. listView: transactionHistory; itemsPerPage: 6; @@ -221,8 +222,26 @@ Item { processPage: function (data) { console.debug('processPage', transactionHistoryModel.listModelName, JSON.stringify(data)); var result, pending; // Set up or get the accumulator for pending. - if (transactionHistoryModel.currentPageToRetrieve == 1) { - pending = {transaction_type: "pendingCount", count: 0}; + if (transactionHistoryModel.currentPageToRetrieve === 1) { + // The initial data elements inside the ListModel MUST contain all keys + // that will be used in future data. + pending = { + transaction_type: "pendingCount", + count: 0, + created_at: 0, + hfc_text: "", + id: "", + message: "", + place_name: "", + received_certs: 0, + received_money: 0, + recipient_name: "", + sender_name: "", + sent_certs: 0, + sent_money: 0, + status: "", + transaction_text: "" + }; result = [pending]; } else { pending = transactionHistoryModel.get(0); @@ -239,6 +258,15 @@ Item { } }); + if (lastPendingCount === 0) { + lastPendingCount = pending.count; + } else { + if (lastPendingCount !== pending.count) { + transactionHistoryModel.getNextPageIfNotEnoughVerticalResults(); + } + lastPendingCount = pending.count; + } + // Only auto-refresh if the user hasn't scrolled // and there is more data to grab if (transactionHistory.atYBeginning && data.history.length) { @@ -257,13 +285,13 @@ Item { ListView { id: transactionHistory; ScrollBar.vertical: ScrollBar { - policy: transactionHistory.contentHeight > parent.parent.height ? ScrollBar.AlwaysOn : ScrollBar.AsNeeded; - parent: transactionHistory.parent; - anchors.top: transactionHistory.top; - anchors.left: transactionHistory.right; - anchors.leftMargin: 4; - anchors.bottom: transactionHistory.bottom; - width: 20; + policy: transactionHistory.contentHeight > parent.parent.height ? ScrollBar.AlwaysOn : ScrollBar.AsNeeded; + parent: transactionHistory.parent; + anchors.top: transactionHistory.top; + anchors.left: transactionHistory.right; + anchors.leftMargin: 4; + anchors.bottom: transactionHistory.bottom; + width: 20; } anchors.centerIn: parent; width: parent.width - 12; @@ -340,7 +368,7 @@ Item { } HifiControlsUit.Separator { - colorScheme: 1; + colorScheme: 1; anchors.left: parent.left; anchors.right: parent.right; anchors.bottom: parent.bottom; diff --git a/interface/resources/qml/hifi/dialogs/AttachmentsDialog.qml b/interface/resources/qml/hifi/dialogs/AttachmentsDialog.qml deleted file mode 100644 index 006a4b7158..0000000000 --- a/interface/resources/qml/hifi/dialogs/AttachmentsDialog.qml +++ /dev/null @@ -1,48 +0,0 @@ -// -// AttachmentsDialog.qml -// -// Created by David Rowe on 9 Mar 2017. -// Copyright 2017 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html -// - -import QtQuick 2.5 -import Qt.labs.settings 1.0 - -import "../../styles-uit" -import "../../windows" -import "content" - -ScrollingWindow { - id: root - title: "Attachments" - objectName: "AttachmentsDialog" - width: 600 - height: 600 - resizable: true - destroyOnHidden: true - minSize: Qt.vector2d(400, 500) - - HifiConstants { id: hifi } - - // This is for JS/QML communication, which is unused in the AttachmentsDialog, - // but not having this here results in spurious warnings about a - // missing signal - signal sendToScript(var message); - - property var settings: Settings { - category: "AttachmentsDialog" - property alias x: root.x - property alias y: root.y - property alias width: root.width - property alias height: root.height - } - - function closeDialog() { - root.destroy(); - } - - AttachmentsContent { } -} diff --git a/interface/resources/qml/hifi/dialogs/AvatarAttachmentsDialog.qml b/interface/resources/qml/hifi/dialogs/AvatarAttachmentsDialog.qml deleted file mode 100644 index b4357a0077..0000000000 --- a/interface/resources/qml/hifi/dialogs/AvatarAttachmentsDialog.qml +++ /dev/null @@ -1,130 +0,0 @@ -import QtQuick 2.7 -import QtQuick.Controls 1.5 -import QtQuick.XmlListModel 2.0 -import QtQuick.Controls.Styles 1.4 - -import "../../windows" -import "../../js/Utils.js" as Utils -import "../models" - -ModalWindow { - id: root - resizable: true - width: 640 - height: 480 - - property var result; - - signal selected(var modelUrl); - signal canceled(); - - Rectangle { - anchors.fill: parent - color: "white" - - Item { - anchors { fill: parent; margins: 8 } - - TextField { - id: filterEdit - anchors { left: parent.left; right: parent.right; top: parent.top } - style: TextFieldStyle { renderType: Text.QtRendering } - placeholderText: "filter" - onTextChanged: tableView.model.filter = text - } - - TableView { - id: tableView - anchors { left: parent.left; right: parent.right; top: filterEdit.bottom; topMargin: 8; bottom: buttonRow.top; bottomMargin: 8 } - model: S3Model{} - onCurrentRowChanged: { - if (currentRow == -1) { - root.result = null; - return; - } - result = model.baseUrl + "/" + model.get(tableView.currentRow).key; - } - itemDelegate: Component { - Item { - clip: true - Text { - x: 3 - anchors.verticalCenter: parent.verticalCenter - color: tableView.activeFocus && styleData.row === tableView.currentRow ? "yellow" : styleData.textColor - elide: styleData.elideMode - text: getText() - - function getText() { - switch(styleData.column) { - case 1: - return Utils.formatSize(styleData.value) - default: - return styleData.value; - } - } - - } - } - } - TableViewColumn { - role: "name" - title: "Name" - width: 200 - } - TableViewColumn { - role: "size" - title: "Size" - width: 100 - } - TableViewColumn { - role: "modified" - title: "Last Modified" - width: 200 - } - Rectangle { - anchors.fill: parent - visible: tableView.model.status !== XmlListModel.Ready - color: "#7fffffff" - BusyIndicator { - anchors.centerIn: parent - width: 48; height: 48 - running: true - } - } - } - - Row { - id: buttonRow - anchors { right: parent.right; bottom: parent.bottom } - Button { action: acceptAction } - Button { action: cancelAction } - } - - Action { - id: acceptAction - text: qsTr("OK") - enabled: root.result ? true : false - shortcut: "Return" - onTriggered: { - root.selected(root.result); - root.destroy(); - } - } - - Action { - id: cancelAction - text: qsTr("Cancel") - shortcut: "Esc" - onTriggered: { - root.canceled(); - root.destroy(); - } - } - } - - } -} - - - - diff --git a/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml b/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml index 6bf8f8a5d5..0eeb252049 100644 --- a/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml +++ b/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml @@ -186,6 +186,8 @@ Rectangle { return; } + var grabbable = MenuInterface.isOptionChecked("Create Entities As Grabbable (except Zones, Particles, and Lights)"); + if (defaultURL.endsWith(".jpg") || defaultURL.endsWith(".png")) { var name = assetProxyModel.data(treeView.selection.currentIndex); var modelURL = "https://hifi-content.s3.amazonaws.com/DomainContent/production/default-image-model.fbx"; @@ -195,7 +197,7 @@ Rectangle { var collisionless = true; var position = Vec3.sum(MyAvatar.position, Vec3.multiply(2, Quat.getForward(MyAvatar.orientation))); var gravity = Vec3.multiply(Vec3.fromPolar(Math.PI / 2, 0), 0); - Entities.addModelEntity(name, modelURL, textures, shapeType, dynamic, collisionless, position, gravity); + Entities.addModelEntity(name, modelURL, textures, shapeType, dynamic, collisionless, grabbable, position, gravity); } else { var SHAPE_TYPE_NONE = 0; var SHAPE_TYPE_SIMPLE_HULL = 1; @@ -281,7 +283,7 @@ Rectangle { print("Asset browser - adding asset " + url + " (" + name + ") to world."); // Entities.addEntity doesn't work from QML, so we use this. - Entities.addModelEntity(name, url, "", shapeType, dynamic, collisionless, addPosition, gravity); + Entities.addModelEntity(name, url, "", shapeType, dynamic, collisionless, grabbable, addPosition, gravity); } } }); diff --git a/interface/resources/qml/hifi/dialogs/attachments/Attachment.qml b/interface/resources/qml/hifi/dialogs/attachments/Attachment.qml deleted file mode 100644 index 54270c2d06..0000000000 --- a/interface/resources/qml/hifi/dialogs/attachments/Attachment.qml +++ /dev/null @@ -1,230 +0,0 @@ -import QtQuick 2.5 - -import "." -import ".." -import "../../tablet" -import "../../../styles-uit" -import "../../../controls-uit" as HifiControls -import "../../../windows" - -Item { - height: column.height + 2 * 8 - - property var attachment; - - HifiConstants { id: hifi } - - signal selectAttachment(); - signal deleteAttachment(var attachment); - signal updateAttachment(); - property bool completed: false; - - function doSelectAttachment(control, focus) { - if (focus) { - selectAttachment(); - - // Refocus control after possibly changing focus to attachment. - if (control.setControlFocus !== undefined) { - control.setControlFocus(); - } else { - control.focus = true; - } - } - } - - Rectangle { color: hifi.colors.baseGray; anchors.fill: parent; radius: 4 } - - Component.onCompleted: { - jointChooser.model = MyAvatar.jointNames; - completed = true; - } - - Column { - y: 8 - id: column - anchors { left: parent.left; right: parent.right; margins: 20 } - spacing: 8 - - Item { - height: modelChooserButton.height + urlLabel.height + 4 - anchors { left: parent.left; right: parent.right;} - HifiControls.Label { id: urlLabel; color: hifi.colors.lightGrayText; text: "Model URL"; anchors.top: parent.top;} - HifiControls.TextField { - id: modelUrl; - height: jointChooser.height; - colorScheme: hifi.colorSchemes.dark - anchors { bottom: parent.bottom; left: parent.left; rightMargin: 8; right: modelChooserButton.left } - text: attachment ? attachment.modelUrl : "" - onTextChanged: { - if (completed && attachment && attachment.modelUrl !== text) { - attachment.modelUrl = text; - updateAttachment(); - } - } - onFocusChanged: doSelectAttachment(this, focus); - } - HifiControls.Button { - id: modelChooserButton; - text: "Choose"; - color: hifi.buttons.black - colorScheme: hifi.colorSchemes.dark - anchors { right: parent.right; verticalCenter: modelUrl.verticalCenter } - Component { - id: modelBrowserBuilder; - ModelBrowserDialog {} - } - Component { - id: tabletModelBrowserBuilder; - TabletModelBrowserDialog {} - } - - onClicked: { - var browser; - if (typeof desktop !== "undefined") { - browser = modelBrowserBuilder.createObject(desktop); - browser.selected.connect(function(newModelUrl){ - modelUrl.text = newModelUrl; - }); - } else { - browser = tabletModelBrowserBuilder.createObject(tabletRoot); - browser.selected.connect(function(newModelUrl){ - modelUrl.text = newModelUrl; - tabletRoot.openModal = null; - }); - browser.canceled.connect(function() { - tabletRoot.openModal = null; - }); - - // Make dialog modal. - tabletRoot.openModal = browser; - } - } - } - } - - Item { - z: 1000 - height: jointChooser.height + jointLabel.height + 4 - anchors { left: parent.left; right: parent.right; } - HifiControls.Label { - id: jointLabel; - text: "Joint"; - color: hifi.colors.lightGrayText; - anchors.top: parent.top - } - HifiControls.ComboBox { - id: jointChooser; - dropdownHeight: (typeof desktop !== "undefined") ? 480 : 206 - anchors { bottom: parent.bottom; left: parent.left; right: parent.right } - colorScheme: hifi.colorSchemes.dark - currentIndex: attachment ? model.indexOf(attachment.jointName) : -1 - onCurrentIndexChanged: { - if (completed && attachment && currentIndex != -1 && attachment.jointName !== model[currentIndex]) { - attachment.jointName = model[currentIndex]; - updateAttachment(); - } - } - onFocusChanged: doSelectAttachment(this, focus); - } - } - - Item { - height: translation.height + translationLabel.height + 4 - anchors { left: parent.left; right: parent.right; } - HifiControls.Label { id: translationLabel; color: hifi.colors.lightGrayText; text: "Translation"; anchors.top: parent.top; } - Translation { - id: translation; - anchors { left: parent.left; right: parent.right; bottom: parent.bottom} - vector: attachment ? attachment.translation : {x: 0, y: 0, z: 0}; - onValueChanged: { - if (completed && attachment) { - attachment.translation = vector; - updateAttachment(); - } - } - onControlFocusChanged: doSelectAttachment(this, controlFocus); - } - } - - Item { - height: rotation.height + rotationLabel.height + 4 - anchors { left: parent.left; right: parent.right; } - HifiControls.Label { id: rotationLabel; color: hifi.colors.lightGrayText; text: "Rotation"; anchors.top: parent.top; } - Rotation { - id: rotation; - anchors { left: parent.left; right: parent.right; bottom: parent.bottom; } - vector: attachment ? attachment.rotation : {x: 0, y: 0, z: 0}; - onValueChanged: { - if (completed && attachment) { - attachment.rotation = vector; - updateAttachment(); - } - } - onControlFocusChanged: doSelectAttachment(this, controlFocus); - } - } - - Item { - height: scaleItem.height - anchors { left: parent.left; right: parent.right; } - - Item { - id: scaleItem - height: scaleSpinner.height + scaleLabel.height + 4 - width: parent.width / 3 - 8 - anchors { right: parent.right; } - HifiControls.Label { id: scaleLabel; color: hifi.colors.lightGrayText; text: "Scale"; anchors.top: parent.top; } - HifiControls.SpinBox { - id: scaleSpinner; - anchors { left: parent.left; right: parent.right; bottom: parent.bottom; } - decimals: 2; - minimumValue: 0.01 - maximumValue: 10 - realStepSize: 0.05; - realValue: attachment ? attachment.scale : 1.0 - colorScheme: hifi.colorSchemes.dark - onRealValueChanged: { - if (completed && attachment && attachment.scale !== realValue) { - attachment.scale = realValue; - updateAttachment(); - } - } - onFocusChanged: doSelectAttachment(this, focus); - } - } - - Item { - id: isSoftItem - height: scaleSpinner.height - anchors { - left: parent.left - bottom: parent.bottom - } - HifiControls.CheckBox { - id: soft - text: "Is soft" - anchors { - left: parent.left - verticalCenter: parent.verticalCenter - } - checked: attachment ? attachment.soft : false - colorScheme: hifi.colorSchemes.dark - onCheckedChanged: { - if (completed && attachment && attachment.soft !== checked) { - attachment.soft = checked; - updateAttachment(); - } - } - onFocusChanged: doSelectAttachment(this, focus); - } - } - } - HifiControls.Button { - color: hifi.buttons.black - colorScheme: hifi.colorSchemes.dark - anchors { left: parent.left; right: parent.right; } - text: "Delete" - onClicked: deleteAttachment(root.attachment); - } - } -} diff --git a/interface/resources/qml/hifi/dialogs/attachments/Rotation.qml b/interface/resources/qml/hifi/dialogs/attachments/Rotation.qml deleted file mode 100644 index 6061efc4c8..0000000000 --- a/interface/resources/qml/hifi/dialogs/attachments/Rotation.qml +++ /dev/null @@ -1,9 +0,0 @@ -import "." - -Vector3 { - decimals: 1; - stepSize: 1; - maximumValue: 180 - minimumValue: -180 -} - diff --git a/interface/resources/qml/hifi/dialogs/attachments/Translation.qml b/interface/resources/qml/hifi/dialogs/attachments/Translation.qml deleted file mode 100644 index 39ac6da55a..0000000000 --- a/interface/resources/qml/hifi/dialogs/attachments/Translation.qml +++ /dev/null @@ -1,9 +0,0 @@ -import "." - -Vector3 { - decimals: 3; - stepSize: 0.01; - maximumValue: 10 - minimumValue: -10 -} - diff --git a/interface/resources/qml/hifi/dialogs/attachments/Vector3.qml b/interface/resources/qml/hifi/dialogs/attachments/Vector3.qml deleted file mode 100644 index 311492858b..0000000000 --- a/interface/resources/qml/hifi/dialogs/attachments/Vector3.qml +++ /dev/null @@ -1,112 +0,0 @@ -import QtQuick 2.5 - -import "../../../styles-uit" -import "../../../controls-uit" as HifiControls -import "../../../windows" - -Item { - id: root - implicitHeight: xspinner.height - readonly property real spacing: 8 - property real spinboxWidth: (width / 3) - spacing - property var vector; - property real decimals: 0 - property real stepSize: 1 - property real maximumValue: 99 - property real minimumValue: 0 - property bool controlFocus: false; // True if one of the ordinate controls has focus. - property var controlFocusControl: undefined - - signal valueChanged(); - - function setControlFocus() { - if (controlFocusControl) { - controlFocusControl.focus = true; - // The controlFocus value is updated via onFocusChanged. - } - } - - function setFocus(control, focus) { - if (focus) { - controlFocusControl = control; - setControlFocusTrue.start(); // After any subsequent false from previous control. - } else { - controlFocus = false; - } - } - - Timer { - id: setControlFocusTrue - interval: 50 - repeat: false - running: false - onTriggered: { - controlFocus = true; - } - } - - HifiConstants { id: hifi } - - HifiControls.SpinBox { - id: xspinner - width: root.spinboxWidth - anchors { left: parent.left } - realValue: root.vector.x - labelInside: "X:" - colorScheme: hifi.colorSchemes.dark - colorLabelInside: hifi.colors.redHighlight - decimals: root.decimals - realStepSize: root.stepSize - maximumValue: root.maximumValue - minimumValue: root.minimumValue - onRealValueChanged: { - if (realValue !== vector.x) { - vector.x = realValue - root.valueChanged(); - } - } - onFocusChanged: setFocus(this, focus); - } - - HifiControls.SpinBox { - id: yspinner - width: root.spinboxWidth - anchors { horizontalCenter: parent.horizontalCenter } - realValue: root.vector.y - labelInside: "Y:" - colorLabelInside: hifi.colors.greenHighlight - colorScheme: hifi.colorSchemes.dark - decimals: root.decimals - realStepSize: root.stepSize - maximumValue: root.maximumValue - minimumValue: root.minimumValue - onRealValueChanged: { - if (realValue !== vector.y) { - vector.y = realValue - root.valueChanged(); - } - } - onFocusChanged: setFocus(this, focus); - } - - HifiControls.SpinBox { - id: zspinner - width: root.spinboxWidth - anchors { right: parent.right; } - realValue: root.vector.z - labelInside: "Z:" - colorLabelInside: hifi.colors.primaryHighlight - colorScheme: hifi.colorSchemes.dark - decimals: root.decimals - realStepSize: root.stepSize - maximumValue: root.maximumValue - minimumValue: root.minimumValue - onRealValueChanged: { - if (realValue !== vector.z) { - vector.z = realValue - root.valueChanged(); - } - } - onFocusChanged: setFocus(this, focus); - } -} diff --git a/interface/resources/qml/hifi/dialogs/content/AttachmentsContent.qml b/interface/resources/qml/hifi/dialogs/content/AttachmentsContent.qml deleted file mode 100644 index fa71116574..0000000000 --- a/interface/resources/qml/hifi/dialogs/content/AttachmentsContent.qml +++ /dev/null @@ -1,282 +0,0 @@ -import QtQuick 2.7 -import QtQuick.Controls 2.3 -import QtQuick.Dialogs 1.2 as OriginalDialogs - -import "../../../styles-uit" -import "../../../controls-uit" as HifiControls -import "../../../windows" -import "../attachments" - -Item { - id: content - - readonly property var originalAttachments: MyAvatar.getAttachmentsVariant(); - property var attachments: []; - - function reload(){ - content.attachments = []; - var currentAttachments = MyAvatar.getAttachmentsVariant(); - listView.model.clear(); - for (var i = 0; i < currentAttachments.length; ++i) { - var attachment = currentAttachments[i]; - content.attachments.push(attachment); - listView.model.append({}); - } - } - - Connections { - id: onAttachmentsChangedConnection - target: MyAvatar - onAttachmentsChanged: reload() - } - - Component.onCompleted: { - reload() - } - - function setAttachmentsVariant(attachments) { - onAttachmentsChangedConnection.enabled = false; - MyAvatar.setAttachmentsVariant(attachments); - onAttachmentsChangedConnection.enabled = true; - } - - Column { - width: pane.width - - Rectangle { - width: parent.width - height: root.height - (keyboardEnabled && keyboardRaised ? 200 : 0) - color: hifi.colors.baseGray - - Rectangle { - id: attachmentsBackground - anchors { - left: parent.left; right: parent.right; top: parent.top; bottom: newAttachmentButton.top; - margins: hifi.dimensions.contentMargin.x - bottomMargin: hifi.dimensions.contentSpacing.y - } - color: hifi.colors.baseGrayShadow - radius: 4 - - ListView { - id: listView - anchors { - top: parent.top - left: parent.left - right: scrollBar.left - bottom: parent.bottom - margins: 4 - } - clip: true - cacheBuffer: 4000 - - model: ListModel {} - delegate: Item { - id: attachmentDelegate - implicitHeight: attachmentView.height + 8; - implicitWidth: attachmentView.width - - MouseArea { - // User can click on whitespace to select item. - anchors.fill: parent - propagateComposedEvents: true - onClicked: { - listView.currentIndex = index; - attachmentsBackground.forceActiveFocus(); // Unfocus from any control. - mouse.accepted = false; - } - } - - Attachment { - id: attachmentView - width: listView.width - attachment: content.attachments[index] - onSelectAttachment: { - listView.currentIndex = index; - } - onDeleteAttachment: { - attachments.splice(index, 1); - listView.model.remove(index, 1); - } - onUpdateAttachment: { - setAttachmentsVariant(attachments); - } - } - } - - onCountChanged: { - setAttachmentsVariant(attachments); - } - - /* - // DEBUG - highlight: Rectangle { color: "#40ffff00" } - highlightFollowsCurrentItem: true - */ - - onHeightChanged: { - // Keyboard has been raised / lowered. - positionViewAtIndex(listView.currentIndex, ListView.SnapPosition); - } - - onCurrentIndexChanged: { - if (!yScrollTimer.running) { - scrollSlider.y = currentIndex * (scrollBar.height - scrollSlider.height) / (listView.count - 1); - } - } - - onContentYChanged: { - // User may have dragged content up/down. - yScrollTimer.restart(); - } - - Timer { - id: yScrollTimer - interval: 200 - repeat: false - running: false - onTriggered: { - var index = (listView.count - 1) * listView.contentY / (listView.contentHeight - scrollBar.height); - index = Math.round(index); - listView.currentIndex = index; - scrollSlider.y = index * (scrollBar.height - scrollSlider.height) / (listView.count - 1); - } - } - } - - Rectangle { - id: scrollBar - - property bool scrolling: listView.contentHeight > listView.height - - anchors { - top: parent.top - right: parent.right - bottom: parent.bottom - topMargin: 4 - bottomMargin: 4 - } - width: scrolling ? 18 : 0 - radius: attachmentsBackground.radius - color: hifi.colors.baseGrayShadow - - MouseArea { - anchors.fill: parent - - onClicked: { - var index = listView.currentIndex; - index = index + (mouse.y <= scrollSlider.y ? -1 : 1); - if (index < 0) { - index = 0; - } - if (index > listView.count - 1) { - index = listView.count - 1; - } - listView.currentIndex = index; - } - } - - Rectangle { - id: scrollSlider - anchors { - right: parent.right - rightMargin: 3 - } - width: 16 - height: (listView.height / listView.contentHeight) * listView.height - radius: width / 2 - color: hifi.colors.lightGray - - visible: scrollBar.scrolling; - - onYChanged: { - var index = y * (listView.count - 1) / (scrollBar.height - scrollSlider.height); - index = Math.round(index); - listView.currentIndex = index; - } - - MouseArea { - anchors.fill: parent - drag.target: scrollSlider - drag.axis: Drag.YAxis - drag.minimumY: 0 - drag.maximumY: scrollBar.height - scrollSlider.height - } - } - } - } - - HifiControls.Button { - id: newAttachmentButton - anchors { - left: parent.left - right: parent.right - bottom: buttonRow.top - margins: hifi.dimensions.contentMargin.x; - topMargin: hifi.dimensions.contentSpacing.y - bottomMargin: hifi.dimensions.contentSpacing.y - } - text: "New Attachment" - color: hifi.buttons.black - colorScheme: hifi.colorSchemes.dark - onClicked: { - var template = { - modelUrl: "", - translation: { x: 0, y: 0, z: 0 }, - rotation: { x: 0, y: 0, z: 0 }, - scale: 1, - jointName: MyAvatar.jointNames[0], - soft: false - }; - attachments.push(template); - listView.model.append({}); - setAttachmentsVariant(attachments); - } - } - - Row { - id: buttonRow - spacing: 8 - anchors { - right: parent.right - bottom: parent.bottom - margins: hifi.dimensions.contentMargin.x - topMargin: hifi.dimensions.contentSpacing.y - bottomMargin: hifi.dimensions.contentSpacing.y - } - HifiControls.Button { - action: okAction - color: hifi.buttons.black - colorScheme: hifi.colorSchemes.dark - } - HifiControls.Button { - action: cancelAction - color: hifi.buttons.black - colorScheme: hifi.colorSchemes.dark - } - } - - Action { - id: cancelAction - text: "Cancel" - onTriggered: { - setAttachmentsVariant(originalAttachments); - closeDialog(); - } - } - - Action { - id: okAction - text: "OK" - onTriggered: { - for (var i = 0; i < attachments.length; ++i) { - console.log("Attachment " + i + ": " + attachments[i]); - } - - setAttachmentsVariant(attachments); - closeDialog(); - } - } - } - } -} diff --git a/interface/resources/qml/hifi/models/PSFListModel.qml b/interface/resources/qml/hifi/models/PSFListModel.qml index 988502dd91..71b253fb27 100644 --- a/interface/resources/qml/hifi/models/PSFListModel.qml +++ b/interface/resources/qml/hifi/models/PSFListModel.qml @@ -38,6 +38,16 @@ ListModel { onSearchFilterChanged: if (initialized) { getFirstPage('delayClear'); } onTagsFilterChanged: if (initialized) { getFirstPage('delayClear'); } + // When considering a value for `itemsPerPage` in YOUR model, consider the following: + // - If your ListView delegates are of variable width/height, ensure you select + // an `itemsPerPage` value that would be sufficient to show one full page of data + // if all of the delegates were at their minimum heights. + // - If your first ListView delegate contains some special data (as in WalletHome's + // "Recent Activity" view), beware that your `itemsPerPage` value may _never_ reasonably be + // high enough such that the first page of data causes the view to be one-screen in height + // after retrieving the first page. This means data will automatically pop-in (after a short delay) + // until the combined heights of your View's delegates reach one-screen in height OR there is + // no more data to retrieve. See "needsMoreVerticalResults()" below. property int itemsPerPage: 100; // State. @@ -47,9 +57,9 @@ ListModel { // Not normally set directly, but rather by giving a truthy argument to getFirstPage(true); property bool delayedClear: false; function resetModel() { - if (!delayedClear) { root.clear(); } currentPageToRetrieve = 1; retrievedAtLeastOnePage = false; + if (!delayedClear) { root.clear(); } totalPages = 0; totalEntries = 0; } @@ -81,19 +91,44 @@ ListModel { function getNextPageIfVerticalScroll() { if (needsEarlyYFetch()) { getNextPage(); } } + function needsMoreHorizontalResults() { + return flickable + && currentPageToRetrieve > 0 + && retrievedAtLeastOnePage + && flickable.contentWidth < flickable.width; + } + function needsMoreVerticalResults() { + return flickable + && currentPageToRetrieve > 0 + && retrievedAtLeastOnePage + && flickable.contentHeight < flickable.height; + } + function getNextPageIfNotEnoughHorizontalResults() { + if (needsMoreHorizontalResults()) { + getNextPage(); + } + } + function getNextPageIfNotEnoughVerticalResults() { + if (needsMoreVerticalResults()) { + getNextPage(); + } + } + Component.onCompleted: { initialized = true; if (flickable && pageAhead > 0.0) { // Pun: Scrollers are usually one direction or another, such that only one of the following will actually fire. flickable.contentXChanged.connect(getNextPageIfHorizontalScroll); flickable.contentYChanged.connect(getNextPageIfVerticalScroll); + flickable.contentWidthChanged.connect(getNextPageIfNotEnoughHorizontalResults); + flickable.contentHeightChanged.connect(getNextPageIfNotEnoughVerticalResults); } } property int totalPages: 0; property int totalEntries: 0; // Check consistency and call processPage. - function handlePage(error, response) { + function handlePage(error, response, cb) { var processed; console.debug('handlePage', listModelName, additionalFirstPageRequested, error, JSON.stringify(response)); function fail(message) { @@ -134,7 +169,9 @@ ListModel { if (additionalFirstPageRequested) { console.debug('deferred getFirstPage', listModelName); additionalFirstPageRequested = false; - getFirstPage('delayedClear'); + getFirstPage('delayedClear', cb); + } else if (cb) { + cb(); } } function debugView(label) { @@ -147,7 +184,7 @@ ListModel { // Override either http or getPage. property var http; // An Item that has a request function. - property var getPage: function () { // Any override MUST call handlePage(), above, even if results empty. + property var getPage: function (cb) { // Any override MUST call handlePage(), above, even if results empty. if (!http) { return console.warn("Neither http nor getPage was set for", listModelName); } // If it is a path starting with slash, add the metaverseServer domain. var url = /^\//.test(endpoint) ? (Account.metaverseServerURL + endpoint) : endpoint; @@ -165,12 +202,12 @@ ListModel { var parametersSeparator = /\?/.test(url) ? '&' : '?'; url = url + parametersSeparator + parameters.join('&'); console.debug('getPage', listModelName, currentPageToRetrieve); - http.request({uri: url}, handlePage); + http.request({uri: url}, cb ? function (error, result) { handlePage(error, result, cb); } : handlePage); } // Start the show by retrieving data according to `getPage()`. // It can be custom-defined by this item's Parent. - property var getFirstPage: function (delayClear) { + property var getFirstPage: function (delayClear, cb) { if (requestPending) { console.debug('deferring getFirstPage', listModelName); additionalFirstPageRequested = true; @@ -180,7 +217,7 @@ ListModel { resetModel(); requestPending = true; console.debug("getFirstPage", listModelName, currentPageToRetrieve); - getPage(); + getPage(cb); } property bool additionalFirstPageRequested: false; property bool requestPending: false; // For de-bouncing getNextPage. diff --git a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml index 9a472de046..c9d05aea51 100644 --- a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml +++ b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml @@ -320,11 +320,12 @@ StackView { width: parent.width; cardWidth: 312 + (2 * 4); cardHeight: 163 + (2 * 4); - labelText: 'HAPPENING NOW'; + labelText: 'FEATURED'; actions: 'announcement'; filter: addressLine.text; goFunction: goCard; http: http; + autoScrollTimerEnabled: true; } Feed { id: places; diff --git a/interface/resources/qml/hifi/tablet/TabletAttachmentsDialog.qml b/interface/resources/qml/hifi/tablet/TabletAttachmentsDialog.qml deleted file mode 100644 index 7e1cdea0db..0000000000 --- a/interface/resources/qml/hifi/tablet/TabletAttachmentsDialog.qml +++ /dev/null @@ -1,103 +0,0 @@ -// -// TabletAttachmentsDialog.qml -// -// Created by David Rowe on 9 Mar 2017. -// Copyright 2017 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html -// - -import QtQuick 2.5 - -import "../../controls-uit" as HifiControls -import "../../styles-uit" -import "../dialogs/content" - -Item { - id: root - objectName: "AttachmentsDialog" - - property string title: "Avatar Attachments" - - property bool keyboardEnabled: false - property bool keyboardRaised: false - property bool punctuationMode: false - - signal sendToScript(var message); - - anchors.fill: parent - - HifiConstants { id: hifi } - - Rectangle { - id: pane // Surrogate for ScrollingWindow's pane. - anchors.fill: parent - } - - function closeDialog() { - Tablet.getTablet("com.highfidelity.interface.tablet.system").gotoHomeScreen(); - } - - anchors.topMargin: hifi.dimensions.tabletMenuHeader // Space for header. - - HifiControls.TabletHeader { - id: header - title: root.title - - anchors { - left: parent.left - right: parent.right - bottom: parent.top - } - } - - AttachmentsContent { - id: attachments - - anchors { - top: header.bottom - left: parent.left - right: parent.right - bottom: keyboard.top - } - - MouseArea { - // Defocuses any current control so that the keyboard gets hidden. - id: defocuser - anchors.fill: parent - propagateComposedEvents: true - acceptedButtons: Qt.AllButtons - onPressed: { - parent.forceActiveFocus(); - mouse.accepted = false; - } - } - } - - HifiControls.Keyboard { - id: keyboard - raised: parent.keyboardEnabled && parent.keyboardRaised - numeric: parent.punctuationMode - anchors { - left: parent.left - right: parent.right - bottom: parent.bottom - } - } - - MouseArea { - id: activator - anchors.fill: parent - propagateComposedEvents: true - enabled: true - acceptedButtons: Qt.AllButtons - onPressed: { - mouse.accepted = false; - } - } - - Component.onCompleted: { - keyboardEnabled = HMD.active; - } -} diff --git a/interface/src/AboutUtil.cpp b/interface/src/AboutUtil.cpp index 634e52b481..56cabce03d 100644 --- a/interface/src/AboutUtil.cpp +++ b/interface/src/AboutUtil.cpp @@ -45,9 +45,7 @@ QString AboutUtil::getQtVersion() const { } void AboutUtil::openUrl(const QString& url) const { - - auto tabletScriptingInterface = DependencyManager::get(); - auto tablet = tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system"); + auto tablet = DependencyManager::get()->getTablet("com.highfidelity.interface.tablet.system"); auto hmd = DependencyManager::get(); auto offscreenUi = DependencyManager::get(); diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 0da3d9445e..fe17de64f2 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -63,6 +63,7 @@ #include #include #include +#include #include #include #include @@ -98,6 +99,8 @@ #include #include #include +#include +#include #include #include #include @@ -127,7 +130,7 @@ #include #include #include -#include +#include #include #include #include @@ -143,6 +146,7 @@ #include #include #include +#include #include #include #include @@ -262,19 +266,10 @@ class RenderEventHandler : public QObject { using Parent = QObject; Q_OBJECT public: - RenderEventHandler(QOpenGLContext* context) { - _renderContext = new OffscreenGLCanvas(); - _renderContext->setObjectName("RenderContext"); - _renderContext->create(context); - if (!_renderContext->makeCurrent()) { - qFatal("Unable to make rendering context current"); - } - _renderContext->doneCurrent(); - + RenderEventHandler() { // Transfer to a new thread moveToNewNamedThread(this, "RenderThread", [this](QThread* renderThread) { hifi::qt::addBlockingForbiddenThread("Render", renderThread); - _renderContext->moveToThreadWithContext(renderThread); qApp->_lastTimeRendered.start(); }, std::bind(&RenderEventHandler::initialize, this), QThread::HighestPriority); } @@ -284,9 +279,6 @@ private: setObjectName("Render"); PROFILE_SET_THREAD_NAME("Render"); setCrashAnnotation("render_thread_id", std::to_string((size_t)QThread::currentThreadId())); - if (!_renderContext->makeCurrent()) { - qFatal("Unable to make rendering context current on render thread"); - } } void render() { @@ -307,8 +299,6 @@ private: } return Parent::event(event); } - - OffscreenGLCanvas* _renderContext { nullptr }; }; @@ -374,6 +364,7 @@ static const int THROTTLED_SIM_FRAME_PERIOD_MS = MSECS_PER_SECOND / THROTTLED_SI static const uint32_t INVALID_FRAME = UINT32_MAX; static const float PHYSICS_READY_RANGE = 3.0f; // how far from avatar to check for entities that aren't ready for simulation +static const float INITIAL_QUERY_RADIUS = 10.0f; // priority radius for entities before physics enabled static const QString DESKTOP_LOCATION = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation); @@ -384,6 +375,7 @@ static const int INTERVAL_TO_CHECK_HMD_WORN_STATUS = 500; // milliseconds static const QString DESKTOP_DISPLAY_PLUGIN_NAME = "Desktop"; static const QString ACTIVE_DISPLAY_PLUGIN_SETTING_NAME = "activeDisplayPlugin"; static const QString SYSTEM_TABLET = "com.highfidelity.interface.tablet.system"; +static const QString AUTO_LOGOUT_SETTING_NAME = "wallet/autoLogout"; const std::vector> Application::_acceptedExtensions { { SVO_EXTENSION, &Application::importSVOFromURL }, @@ -734,6 +726,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(); @@ -866,16 +861,20 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) { DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); + DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); + DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); + DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); + DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); @@ -913,7 +912,8 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) { DependencyManager::set(); controller::StateController::setStateVariables({ { STATE_IN_HMD, STATE_CAMERA_FULL_SCREEN_MIRROR, STATE_CAMERA_FIRST_PERSON, STATE_CAMERA_THIRD_PERSON, STATE_CAMERA_ENTITY, STATE_CAMERA_INDEPENDENT, - STATE_SNAP_TURN, STATE_ADVANCED_MOVEMENT_CONTROLS, STATE_GROUNDED, STATE_NAV_FOCUSED } }); + STATE_SNAP_TURN, STATE_ADVANCED_MOVEMENT_CONTROLS, STATE_GROUNDED, STATE_NAV_FOCUSED, + STATE_PLATFORM_WINDOWS, STATE_PLATFORM_MAC, STATE_PLATFORM_ANDROID } }); DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); @@ -1018,7 +1018,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo // This is done so as not break previous command line scripts if (testScriptPath.left(URL_SCHEME_HTTP.length()) == URL_SCHEME_HTTP || testScriptPath.left(URL_SCHEME_FTP.length()) == URL_SCHEME_FTP) { - + setProperty(hifi::properties::TEST, QUrl::fromUserInput(testScriptPath)); } else if (QFileInfo(testScriptPath).exists()) { setProperty(hifi::properties::TEST, QUrl::fromLocalFile(testScriptPath)); @@ -1099,6 +1099,10 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo // Set File Logger Session UUID auto avatarManager = DependencyManager::get(); auto myAvatar = avatarManager ? avatarManager->getMyAvatar() : nullptr; + if (avatarManager) { + workload::SpacePointer space = getEntities()->getWorkloadSpace(); + avatarManager->setSpace(space); + } auto accountManager = DependencyManager::get(); _logger->setSessionID(accountManager->getSessionID()); @@ -1130,8 +1134,10 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo // setup a timer for domain-server check ins QTimer* domainCheckInTimer = new QTimer(this); - connect(domainCheckInTimer, &QTimer::timeout, [this, nodeList] { - if (!isServerlessMode()) { + QWeakPointer nodeListWeak = nodeList; + connect(domainCheckInTimer, &QTimer::timeout, [this, nodeListWeak] { + auto nodeList = nodeListWeak.lock(); + if (!isServerlessMode() && nodeList) { nodeList->sendDomainServerCheckIn(); } }); @@ -1141,33 +1147,34 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo domainCheckInTimer->deleteLater(); }); + { + auto audioIO = DependencyManager::get().data(); + audioIO->setPositionGetter([] { + auto avatarManager = DependencyManager::get(); + auto myAvatar = avatarManager ? avatarManager->getMyAvatar() : nullptr; - auto audioIO = DependencyManager::get(); - audioIO->setPositionGetter([]{ - auto avatarManager = DependencyManager::get(); - auto myAvatar = avatarManager ? avatarManager->getMyAvatar() : nullptr; + return myAvatar ? myAvatar->getPositionForAudio() : Vectors::ZERO; + }); + audioIO->setOrientationGetter([] { + auto avatarManager = DependencyManager::get(); + auto myAvatar = avatarManager ? avatarManager->getMyAvatar() : nullptr; - return myAvatar ? myAvatar->getPositionForAudio() : Vectors::ZERO; - }); - audioIO->setOrientationGetter([]{ - auto avatarManager = DependencyManager::get(); - auto myAvatar = avatarManager ? avatarManager->getMyAvatar() : nullptr; + return myAvatar ? myAvatar->getOrientationForAudio() : Quaternions::IDENTITY; + }); - return myAvatar ? myAvatar->getOrientationForAudio() : Quaternions::IDENTITY; - }); + recording::Frame::registerFrameHandler(AudioConstants::getAudioFrameName(), [&audioIO](recording::Frame::ConstPointer frame) { + audioIO->handleRecordedAudioInput(frame->data); + }); - recording::Frame::registerFrameHandler(AudioConstants::getAudioFrameName(), [=](recording::Frame::ConstPointer frame) { - audioIO->handleRecordedAudioInput(frame->data); - }); - - connect(audioIO.data(), &AudioClient::inputReceived, [](const QByteArray& audio){ - static auto recorder = DependencyManager::get(); - if (recorder->isRecording()) { - static const recording::FrameType AUDIO_FRAME_TYPE = recording::Frame::registerFrameType(AudioConstants::getAudioFrameName()); - recorder->recordFrame(AUDIO_FRAME_TYPE, audio); - } - }); - audioIO->startThread(); + connect(audioIO, &AudioClient::inputReceived, [](const QByteArray& audio) { + static auto recorder = DependencyManager::get(); + if (recorder->isRecording()) { + static const recording::FrameType AUDIO_FRAME_TYPE = recording::Frame::registerFrameType(AudioConstants::getAudioFrameName()); + recorder->recordFrame(AUDIO_FRAME_TYPE, audio); + } + }); + audioIO->startThread(); + } // Make sure we don't time out during slow operations at startup updateHeartbeat(); @@ -1238,7 +1245,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo accountManager->setAuthURL(NetworkingConstants::METAVERSE_SERVER_URL()); // use our MyAvatar position and quat for address manager path - addressManager->setPositionGetter([this]{ return getMyAvatar()->getWorldPosition(); }); + addressManager->setPositionGetter([this]{ return getMyAvatar()->getWorldFeetPosition(); }); addressManager->setOrientationGetter([this]{ return getMyAvatar()->getWorldOrientation(); }); connect(addressManager.data(), &AddressManager::hostChanged, this, &Application::updateWindowTitle); @@ -1266,27 +1273,29 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo // Inititalize sample before registering _sampleSound = DependencyManager::get()->getSound(PathUtils::resourcesUrl("sounds/sample.wav")); - auto scriptEngines = DependencyManager::get().data(); - scriptEngines->registerScriptInitializer([this](ScriptEnginePointer engine){ - registerScriptEngineWithApplicationServices(engine); - }); + { + auto scriptEngines = DependencyManager::get().data(); + scriptEngines->registerScriptInitializer([this](ScriptEnginePointer engine) { + registerScriptEngineWithApplicationServices(engine); + }); - connect(scriptEngines, &ScriptEngines::scriptCountChanged, scriptEngines, [this] { - auto scriptEngines = DependencyManager::get(); - if (scriptEngines->getRunningScripts().isEmpty()) { - getMyAvatar()->clearScriptableSettings(); - } - }, Qt::QueuedConnection); + connect(scriptEngines, &ScriptEngines::scriptCountChanged, this, [this] { + auto scriptEngines = DependencyManager::get(); + if (scriptEngines->getRunningScripts().isEmpty()) { + getMyAvatar()->clearScriptableSettings(); + } + }, Qt::QueuedConnection); - connect(scriptEngines, &ScriptEngines::scriptsReloading, scriptEngines, [this] { - getEntities()->reloadEntityScripts(); - loadAvatarScripts(getMyAvatar()->getScriptUrls()); - }, Qt::QueuedConnection); + connect(scriptEngines, &ScriptEngines::scriptsReloading, this, [this] { + getEntities()->reloadEntityScripts(); + loadAvatarScripts(getMyAvatar()->getScriptUrls()); + }, Qt::QueuedConnection); - connect(scriptEngines, &ScriptEngines::scriptLoadError, - scriptEngines, [](const QString& filename, const QString& error){ - OffscreenUi::asyncWarning(nullptr, "Error Loading Script", filename + " failed to load."); - }, Qt::QueuedConnection); + connect(scriptEngines, &ScriptEngines::scriptLoadError, + this, [](const QString& filename, const QString& error) { + OffscreenUi::asyncWarning(nullptr, "Error Loading Script", filename + " failed to load."); + }, Qt::QueuedConnection); + } #ifdef _WIN32 WSADATA WsaData; @@ -1356,10 +1365,11 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo // so we defer the setup of the `scripting::Audio` class until this point { auto audioScriptingInterface = DependencyManager::set(); - connect(audioIO.data(), &AudioClient::mutedByMixer, audioScriptingInterface.data(), &AudioScriptingInterface::mutedByMixer); - connect(audioIO.data(), &AudioClient::receivedFirstPacket, audioScriptingInterface.data(), &AudioScriptingInterface::receivedFirstPacket); - connect(audioIO.data(), &AudioClient::disconnected, audioScriptingInterface.data(), &AudioScriptingInterface::disconnected); - connect(audioIO.data(), &AudioClient::muteEnvironmentRequested, [](glm::vec3 position, float radius) { + auto audioIO = DependencyManager::get().data(); + connect(audioIO, &AudioClient::mutedByMixer, audioScriptingInterface.data(), &AudioScriptingInterface::mutedByMixer); + connect(audioIO, &AudioClient::receivedFirstPacket, audioScriptingInterface.data(), &AudioScriptingInterface::receivedFirstPacket); + connect(audioIO, &AudioClient::disconnected, audioScriptingInterface.data(), &AudioScriptingInterface::disconnected); + connect(audioIO, &AudioClient::muteEnvironmentRequested, [](glm::vec3 position, float radius) { auto audioClient = DependencyManager::get(); auto audioScriptingInterface = DependencyManager::get(); auto myAvatarPosition = DependencyManager::get()->getMyAvatar()->getWorldPosition(); @@ -1677,6 +1687,27 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo _applicationStateDevice->setInputVariant(STATE_NAV_FOCUSED, []() -> float { return DependencyManager::get()->navigationFocused() ? 1 : 0; }); + _applicationStateDevice->setInputVariant(STATE_PLATFORM_WINDOWS, []() -> float { +#if defined(Q_OS_WIN) + return 1; +#else + return 0; +#endif + }); + _applicationStateDevice->setInputVariant(STATE_PLATFORM_MAC, []() -> float { +#if defined(Q_OS_MAC) + return 1; +#else + return 0; +#endif + }); + _applicationStateDevice->setInputVariant(STATE_PLATFORM_ANDROID, []() -> float { +#if defined(Q_OS_ANDROID) + return 1; +#else + return 0; +#endif + }); // Setup the _keyboardMouseDevice, _touchscreenDevice, _touchscreenVirtualPadDevice and the user input mapper with the default bindings userInputMapper->registerDevice(_keyboardMouseDevice->getInputDevice()); @@ -1688,23 +1719,26 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo userInputMapper->registerDevice(_touchscreenVirtualPadDevice->getInputDevice()); } - // this will force the model the look at the correct directory (weird order of operations issue) - scriptEngines->reloadLocalFiles(); + { + auto scriptEngines = DependencyManager::get().data(); + // this will force the model the look at the correct directory (weird order of operations issue) + scriptEngines->reloadLocalFiles(); - // do this as late as possible so that all required subsystems are initialized - // If we've overridden the default scripts location, just load default scripts - // otherwise, load 'em all + // do this as late as possible so that all required subsystems are initialized + // If we've overridden the default scripts location, just load default scripts + // otherwise, load 'em all - // we just want to see if --scripts was set, we've already parsed it and done - // the change in PathUtils. Rather than pass that in the constructor, lets just - // look (this could be debated) - QString scriptsSwitch = QString("--").append(SCRIPTS_SWITCH); - QDir defaultScriptsLocation(getCmdOption(argc, constArgv, scriptsSwitch.toStdString().c_str())); - if (!defaultScriptsLocation.exists()) { - scriptEngines->loadDefaultScripts(); - scriptEngines->defaultScriptsLocationOverridden(true); - } else { - scriptEngines->loadScripts(); + // we just want to see if --scripts was set, we've already parsed it and done + // the change in PathUtils. Rather than pass that in the constructor, lets just + // look (this could be debated) + QString scriptsSwitch = QString("--").append(SCRIPTS_SWITCH); + QDir defaultScriptsLocation(getCmdOption(argc, constArgv, scriptsSwitch.toStdString().c_str())); + if (!defaultScriptsLocation.exists()) { + scriptEngines->loadDefaultScripts(); + scriptEngines->defaultScriptsLocationOverridden(true); + } else { + scriptEngines->loadScripts(); + } } // Make sure we don't time out during slow operations at startup @@ -1754,13 +1788,16 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo cameraMenuChanged(); } - // set the local loopback interface for local sounds - AudioInjector::setLocalAudioInterface(audioIO.data()); - auto audioScriptingInterface = DependencyManager::get(); - audioScriptingInterface->setLocalAudioInterface(audioIO.data()); - connect(audioIO.data(), &AudioClient::noiseGateOpened, audioScriptingInterface.data(), &AudioScriptingInterface::noiseGateOpened); - connect(audioIO.data(), &AudioClient::noiseGateClosed, audioScriptingInterface.data(), &AudioScriptingInterface::noiseGateClosed); - connect(audioIO.data(), &AudioClient::inputReceived, audioScriptingInterface.data(), &AudioScriptingInterface::inputReceived); + { + auto audioIO = DependencyManager::get().data(); + // set the local loopback interface for local sounds + AudioInjector::setLocalAudioInterface(audioIO); + auto audioScriptingInterface = DependencyManager::get(); + audioScriptingInterface->setLocalAudioInterface(audioIO); + connect(audioIO, &AudioClient::noiseGateOpened, audioScriptingInterface.data(), &AudioScriptingInterface::noiseGateOpened); + connect(audioIO, &AudioClient::noiseGateClosed, audioScriptingInterface.data(), &AudioScriptingInterface::noiseGateClosed); + connect(audioIO, &AudioClient::inputReceived, audioScriptingInterface.data(), &AudioScriptingInterface::inputReceived); + } this->installEventFilter(this); @@ -1817,21 +1854,17 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo } }); - connect(entityScriptingInterface.data(), &EntityScriptingInterface::deletingEntity, [=](const EntityItemID& entityItemID) { + connect(entityScriptingInterface.data(), &EntityScriptingInterface::deletingEntity, [this](const EntityItemID& entityItemID) { if (entityItemID == _keyboardFocusedEntity.get()) { setKeyboardFocusEntity(UNKNOWN_ENTITY_ID); } }); - connect(getEntities()->getTree().get(), &EntityTree::deletingEntity, [=](const EntityItemID& entityItemID) { - auto avatarManager = DependencyManager::get(); - auto myAvatar = avatarManager ? avatarManager->getMyAvatar() : nullptr; - if (myAvatar) { - myAvatar->clearAvatarEntity(entityItemID); + EntityTree::setAddMaterialToEntityOperator([this](const QUuid& entityID, graphics::MaterialLayer material, const std::string& parentMaterialName) { + if (_aboutToQuit) { + return false; } - }); - EntityTree::setAddMaterialToEntityOperator([&](const QUuid& entityID, graphics::MaterialLayer material, const std::string& parentMaterialName) { // try to find the renderable auto renderable = getEntities()->renderableForEntityId(entityID); if (renderable) { @@ -1846,7 +1879,11 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo } return false; }); - EntityTree::setRemoveMaterialFromEntityOperator([&](const QUuid& entityID, graphics::MaterialPointer material, const std::string& parentMaterialName) { + 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) { @@ -1881,7 +1918,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo return false; }); - EntityTree::setAddMaterialToOverlayOperator([&](const QUuid& overlayID, graphics::MaterialLayer material, const std::string& parentMaterialName) { + EntityTree::setAddMaterialToOverlayOperator([this](const QUuid& overlayID, graphics::MaterialLayer material, const std::string& parentMaterialName) { auto overlay = _overlays.getOverlay(overlayID); if (overlay) { overlay->addMaterial(material, parentMaterialName); @@ -1889,7 +1926,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo } return false; }); - EntityTree::setRemoveMaterialFromOverlayOperator([&](const QUuid& overlayID, graphics::MaterialPointer material, const std::string& parentMaterialName) { + EntityTree::setRemoveMaterialFromOverlayOperator([this](const QUuid& overlayID, graphics::MaterialPointer material, const std::string& parentMaterialName) { auto overlay = _overlays.getOverlay(overlayID); if (overlay) { overlay->removeMaterial(material, parentMaterialName); @@ -1900,13 +1937,13 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo // Keyboard focus handling for Web overlays. auto overlays = &(qApp->getOverlays()); - connect(overlays, &Overlays::overlayDeleted, [=](const OverlayID& overlayID) { + connect(overlays, &Overlays::overlayDeleted, [this](const OverlayID& overlayID) { if (overlayID == _keyboardFocusedOverlay.get()) { setKeyboardFocusOverlay(UNKNOWN_OVERLAY_ID); } }); - connect(this, &Application::aboutToQuit, [=]() { + connect(this, &Application::aboutToQuit, [this]() { setKeyboardFocusOverlay(UNKNOWN_OVERLAY_ID); setKeyboardFocusEntity(UNKNOWN_ENTITY_ID); }); @@ -2175,23 +2212,22 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo QVariant testProperty = property(hifi::properties::TEST); qDebug() << testProperty; if (testProperty.isValid()) { - auto scriptEngines = DependencyManager::get(); const auto testScript = property(hifi::properties::TEST).toUrl(); // Set last parameter to exit interface when the test script finishes, if so requested - scriptEngines->loadScript(testScript, false, false, false, false, quitWhenFinished); + DependencyManager::get()->loadScript(testScript, false, false, false, false, quitWhenFinished); // This is done so we don't get a "connection time-out" message when we haven't passed in a URL. if (arguments().contains("--url")) { auto reply = SandboxUtils::getStatus(); - connect(reply, &QNetworkReply::finished, this, [=] { + connect(reply, &QNetworkReply::finished, this, [this, reply] { handleSandboxStatus(reply); }); } } else { PROFILE_RANGE(render, "GetSandboxStatus"); auto reply = SandboxUtils::getStatus(); - connect(reply, &QNetworkReply::finished, this, [=] { + connect(reply, &QNetworkReply::finished, this, [this, reply] { handleSandboxStatus(reply); }); } @@ -2218,8 +2254,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo connect(&_myCamera, &Camera::modeUpdated, this, &Application::cameraModeChanged); - DependencyManager::get()->setShouldPickHUDOperator([&]() { return DependencyManager::get()->isHMDMode(); }); - DependencyManager::get()->setCalculatePos2DFromHUDOperator([&](const glm::vec3& intersection) { + DependencyManager::get()->setShouldPickHUDOperator([]() { return DependencyManager::get()->isHMDMode(); }); + DependencyManager::get()->setCalculatePos2DFromHUDOperator([this](const glm::vec3& intersection) { const glm::vec2 MARGIN(25.0f); glm::vec2 maxPos = _controllerScriptingInterface->getViewportDimensions() - MARGIN; glm::vec2 pos2D = DependencyManager::get()->overlayFromWorldPoint(intersection); @@ -2229,7 +2265,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo // Setup the mouse ray pick and related operators DependencyManager::get()->setMouseRayPickID(DependencyManager::get()->addPick(PickQuery::Ray, std::make_shared( PickFilter(PickScriptingInterface::PICK_ENTITIES() | PickScriptingInterface::PICK_INCLUDE_NONCOLLIDABLE()), 0.0f, true))); - DependencyManager::get()->setMouseRayPickResultOperator([&](unsigned int rayPickID) { + DependencyManager::get()->setMouseRayPickResultOperator([](unsigned int rayPickID) { RayToEntityIntersectionResult entityResult; entityResult.intersects = false; auto pickResult = DependencyManager::get()->getPrevPickResultTyped(rayPickID); @@ -2245,7 +2281,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo } return entityResult; }); - DependencyManager::get()->setSetPrecisionPickingOperator([&](unsigned int rayPickID, bool value) { + DependencyManager::get()->setSetPrecisionPickingOperator([](unsigned int rayPickID, bool value) { DependencyManager::get()->setPrecisionPicking(rayPickID, value); }); @@ -2444,23 +2480,42 @@ void Application::cleanupBeforeQuit() { _keyboardFocusHighlight = nullptr; } - auto nodeList = DependencyManager::get(); + { + auto nodeList = DependencyManager::get(); - // send the domain a disconnect packet, force stoppage of domain-server check-ins - nodeList->getDomainHandler().disconnect(); - nodeList->setIsShuttingDown(true); + // send the domain a disconnect packet, force stoppage of domain-server check-ins + nodeList->getDomainHandler().disconnect(); + nodeList->setIsShuttingDown(true); - // tell the packet receiver we're shutting down, so it can drop packets - nodeList->getPacketReceiver().setShouldDropPackets(true); + // tell the packet receiver we're shutting down, so it can drop packets + nodeList->getPacketReceiver().setShouldDropPackets(true); + } getEntities()->shutdown(); // tell the entities system we're shutting down, so it will stop running scripts // Clear any queued processing (I/O, FBX/OBJ/Texture parsing) QThreadPool::globalInstance()->clear(); + DependencyManager::destroy(); + + // FIXME: Something is still holding on to the ScriptEnginePointers contained in ScriptEngines, and they hold backpointers to ScriptEngines, + // so this doesn't shut down properly DependencyManager::get()->shutdownScripting(); // stop all currently running global scripts + // These classes hold ScriptEnginePointers, so they must be destroyed before ScriptEngines + // Must be done after shutdownScripting in case any scripts try to access these things + { + DependencyManager::destroy(); + EntityTreePointer tree = getEntities()->getTree(); + tree->setSimulation(nullptr); + DependencyManager::destroy(); + } DependencyManager::destroy(); + bool autoLogout = Setting::Handle(AUTO_LOGOUT_SETTING_NAME, false).get(); + if (autoLogout) { + DependencyManager::get()->removeAccountFromFile(); + } + _displayPlugin.reset(); PluginManager::getInstance()->shutdown(); @@ -2495,6 +2550,8 @@ void Application::cleanupBeforeQuit() { DependencyManager::destroy(); #endif + DependencyManager::destroy(); // Must be destroyed before TabletScriptingInterface + // stop QML DependencyManager::destroy(); DependencyManager::destroy(); @@ -2506,10 +2563,6 @@ void Application::cleanupBeforeQuit() { _snapshotSoundInjector->stop(); } - // FIXME: something else is holding a reference to AudioClient, - // so it must be explicitly synchronously stopped here - DependencyManager::get()->cleanupBeforeQuit(); - // destroy Audio so it and its threads have a chance to go down safely // this must happen after QML, as there are unexplained audio crashes originating in qtwebengine DependencyManager::destroy(); @@ -2526,11 +2579,15 @@ void Application::cleanupBeforeQuit() { Application::~Application() { // remove avatars from physics engine - DependencyManager::get()->clearOtherAvatars(); - VectorOfMotionStates motionStates; - DependencyManager::get()->getObjectsToRemoveFromPhysics(motionStates); - _physicsEngine->removeObjects(motionStates); - DependencyManager::get()->deleteAllAvatars(); + auto avatarManager = DependencyManager::get(); + avatarManager->clearOtherAvatars(); + + PhysicsEngine::Transaction transaction; + avatarManager->buildPhysicsTransaction(transaction); + _physicsEngine->processTransaction(transaction); + avatarManager->handleProcessedPhysicsTransaction(transaction); + + avatarManager->deleteAllAvatars(); _physicsEngine->setCharacterController(nullptr); @@ -2549,9 +2606,6 @@ Application::~Application() { _entityClipboard->eraseAllOctreeElements(); _entityClipboard.reset(); - EntityTreePointer tree = getEntities()->getTree(); - tree->setSimulation(nullptr); - _octreeProcessor.terminate(); _entityEditSender.terminate(); @@ -2562,12 +2616,18 @@ Application::~Application() { DependencyManager::destroy(); // must be destroyed before the FramebufferCache + DependencyManager::destroy(); + DependencyManager::destroy(); + DependencyManager::destroy(); DependencyManager::destroy(); DependencyManager::destroy(); + DependencyManager::destroy(); DependencyManager::destroy(); + DependencyManager::destroy(); DependencyManager::destroy(); DependencyManager::destroy(); + DependencyManager::destroy(); DependencyManager::destroy(); DependencyManager::destroy(); DependencyManager::destroy(); @@ -2596,7 +2656,7 @@ Application::~Application() { // Can't log to file passed this point, FileLogger about to be deleted qInstallMessageHandler(LogHandler::verboseMessageHandler); - + _renderEventHandler->deleteLater(); } @@ -2660,26 +2720,14 @@ void Application::initializeGL() { } } - // Build an offscreen GL context for the main thread. - _offscreenContext = new OffscreenGLCanvas(); - _offscreenContext->setObjectName("MainThreadContext"); - _offscreenContext->create(_glWidget->qglContext()); - if (!_offscreenContext->makeCurrent()) { - qFatal("Unable to make offscreen context current"); - } - _offscreenContext->doneCurrent(); - _offscreenContext->setThreadContext(); + _renderEventHandler = new RenderEventHandler(); + // Build an offscreen GL context for the main thread. _glWidget->makeCurrent(); glClearColor(0.2f, 0.2f, 0.2f, 1); glClear(GL_COLOR_BUFFER_BIT); _glWidget->swapBuffers(); - // Move the GL widget context to the render event handler thread - _renderEventHandler = new RenderEventHandler(_glWidget->qglContext()); - if (!_offscreenContext->makeCurrent()) { - qFatal("Unable to make offscreen context current"); - } // Create the GPU backend @@ -2688,15 +2736,10 @@ void Application::initializeGL() { // contexts _glWidget->makeCurrent(); gpu::Context::init(); - qApp->setProperty(hifi::properties::gl::MAKE_PROGRAM_CALLBACK, - QVariant::fromValue((void*)(&gpu::gl::GLBackend::makeProgram))); _glWidget->makeCurrent(); _gpuContext = std::make_shared(); DependencyManager::get()->setGPUContext(_gpuContext); - - // Restore the default main thread context - _offscreenContext->makeCurrent(); } static const QString SPLASH_SKYBOX{ "{\"ProceduralEntity\":{ \"version\":2, \"shaderUrl\":\"qrc:///shaders/splashSkybox.frag\" } }" }; @@ -2717,6 +2760,10 @@ void Application::initializeDisplayPlugins() { QObject::connect(displayPlugin.get(), &DisplayPlugin::recommendedFramebufferSizeChanged, [this](const QSize& size) { resizeGL(); }); QObject::connect(displayPlugin.get(), &DisplayPlugin::resetSensorsRequested, this, &Application::requestReset); + if (displayPlugin->isHmd()) { + QObject::connect(dynamic_cast(displayPlugin.get()), &HmdDisplayPlugin::hmdMountedChanged, + DependencyManager::get().data(), &HMDScriptingInterface::mountedChanged); + } } // The default display plugin needs to be activated first, otherwise the display plugin thread @@ -2731,8 +2778,6 @@ void Application::initializeDisplayPlugins() { // Submit a default frame to render until the engine starts up updateRenderArgs(0.0f); - _offscreenContext->makeCurrent(); - #define ENABLE_SPLASH_FRAME 0 #if ENABLE_SPLASH_FRAME { @@ -2774,8 +2819,6 @@ void Application::initializeDisplayPlugins() { } void Application::initializeRenderEngine() { - _offscreenContext->makeCurrent(); - // FIXME: on low end systems os the shaders take up to 1 minute to compile, so we pause the deadlock watchdog thread. DeadlockWatchdogThread::withPause([&] { // Set up the render engine @@ -2907,6 +2950,15 @@ void Application::initializeUi() { // Pre-create a couple of Web3D overlays to speed up tablet UI auto offscreenSurfaceCache = DependencyManager::get(); + offscreenSurfaceCache->setOnRootContextCreated([&](const QString& rootObject, QQmlContext* surfaceContext) { + if (rootObject == TabletScriptingInterface::QML) { + // in Qt 5.10.0 there is already an "Audio" object in the QML context + // though I failed to find it (from QtMultimedia??). So.. let it be "AudioScriptingInterface" + surfaceContext->setContextProperty("AudioScriptingInterface", DependencyManager::get().data()); + surfaceContext->setContextProperty("Account", AccountServicesScriptingInterface::getInstance()); // DEPRECATED - TO BE REMOVED + } + }); + offscreenSurfaceCache->reserve(TabletScriptingInterface::QML, 1); offscreenSurfaceCache->reserve(Web3DOverlay::QML, 2); @@ -2989,10 +3041,11 @@ void Application::onDesktopRootContextCreated(QQmlContext* surfaceContext) { surfaceContext->setContextProperty("LocationBookmarks", DependencyManager::get().data()); // Caches - surfaceContext->setContextProperty("AnimationCache", DependencyManager::get().data()); - surfaceContext->setContextProperty("TextureCache", DependencyManager::get().data()); - surfaceContext->setContextProperty("ModelCache", DependencyManager::get().data()); - surfaceContext->setContextProperty("SoundCache", DependencyManager::get().data()); + surfaceContext->setContextProperty("AnimationCache", DependencyManager::get().data()); + surfaceContext->setContextProperty("TextureCache", DependencyManager::get().data()); + surfaceContext->setContextProperty("ModelCache", DependencyManager::get().data()); + surfaceContext->setContextProperty("SoundCache", DependencyManager::get().data()); + surfaceContext->setContextProperty("InputConfiguration", DependencyManager::get().data()); surfaceContext->setContextProperty("Account", AccountServicesScriptingInterface::getInstance()); // DEPRECATED - TO BE REMOVED @@ -3247,8 +3300,7 @@ void Application::showHelp() { QUrlQuery queryString; queryString.addQueryItem("handControllerName", handControllerName); queryString.addQueryItem("defaultTab", defaultTab); - auto tabletScriptingInterface = DependencyManager::get(); - TabletProxy* tablet = dynamic_cast(tabletScriptingInterface->getTablet(SYSTEM_TABLET)); + TabletProxy* tablet = dynamic_cast(DependencyManager::get()->getTablet(SYSTEM_TABLET)); tablet->gotoWebScreen(PathUtils::resourcesUrl() + INFO_HELP_PATH + "?" + queryString.toString()); DependencyManager::get()->openTablet(); //InfoView::show(INFO_HELP_PATH, false, queryString.toString()); @@ -3561,6 +3613,10 @@ static void dumpEventQueue(QThread* thread) { bool Application::event(QEvent* event) { + if (_aboutToQuit) { + return false; + } + if (!Menu::getInstance()) { return false; } @@ -3650,6 +3706,10 @@ bool Application::event(QEvent* event) { bool Application::eventFilter(QObject* object, QEvent* event) { + if (_aboutToQuit && event->type() != QEvent::DeferredDelete && event->type() != QEvent::Destroy) { + return true; + } + if (event->type() == QEvent::Leave) { getApplicationCompositor().handleLeaveEvent(); } @@ -3674,7 +3734,10 @@ static bool _altPressed{ false }; void Application::keyPressEvent(QKeyEvent* event) { _altPressed = event->key() == Qt::Key_Alt; - _keysPressed.insert(event->key()); + + if (!event->isAutoRepeat()) { + _keysPressed.insert(event->key(), *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 @@ -3885,7 +3948,9 @@ void Application::keyPressEvent(QKeyEvent* event) { } void Application::keyReleaseEvent(QKeyEvent* event) { - _keysPressed.remove(event->key()); + if (!event->isAutoRepeat()) { + _keysPressed.remove(event->key()); + } #if defined(Q_OS_ANDROID) if (event->key() == Qt::Key_Back) { @@ -3921,11 +3986,14 @@ void Application::focusOutEvent(QFocusEvent* event) { #endif // synthesize events for keys currently pressed, since we may not get their release events - foreach (int key, _keysPressed) { - QKeyEvent keyEvent(QEvent::KeyRelease, key, Qt::NoModifier); - keyReleaseEvent(&keyEvent); + // Because our key event handlers may manipulate _keysPressed, lets swap the keys pressed into a local copy, + // clearing the existing list. + QHash keysPressed; + std::swap(keysPressed, _keysPressed); + for (auto& ev : keysPressed) { + QKeyEvent synthesizedEvent { QKeyEvent::KeyRelease, ev.key(), Qt::NoModifier, ev.text() }; + keyReleaseEvent(&synthesizedEvent); } - _keysPressed.clear(); } void Application::maybeToggleMenuVisible(QMouseEvent* event) const { @@ -3953,10 +4021,6 @@ void Application::maybeToggleMenuVisible(QMouseEvent* event) const { void Application::mouseMoveEvent(QMouseEvent* event) { PROFILE_RANGE(app_input_mouse, __FUNCTION__); - if (_aboutToQuit) { - return; - } - maybeToggleMenuVisible(event); auto& compositor = getApplicationCompositor(); @@ -4021,11 +4085,9 @@ void Application::mousePressEvent(QMouseEvent* event) { event->screenPos(), event->button(), event->buttons(), event->modifiers()); - if (!_aboutToQuit) { - getOverlays().mousePressEvent(&mappedEvent); - if (!_controllerScriptingInterface->areEntityClicksCaptured()) { - getEntities()->mousePressEvent(&mappedEvent); - } + getOverlays().mousePressEvent(&mappedEvent); + if (!_controllerScriptingInterface->areEntityClicksCaptured()) { + getEntities()->mousePressEvent(&mappedEvent); } _controllerScriptingInterface->emitMousePressEvent(&mappedEvent); // send events to any registered scripts @@ -4062,14 +4124,11 @@ void Application::mouseDoublePressEvent(QMouseEvent* event) { event->screenPos(), event->button(), event->buttons(), event->modifiers()); - if (!_aboutToQuit) { - getOverlays().mouseDoublePressEvent(&mappedEvent); - if (!_controllerScriptingInterface->areEntityClicksCaptured()) { - getEntities()->mouseDoublePressEvent(&mappedEvent); - } + getOverlays().mouseDoublePressEvent(&mappedEvent); + if (!_controllerScriptingInterface->areEntityClicksCaptured()) { + getEntities()->mouseDoublePressEvent(&mappedEvent); } - // if one of our scripts have asked to capture this event, then stop processing it if (_controllerScriptingInterface->isMouseCaptured()) { return; @@ -4088,10 +4147,8 @@ void Application::mouseReleaseEvent(QMouseEvent* event) { event->screenPos(), event->button(), event->buttons(), event->modifiers()); - if (!_aboutToQuit) { - getOverlays().mouseReleaseEvent(&mappedEvent); - getEntities()->mouseReleaseEvent(&mappedEvent); - } + getOverlays().mouseReleaseEvent(&mappedEvent); + getEntities()->mouseReleaseEvent(&mappedEvent); _controllerScriptingInterface->emitMouseReleaseEvent(&mappedEvent); // send events to any registered scripts @@ -4242,7 +4299,6 @@ bool Application::shouldPaint() const { return false; } - auto displayPlugin = getActiveDisplayPlugin(); #ifdef DEBUG_PAINT_DELAY @@ -4512,7 +4568,6 @@ void Application::idle() { if (offscreenUi->size() != fromGlm(uiSize)) { qCDebug(interfaceapp) << "Device pixel ratio changed, triggering resize to " << uiSize; offscreenUi->resize(fromGlm(uiSize)); - _offscreenContext->makeCurrent(); } } @@ -4550,11 +4605,14 @@ void Application::idle() { _lastTimeUpdated.start(); // If the offscreen Ui has something active that is NOT the root, then assume it has keyboard focus. - if (_keyboardDeviceHasFocus && offscreenUi && offscreenUi->getWindow()->activeFocusItem() != offscreenUi->getRootItem()) { - _keyboardMouseDevice->pluginFocusOutEvent(); - _keyboardDeviceHasFocus = false; - } else if (offscreenUi && offscreenUi->getWindow()->activeFocusItem() == offscreenUi->getRootItem()) { - _keyboardDeviceHasFocus = true; + if (offscreenUi && offscreenUi->getWindow()) { + auto activeFocusItem = offscreenUi->getWindow()->activeFocusItem(); + if (_keyboardDeviceHasFocus && activeFocusItem != offscreenUi->getRootItem()) { + _keyboardMouseDevice->pluginFocusOutEvent(); + _keyboardDeviceHasFocus = false; + } else if (activeFocusItem == offscreenUi->getRootItem()) { + _keyboardDeviceHasFocus = true; + } } checkChangeCursor(); @@ -4567,10 +4625,6 @@ void Application::idle() { bool showWarnings = getLogger()->extraDebugging(); PerformanceWarning warn(showWarnings, "idle()"); - if (!_offscreenContext->makeCurrent()) { - qFatal("Unable to make main thread context current"); - } - { _gameWorkload.updateViews(_viewFrustum, getMyAvatar()->getHeadPosition()); _gameWorkload._engine->run(); @@ -4719,7 +4773,7 @@ bool Application::exportEntities(const QString& filename, exportTree->createRootElement(); glm::vec3 root(TREE_SCALE, TREE_SCALE, TREE_SCALE); bool success = true; - entityTree->withReadLock([&] { + entityTree->withReadLock([entityIDs, entityTree, givenOffset, myAvatarID, &root, &entities, &success, &exportTree] { for (auto entityID : entityIDs) { // Gather entities and properties. auto entityItem = entityTree->findEntityByEntityItemID(entityID); if (!entityItem) { @@ -4839,6 +4893,7 @@ void Application::loadSettings() { } isFirstPerson = (qApp->isHMDMode()); + } else { // if this is not the first run, the camera will be initialized differently depending on user settings @@ -4897,7 +4952,6 @@ QVector Application::pasteEntities(float x, float y, float z) { } void Application::init() { - _offscreenContext->makeCurrent(); // Make sure Login state is up to date DependencyManager::get()->toggleLoginDialog(); if (!DISABLE_DEFERRED) { @@ -5007,8 +5061,9 @@ void Application::updateLOD(float deltaTime) const { float presentTime = getActiveDisplayPlugin()->getAveragePresentTime(); float engineRunTime = (float)(_renderEngine->getConfiguration().get()->getCPURunTime()); float gpuTime = getGPUContext()->getFrameTimerGPUAverage(); + float batchTime = getGPUContext()->getFrameTimerBatchAverage(); auto lodManager = DependencyManager::get(); - lodManager->setRenderTimes(presentTime, engineRunTime, gpuTime); + lodManager->setRenderTimes(presentTime, engineRunTime, batchTime, gpuTime); lodManager->autoAdjustLOD(deltaTime); } else { DependencyManager::get()->resetLODAdjust(); @@ -5246,6 +5301,7 @@ void Application::resetPhysicsReadyInformation() { _nearbyEntitiesCountAtLastPhysicsCheck = 0; _nearbyEntitiesStabilityCount = 0; _physicsEnabled = false; + _octreeProcessor.startEntitySequence(); } @@ -5259,6 +5315,7 @@ void Application::reloadResourceCaches() { queryOctree(NodeType::EntityServer, PacketType::EntityQuery); DependencyManager::get()->clearCache(); + DependencyManager::get()->clearCache(); DependencyManager::get()->refreshAll(); DependencyManager::get()->refreshAll(); @@ -5286,7 +5343,7 @@ void Application::setKeyboardFocusHighlight(const glm::vec3& position, const glm _keyboardFocusHighlight->setPulseMin(0.5); _keyboardFocusHighlight->setPulseMax(1.0); _keyboardFocusHighlight->setColorPulse(1.0); - _keyboardFocusHighlight->setIgnoreRayIntersection(true); + _keyboardFocusHighlight->setIgnorePickIntersection(true); _keyboardFocusHighlight->setDrawInFront(false); _keyboardFocusHighlightID = getOverlays().addOverlay(_keyboardFocusHighlight); } @@ -5466,6 +5523,10 @@ static bool domainLoadingInProgress = false; void Application::update(float deltaTime) { PROFILE_RANGE_EX(app, __FUNCTION__, 0xffff0000, (uint64_t)_renderFrameCount + 1); + if (_aboutToQuit) { + return; + } + if (!_physicsEnabled) { if (!domainLoadingInProgress) { PROFILE_ASYNC_BEGIN(app, "Scene Loading", ""); @@ -5475,17 +5536,16 @@ void Application::update(float deltaTime) { // we haven't yet enabled physics. we wait until we think we have all the collision information // for nearby entities before starting bullet up. quint64 now = usecTimestampNow(); - const int PHYSICS_CHECK_TIMEOUT = 2 * USECS_PER_SECOND; - - if (now - _lastPhysicsCheckTime > PHYSICS_CHECK_TIMEOUT || _fullSceneReceivedCounter > _fullSceneCounterAtLastPhysicsCheck) { + if (isServerlessMode() || _octreeProcessor.isLoadSequenceComplete()) { // we've received a new full-scene octree stats packet, or it's been long enough to try again anyway _lastPhysicsCheckTime = now; _fullSceneCounterAtLastPhysicsCheck = _fullSceneReceivedCounter; + _lastQueriedViews.clear(); // Force new view. // process octree stats packets are sent in between full sends of a scene (this isn't currently true). // We keep physics disabled until we've received a full scene and everything near the avatar in that // scene is ready to compute its collision shape. - if (nearbyEntitiesAreReadyForPhysics() && getMyAvatar()->isReadyForPhysics()) { + if (getMyAvatar()->isReadyForPhysics()) { _physicsEnabled = true; getMyAvatar()->updateMotionBehaviorFromMenu(); } @@ -5690,12 +5750,10 @@ void Application::update(float deltaTime) { t1 = std::chrono::high_resolution_clock::now(); - avatarManager->getObjectsToRemoveFromPhysics(motionStates); - _physicsEngine->removeObjects(motionStates); - avatarManager->getObjectsToAddToPhysics(motionStates); - _physicsEngine->addObjects(motionStates); - avatarManager->getObjectsToChange(motionStates); - _physicsEngine->changeObjects(motionStates); + PhysicsEngine::Transaction transaction; + avatarManager->buildPhysicsTransaction(transaction); + _physicsEngine->processTransaction(transaction); + avatarManager->handleProcessedPhysicsTransaction(transaction); myAvatar->prepareForPhysicsSimulation(); _physicsEngine->forEachDynamic([&](EntityDynamicPointer dynamic) { @@ -5732,15 +5790,13 @@ void Application::update(float deltaTime) { _entitySimulation->handleDeactivatedMotionStates(deactivations); }); - if (!_aboutToQuit) { - // handleCollisionEvents() AFTER handleChangedMotionStates() - { - PROFILE_RANGE(simulation_physics, "CollisionEvents"); - avatarManager->handleCollisionEvents(collisionEvents); - // Collision events (and their scripts) must not be handled when we're locked, above. (That would risk - // deadlock.) - _entitySimulation->handleCollisionEvents(collisionEvents); - } + // handleCollisionEvents() AFTER handleChangedMotionStates() + { + PROFILE_RANGE(simulation_physics, "CollisionEvents"); + avatarManager->handleCollisionEvents(collisionEvents); + // Collision events (and their scripts) must not be handled when we're locked, above. (That would risk + // deadlock.) + _entitySimulation->handleCollisionEvents(collisionEvents); } { @@ -5758,24 +5814,20 @@ void Application::update(float deltaTime) { } auto t4 = std::chrono::high_resolution_clock::now(); - if (!_aboutToQuit) { - // NOTE: the getEntities()->update() call below will wait for lock - // and will provide non-physical entity motion - getEntities()->update(true); // update the models... - } + // NOTE: the getEntities()->update() call below will wait for lock + // and will provide non-physical entity motion + getEntities()->update(true); // update the models... 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 { @@ -5965,7 +6017,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(); @@ -6127,11 +6179,23 @@ void Application::queryOctree(NodeType_t serverType, PacketType packetType) { return; // bail early if settings are not loaded } - _octreeQuery.setConicalViews(_conicalViews); + const bool isModifiedQuery = !_physicsEnabled; + if (isModifiedQuery) { + // Create modified view that is a simple sphere. + ConicalViewFrustum sphericalView; + sphericalView.setSimpleRadius(INITIAL_QUERY_RADIUS); + _octreeQuery.setConicalViews({ sphericalView }); + _octreeQuery.setOctreeSizeScale(DEFAULT_OCTREE_SIZE_SCALE); + static constexpr float MIN_LOD_ADJUST = -20.0f; + _octreeQuery.setBoundaryLevelAdjust(MIN_LOD_ADJUST); + } else { + _octreeQuery.setConicalViews(_conicalViews); + auto lodManager = DependencyManager::get(); + _octreeQuery.setOctreeSizeScale(lodManager->getOctreeSizeScale()); + _octreeQuery.setBoundaryLevelAdjust(lodManager->getBoundaryLevelAdjust()); + } + _octreeQuery.setReportInitialCompletion(isModifiedQuery); - auto lodManager = DependencyManager::get(); - _octreeQuery.setOctreeSizeScale(lodManager->getOctreeSizeScale()); - _octreeQuery.setBoundaryLevelAdjust(lodManager->getBoundaryLevelAdjust()); auto nodeList = DependencyManager::get(); @@ -6156,6 +6220,10 @@ bool Application::isHMDMode() const { return getActiveDisplayPlugin()->isHmd(); } +float Application::getNumCollisionObjects() const { + return _physicsEngine ? _physicsEngine->getNumCollisionObjects() : 0; +} + float Application::getTargetRenderFrameRate() const { return getActiveDisplayPlugin()->getTargetFrameRate(); } QRect Application::getDesirableApplicationGeometry() const { @@ -6188,6 +6256,9 @@ PickRay Application::computePickRay(float x, float y) const { getApplicationCompositor().computeHmdPickRay(pickPoint, result.origin, result.direction); } else { pickPoint /= getCanvasSize(); + if (_myCamera.getMode() == CameraMode::CAMERA_MODE_MIRROR) { + pickPoint.x = 1.0f - pickPoint.x; + } QMutexLocker viewLocker(&_viewMutex); _viewFrustum.computePickRay(pickPoint.x, pickPoint.y, result.origin, result.direction); } @@ -6292,7 +6363,6 @@ void Application::clearDomainOctreeDetails() { } void Application::clearDomainAvatars() { - getMyAvatar()->setAvatarEntityDataChanged(true); // to recreate worn entities DependencyManager::get()->clearOtherAvatars(); } @@ -6310,7 +6380,6 @@ void Application::domainURLChanged(QUrl domainURL) { void Application::resettingDomain() { _notifiedPacketVersionMismatchThisDomain = false; - auto nodeList = DependencyManager::get(); clearDomainOctreeDetails(); } @@ -6358,8 +6427,8 @@ void Application::nodeActivated(SharedNodePointer node) { if (_avatarOverrideUrl.isValid()) { getMyAvatar()->useFullAvatarURL(_avatarOverrideUrl); } - static const QUrl empty{}; - if (getMyAvatar()->getFullAvatarURLFromPreferences() != getMyAvatar()->cannonicalSkeletonModelURL(empty)) { + + if (getMyAvatar()->getFullAvatarURLFromPreferences() != getMyAvatar()->getSkeletonModelURL()) { getMyAvatar()->resetFullAvatarURL(); } getMyAvatar()->markIdentityDataChanged(); @@ -6523,9 +6592,6 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEnginePointe entityScriptingInterface->setPacketSender(&_entityEditSender); entityScriptingInterface->setEntityTree(getEntities()->getTree()); - // give the script engine to the RecordingScriptingInterface for its callbacks - DependencyManager::get()->setScriptEngine(scriptEngine); - if (property(hifi::properties::TEST).isValid()) { scriptEngine->registerGlobalObject("Test", TestScriptingInterface::getInstance()); } @@ -6577,11 +6643,12 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEnginePointe scriptEngine->registerGetterSetter("location", LocationScriptingInterface::locationGetter, LocationScriptingInterface::locationSetter); + bool clientScript = scriptEngine->isClientScript(); + scriptEngine->registerFunction("OverlayWindow", clientScript ? QmlWindowClass::constructor : QmlWindowClass::restricted_constructor); #if !defined(Q_OS_ANDROID) - scriptEngine->registerFunction("OverlayWebWindow", QmlWebWindowClass::constructor); + scriptEngine->registerFunction("OverlayWebWindow", clientScript ? QmlWebWindowClass::constructor : QmlWebWindowClass::restricted_constructor); #endif - scriptEngine->registerFunction("OverlayWindow", QmlWindowClass::constructor); - scriptEngine->registerFunction("QmlFragment", QmlFragmentClass::constructor); + scriptEngine->registerFunction("QmlFragment", clientScript ? QmlFragmentClass::constructor : QmlFragmentClass::restricted_constructor); scriptEngine->registerGlobalObject("Menu", MenuScriptingInterface::getInstance()); scriptEngine->registerGlobalObject("DesktopPreviewProvider", DependencyManager::get().data()); @@ -6599,10 +6666,10 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEnginePointe scriptEngine->registerGlobalObject("Pointers", DependencyManager::get().data()); // Caches - scriptEngine->registerGlobalObject("AnimationCache", DependencyManager::get().data()); - scriptEngine->registerGlobalObject("TextureCache", DependencyManager::get().data()); - scriptEngine->registerGlobalObject("ModelCache", DependencyManager::get().data()); - scriptEngine->registerGlobalObject("SoundCache", DependencyManager::get().data()); + scriptEngine->registerGlobalObject("AnimationCache", DependencyManager::get().data()); + scriptEngine->registerGlobalObject("TextureCache", DependencyManager::get().data()); + scriptEngine->registerGlobalObject("ModelCache", DependencyManager::get().data()); + scriptEngine->registerGlobalObject("SoundCache", DependencyManager::get().data()); scriptEngine->registerGlobalObject("DialogsManager", _dialogsManagerScriptingInterface); @@ -6663,19 +6730,16 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEnginePointe registerInteractiveWindowMetaType(scriptEngine.data()); - DependencyManager::get()->registerMetaTypes(scriptEngine.data()); + auto pickScriptingInterface = DependencyManager::get(); + pickScriptingInterface->registerMetaTypes(scriptEngine.data()); // connect this script engines printedMessage signal to the global ScriptEngines these various messages - connect(scriptEngine.data(), &ScriptEngine::printedMessage, - DependencyManager::get().data(), &ScriptEngines::onPrintedMessage); - connect(scriptEngine.data(), &ScriptEngine::errorMessage, - DependencyManager::get().data(), &ScriptEngines::onErrorMessage); - connect(scriptEngine.data(), &ScriptEngine::warningMessage, - DependencyManager::get().data(), &ScriptEngines::onWarningMessage); - connect(scriptEngine.data(), &ScriptEngine::infoMessage, - DependencyManager::get().data(), &ScriptEngines::onInfoMessage); - connect(scriptEngine.data(), &ScriptEngine::clearDebugWindow, - DependencyManager::get().data(), &ScriptEngines::onClearDebugWindow); + auto scriptEngines = DependencyManager::get().data(); + connect(scriptEngine.data(), &ScriptEngine::printedMessage, scriptEngines, &ScriptEngines::onPrintedMessage); + connect(scriptEngine.data(), &ScriptEngine::errorMessage, scriptEngines, &ScriptEngines::onErrorMessage); + connect(scriptEngine.data(), &ScriptEngine::warningMessage, scriptEngines, &ScriptEngines::onWarningMessage); + connect(scriptEngine.data(), &ScriptEngine::infoMessage, scriptEngines, &ScriptEngines::onInfoMessage); + connect(scriptEngine.data(), &ScriptEngine::clearDebugWindow, scriptEngines, &ScriptEngines::onClearDebugWindow); } @@ -6983,10 +7047,9 @@ void Application::showDialog(const QUrl& widgetUrl, const QUrl& tabletUrl, const } void Application::showScriptLogs() { - auto scriptEngines = DependencyManager::get(); QUrl defaultScriptsLoc = PathUtils::defaultScriptsLocation(); defaultScriptsLoc.setPath(defaultScriptsLoc.path() + "developer/debugging/debugWindow.js"); - scriptEngines->loadScript(defaultScriptsLoc.toString()); + DependencyManager::get()->loadScript(defaultScriptsLoc.toString()); } void Application::showAssetServerWidget(QString filePath) { @@ -7239,6 +7302,8 @@ void Application::addAssetToWorldAddEntity(QString filePath, QString mapping) { } properties.setCollisionless(true); // Temporarily set so that doesn't collide with avatar. properties.setVisible(false); // Temporarily set so that don't see at large unresized dimensions. + bool grabbable = (Menu::getInstance()->isOptionChecked(MenuOption::CreateEntitiesGrabbable)); + properties.setUserData(grabbable ? GRABBABLE_USER_DATA : NOT_GRABBABLE_USER_DATA); glm::vec3 positionOffset = getMyAvatar()->getWorldOrientation() * (getMyAvatar()->getSensorToWorldScale() * glm::vec3(0.0f, 0.0f, -2.0f)); properties.setPosition(getMyAvatar()->getWorldPosition() + positionOffset); properties.setRotation(getMyAvatar()->getWorldOrientation()); @@ -7281,7 +7346,6 @@ void Application::addAssetToWorldCheckModelSize() { auto name = properties.getName(); auto dimensions = properties.getDimensions(); - const QString GRABBABLE_USER_DATA = "{\"grabbableKey\":{\"grabbable\":true}}"; bool doResize = false; const glm::vec3 DEFAULT_DIMENSIONS = glm::vec3(0.1f, 0.1f, 0.1f); @@ -7325,7 +7389,8 @@ void Application::addAssetToWorldCheckModelSize() { if (!name.toLower().endsWith(PNG_EXTENSION) && !name.toLower().endsWith(JPG_EXTENSION)) { properties.setCollisionless(false); } - properties.setUserData(GRABBABLE_USER_DATA); + bool grabbable = (Menu::getInstance()->isOptionChecked(MenuOption::CreateEntitiesGrabbable)); + properties.setUserData(grabbable ? GRABBABLE_USER_DATA : NOT_GRABBABLE_USER_DATA); properties.setLastEdited(usecTimestampNow()); entityScriptingInterface->editEntity(entityID, properties); } @@ -7550,7 +7615,6 @@ void Application::openUrl(const QUrl& url) const { } void Application::loadDialog() { - auto scriptEngines = DependencyManager::get(); ModalDialogListener* dlg = OffscreenUi::getOpenFileNameAsync(_glWidget, tr("Open Script"), getPreviousScriptLocation(), tr("JavaScript Files (*.js)")); @@ -8273,7 +8337,7 @@ QOpenGLContext* Application::getPrimaryContext() { } bool Application::makeRenderingContextCurrent() { - return _offscreenContext->makeCurrent(); + return true; } bool Application::isForeground() const { diff --git a/interface/src/Application.h b/interface/src/Application.h index 94e561e550..aa8323cd38 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -81,7 +81,6 @@ #include "Sound.h" -class OffscreenGLCanvas; class GLCanvas; class FaceTracker; class MainWindow; @@ -208,6 +207,7 @@ public: size_t getRenderFrameCount() const { return _renderFrameCount; } float getRenderLoopRate() const { return _renderLoopCounter.rate(); } + float getNumCollisionObjects() const; float getTargetRenderFrameRate() const; // frames/second float getFieldOfView() { return _fieldOfView.get(); } @@ -312,6 +312,9 @@ public: Q_INVOKABLE void copyToClipboard(const QString& text); + int getOtherAvatarsReplicaCount() { return DependencyManager::get()->getReplicaCount(); } + void setOtherAvatarsReplicaCount(int count) { DependencyManager::get()->setReplicaCount(count); } + #if defined(Q_OS_ANDROID) void beforeEnterBackground(); void enterBackground(); @@ -554,7 +557,6 @@ private: bool _previousSessionCrashed; - OffscreenGLCanvas* _offscreenContext { nullptr }; DisplayPluginPointer _displayPlugin; QMetaObject::Connection _displayPluginPresentConnection; mutable std::mutex _displayPluginLock; @@ -621,7 +623,7 @@ private: float _mirrorYawOffset; float _raiseMirror; - QSet _keysPressed; + QHash _keysPressed; bool _enableProcessOctreeThread; diff --git a/interface/src/AvatarBookmarks.cpp b/interface/src/AvatarBookmarks.cpp index 4e3e539dea..5c79bedc9a 100644 --- a/interface/src/AvatarBookmarks.cpp +++ b/interface/src/AvatarBookmarks.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -35,7 +36,7 @@ #include "QVariantGLM.h" #include - +#include void addAvatarEntities(const QVariantList& avatarEntities) { auto nodeList = DependencyManager::get(); @@ -64,7 +65,7 @@ void addAvatarEntities(const QVariantList& avatarEntities) { EntityItemID id = EntityItemID(QUuid::createUuid()); bool success = true; - entityTree->withWriteLock([&] { + entityTree->withWriteLock([&entityTree, id, &entityProperties, &success] { EntityItemPointer entity = entityTree->addEntity(id, entityProperties); if (entity) { if (entityProperties.queryAACubeRelatedPropertyChanged()) { @@ -146,8 +147,7 @@ void AvatarBookmarks::removeBookmark(const QString& bookmarkName) { void AvatarBookmarks::updateAvatarEntities(const QVariantList &avatarEntities) { auto myAvatar = DependencyManager::get()->getMyAvatar(); - myAvatar->removeAvatarEntities(); - + myAvatar->removeWearableAvatarEntities(); addAvatarEntities(avatarEntities); } @@ -161,9 +161,18 @@ void AvatarBookmarks::loadBookmark(const QString& bookmarkName) { if (bookmarkEntry != _bookmarks.end()) { QVariantMap bookmark = bookmarkEntry.value().toMap(); + if (bookmark.empty()) { // compatibility with bookmarks like this: "Wooden Doll": "http://mpassets.highfidelity.com/7fe80a1e-f445-4800-9e89-40e677b03bee-v1/mannequin.fst?noDownload=false", + auto avatarUrl = bookmarkEntry.value().toString(); + if (!avatarUrl.isEmpty()) { + bookmark.insert(ENTRY_AVATAR_URL, avatarUrl); + } + } + if (!bookmark.empty()) { auto myAvatar = DependencyManager::get()->getMyAvatar(); - myAvatar->removeAvatarEntities(); + auto treeRenderer = DependencyManager::get(); + EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr; + myAvatar->removeWearableAvatarEntities(); const QString& avatarUrl = bookmark.value(ENTRY_AVATAR_URL, "").toString(); myAvatar->useFullAvatarURL(avatarUrl); qCDebug(interfaceapp) << "Avatar On " << avatarUrl; @@ -232,7 +241,27 @@ QVariantMap AvatarBookmarks::getAvatarDataToBookmark() { bookmark.insert(ENTRY_VERSION, AVATAR_BOOKMARK_VERSION); bookmark.insert(ENTRY_AVATAR_URL, avatarUrl); bookmark.insert(ENTRY_AVATAR_SCALE, avatarScale); - bookmark.insert(ENTRY_AVATAR_ATTACHMENTS, myAvatar->getAttachmentsVariant()); - bookmark.insert(ENTRY_AVATAR_ENTITIES, myAvatar->getAvatarEntitiesVariant()); + + QScriptEngine scriptEngine; + QVariantList wearableEntities; + auto treeRenderer = DependencyManager::get(); + EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr; + auto avatarEntities = myAvatar->getAvatarEntityData(); + for (auto entityID : avatarEntities.keys()) { + auto entity = entityTree->findEntityByID(entityID); + if (!entity || !isWearableEntity(entity)) { + continue; + } + QVariantMap avatarEntityData; + EncodeBitstreamParams params; + auto desiredProperties = entity->getEntityProperties(params); + desiredProperties += PROP_LOCAL_POSITION; + desiredProperties += PROP_LOCAL_ROTATION; + EntityItemProperties entityProperties = entity->getProperties(desiredProperties); + QScriptValue scriptProperties = EntityItemPropertiesToScriptValue(&scriptEngine, entityProperties); + avatarEntityData["properties"] = scriptProperties.toVariant(); + wearableEntities.append(QVariant(avatarEntityData)); + } + bookmark.insert(ENTRY_AVATAR_ENTITIES, wearableEntities); return bookmark; } diff --git a/interface/src/LODManager.cpp b/interface/src/LODManager.cpp index da1f14c450..2b5ca9ae8c 100644 --- a/interface/src/LODManager.cpp +++ b/interface/src/LODManager.cpp @@ -19,27 +19,15 @@ #include "ui/DialogsManager.h" #include "InterfaceLogging.h" +const float LODManager::DEFAULT_DESKTOP_LOD_DOWN_FPS = LOD_DEFAULT_QUALITY_LEVEL * LOD_MAX_LIKELY_DESKTOP_FPS; +const float LODManager::DEFAULT_HMD_LOD_DOWN_FPS = LOD_DEFAULT_QUALITY_LEVEL * LOD_MAX_LIKELY_HMD_FPS; -Setting::Handle desktopLODDecreaseFPS("desktopLODDecreaseFPS", DEFAULT_DESKTOP_LOD_DOWN_FPS); -Setting::Handle hmdLODDecreaseFPS("hmdLODDecreaseFPS", DEFAULT_HMD_LOD_DOWN_FPS); +Setting::Handle desktopLODDecreaseFPS("desktopLODDecreaseFPS", LODManager::DEFAULT_DESKTOP_LOD_DOWN_FPS); +Setting::Handle hmdLODDecreaseFPS("hmdLODDecreaseFPS", LODManager::DEFAULT_HMD_LOD_DOWN_FPS); LODManager::LODManager() { } -float LODManager::getLODDecreaseFPS() const { - if (qApp->isHMDMode()) { - return getHMDLODDecreaseFPS(); - } - return getDesktopLODDecreaseFPS(); -} - -float LODManager::getLODIncreaseFPS() const { - if (qApp->isHMDMode()) { - return getHMDLODIncreaseFPS(); - } - return getDesktopLODIncreaseFPS(); -} - // We use a "time-weighted running average" of the maxRenderTime and compare it against min/max thresholds // to determine if we should adjust the level of detail (LOD). // @@ -48,79 +36,118 @@ 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) { +void LODManager::setRenderTimes(float presentTime, float engineRunTime, float batchTime, float gpuTime) { _presentTime = presentTime; _engineRunTime = engineRunTime; + _batchTime = batchTime; _gpuTime = gpuTime; } void LODManager::autoAdjustLOD(float realTimeDelta) { - float maxRenderTime = glm::max(glm::max(_presentTime, _engineRunTime), _gpuTime); + + // 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); + // 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) { + float nowBlend = (realTimeDelta < LOD_ADJUST_RUNNING_AVG_TIMESCALE) ? realTimeDelta / LOD_ADJUST_RUNNING_AVG_TIMESCALE : 1.0f; + _nowRenderTime = (1.0f - nowBlend) * _nowRenderTime + nowBlend * maxRenderTime; // msec + + float smoothBlend = (realTimeDelta < LOD_ADJUST_RUNNING_AVG_TIMESCALE * _smoothScale) ? realTimeDelta / (LOD_ADJUST_RUNNING_AVG_TIMESCALE * _smoothScale) : 1.0f; + _smoothRenderTime = (1.0f - smoothBlend) * _smoothRenderTime + smoothBlend * maxRenderTime; // msec + + if (!_automaticLODAdjust || _nowRenderTime == 0.0f || _smoothRenderTime == 0.0f) { // early exit 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 + auto derivative = (error - previous_error) / dt; + + // remember history + _pidHistory.x = error; + _pidHistory.y = integral; + _pidHistory.z = derivative; + + // Compute the output of the PID and record intermediate results for tuning + _pidOutputs.x = _pidCoefs.x * error; // Kp * error + _pidOutputs.y = _pidCoefs.y * integral; // Ki * integral + _pidOutputs.z = _pidCoefs.z * derivative; // Kd * derivative + + auto output = _pidOutputs.x + _pidOutputs.y + _pidOutputs.z; + _pidOutputs.w = output; + + // And now add the output of the controller to the LODAngle where we will guarantee it is in the proper range + setLODAngleDeg(oldLODAngle + output); + if (oldOctreeSizeScale != _octreeSizeScale) { auto lodToolsDialog = DependencyManager::get()->getLodToolsDialog(); if (lodToolsDialog) { @@ -129,97 +156,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 +256,140 @@ void LODManager::setBoundaryLevelAdjust(int boundaryLevelAdjust) { _boundaryLevelAdjust = boundaryLevelAdjust; } +QString LODManager::getLODFeedbackText() { + // determine granularity feedback + int boundaryLevelAdjust = getBoundaryLevelAdjust(); + QString granularityFeedback; + switch (boundaryLevelAdjust) { + case 0: { + granularityFeedback = QString("."); + } break; + case 1: { + granularityFeedback = QString(" at half of standard granularity."); + } break; + case 2: { + granularityFeedback = QString(" at a third of standard granularity."); + } break; + default: { + granularityFeedback = QString(" at 1/%1th of standard granularity.").arg(boundaryLevelAdjust + 1); + } break; + } + // distance feedback + float octreeSizeScale = getOctreeSizeScale(); + float relativeToDefault = octreeSizeScale / DEFAULT_OCTREE_SIZE_SCALE; + int relativeToTwentyTwenty = 20 / relativeToDefault; + + QString result; + if (relativeToDefault > 1.01f) { + result = QString("20:%1 or %2 times further than average vision%3").arg(relativeToTwentyTwenty).arg(relativeToDefault, 0, 'f', 2).arg(granularityFeedback); + } else if (relativeToDefault > 0.99f) { + result = QString("20:20 or the default distance for average vision%1").arg(granularityFeedback); + } else if (relativeToDefault > 0.01f) { + result = QString("20:%1 or %2 of default distance for average vision%3").arg(relativeToTwentyTwenty).arg(relativeToDefault, 0, 'f', 3).arg(granularityFeedback); + } else { + result = QString("%2 of default distance for average vision%3").arg(relativeToDefault, 0, 'f', 3).arg(granularityFeedback); + } + return result; +} + void LODManager::loadSettings() { - setDesktopLODDecreaseFPS(desktopLODDecreaseFPS.get()); - setHMDLODDecreaseFPS(hmdLODDecreaseFPS.get()); + setDesktopLODTargetFPS(desktopLODDecreaseFPS.get()); + setHMDLODTargetFPS(hmdLODDecreaseFPS.get()); } void LODManager::saveSettings() { - desktopLODDecreaseFPS.set(getDesktopLODDecreaseFPS()); - hmdLODDecreaseFPS.set(getHMDLODDecreaseFPS()); + desktopLODDecreaseFPS.set(getDesktopLODTargetFPS()); + hmdLODDecreaseFPS.set(getHMDLODTargetFPS()); } +const float MIN_DECREASE_FPS = 0.5f; + +void LODManager::setDesktopLODTargetFPS(float fps) { + if (fps < MIN_DECREASE_FPS) { + // avoid divide by zero + fps = MIN_DECREASE_FPS; + } + _desktopTargetFPS = fps; +} + +float LODManager::getDesktopLODTargetFPS() const { + return _desktopTargetFPS; +} + +void LODManager::setHMDLODTargetFPS(float fps) { + if (fps < MIN_DECREASE_FPS) { + // avoid divide by zero + fps = MIN_DECREASE_FPS; + } + _hmdTargetFPS = fps; +} + +float LODManager::getHMDLODTargetFPS() const { + return _hmdTargetFPS; +} + +float LODManager::getLODTargetFPS() const { + if (qApp->isHMDMode()) { + return getHMDLODTargetFPS(); + } + return getDesktopLODTargetFPS(); +} + +void LODManager::setWorldDetailQuality(float quality) { + static const float MIN_FPS = 10; + static const float LOW = 0.25f; + + bool isLowestValue = quality == LOW; + bool isHMDMode = qApp->isHMDMode(); + + float maxFPS = isHMDMode ? LOD_MAX_LIKELY_HMD_FPS : LOD_MAX_LIKELY_DESKTOP_FPS; + float desiredFPS = maxFPS; + + if (!isLowestValue) { + float calculatedFPS = (maxFPS - (maxFPS * quality)); + desiredFPS = calculatedFPS < MIN_FPS ? MIN_FPS : calculatedFPS; + } + + if (isHMDMode) { + setHMDLODTargetFPS(desiredFPS); + } else { + setDesktopLODTargetFPS(desiredFPS); + } + + emit worldDetailQualityChanged(); +} + +float LODManager::getWorldDetailQuality() const { + + static const float LOW = 0.25f; + static const float MEDIUM = 0.5f; + static const float HIGH = 0.75f; + + bool inHMD = qApp->isHMDMode(); + + float targetFPS = 0.0f; + if (inHMD) { + targetFPS = getHMDLODTargetFPS(); + } else { + targetFPS = getDesktopLODTargetFPS(); + } + float maxFPS = inHMD ? LOD_MAX_LIKELY_HMD_FPS : LOD_MAX_LIKELY_DESKTOP_FPS; + float percentage = 1.0f - targetFPS / maxFPS; + + if (percentage <= LOW) { + return LOW; + } else if (percentage <= MEDIUM) { + return MEDIUM; + } + + return HIGH; +} + + +void LODManager::setLODQualityLevel(float quality) { + _lodQualityLevel = quality; +} + +float LODManager::getLODQualityLevel() const { + return _lodQualityLevel; +} diff --git a/interface/src/LODManager.h b/interface/src/LODManager.h index 8cae179f1e..6206fd3539 100644 --- a/interface/src/LODManager.h +++ b/interface/src/LODManager.h @@ -20,21 +20,13 @@ #include #ifdef Q_OS_ANDROID -const float DEFAULT_DESKTOP_LOD_DOWN_FPS = 10.0f; +const float LOD_DEFAULT_QUALITY_LEVEL = 0.75f; // default quality level setting is High (lower framerate) #else -const float DEFAULT_DESKTOP_LOD_DOWN_FPS = 30.0f; +const float LOD_DEFAULT_QUALITY_LEVEL = 0.5f; // default quality level setting is Mid #endif -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; +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; @@ -57,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 @@ -83,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 @@ -152,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: @@ -193,22 +226,35 @@ signals: */ void LODDecreased(); + void autoLODChanged(); + void lodQualityLevelChanged(); + void worldDetailQualityChanged(); + private: LODManager(); bool _automaticLODAdjust = true; - float _presentTime { 0.0f }; // msec - float _engineRunTime { 0.0f }; // msec - float _gpuTime { 0.0f }; // msec - float _avgRenderTime { 0.0f }; // msec - float _desktopMaxRenderTime { DEFAULT_DESKTOP_MAX_RENDER_TIME }; - float _hmdMaxRenderTime { DEFAULT_HMD_MAX_RENDER_TIME }; + + float _presentTime{ 0.0f }; // msec + float _engineRunTime{ 0.0f }; // msec + float _batchTime{ 0.0f }; // msec + float _gpuTime{ 0.0f }; // msec + + float _nowRenderTime{ 0.0f }; // msec + float _smoothScale{ 10.0f }; // smooth is evaluated over 10 times longer than now + float _smoothRenderTime{ 0.0f }; // msec + + float _lodQualityLevel{ LOD_DEFAULT_QUALITY_LEVEL }; + + float _desktopTargetFPS { LOD_OFFSET_FPS + LOD_DEFAULT_QUALITY_LEVEL * LOD_MAX_LIKELY_DESKTOP_FPS }; + float _hmdTargetFPS { LOD_OFFSET_FPS + LOD_DEFAULT_QUALITY_LEVEL * LOD_MAX_LIKELY_HMD_FPS }; float _octreeSizeScale = DEFAULT_OCTREE_SIZE_SCALE; int _boundaryLevelAdjust = 0; - uint64_t _decreaseFPSExpiry { 0 }; - uint64_t _increaseFPSExpiry { 0 }; + glm::vec4 _pidCoefs{ 1.0f, 0.0f, 0.0f, 1.0f }; // Kp, Ki, Kd, Kv + glm::vec4 _pidHistory{ 0.0f }; + glm::vec4 _pidOutputs{ 0.0f }; }; #endif // hifi_LODManager_h diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 8524c40262..a6ba983ab5 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -46,6 +46,7 @@ #include "InterfaceLogging.h" #include "LocationBookmarks.h" #include "DeferredLightingEffect.h" +#include "PickManager.h" #include "AmbientOcclusionEffect.h" #include "RenderShadowTask.h" @@ -147,9 +148,11 @@ Menu::Menu() { auto assetServerAction = addActionToQMenuAndActionHash(editMenu, MenuOption::AssetServer, Qt::CTRL | Qt::SHIFT | Qt::Key_A, qApp, SLOT(showAssetServerWidget())); - auto nodeList = DependencyManager::get(); - QObject::connect(nodeList.data(), &NodeList::canWriteAssetsChanged, assetServerAction, &QAction::setEnabled); - assetServerAction->setEnabled(nodeList->getThisNodeCanWriteAssets()); + { + auto nodeList = DependencyManager::get(); + QObject::connect(nodeList.data(), &NodeList::canWriteAssetsChanged, assetServerAction, &QAction::setEnabled); + assetServerAction->setEnabled(nodeList->getThisNodeCanWriteAssets()); + } // Edit > Package Model as .fst... addActionToQMenuAndActionHash(editMenu, MenuOption::PackageModel, 0, @@ -449,6 +452,9 @@ Menu::Menu() { }); } + addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::ComputeBlendshapes, 0, true, + DependencyManager::get().data(), SLOT(setComputeBlendshapes(bool))); + // Developer > Assets >>> // Menu item is not currently needed but code should be kept in case it proves useful again at some stage. //#define WANT_ASSET_MIGRATION @@ -613,8 +619,11 @@ Menu::Menu() { addCheckableActionToQMenuAndActionHash(networkMenu, MenuOption::SendWrongProtocolVersion, 0, false, qApp, SLOT(sendWrongProtocolVersionsSignature(bool))); - addCheckableActionToQMenuAndActionHash(networkMenu, MenuOption::SendWrongDSConnectVersion, 0, false, - nodeList.data(), SLOT(toggleSendNewerDSConnectVersion(bool))); + { + auto nodeList = DependencyManager::get(); + addCheckableActionToQMenuAndActionHash(networkMenu, MenuOption::SendWrongDSConnectVersion, 0, false, + nodeList.data(), SLOT(toggleSendNewerDSConnectVersion(bool))); + } #endif @@ -648,10 +657,9 @@ Menu::Menu() { action = addActionToQMenuAndActionHash(audioDebugMenu, "Stats..."); connect(action, &QAction::triggered, [] { - auto scriptEngines = DependencyManager::get(); QUrl defaultScriptsLoc = PathUtils::defaultScriptsLocation(); defaultScriptsLoc.setPath(defaultScriptsLoc.path() + "developer/utilities/audio/stats.js"); - scriptEngines->loadScript(defaultScriptsLoc.toString()); + DependencyManager::get()->loadScript(defaultScriptsLoc.toString()); }); action = addActionToQMenuAndActionHash(audioDebugMenu, "Buffers..."); @@ -660,16 +668,14 @@ Menu::Menu() { QString("hifi/tablet/TabletAudioBuffers.qml"), "AudioBuffersDialog"); }); - auto audioIO = DependencyManager::get(); addActionToQMenuAndActionHash(audioDebugMenu, MenuOption::MuteEnvironment, 0, - audioIO.data(), SLOT(sendMuteEnvironmentPacket())); + DependencyManager::get().data(), SLOT(sendMuteEnvironmentPacket())); action = addActionToQMenuAndActionHash(audioDebugMenu, MenuOption::AudioScope); connect(action, &QAction::triggered, [] { - auto scriptEngines = DependencyManager::get(); QUrl defaultScriptsLoc = PathUtils::defaultScriptsLocation(); defaultScriptsLoc.setPath(defaultScriptsLoc.path() + "developer/utilities/audio/audioScope.js"); - scriptEngines->loadScript(defaultScriptsLoc.toString()); + DependencyManager::get()->loadScript(defaultScriptsLoc.toString()); }); // Developer > Physics >>> @@ -686,6 +692,11 @@ Menu::Menu() { addCheckableActionToQMenuAndActionHash(physicsOptionsMenu, MenuOption::PhysicsShowBulletConstraints, 0, false, qApp, SLOT(setShowBulletConstraints(bool))); addCheckableActionToQMenuAndActionHash(physicsOptionsMenu, MenuOption::PhysicsShowBulletConstraintLimits, 0, false, qApp, SLOT(setShowBulletConstraintLimits(bool))); + // Developer > Picking >>> + MenuWrapper* pickingOptionsMenu = developerMenu->addMenu("Picking"); + addCheckableActionToQMenuAndActionHash(pickingOptionsMenu, MenuOption::ForceCoarsePicking, 0, false, + DependencyManager::get().data(), SLOT(setForceCoarsePicking(bool))); + // Developer > Display Crash Options addCheckableActionToQMenuAndActionHash(developerMenu, MenuOption::DisplayCrashOptions, 0, true); // Developer > Crash >>> @@ -749,10 +760,9 @@ Menu::Menu() { // Developer > API Debugger action = addActionToQMenuAndActionHash(developerMenu, "API Debugger"); connect(action, &QAction::triggered, [] { - auto scriptEngines = DependencyManager::get(); QUrl defaultScriptsLoc = PathUtils::defaultScriptsLocation(); defaultScriptsLoc.setPath(defaultScriptsLoc.path() + "developer/utilities/tools/currentAPI.js"); - scriptEngines->loadScript(defaultScriptsLoc.toString()); + DependencyManager::get()->loadScript(defaultScriptsLoc.toString()); }); // Developer > Log... @@ -760,11 +770,14 @@ Menu::Menu() { qApp, SLOT(toggleLogDialog())); auto essLogAction = addActionToQMenuAndActionHash(developerMenu, MenuOption::EntityScriptServerLog, 0, qApp, SLOT(toggleEntityScriptServerLogDialog())); - QObject::connect(nodeList.data(), &NodeList::canRezChanged, essLogAction, [essLogAction] { + { auto nodeList = DependencyManager::get(); + QObject::connect(nodeList.data(), &NodeList::canRezChanged, essLogAction, [essLogAction] { + auto nodeList = DependencyManager::get(); + essLogAction->setEnabled(nodeList->getThisNodeCanRez()); + }); essLogAction->setEnabled(nodeList->getThisNodeCanRez()); - }); - essLogAction->setEnabled(nodeList->getThisNodeCanRez()); + } addActionToQMenuAndActionHash(developerMenu, "Script Log (HMD friendly)...", Qt::NoButton, qApp, SLOT(showScriptLogs())); diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 1ab7faa82b..c4ea3734f5 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -36,7 +36,6 @@ namespace MenuOption { const QString AskToResetSettings = "Ask To Reset Settings on Start"; const QString AssetMigration = "ATP Asset Migration"; const QString AssetServer = "Asset Browser"; - const QString Attachments = "Attachments..."; const QString AudioScope = "Show Scope"; const QString AudioScopeFiftyFrames = "Fifty"; const QString AudioScopeFiveFrames = "Five"; @@ -76,6 +75,7 @@ namespace MenuOption { const QString CrashOutOfBoundsVectorAccessThreaded = "Out of Bounds Vector Access (threaded)"; const QString CrashNewFault = "New Fault"; const QString CrashNewFaultThreaded = "New Fault (threaded)"; + const QString CreateEntitiesGrabbable = "Create Entities As Grabbable (except Zones, Particles, and Lights)"; const QString DeadlockInterface = "Deadlock Interface"; const QString UnresponsiveInterface = "Unresponsive Interface"; const QString DecreaseAvatarSize = "Decrease Avatar Size"; @@ -221,6 +221,8 @@ namespace MenuOption { const QString NotificationSounds = "play_notification_sounds"; const QString NotificationSoundsSnapshot = "play_notification_sounds_snapshot"; const QString NotificationSoundsTablet = "play_notification_sounds_tablet"; + const QString ForceCoarsePicking = "Force Coarse Picking"; + const QString ComputeBlendshapes = "Compute Blendshapes"; } #endif // hifi_Menu_h diff --git a/interface/src/SecondaryCamera.cpp b/interface/src/SecondaryCamera.cpp index 559cb140b5..f99a373259 100644 --- a/interface/src/SecondaryCamera.cpp +++ b/interface/src/SecondaryCamera.cpp @@ -205,7 +205,9 @@ void SecondaryCameraRenderTask::build(JobModel& task, const render::Varying& inp const auto items = task.addJob("FetchCullSort", cullFunctor, render::ItemKey::TAG_BITS_1, render::ItemKey::TAG_BITS_1); assert(items.canCast()); if (isDeferred) { - task.addJob("RenderDeferredTask", items, false); + const render::Varying cascadeSceneBBoxes; + const auto renderInput = RenderDeferredTask::Input(items, cascadeSceneBBoxes).asVarying(); + task.addJob("RenderDeferredTask", renderInput, false); } else { task.addJob("Forward", items); } diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index bb9a78d546..e9486b9def 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -14,7 +14,6 @@ #include #include -#include #include "AvatarLogging.h" @@ -45,7 +44,6 @@ #include "InterfaceLogging.h" #include "Menu.h" #include "MyAvatar.h" -#include "OtherAvatar.h" #include "SceneScriptingInterface.h" // 50 times per second - target is 45hz, but this helps account for any small deviations @@ -72,22 +70,35 @@ AvatarManager::AvatarManager(QObject* parent) : qRegisterMetaType >("NodeWeakPointer"); auto nodeList = DependencyManager::get(); - auto& packetReceiver = nodeList->getPacketReceiver(); - packetReceiver.registerListener(PacketType::BulkAvatarData, this, "processAvatarDataPacket"); - packetReceiver.registerListener(PacketType::KillAvatar, this, "processKillAvatar"); - packetReceiver.registerListener(PacketType::AvatarIdentity, this, "processAvatarIdentityPacket"); // when we hear that the user has ignored an avatar by session UUID // immediately remove that avatar instead of waiting for the absence of packets from avatar mixer - connect(nodeList.data(), &NodeList::ignoredNode, this, [=](const QUuid& nodeID, bool enabled) { + connect(nodeList.data(), &NodeList::ignoredNode, this, [this](const QUuid& nodeID, bool enabled) { if (enabled) { removeAvatar(nodeID, KillAvatarReason::AvatarIgnored); } }); } +AvatarSharedPointer AvatarManager::addAvatar(const QUuid& sessionUUID, const QWeakPointer& mixerWeakPointer) { + AvatarSharedPointer avatar = AvatarHashMap::addAvatar(sessionUUID, mixerWeakPointer); + + const auto otherAvatar = std::static_pointer_cast(avatar); + if (otherAvatar && _space) { + std::unique_lock lock(_spaceLock); + auto spaceIndex = _space->allocateID(); + otherAvatar->setSpaceIndex(spaceIndex); + workload::Sphere sphere(otherAvatar->getWorldPosition(), otherAvatar->getBoundingRadius()); + workload::Transaction transaction; + SpatiallyNestablePointer nestable = std::static_pointer_cast(otherAvatar); + transaction.reset(spaceIndex, sphere, workload::Owner(nestable)); + _space->enqueueTransaction(transaction); + } + return avatar; +} + AvatarManager::~AvatarManager() { - assert(_motionStates.empty()); + assert(_avatarsToChangeInPhysics.empty()); } void AvatarManager::init() { @@ -109,6 +120,11 @@ void AvatarManager::init() { } } +void AvatarManager::setSpace(workload::SpacePointer& space ) { + assert(!_space); + _space = space; +} + void AvatarManager::updateMyAvatar(float deltaTime) { bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings); PerformanceWarning warn(showWarnings, "AvatarManager::updateMyAvatar()"); @@ -171,16 +187,17 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { AvatarSharedPointer _avatar; }; + auto avatarMap = getHashCopy(); + AvatarHash::iterator itr = avatarMap.begin(); const auto& views = qApp->getConicalViews(); PrioritySortUtil::PriorityQueue sortedAvatars(views, AvatarData::_avatarSortCoefficientSize, AvatarData::_avatarSortCoefficientCenter, AvatarData::_avatarSortCoefficientAge); + sortedAvatars.reserve(avatarMap.size() - 1); // don't include MyAvatar // sort - auto avatarMap = getHashCopy(); - AvatarHash::iterator itr = avatarMap.begin(); while (itr != avatarMap.end()) { const auto& avatar = std::static_pointer_cast(*itr); // DO NOT update _myAvatar! Its update has already been done earlier in the main loop. @@ -190,6 +207,7 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { } ++itr; } + const auto& sortedAvatarVector = sortedAvatars.getSortedVector(); // process in sorted order uint64_t startTime = usecTimestampNow(); @@ -197,25 +215,24 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { uint64_t updateExpiry = startTime + UPDATE_BUDGET; int numAvatarsUpdated = 0; int numAVatarsNotUpdated = 0; - bool physicsEnabled = qApp->isPhysicsEnabled(); - render::Transaction transaction; - while (!sortedAvatars.empty()) { - const SortableAvatar& sortData = sortedAvatars.top(); - const auto avatar = std::static_pointer_cast(sortData.getAvatar()); - const auto otherAvatar = std::static_pointer_cast(sortData.getAvatar()); + render::Transaction renderTransaction; + workload::Transaction workloadTransaction; + for (auto it = sortedAvatarVector.begin(); it != sortedAvatarVector.end(); ++it) { + const SortableAvatar& sortData = *it; + const auto avatar = std::static_pointer_cast(sortData.getAvatar()); + // TODO: to help us scale to more avatars it would be nice to not have to poll orb state here // if the geometry is loaded then turn off the orb if (avatar->getSkeletonModel()->isLoaded()) { // remove the orb if it is there - otherAvatar->removeOrb(); + avatar->removeOrb(); } else { - otherAvatar->updateOrbPosition(); + avatar->updateOrbPosition(); } bool ignoring = DependencyManager::get()->isPersonalMutingNode(avatar->getID()); if (ignoring) { - sortedAvatars.pop(); continue; } @@ -223,18 +240,6 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { if (_shouldRender) { avatar->ensureInScene(avatar, qApp->getMain3DScene()); } - if (physicsEnabled && !avatar->isInPhysicsSimulation()) { - ShapeInfo shapeInfo; - avatar->computeShapeInfo(shapeInfo); - btCollisionShape* shape = const_cast(ObjectMotionState::getShapeManager()->getShape(shapeInfo)); - if (shape) { - AvatarMotionState* motionState = new AvatarMotionState(avatar, shape); - motionState->setMass(avatar->computeMass()); - avatar->setPhysicsCallback([=] (uint32_t flags) { motionState->addDirtyFlags(flags); }); - _motionStates.insert(avatar.get(), motionState); - _motionStatesToAddToPhysics.insert(motionState); - } - } avatar->animateScaleChanges(deltaTime); const float OUT_OF_VIEW_THRESHOLD = 0.5f * AvatarData::OUT_OF_VIEW_PENALTY; @@ -246,52 +251,42 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { numAvatarsUpdated++; } avatar->simulate(deltaTime, inView); - avatar->updateRenderItem(transaction); + avatar->updateRenderItem(renderTransaction); + avatar->updateSpaceProxy(workloadTransaction); avatar->setLastRenderUpdateTime(startTime); } else { // we've spent our full time budget --> bail on the rest of the avatar updates // --> more avatars may freeze until their priority trickles up - // --> some scale or fade animations may glitch + // --> some scale animations may glitch // --> some avatar velocity measurements may be a little off - // no time 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(); + // no time to simulate, but we take the time to count how many were tragically missed + while (it != sortedAvatarVector.end()) { + const SortableAvatar& newSortData = *it; const auto newAvatar = std::static_pointer_cast(newSortData.getAvatar()); - inView = newSortData.getPriority() > OUT_OF_VIEW_THRESHOLD; - if (inView && newAvatar->hasNewJointData()) { - numAVatarsNotUpdated++; + 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) { - if (!_avatarsToFade.empty()) { - QReadLocker lock(&_hashLock); - QVector::iterator itr = _avatarsToFade.begin(); - while (itr != _avatarsToFade.end() && usecTimestampNow() > updateExpiry) { - auto avatar = std::static_pointer_cast(*itr); - avatar->animateScaleChanges(deltaTime); - avatar->simulate(deltaTime, true); - avatar->updateRenderItem(transaction); - ++itr; - } - } - qApp->getMain3DScene()->enqueueTransaction(transaction); + qApp->getMain3DScene()->enqueueTransaction(renderTransaction); } + if (!_spaceProxiesToDelete.empty() && _space) { + std::unique_lock lock(_spaceLock); + workloadTransaction.remove(_spaceProxiesToDelete); + _spaceProxiesToDelete.clear(); + } + _space->enqueueTransaction(workloadTransaction); + _numAvatarsUpdated = numAvatarsUpdated; _numAvatarsNotUpdated = numAVatarsNotUpdated; @@ -333,16 +328,21 @@ void AvatarManager::postUpdate(float deltaTime, const render::ScenePointer& scen void AvatarManager::sendIdentityRequest(const QUuid& avatarID) const { auto nodeList = DependencyManager::get(); + QWeakPointer nodeListWeak = nodeList; nodeList->eachMatchingNode( - [&](const SharedNodePointer& node)->bool { - return node->getType() == NodeType::AvatarMixer && node->getActiveSocket(); - }, - [&](const SharedNodePointer& node) { - auto packet = NLPacket::create(PacketType::AvatarIdentityRequest, NUM_BYTES_RFC4122_UUID, true); - packet->write(avatarID.toRfc4122()); - nodeList->sendPacket(std::move(packet), *node); - ++_identityRequestsSent; - }); + [](const SharedNodePointer& node)->bool { + return node->getType() == NodeType::AvatarMixer && node->getActiveSocket(); + }, + [this, avatarID, nodeListWeak](const SharedNodePointer& node) { + auto nodeList = nodeListWeak.lock(); + if (nodeList) { + auto packet = NLPacket::create(PacketType::AvatarIdentityRequest, NUM_BYTES_RFC4122_UUID, true); + packet->write(avatarID.toRfc4122()); + nodeList->sendPacket(std::move(packet), *node); + ++_identityRequestsSent; + } + } + ); } void AvatarManager::simulateAvatarFades(float deltaTime) { @@ -353,42 +353,85 @@ void AvatarManager::simulateAvatarFades(float deltaTime) { QReadLocker locker(&_hashLock); QVector::iterator avatarItr = _avatarsToFade.begin(); const render::ScenePointer& scene = qApp->getMain3DScene(); + render::Transaction transaction; while (avatarItr != _avatarsToFade.end()) { auto avatar = std::static_pointer_cast(*avatarItr); avatar->updateFadingStatus(scene); if (!avatar->isFading()) { // fading to zero is such a rare event we push a unique transaction for each if (avatar->isInScene()) { - render::Transaction transaction; avatar->removeFromScene(*avatarItr, scene, transaction); - scene->enqueueTransaction(transaction); } avatarItr = _avatarsToFade.erase(avatarItr); } else { - const bool inView = true; // HACK - avatar->simulate(deltaTime, inView); ++avatarItr; } } + scene->enqueueTransaction(transaction); } AvatarSharedPointer AvatarManager::newSharedAvatar() { return AvatarSharedPointer(new OtherAvatar(qApp->thread()), [](OtherAvatar* ptr) { ptr->deleteLater(); }); } -void AvatarManager::handleRemovedAvatar(const AvatarSharedPointer& removedAvatar, KillAvatarReason removalReason) { - AvatarHashMap::handleRemovedAvatar(removedAvatar, removalReason); +void AvatarManager::queuePhysicsChange(const OtherAvatarPointer& avatar) { + _avatarsToChangeInPhysics.insert(avatar); +} - // remove from physics - auto avatar = std::static_pointer_cast(removedAvatar); - avatar->setPhysicsCallback(nullptr); - AvatarMotionStateMap::iterator itr = _motionStates.find(avatar.get()); - if (itr != _motionStates.end()) { - AvatarMotionState* motionState = *itr; - _motionStatesToAddToPhysics.remove(motionState); - _motionStatesToRemoveFromPhysics.push_back(motionState); - _motionStates.erase(itr); +void AvatarManager::buildPhysicsTransaction(PhysicsEngine::Transaction& transaction) { + SetOfOtherAvatars failedShapeBuilds; + for (auto avatar : _avatarsToChangeInPhysics) { + bool isInPhysics = avatar->isInPhysicsSimulation(); + if (isInPhysics != avatar->shouldBeInPhysicsSimulation()) { + if (isInPhysics) { + transaction.objectsToRemove.push_back(avatar->_motionState); + avatar->_motionState = nullptr; + } else { + ShapeInfo shapeInfo; + avatar->computeShapeInfo(shapeInfo); + btCollisionShape* shape = const_cast(ObjectMotionState::getShapeManager()->getShape(shapeInfo)); + if (shape) { + AvatarMotionState* motionState = new AvatarMotionState(avatar, shape); + motionState->setMass(avatar->computeMass()); + avatar->_motionState = motionState; + transaction.objectsToAdd.push_back(motionState); + } else { + failedShapeBuilds.insert(avatar); + } + } + } else if (isInPhysics) { + transaction.objectsToChange.push_back(avatar->_motionState); + } } + _avatarsToChangeInPhysics.swap(failedShapeBuilds); +} + +void AvatarManager::handleProcessedPhysicsTransaction(PhysicsEngine::Transaction& transaction) { + // things on objectsToChange correspond to failed changes + // so we push them back onto _avatarsToChangeInPhysics + for (auto object : transaction.objectsToChange) { + AvatarMotionState* motionState = static_cast(object); + assert(motionState); + assert(motionState->_avatar); + _avatarsToChangeInPhysics.insert(motionState->_avatar); + } + // things on objectsToRemove are ready for delete + for (auto object : transaction.objectsToRemove) { + delete object; + } + transaction.clear(); +} + +void AvatarManager::handleRemovedAvatar(const AvatarSharedPointer& removedAvatar, KillAvatarReason removalReason) { + auto avatar = std::static_pointer_cast(removedAvatar); + { + std::unique_lock lock(_spaceLock); + _spaceProxiesToDelete.push_back(avatar->getSpaceIndex()); + } + AvatarHashMap::handleRemovedAvatar(avatar, removalReason); + + avatar->die(); + queuePhysicsChange(avatar); if (removalReason == KillAvatarReason::TheirAvatarEnteredYourBubble) { emit DependencyManager::get()->enteredIgnoreRadius(); @@ -412,9 +455,6 @@ void AvatarManager::clearOtherAvatars() { while (avatarIterator != _avatarHash.end()) { auto avatar = std::static_pointer_cast(avatarIterator.value()); if (avatar != _myAvatar) { - if (avatar->isInScene()) { - avatar->removeFromScene(avatar, scene, transaction); - } handleRemovedAvatar(avatar); avatarIterator = _avatarHash.erase(avatarIterator); } else { @@ -427,48 +467,15 @@ void AvatarManager::clearOtherAvatars() { } void AvatarManager::deleteAllAvatars() { - assert(_motionStates.empty()); // should have called clearOtherAvatars() before getting here - deleteMotionStates(); + assert(_avatarsToChangeInPhysics.empty()); QReadLocker locker(&_hashLock); AvatarHash::iterator avatarIterator = _avatarHash.begin(); while (avatarIterator != _avatarHash.end()) { - auto avatar = std::static_pointer_cast(avatarIterator.value()); + auto avatar = std::static_pointer_cast(avatarIterator.value()); avatarIterator = _avatarHash.erase(avatarIterator); avatar->die(); - } -} - -void AvatarManager::deleteMotionStates() { - // delete motionstates that were removed from physics last frame - for (auto state : _motionStatesToDelete) { - delete state; - } - _motionStatesToDelete.clear(); -} - -void AvatarManager::getObjectsToRemoveFromPhysics(VectorOfMotionStates& result) { - deleteMotionStates(); - result = _motionStatesToRemoveFromPhysics; - _motionStatesToDelete.swap(_motionStatesToRemoveFromPhysics); -} - -void AvatarManager::getObjectsToAddToPhysics(VectorOfMotionStates& result) { - result.clear(); - for (auto motionState : _motionStatesToAddToPhysics) { - result.push_back(motionState); - } - _motionStatesToAddToPhysics.clear(); -} - -void AvatarManager::getObjectsToChange(VectorOfMotionStates& result) { - result.clear(); - AvatarMotionStateMap::iterator motionStateItr = _motionStates.begin(); - while (motionStateItr != _motionStates.end()) { - if ((*motionStateItr)->getIncomingDirtyFlags() != 0) { - result.push_back(*motionStateItr); - } - ++motionStateItr; + assert(!avatar->_motionState); } } @@ -570,8 +577,14 @@ RayToAvatarIntersectionResult AvatarManager::findRayIntersectionVector(const Pic return result; } - glm::vec3 normDirection = glm::normalize(ray.direction); + // It's better to intersect the ray against the avatar's actual mesh, but this is currently difficult to + // do, because the transformed mesh data only exists over in GPU-land. As a compromise, this code + // intersects against the avatars capsule and then against the (T-pose) mesh. The end effect is that picking + // against the avatar is sort-of right, but you likely wont be able to pick against the arms. + // TODO -- find a way to extract transformed avatar mesh data from the rendering engine. + + std::vector sortedAvatars; auto avatarHashCopy = getHashCopy(); for (auto avatarData : avatarHashCopy) { auto avatar = std::static_pointer_cast(avatarData); @@ -580,50 +593,159 @@ 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.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; +} + +ParabolaToAvatarIntersectionResult AvatarManager::findParabolaIntersectionVector(const PickParabola& pick, + const QVector& avatarsToInclude, + const QVector& avatarsToDiscard) { + ParabolaToAvatarIntersectionResult result; + if (QThread::currentThread() != thread()) { + BLOCKING_INVOKE_METHOD(const_cast(this), "findParabolaIntersectionVector", + Q_RETURN_ARG(ParabolaToAvatarIntersectionResult, result), + Q_ARG(const PickParabola&, pick), + Q_ARG(const QVector&, avatarsToInclude), + Q_ARG(const QVector&, avatarsToDiscard)); + return result; + } + + // It's better to intersect the ray against the avatar's actual mesh, but this is currently difficult to + // do, because the transformed mesh data only exists over in GPU-land. As a compromise, this code + // intersects against the avatars capsule and then against the (T-pose) mesh. The end effect is that picking + // against the avatar is sort-of right, but you likely wont be able to pick against the arms. + + // TODO -- find a way to extract transformed avatar mesh data from the rendering engine. + + std::vector sortedAvatars; + auto avatarHashCopy = getHashCopy(); + for (auto avatarData : avatarHashCopy) { + auto avatar = std::static_pointer_cast(avatarData); + if ((avatarsToInclude.size() > 0 && !avatarsToInclude.contains(avatar->getID())) || + (avatarsToDiscard.size() > 0 && avatarsToDiscard.contains(avatar->getID()))) { + continue; + } + + float distance = FLT_MAX; +#if 0 + // if we weren't picking against the capsule, we would want to pick against the avatarBounds... + 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); + 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; + 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; + } + } + } + + if (result.intersects) { + result.intersection = pick.origin + pick.velocity * result.parabolicDistance + 0.5f * pick.acceleration * result.parabolicDistance * result.parabolicDistance; + result.distance = glm::distance(pick.origin, result.intersection); } return result; diff --git a/interface/src/avatar/AvatarManager.h b/interface/src/avatar/AvatarManager.h index 53461146e5..bcdfc064bd 100644 --- a/interface/src/avatar/AvatarManager.h +++ b/interface/src/avatar/AvatarManager.h @@ -12,6 +12,8 @@ #ifndef hifi_AvatarManager_h #define hifi_AvatarManager_h +#include + #include #include #include @@ -23,9 +25,13 @@ #include #include #include +#include #include "AvatarMotionState.h" #include "MyAvatar.h" +#include "OtherAvatar.h" + +using SortedAvatar = std::pair>; /**jsdoc * The AvatarManager API has properties and methods which manage Avatars within the same domain. @@ -62,6 +68,7 @@ public: virtual ~AvatarManager(); void init(); + void setSpace(workload::SpacePointer& space ); std::shared_ptr getMyAvatar() { return _myAvatar; } glm::vec3 getMyAvatarPosition() const { return _myAvatar->getWorldPosition(); } @@ -92,6 +99,7 @@ public: void getObjectsToRemoveFromPhysics(VectorOfMotionStates& motionStates); void getObjectsToAddToPhysics(VectorOfMotionStates& motionStates); void getObjectsToChange(VectorOfMotionStates& motionStates); + void handleChangedMotionStates(const VectorOfMotionStates& motionStates); void handleCollisionEvents(const CollisionEvents& collisionEvents); @@ -102,23 +110,21 @@ public: * @returns {number} */ Q_INVOKABLE float getAvatarDataRate(const QUuid& sessionID, const QString& rateName = QString("")) const; - + /**jsdoc * @function AvatarManager.getAvatarUpdateRate * @param {Uuid} sessionID * @param {string} [rateName=""] * @returns {number} */ - Q_INVOKABLE float getAvatarUpdateRate(const QUuid& sessionID, const QString& rateName = QString("")) const; - + /**jsdoc * @function AvatarManager.getAvatarSimulationRate * @param {Uuid} sessionID * @param {string} [rateName=""] * @returns {number} */ - Q_INVOKABLE float getAvatarSimulationRate(const QUuid& sessionID, const QString& rateName = QString("")) const; /**jsdoc @@ -142,6 +148,10 @@ public: const QVector& avatarsToInclude, const QVector& avatarsToDiscard); + Q_INVOKABLE ParabolaToAvatarIntersectionResult findParabolaIntersectionVector(const PickParabola& pick, + const QVector& avatarsToInclude, + const QVector& avatarsToDiscard); + /**jsdoc * @function AvatarManager.getAvatarSortCoefficient * @param {string} name @@ -149,7 +159,7 @@ public: */ // TODO: remove this HACK once we settle on optimal default sort coefficients Q_INVOKABLE float getAvatarSortCoefficient(const QString& name); - + /**jsdoc * @function AvatarManager.setAvatarSortCoefficient * @param {string} name @@ -171,14 +181,20 @@ public: float getMyAvatarSendRate() const { return _myAvatarSendRate.rate(); } int getIdentityRequestsSent() const { return _identityRequestsSent; } -public slots: + void queuePhysicsChange(const OtherAvatarPointer& avatar); + void buildPhysicsTransaction(PhysicsEngine::Transaction& transaction); + void handleProcessedPhysicsTransaction(PhysicsEngine::Transaction& transaction); +public slots: /**jsdoc * @function AvatarManager.updateAvatarRenderStatus * @param {boolean} shouldRenderAvatars */ void updateAvatarRenderStatus(bool shouldRenderAvatars); +protected: + AvatarSharedPointer addAvatar(const QUuid& sessionUUID, const QWeakPointer& mixerWeakPointer) override; + private: explicit AvatarManager(QObject* parent = 0); explicit AvatarManager(const AvatarManager& other); @@ -186,16 +202,12 @@ private: void simulateAvatarFades(float deltaTime); AvatarSharedPointer newSharedAvatar() override; - void deleteMotionStates(); void handleRemovedAvatar(const AvatarSharedPointer& removedAvatar, KillAvatarReason removalReason = KillAvatarReason::NoReason) override; QVector _avatarsToFade; - using AvatarMotionStateMap = QMap; - AvatarMotionStateMap _motionStates; - VectorOfMotionStates _motionStatesToRemoveFromPhysics; - VectorOfMotionStates _motionStatesToDelete; - SetOfMotionStates _motionStatesToAddToPhysics; + using SetOfOtherAvatars = std::set; + SetOfOtherAvatars _avatarsToChangeInPhysics; std::shared_ptr _myAvatar; quint64 _lastSendAvatarDataTime = 0; // Controls MyAvatar send data rate. @@ -208,6 +220,10 @@ private: float _avatarSimulationTime { 0.0f }; bool _shouldRender { true }; mutable int _identityRequestsSent { 0 }; + + mutable std::mutex _spaceLock; + workload::SpacePointer _space; + std::vector _spaceProxiesToDelete; }; #endif // hifi_AvatarManager_h diff --git a/interface/src/avatar/AvatarMotionState.cpp b/interface/src/avatar/AvatarMotionState.cpp index 50c715b14a..ca67f634c8 100644 --- a/interface/src/avatar/AvatarMotionState.cpp +++ b/interface/src/avatar/AvatarMotionState.cpp @@ -16,9 +16,10 @@ #include -AvatarMotionState::AvatarMotionState(AvatarSharedPointer avatar, const btCollisionShape* shape) : ObjectMotionState(shape), _avatar(avatar) { +AvatarMotionState::AvatarMotionState(OtherAvatarPointer avatar, const btCollisionShape* shape) : ObjectMotionState(shape), _avatar(avatar) { assert(_avatar); _type = MOTIONSTATE_TYPE_AVATAR; + cacheShapeDiameter(); } void AvatarMotionState::handleEasyChanges(uint32_t& flags) { @@ -56,10 +57,7 @@ PhysicsMotionType AvatarMotionState::computePhysicsMotionType() const { // virtual and protected const btCollisionShape* AvatarMotionState::computeNewShape() { ShapeInfo shapeInfo; - std::static_pointer_cast(_avatar)->computeShapeInfo(shapeInfo); - glm::vec3 halfExtents = shapeInfo.getHalfExtents(); - halfExtents.y = 0.0f; - _diameter = 2.0f * glm::length(halfExtents); + _avatar->computeShapeInfo(shapeInfo); return getShapeManager()->getShape(shapeInfo); } @@ -98,6 +96,10 @@ void AvatarMotionState::setWorldTransform(const btTransform& worldTrans) { btVector3 velocity = glmToBullet(getObjectLinearVelocity()) + (1.0f / SPRING_TIMESCALE) * offsetToTarget; _body->setLinearVelocity(velocity); _body->setAngularVelocity(glmToBullet(getObjectAngularVelocity())); + // slam its rotation + btTransform newTransform = worldTrans; + newTransform.setRotation(glmToBullet(getObjectRotation())); + _body->setWorldTransform(newTransform); } } @@ -141,12 +143,15 @@ glm::vec3 AvatarMotionState::getObjectLinearVelocity() const { // virtual glm::vec3 AvatarMotionState::getObjectAngularVelocity() const { - return _avatar->getWorldAngularVelocity(); + // HACK: avatars use a capusle collision shape and their angularVelocity in the local simulation is unimportant. + // Therefore, as optimization toward support for larger crowds we ignore it and return zero. + //return _avatar->getWorldAngularVelocity(); + return glm::vec3(0.0f); } // virtual glm::vec3 AvatarMotionState::getObjectGravity() const { - return std::static_pointer_cast(_avatar)->getAcceleration(); + return _avatar->getAcceleration(); } // virtual @@ -171,6 +176,31 @@ void AvatarMotionState::computeCollisionGroupAndMask(int32_t& group, int32_t& ma // virtual float AvatarMotionState::getMass() const { - return std::static_pointer_cast(_avatar)->computeMass(); + return _avatar->computeMass(); } +void AvatarMotionState::cacheShapeDiameter() { + if (_shape) { + // shape is capsuleY + btVector3 aabbMin, aabbMax; + btTransform transform; + transform.setIdentity(); + _shape->getAabb(transform, aabbMin, aabbMax); + _diameter = (aabbMax - aabbMin).getX(); + } else { + _diameter = 0.0f; + } +} + +void AvatarMotionState::setRigidBody(btRigidBody* body) { + ObjectMotionState::setRigidBody(body); + if (_body) { + // remove angular dynamics from this body + _body->setAngularFactor(0.0f); + } +} + +void AvatarMotionState::setShape(const btCollisionShape* shape) { + ObjectMotionState::setShape(shape); + cacheShapeDiameter(); +} diff --git a/interface/src/avatar/AvatarMotionState.h b/interface/src/avatar/AvatarMotionState.h index 9228641b25..2533c11d56 100644 --- a/interface/src/avatar/AvatarMotionState.h +++ b/interface/src/avatar/AvatarMotionState.h @@ -14,14 +14,14 @@ #include -#include #include #include +#include "OtherAvatar.h" class AvatarMotionState : public ObjectMotionState { public: - AvatarMotionState(AvatarSharedPointer avatar, const btCollisionShape* shape); + AvatarMotionState(OtherAvatarPointer avatar, const btCollisionShape* shape); virtual void handleEasyChanges(uint32_t& flags) override; virtual bool handleHardAndEasyChanges(uint32_t& flags, PhysicsEngine* engine) override; @@ -74,6 +74,10 @@ public: friend class Avatar; protected: + void setRigidBody(btRigidBody* body) override; + void setShape(const btCollisionShape* shape) override; + void cacheShapeDiameter(); + // the dtor had been made protected to force the compiler to verify that it is only // ever called by the Avatar class dtor. ~AvatarMotionState(); @@ -81,7 +85,7 @@ protected: virtual bool isReadyToComputeShape() const override { return true; } virtual const btCollisionShape* computeNewShape() override; - AvatarSharedPointer _avatar; + OtherAvatarPointer _avatar; float _diameter { 0.0f }; uint32_t _dirtyFlags; diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index fd121055a1..e4503b4e78 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -91,8 +91,6 @@ 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 - MyAvatar::MyAvatar(QThread* thread) : Avatar(thread), _yawSpeed(YAW_SPEED_DEFAULT), @@ -105,7 +103,7 @@ MyAvatar::MyAvatar(QThread* thread) : _eyeContactTarget(LEFT_EYE), _realWorldFieldOfView("realWorldFieldOfView", DEFAULT_REAL_WORLD_FIELD_OF_VIEW_DEGREES), - _useAdvancedMovementControls("advancedMovementForHandControllersIsChecked", false), + _useAdvancedMovementControls("advancedMovementForHandControllersIsChecked", true), _smoothOrientationTimer(std::numeric_limits::max()), _smoothOrientationInitial(), _smoothOrientationTarget(), @@ -121,6 +119,7 @@ MyAvatar::MyAvatar(QThread* thread) : _audioListenerMode(FROM_HEAD), _hmdAtRestDetector(glm::vec3(0), glm::quat()) { + _clientTraitsHandler = std::unique_ptr(new ClientTraitsHandler(this)); // give the pointer to our head to inherited _headData variable from AvatarData _headData = new MyHead(this); @@ -139,6 +138,12 @@ MyAvatar::MyAvatar(QThread* thread) : auto geometry = getSkeletonModel()->getFBXGeometry(); qApp->loadAvatarScripts(geometry.scripts); _shouldLoadScripts = false; + } + // Load and convert old attachments to avatar entities + if (_oldAttachmentData.size() > 0) { + setAttachmentData(_oldAttachmentData); + _oldAttachmentData.clear(); + _attachmentData.clear(); } }); connect(_skeletonModel.get(), &Model::rigReady, this, &Avatar::rigReady); @@ -154,7 +159,7 @@ MyAvatar::MyAvatar(QThread* thread) : // connect to AddressManager signal for location jumps connect(DependencyManager::get().data(), &AddressManager::locationChangeRequired, - this, static_cast(&MyAvatar::goToLocation)); + this, static_cast(&MyAvatar::goToFeetLocation)); // handle scale constraints imposed on us by the domain-server auto& domainHandler = DependencyManager::get()->getDomainHandler(); @@ -196,6 +201,7 @@ MyAvatar::MyAvatar(QThread* thread) : connect(recorder.data(), &Recorder::recordingStateChanged, [=] { if (recorder->isRecording()) { + createRecordingIDs(); setRecordingBasis(); } else { clearRecordingBasis(); @@ -437,12 +443,29 @@ 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(); @@ -455,19 +478,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). @@ -492,14 +519,14 @@ void MyAvatar::update(float deltaTime) { // 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. - auto audio = DependencyManager::get(); + auto audio = DependencyManager::get().data(); setAudioLoudness(audio->getLastInputLoudness()); setAudioAverageLoudness(audio->getAudioAverageInputLoudness()); glm::vec3 halfBoundingBoxDimensions(_characterController.getCapsuleRadius(), _characterController.getCapsuleHalfHeight(), _characterController.getCapsuleRadius()); // This might not be right! Isn't the capsule local offset in avatar space? -HRS 5/26/17 halfBoundingBoxDimensions += _characterController.getCapsuleLocalOffset(); - QMetaObject::invokeMethod(audio.data(), "setAvatarBoundingBoxParameters", + QMetaObject::invokeMethod(audio, "setAvatarBoundingBoxParameters", Q_ARG(glm::vec3, (getWorldPosition() - halfBoundingBoxDimensions)), Q_ARG(glm::vec3, (halfBoundingBoxDimensions*2.0f))); @@ -507,6 +534,8 @@ void MyAvatar::update(float deltaTime) { sendIdentityPacket(); } + _clientTraitsHandler->sendChangedTraitsToMixer(); + simulate(deltaTime); currentEnergy += energyChargeRate; @@ -577,9 +606,11 @@ void MyAvatar::updateChildCauterization(SpatiallyNestablePointer object, bool ca void MyAvatar::simulate(float deltaTime) { PerformanceTimer perfTimer("simulate"); - + animateScaleChanges(deltaTime); + setFlyingEnabled(getFlyingEnabled()); + if (_cauterizationNeedsUpdate) { _cauterizationNeedsUpdate = false; @@ -724,16 +755,18 @@ void MyAvatar::simulate(float deltaTime) { properties.setQueryAACubeDirty(); properties.setLastEdited(now); - packetSender->queueEditEntityMessage(PacketType::EntityEdit, entityTree, entity->getID(), properties); + packetSender->queueEditEntityMessage(PacketType::EntityEdit, entityTree, + entity->getID(), properties); entity->setLastBroadcast(usecTimestampNow()); entity->forEachDescendant([&](SpatiallyNestablePointer descendant) { - EntityItemPointer entityDescendant = std::static_pointer_cast(descendant); - if (!entityDescendant->getClientOnly() && descendant->updateQueryAACube()) { + EntityItemPointer entityDescendant = std::dynamic_pointer_cast(descendant); + if (entityDescendant && !entityDescendant->getClientOnly() && descendant->updateQueryAACube()) { EntityItemProperties descendantProperties; descendantProperties.setQueryAACube(descendant->getQueryAACube()); descendantProperties.setLastEdited(now); - packetSender->queueEditEntityMessage(PacketType::EntityEdit, entityTree, entityDescendant->getID(), descendantProperties); + packetSender->queueEditEntityMessage(PacketType::EntityEdit, entityTree, + entityDescendant->getID(), descendantProperties); entityDescendant->setLastBroadcast(now); // for debug/physics status icons } }); @@ -783,6 +816,47 @@ void MyAvatar::updateFromHMDSensorMatrix(const glm::mat4& hmdSensorMatrix) { } } +// Find the vector halfway between the hip to hand azimuth vectors +// This midpoint hand azimuth is in Avatar space +glm::vec2 MyAvatar::computeHandAzimuth() const { + controller::Pose leftHandPoseAvatarSpace = getLeftHandPose(); + controller::Pose rightHandPoseAvatarSpace = getRightHandPose(); + controller::Pose headPoseAvatarSpace = getControllerPoseInAvatarFrame(controller::Action::HEAD); + const float HALFWAY = 0.50f; + glm::vec2 latestHipToHandController = _hipToHandController; + + if (leftHandPoseAvatarSpace.isValid() && rightHandPoseAvatarSpace.isValid() && headPoseAvatarSpace.isValid()) { + // we need the old azimuth reading to prevent flipping the facing direction 180 + // in the case where the hands go from being slightly less than 180 apart to slightly more than 180 apart. + glm::vec2 oldAzimuthReading = _hipToHandController; + if ((glm::length(glm::vec2(rightHandPoseAvatarSpace.translation.x, rightHandPoseAvatarSpace.translation.z)) > 0.0f) && (glm::length(glm::vec2(leftHandPoseAvatarSpace.translation.x, leftHandPoseAvatarSpace.translation.z)) > 0.0f)) { + latestHipToHandController = lerp(glm::normalize(glm::vec2(rightHandPoseAvatarSpace.translation.x, rightHandPoseAvatarSpace.translation.z)), glm::normalize(glm::vec2(leftHandPoseAvatarSpace.translation.x, leftHandPoseAvatarSpace.translation.z)), HALFWAY); + } else { + latestHipToHandController = glm::vec2(0.0f, -1.0f); + } + + glm::vec3 headLookAtAvatarSpace = transformVectorFast(headPoseAvatarSpace.getMatrix(), glm::vec3(0.0f, 0.0f, 1.0f)); + glm::vec2 headAzimuthAvatarSpace = glm::vec2(headLookAtAvatarSpace.x, headLookAtAvatarSpace.z); + if (glm::length(headAzimuthAvatarSpace) > 0.0f) { + headAzimuthAvatarSpace = glm::normalize(headAzimuthAvatarSpace); + } else { + headAzimuthAvatarSpace = -latestHipToHandController; + } + + // check the angular distance from forward and back + float cosForwardAngle = glm::dot(latestHipToHandController, oldAzimuthReading); + float cosHeadShoulder = glm::dot(-latestHipToHandController, headAzimuthAvatarSpace); + // if we are now closer to the 180 flip of the previous chest forward + // then we negate our computed latestHipToHandController to keep the chest from flipping. + // also check the head to shoulder azimuth difference if we negate. + // don't negate the chest azimuth if this is greater than 100 degrees. + if ((cosForwardAngle < 0.0f) && !(cosHeadShoulder < -0.2f)) { + latestHipToHandController = -latestHipToHandController; + } + } + return latestHipToHandController; +} + void MyAvatar::updateJointFromController(controller::Action poseKey, ThreadSafeValueCache& matrixCache) { assert(QThread::currentThread() == thread()); auto userInputMapper = DependencyManager::get(); @@ -1131,7 +1205,7 @@ void MyAvatar::saveData() { settings.setValue("collisionSoundURL", _collisionSoundURL); settings.setValue("useSnapTurn", _useSnapTurn); settings.setValue("userHeight", getUserHeight()); - settings.setValue("enabledFlying", getFlyingEnabled()); + settings.setValue("flyingHMD", getFlyingHMDPref()); settings.endGroup(); } @@ -1245,7 +1319,6 @@ void MyAvatar::loadData() { useFullAvatarURL(_fullAvatarURLFromPreferences, _fullAvatarModelName); - QVector attachmentData; int attachmentCount = settings.beginReadArray("attachmentData"); for (int i = 0; i < attachmentCount; i++) { settings.setArrayIndex(i); @@ -1262,10 +1335,10 @@ void MyAvatar::loadData() { attachment.rotation = glm::quat(eulers); attachment.scale = loadSetting(settings, "scale", 1.0f); attachment.isSoft = settings.value("isSoft").toBool(); - attachmentData.append(attachment); + // old attachments are stored and loaded/converted later when rig is ready + _oldAttachmentData.append(attachment); } settings.endArray(); - setAttachmentData(attachmentData); int avatarEntityCount = settings.beginReadArray("avatarEntityData"); for (int i = 0; i < avatarEntityCount; i++) { @@ -1280,8 +1353,12 @@ void MyAvatar::loadData() { // HACK: manually remove empty 'avatarEntityData' else legacy data may persist in settings file settings.remove("avatarEntityData"); } - setAvatarEntityDataChanged(true); - setFlyingEnabled(settings.value("enabledFlying").toBool()); + + // Flying preferences must be loaded before calling setFlyingEnabled() + Setting::Handle firstRunVal { Settings::firstRun, true }; + setFlyingHMDPref(firstRunVal.get() ? false : settings.value("flyingHMD").toBool()); + setFlyingEnabled(getFlyingEnabled()); + setDisplayName(settings.value("displayName").toString()); setCollisionSoundURL(settings.value("collisionSoundURL", DEFAULT_AVATAR_COLLISION_SOUND_URL).toString()); setSnapTurn(settings.value("useSnapTurn", _useSnapTurn).toBool()); @@ -1485,50 +1562,126 @@ void MyAvatar::setJointRotations(const QVector& jointRotations) { } void MyAvatar::setJointData(int index, const glm::quat& rotation, const glm::vec3& translation) { - if (QThread::currentThread() != thread()) { - QMetaObject::invokeMethod(this, "setJointData", Q_ARG(int, index), Q_ARG(const glm::quat&, rotation), - Q_ARG(const glm::vec3&, translation)); - return; + switch (index) { + case FARGRAB_RIGHTHAND_INDEX: { + _farGrabRightMatrixCache.set(createMatFromQuatAndPos(rotation, translation)); + break; + } + case FARGRAB_LEFTHAND_INDEX: { + _farGrabLeftMatrixCache.set(createMatFromQuatAndPos(rotation, translation)); + break; + } + case FARGRAB_MOUSE_INDEX: { + _farGrabMouseMatrixCache.set(createMatFromQuatAndPos(rotation, translation)); + break; + } + default: { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "setJointData", Q_ARG(int, index), Q_ARG(const glm::quat&, rotation), + Q_ARG(const glm::vec3&, translation)); + return; + } + // HACK: ATM only JS scripts call setJointData() on MyAvatar so we hardcode the priority + _skeletonModel->getRig().setJointState(index, true, rotation, translation, SCRIPT_PRIORITY); + } } - // HACK: ATM only JS scripts call setJointData() on MyAvatar so we hardcode the priority - _skeletonModel->getRig().setJointState(index, true, rotation, translation, SCRIPT_PRIORITY); } void MyAvatar::setJointRotation(int index, const glm::quat& rotation) { - if (QThread::currentThread() != thread()) { - QMetaObject::invokeMethod(this, "setJointRotation", Q_ARG(int, index), Q_ARG(const glm::quat&, rotation)); - return; + switch (index) { + case FARGRAB_RIGHTHAND_INDEX: { + glm::mat4 prevMat = _farGrabRightMatrixCache.get(); + glm::vec3 previousTranslation = extractTranslation(prevMat); + _farGrabRightMatrixCache.set(createMatFromQuatAndPos(rotation, previousTranslation)); + break; + } + case FARGRAB_LEFTHAND_INDEX: { + glm::mat4 prevMat = _farGrabLeftMatrixCache.get(); + glm::vec3 previousTranslation = extractTranslation(prevMat); + _farGrabLeftMatrixCache.set(createMatFromQuatAndPos(rotation, previousTranslation)); + break; + } + case FARGRAB_MOUSE_INDEX: { + glm::mat4 prevMat = _farGrabMouseMatrixCache.get(); + glm::vec3 previousTranslation = extractTranslation(prevMat); + _farGrabMouseMatrixCache.set(createMatFromQuatAndPos(rotation, previousTranslation)); + break; + } + default: { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "setJointRotation", Q_ARG(int, index), Q_ARG(const glm::quat&, rotation)); + return; + } + // HACK: ATM only JS scripts call setJointData() on MyAvatar so we hardcode the priority + _skeletonModel->getRig().setJointRotation(index, true, rotation, SCRIPT_PRIORITY); + } } - // HACK: ATM only JS scripts call setJointData() on MyAvatar so we hardcode the priority - _skeletonModel->getRig().setJointRotation(index, true, rotation, SCRIPT_PRIORITY); } void MyAvatar::setJointTranslation(int index, const glm::vec3& translation) { - if (QThread::currentThread() != thread()) { - QMetaObject::invokeMethod(this, "setJointTranslation", Q_ARG(int, index), Q_ARG(const glm::vec3&, translation)); - return; + switch (index) { + case FARGRAB_RIGHTHAND_INDEX: { + glm::mat4 prevMat = _farGrabRightMatrixCache.get(); + glm::quat previousRotation = extractRotation(prevMat); + _farGrabRightMatrixCache.set(createMatFromQuatAndPos(previousRotation, translation)); + break; + } + case FARGRAB_LEFTHAND_INDEX: { + glm::mat4 prevMat = _farGrabLeftMatrixCache.get(); + glm::quat previousRotation = extractRotation(prevMat); + _farGrabLeftMatrixCache.set(createMatFromQuatAndPos(previousRotation, translation)); + break; + } + case FARGRAB_MOUSE_INDEX: { + glm::mat4 prevMat = _farGrabMouseMatrixCache.get(); + glm::quat previousRotation = extractRotation(prevMat); + _farGrabMouseMatrixCache.set(createMatFromQuatAndPos(previousRotation, translation)); + break; + } + default: { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "setJointTranslation", + Q_ARG(int, index), Q_ARG(const glm::vec3&, translation)); + return; + } + // HACK: ATM only JS scripts call setJointData() on MyAvatar so we hardcode the priority + _skeletonModel->getRig().setJointTranslation(index, true, translation, SCRIPT_PRIORITY); + } } - // HACK: ATM only JS scripts call setJointData() on MyAvatar so we hardcode the priority - _skeletonModel->getRig().setJointTranslation(index, true, translation, SCRIPT_PRIORITY); } void MyAvatar::clearJointData(int index) { - if (QThread::currentThread() != thread()) { - QMetaObject::invokeMethod(this, "clearJointData", Q_ARG(int, index)); - return; + switch (index) { + case FARGRAB_RIGHTHAND_INDEX: { + _farGrabRightMatrixCache.invalidate(); + break; + } + case FARGRAB_LEFTHAND_INDEX: { + _farGrabLeftMatrixCache.invalidate(); + break; + } + case FARGRAB_MOUSE_INDEX: { + _farGrabMouseMatrixCache.invalidate(); + break; + } + default: { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "clearJointData", Q_ARG(int, index)); + return; + } + _skeletonModel->getRig().clearJointAnimationPriority(index); + } } - _skeletonModel->getRig().clearJointAnimationPriority(index); } void MyAvatar::setJointData(const QString& name, const glm::quat& rotation, const glm::vec3& translation) { if (QThread::currentThread() != thread()) { QMetaObject::invokeMethod(this, "setJointData", Q_ARG(QString, name), Q_ARG(const glm::quat&, rotation), - Q_ARG(const glm::vec3&, translation)); + Q_ARG(const glm::vec3&, translation)); return; } writeLockWithNamedJointIndex(name, [&](int index) { - // HACK: ATM only JS scripts call setJointData() on MyAvatar so we hardcode the priority - _skeletonModel->getRig().setJointState(index, true, rotation, translation, SCRIPT_PRIORITY); + setJointData(index, rotation, translation); }); } @@ -1538,8 +1691,7 @@ void MyAvatar::setJointRotation(const QString& name, const glm::quat& rotation) return; } writeLockWithNamedJointIndex(name, [&](int index) { - // HACK: ATM only JS scripts call setJointData() on MyAvatar so we hardcode the priority - _skeletonModel->getRig().setJointRotation(index, true, rotation, SCRIPT_PRIORITY); + setJointRotation(index, rotation); }); } @@ -1549,8 +1701,7 @@ void MyAvatar::setJointTranslation(const QString& name, const glm::vec3& transla return; } writeLockWithNamedJointIndex(name, [&](int index) { - // HACK: ATM only JS scripts call setJointData() on MyAvatar so we hardcode the priority - _skeletonModel->getRig().setJointTranslation(index, true, translation, SCRIPT_PRIORITY); + setJointTranslation(index, translation); }); } @@ -1560,7 +1711,7 @@ void MyAvatar::clearJointData(const QString& name) { return; } writeLockWithNamedJointIndex(name, [&](int index) { - _skeletonModel->getRig().clearJointAnimationPriority(index); + clearJointData(index); }); } @@ -1569,13 +1720,19 @@ void MyAvatar::clearJointsData() { QMetaObject::invokeMethod(this, "clearJointsData"); return; } + _farGrabRightMatrixCache.invalidate(); + _farGrabLeftMatrixCache.invalidate(); + _farGrabMouseMatrixCache.invalidate(); _skeletonModel->getRig().clearJointStates(); } void MyAvatar::setSkeletonModelURL(const QUrl& skeletonModelURL) { _skeletonModelChangeCount++; int skeletonModelChangeCount = _skeletonModelChangeCount; + + auto previousSkeletonModelURL = _skeletonModelURL; Avatar::setSkeletonModelURL(skeletonModelURL); + _skeletonModel->setTagMask(render::hifi::TAG_NONE); _skeletonModel->setGroupCulled(true); _skeletonModel->setVisibleInScene(true, qApp->getMain3DScene()); @@ -1602,21 +1759,55 @@ void MyAvatar::setSkeletonModelURL(const QUrl& skeletonModelURL) { } QObject::disconnect(*skeletonConnection); }); + saveAvatarUrl(); emit skeletonChanged(); - emit skeletonModelURLChanged(); } -void MyAvatar::removeAvatarEntities() { +bool isWearableEntity(const EntityItemPointer& entity) { + return entity->isVisible() + && (entity->getParentJointIndex() != INVALID_JOINT_INDEX + || (entity->getType() == EntityTypes::Model && (std::static_pointer_cast(entity))->getRelayParentJoints())) + && (entity->getParentID() == DependencyManager::get()->getSessionUUID() + || entity->getParentID() == AVATAR_SELF_ID); +} + +void MyAvatar::clearAvatarEntities() { auto treeRenderer = DependencyManager::get(); EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr; - if (entityTree) { - entityTree->withWriteLock([&] { - AvatarEntityMap avatarEntities = getAvatarEntityData(); - for (auto entityID : avatarEntities.keys()) { - entityTree->deleteEntity(entityID, true, true); - } + + AvatarEntityMap avatarEntities = getAvatarEntityData(); + for (auto entityID : avatarEntities.keys()) { + entityTree->withWriteLock([&entityID, &entityTree] { + // remove this entity first from the entity tree + entityTree->deleteEntity(entityID, true, true); }); + + // remove the avatar entity from our internal list + // (but indicate it doesn't need to be pulled from the tree) + clearAvatarEntity(entityID, false); + } +} + +void MyAvatar::removeWearableAvatarEntities() { + auto treeRenderer = DependencyManager::get(); + EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr; + + if (entityTree) { + AvatarEntityMap avatarEntities = getAvatarEntityData(); + for (auto entityID : avatarEntities.keys()) { + auto entity = entityTree->findEntityByID(entityID); + if (entity && isWearableEntity(entity)) { + entityTree->withWriteLock([&entityID, &entityTree] { + // remove this entity first from the entity tree + entityTree->deleteEntity(entityID, true, true); + }); + + // remove the avatar entity from our internal list + // (but indicate it doesn't need to be pulled from the tree) + clearAvatarEntity(entityID, false); + } + } } } @@ -1673,18 +1864,6 @@ void MyAvatar::useFullAvatarURL(const QUrl& fullAvatarURL, const QString& modelN setSkeletonModelURL(fullAvatarURL); UserActivityLogger::getInstance().changedModel("skeleton", urlString); } - - markIdentityDataChanged(); -} - -void MyAvatar::setAttachmentData(const QVector& attachmentData) { - if (QThread::currentThread() != thread()) { - BLOCKING_INVOKE_METHOD(this, "setAttachmentData", - Q_ARG(const QVector, attachmentData)); - return; - } - Avatar::setAttachmentData(attachmentData); - emit attachmentsChanged(); } glm::vec3 MyAvatar::getSkeletonPosition() const { @@ -1957,20 +2136,167 @@ void MyAvatar::attach(const QString& modelURL, const QString& jointName, float scale, bool isSoft, bool allowDuplicates, bool useSaved) { if (QThread::currentThread() != thread()) { - Avatar::attach(modelURL, jointName, translation, rotation, scale, isSoft, allowDuplicates, useSaved); + BLOCKING_INVOKE_METHOD(this, "attach", + Q_ARG(const QString&, modelURL), + Q_ARG(const QString&, jointName), + Q_ARG(const glm::vec3&, translation), + Q_ARG(const glm::quat&, rotation), + Q_ARG(float, scale), + Q_ARG(bool, isSoft), + Q_ARG(bool, allowDuplicates), + Q_ARG(bool, useSaved) + ); return; } - if (useSaved) { - AttachmentData attachment = loadAttachmentData(modelURL, jointName); - if (attachment.isValid()) { - Avatar::attach(modelURL, attachment.jointName, - attachment.translation, attachment.rotation, - attachment.scale, attachment.isSoft, - allowDuplicates, useSaved); - return; + AttachmentData data; + data.modelURL = modelURL; + data.jointName = jointName; + data.translation = translation; + data.rotation = rotation; + data.scale = scale; + data.isSoft = isSoft; + EntityItemProperties properties; + attachmentDataToEntityProperties(data, properties); + DependencyManager::get()->addEntity(properties, true); + emit attachmentsChanged(); +} + +void MyAvatar::detachOne(const QString& modelURL, const QString& jointName) { + if (QThread::currentThread() != thread()) { + BLOCKING_INVOKE_METHOD(this, "detachOne", + Q_ARG(const QString&, modelURL), + Q_ARG(const QString&, jointName) + ); + return; + } + QUuid entityID; + if (findAvatarEntity(modelURL, jointName, entityID)) { + DependencyManager::get()->deleteEntity(entityID); + } + emit attachmentsChanged(); +} + +void MyAvatar::detachAll(const QString& modelURL, const QString& jointName) { + if (QThread::currentThread() != thread()) { + BLOCKING_INVOKE_METHOD(this, "detachAll", + Q_ARG(const QString&, modelURL), + Q_ARG(const QString&, jointName) + ); + return; + } + QUuid entityID; + while (findAvatarEntity(modelURL, jointName, entityID)) { + DependencyManager::get()->deleteEntity(entityID); + } + emit attachmentsChanged(); +} + +void MyAvatar::setAttachmentData(const QVector& attachmentData) { + if (QThread::currentThread() != thread()) { + BLOCKING_INVOKE_METHOD(this, "setAttachmentData", + Q_ARG(const QVector&, attachmentData)); + return; + } + std::vector newEntitiesProperties; + for (auto& data : attachmentData) { + QUuid entityID; + EntityItemProperties properties; + if (findAvatarEntity(data.modelURL.toString(), data.jointName, entityID)) { + properties = DependencyManager::get()->getEntityProperties(entityID); + } + attachmentDataToEntityProperties(data, properties); + newEntitiesProperties.push_back(properties); + } + + // clear any existing avatar entities + clearAvatarEntities(); + + for (auto& properties : newEntitiesProperties) { + DependencyManager::get()->addEntity(properties, true); + } + emit attachmentsChanged(); +} + +QVector MyAvatar::getAttachmentData() const { + QVector avatarData; + auto avatarEntities = getAvatarEntityData(); + AvatarEntityMap::const_iterator dataItr = avatarEntities.begin(); + while (dataItr != avatarEntities.end()) { + QUuid entityID = dataItr.key(); + auto properties = DependencyManager::get()->getEntityProperties(entityID); + AttachmentData data = entityPropertiesToAttachmentData(properties); + avatarData.append(data); + dataItr++; + } + return avatarData; +} + +QVariantList MyAvatar::getAttachmentsVariant() const { + QVariantList result; + for (const auto& attachment : getAttachmentData()) { + result.append(attachment.toVariant()); + } + return result; +} + +void MyAvatar::setAttachmentsVariant(const QVariantList& variant) { + if (QThread::currentThread() != thread()) { + BLOCKING_INVOKE_METHOD(this, "setAttachmentsVariant", + Q_ARG(const QVariantList&, variant)); + return; + } + QVector newAttachments; + newAttachments.reserve(variant.size()); + for (const auto& attachmentVar : variant) { + AttachmentData attachment; + if (attachment.fromVariant(attachmentVar)) { + newAttachments.append(attachment); } } - Avatar::attach(modelURL, jointName, translation, rotation, scale, isSoft, allowDuplicates, useSaved); + setAttachmentData(newAttachments); +} + +bool MyAvatar::findAvatarEntity(const QString& modelURL, const QString& jointName, QUuid& entityID) { + auto avatarEntities = getAvatarEntityData(); + AvatarEntityMap::const_iterator dataItr = avatarEntities.begin(); + while (dataItr != avatarEntities.end()) { + entityID = dataItr.key(); + auto props = DependencyManager::get()->getEntityProperties(entityID); + if (props.getModelURL() == modelURL && + (jointName.isEmpty() || props.getParentJointIndex() == getJointIndex(jointName))) { + return true; + } + dataItr++; + } + return false; +} + +AttachmentData MyAvatar::entityPropertiesToAttachmentData(const EntityItemProperties& properties) const { + AttachmentData data; + data.modelURL = properties.getModelURL(); + data.translation = properties.getLocalPosition(); + data.rotation = properties.getLocalRotation(); + data.isSoft = properties.getRelayParentJoints(); + int jointIndex = (int)properties.getParentJointIndex(); + if (jointIndex > -1 && jointIndex < getJointNames().size()) { + data.jointName = getJointNames()[jointIndex]; + } + return data; +} + +void MyAvatar::attachmentDataToEntityProperties(const AttachmentData& data, EntityItemProperties& properties) { + QString url = data.modelURL.toString(); + properties.setName(QFileInfo(url).baseName()); + properties.setType(EntityTypes::Model); + properties.setParentID(AVATAR_SELF_ID); + properties.setLocalPosition(data.translation); + properties.setLocalRotation(data.rotation); + if (!data.isSoft) { + properties.setParentJointIndex(getJointIndex(data.jointName)); + } else { + properties.setRelayParentJoints(true); + } + properties.setModelURL(url); } void MyAvatar::initHeadBones() { @@ -1979,7 +2305,11 @@ void MyAvatar::initHeadBones() { neckJointIndex = _skeletonModel->getFBXGeometry().neckJointIndex; } if (neckJointIndex == -1) { - return; + neckJointIndex = (_skeletonModel->getFBXGeometry().headJointIndex - 1); + if (neckJointIndex < 0) { + // return if the head is not even there. can't cauterize!! + return; + } } _headBoneSet.clear(); std::queue q; @@ -2048,6 +2378,8 @@ void MyAvatar::initAnimGraph() { graphUrl = PathUtils::resourcesUrl("avatar/avatar-animation.json"); } + emit animGraphUrlChanged(graphUrl); + _skeletonModel->getRig().initAnimGraph(graphUrl); _currentAnimGraphUrl.set(graphUrl); connect(&(_skeletonModel->getRig()), SIGNAL(onLoadComplete()), this, SLOT(animGraphLoaded())); @@ -2323,6 +2655,23 @@ void MyAvatar::updateOrientation(float deltaTime) { } } +static float scaleSpeedByDirection(const glm::vec2 velocityDirection, const float forwardSpeed, const float backwardSpeed) { + // for the elipse function --> (x^2)/(backwardSpeed*backwardSpeed) + y^2/(forwardSpeed*forwardSpeed) = 1, scale == y^2 when x is 0 + float fwdScale = forwardSpeed * forwardSpeed; + float backScale = backwardSpeed * backwardSpeed; + float scaledX = velocityDirection.x * backwardSpeed; + float scaledSpeed = forwardSpeed; + if (velocityDirection.y < 0.0f) { + if (backScale > 0.0f) { + float yValue = sqrtf(fwdScale * (1.0f - ((scaledX * scaledX) / backScale))); + scaledSpeed = sqrtf((scaledX * scaledX) + (yValue * yValue)); + } + } else { + scaledSpeed = backwardSpeed; + } + return scaledSpeed; +} + void MyAvatar::updateActionMotor(float deltaTime) { bool thrustIsPushing = (glm::length2(_thrust) > EPSILON); bool scriptedMotorIsPushing = (_motionBehaviors & AVATAR_MOTION_SCRIPTED_MOTOR_ENABLED) @@ -2384,7 +2733,10 @@ void MyAvatar::updateActionMotor(float deltaTime) { _actionMotorVelocity = motorSpeed * direction; } else { // we're interacting with a floor --> simple horizontal speed and exponential decay - _actionMotorVelocity = getSensorToWorldScale() * (_walkSpeed.get() * _walkSpeedScalar) * direction; + const glm::vec2 currentVel = { direction.x, direction.z }; + float scaledSpeed = scaleSpeedByDirection(currentVel, _walkSpeed.get(), _walkBackwardSpeed.get()); + // _walkSpeedScalar is a multiplier if we are in sprint mode, otherwise 1.0 + _actionMotorVelocity = getSensorToWorldScale() * (scaledSpeed * _walkSpeedScalar) * direction; } float previousBoomLength = _boomLength; @@ -2610,6 +2962,49 @@ void MyAvatar::goToLocation(const QVariant& propertiesVar) { } } +void MyAvatar::goToFeetLocation(const glm::vec3& newPosition, + bool hasOrientation, const glm::quat& newOrientation, + bool shouldFaceLocation) { + + qCDebug(interfaceapp).nospace() << "MyAvatar goToFeetLocation - moving to " << newPosition.x << ", " + << newPosition.y << ", " << newPosition.z; + + ShapeInfo shapeInfo; + computeShapeInfo(shapeInfo); + glm::vec3 halfExtents = shapeInfo.getHalfExtents(); + glm::vec3 localFeetPos = shapeInfo.getOffset() - glm::vec3(0.0f, halfExtents.y + halfExtents.x, 0.0f); + glm::mat4 localFeet = createMatFromQuatAndPos(Quaternions::IDENTITY, localFeetPos); + + glm::mat4 worldFeet = createMatFromQuatAndPos(Quaternions::IDENTITY, newPosition); + + glm::mat4 avatarMat = worldFeet * glm::inverse(localFeet); + + glm::vec3 adjustedPosition = extractTranslation(avatarMat); + + _goToPending = true; + _goToPosition = adjustedPosition; + _goToOrientation = getWorldOrientation(); + if (hasOrientation) { + qCDebug(interfaceapp).nospace() << "MyAvatar goToFeetLocation - new orientation is " + << newOrientation.x << ", " << newOrientation.y << ", " << newOrientation.z << ", " << newOrientation.w; + + // orient the user to face the target + glm::quat quatOrientation = cancelOutRollAndPitch(newOrientation); + + if (shouldFaceLocation) { + quatOrientation = newOrientation * glm::angleAxis(PI, Vectors::UP); + + // move the user a couple units away + const float DISTANCE_TO_USER = 2.0f; + _goToPosition = adjustedPosition - quatOrientation * IDENTITY_FORWARD * DISTANCE_TO_USER; + } + + _goToOrientation = quatOrientation; + } + + emit transformChanged(); +} + void MyAvatar::goToLocation(const glm::vec3& newPosition, bool hasOrientation, const glm::quat& newOrientation, bool shouldFaceLocation) { @@ -2822,6 +3217,12 @@ void MyAvatar::setFlyingEnabled(bool enabled) { return; } + if (qApp->isHMDMode()) { + setFlyingHMDPref(enabled); + } else { + setFlyingDesktopPref(enabled); + } + _enableFlying = enabled; } @@ -2837,7 +3238,33 @@ bool MyAvatar::isInAir() { bool MyAvatar::getFlyingEnabled() { // May return true even if client is not allowed to fly in the zone. - return _enableFlying; + return (qApp->isHMDMode() ? getFlyingHMDPref() : getFlyingDesktopPref()); +} + +void MyAvatar::setFlyingDesktopPref(bool enabled) { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "setFlyingDesktopPref", Q_ARG(bool, enabled)); + return; + } + + _flyingPrefDesktop = enabled; +} + +bool MyAvatar::getFlyingDesktopPref() { + return _flyingPrefDesktop; +} + +void MyAvatar::setFlyingHMDPref(bool enabled) { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "setFlyingHMDPref", Q_ARG(bool, enabled)); + return; + } + + _flyingPrefHMD = enabled; +} + +bool MyAvatar::getFlyingHMDPref() { + return _flyingPrefHMD; } // Public interface for targetscale @@ -2991,6 +3418,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; @@ -3039,7 +3484,7 @@ static glm::vec3 dampenCgMovement(glm::vec3 cgUnderHeadHandsAvatarSpace, float b } // computeCounterBalance returns the center of gravity in Avatar space -glm::vec3 MyAvatar::computeCounterBalance() const { +glm::vec3 MyAvatar::computeCounterBalance() { struct JointMass { QString name; float weight; @@ -3057,7 +3502,8 @@ glm::vec3 MyAvatar::computeCounterBalance() const { JointMass cgLeftHandMass(QString("LeftHand"), DEFAULT_AVATAR_LEFTHAND_MASS, glm::vec3(0.0f, 0.0f, 0.0f)); JointMass cgRightHandMass(QString("RightHand"), DEFAULT_AVATAR_RIGHTHAND_MASS, glm::vec3(0.0f, 0.0f, 0.0f)); glm::vec3 tposeHead = DEFAULT_AVATAR_HEAD_POS; - glm::vec3 tposeHips = glm::vec3(0.0f, 0.0f, 0.0f); + glm::vec3 tposeHips = DEFAULT_AVATAR_HIPS_POS; + glm::vec3 tposeRightFoot = DEFAULT_AVATAR_RIGHTFOOT_POS; if (_skeletonModel->getRig().indexOfJoint(cgHeadMass.name) != -1) { cgHeadMass.position = getAbsoluteJointTranslationInObjectFrame(_skeletonModel->getRig().indexOfJoint(cgHeadMass.name)); @@ -3076,6 +3522,9 @@ glm::vec3 MyAvatar::computeCounterBalance() const { if (_skeletonModel->getRig().indexOfJoint("Hips") != -1) { tposeHips = getAbsoluteDefaultJointTranslationInObjectFrame(_skeletonModel->getRig().indexOfJoint("Hips")); } + if (_skeletonModel->getRig().indexOfJoint("RightFoot") != -1) { + tposeRightFoot = getAbsoluteDefaultJointTranslationInObjectFrame(_skeletonModel->getRig().indexOfJoint("RightFoot")); + } // find the current center of gravity position based on head and hand moments glm::vec3 sumOfMoments = (cgHeadMass.weight * cgHeadMass.position) + (cgLeftHandMass.weight * cgLeftHandMass.position) + (cgRightHandMass.weight * cgRightHandMass.position); @@ -3096,9 +3545,12 @@ glm::vec3 MyAvatar::computeCounterBalance() const { glm::vec3 counterBalancedCg = (1.0f / DEFAULT_AVATAR_HIPS_MASS) * counterBalancedForHead; // find the height of the hips + const float UPPER_LEG_FRACTION = 0.3333f; glm::vec3 xzDiff((cgHeadMass.position.x - counterBalancedCg.x), 0.0f, (cgHeadMass.position.z - counterBalancedCg.z)); float headMinusHipXz = glm::length(xzDiff); float headHipDefault = glm::length(tposeHead - tposeHips); + float hipFootDefault = tposeHips.y - tposeRightFoot.y; + float sitSquatThreshold = tposeHips.y - (UPPER_LEG_FRACTION * hipFootDefault); float hipHeight = 0.0f; if (headHipDefault > headMinusHipXz) { hipHeight = sqrtf((headHipDefault * headHipDefault) - (headMinusHipXz * headMinusHipXz)); @@ -3110,6 +3562,10 @@ glm::vec3 MyAvatar::computeCounterBalance() const { if (counterBalancedCg.y > (tposeHips.y + 0.05f)) { // if the height is higher than default hips, clamp to default hips counterBalancedCg.y = tposeHips.y + 0.05f; + } else if (counterBalancedCg.y < sitSquatThreshold) { + //do a height reset + setResetMode(true); + _follow.activate(FollowHelper::Vertical); } return counterBalancedCg; } @@ -3159,7 +3615,7 @@ static void drawBaseOfSupport(float baseOfSupportScale, float footLocal, glm::ma // this function finds the hips position using a center of gravity model that // balances the head and hands with the hips over the base of support // returns the rotation (-z forward) and position of the Avatar in Sensor space -glm::mat4 MyAvatar::deriveBodyUsingCgModel() const { +glm::mat4 MyAvatar::deriveBodyUsingCgModel() { glm::mat4 sensorToWorldMat = getSensorToWorldMatrix(); glm::mat4 worldToSensorMat = glm::inverse(sensorToWorldMat); auto headPose = getControllerPoseInSensorFrame(controller::Action::HEAD); @@ -3177,7 +3633,7 @@ glm::mat4 MyAvatar::deriveBodyUsingCgModel() const { } // get the new center of gravity - const glm::vec3 cgHipsPosition = computeCounterBalance(); + glm::vec3 cgHipsPosition = computeCounterBalance(); // find the new hips rotation using the new head-hips axis as the up axis glm::mat4 avatarHipsMat = computeNewHipsMatrix(glmExtractRotation(avatarHeadMat), extractTranslation(avatarHeadMat), cgHipsPosition); @@ -3350,18 +3806,34 @@ float MyAvatar::getWalkSpeed() const { return _walkSpeed.get() * _walkSpeedScalar; } +float MyAvatar::getWalkBackwardSpeed() const { + return _walkSpeed.get() * _walkSpeedScalar; +} + bool MyAvatar::isReadyForPhysics() const { return qApp->isServerlessMode() || _haveReceivedHeightLimitsFromDomain; } void MyAvatar::setSprintMode(bool sprint) { - _walkSpeedScalar = sprint ? AVATAR_SPRINT_SPEED_SCALAR : AVATAR_WALK_SPEED_SCALAR; + _walkSpeedScalar = sprint ? _sprintSpeed.get() : AVATAR_WALK_SPEED_SCALAR; } void MyAvatar::setWalkSpeed(float value) { _walkSpeed.set(value); } +void MyAvatar::setWalkBackwardSpeed(float value) { + _walkBackwardSpeed.set(value); +} + +void MyAvatar::setSprintSpeed(float value) { + _sprintSpeed.set(value); +} + +float MyAvatar::getSprintSpeed() const { + return _sprintSpeed.get(); +} + QVector MyAvatar::getScriptUrls() { QVector scripts = _skeletonModel->isLoaded() ? _skeletonModel->getFBXGeometry().scripts : QVector(); return scripts; @@ -3547,7 +4019,7 @@ bool MyAvatar::FollowHelper::shouldActivateHorizontalCG(MyAvatar& myAvatar) cons glm::vec3 currentHeadPosition = currentHeadPose.getTranslation(); float anatomicalHeadToHipsDistance = glm::length(defaultHeadPosition - defaultHipsPosition); if (!isActive(Horizontal) && - (glm::length(currentHeadPosition - defaultHipsPosition) > (anatomicalHeadToHipsDistance + DEFAULT_AVATAR_SPINE_STRETCH_LIMIT))) { + (glm::length(currentHeadPosition - defaultHipsPosition) > (anatomicalHeadToHipsDistance + (DEFAULT_AVATAR_SPINE_STRETCH_LIMIT * anatomicalHeadToHipsDistance)))) { myAvatar.setResetMode(true); stepDetected = true; } @@ -3571,25 +4043,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()) { @@ -3834,7 +4313,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); } } @@ -3844,9 +4324,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); } } @@ -3858,7 +4339,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); } } @@ -3870,7 +4351,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); } } @@ -3882,7 +4363,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); } } @@ -3894,11 +4375,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) { @@ -3906,7 +4386,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); } } @@ -3917,7 +4397,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); } } @@ -3928,7 +4408,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); } } @@ -3939,7 +4419,7 @@ glm::mat4 MyAvatar::getLeftHandCalibrationMat() const { auto leftHandRot = getAbsoluteDefaultJointRotationInObjectFrame(leftHandIndex); return createMatFromQuatAndPos(leftHandRot, leftHandPos / getSensorToWorldScale()); } else { - return createMatFromQuatAndPos(DEFAULT_AVATAR_LEFTHAND_ROT, DEFAULT_AVATAR_LEFTHAND_POS / getSensorToWorldScale()); + return createMatFromQuatAndPos(DEFAULT_AVATAR_LEFTHAND_ROT, DEFAULT_AVATAR_LEFTHAND_POS); } } diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 9b5ddd360d..06267b3819 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -18,21 +18,22 @@ #include -#include -#include -#include -#include - -#include -#include #include #include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include "AtRestDetector.h" #include "MyCharacterController.h" #include "RingBufferHistory.h" -#include class AvatarActionHold; class ModelItemID; @@ -137,12 +138,14 @@ class MyAvatar : public Avatar { * where MyAvatar.sessionUUID is not available (e.g., if not connected to a domain). Note: Likely to be deprecated. * Read-only. * @property {number} walkSpeed + * @property {number} walkBackwardSpeed + * @property {number} sprintSpeed * * @property {Vec3} skeletonOffset - Can be used to apply a translation offset between the avatar's position and the * registration point of the 3D model. * * @property {Vec3} position - * @property {number} scale + * @property {number} scale - Returns the clamped scale of the avatar. * @property {number} density Read-only. * @property {Vec3} handPosition * @property {number} bodyYaw - The rotation left or right about an axis running from the head to the feet of the avatar. @@ -198,6 +201,8 @@ class MyAvatar : public Avatar { Q_PROPERTY(bool hasAudioEnabledFaceMovement READ getHasAudioEnabledFaceMovement WRITE setHasAudioEnabledFaceMovement) Q_PROPERTY(float rotationRecenterFilterLength READ getRotationRecenterFilterLength WRITE setRotationRecenterFilterLength) Q_PROPERTY(float rotationThreshold READ getRotationThreshold WRITE setRotationThreshold) + Q_PROPERTY(bool enableStepResetRotation READ getEnableStepResetRotation WRITE setEnableStepResetRotation) + Q_PROPERTY(bool enableDrawAverageFacing READ getEnableDrawAverageFacing WRITE setEnableDrawAverageFacing) //TODO: make gravity feature work Q_PROPERTY(glm::vec3 gravity READ getGravity WRITE setGravity) Q_PROPERTY(glm::vec3 leftHandPosition READ getLeftHandPosition) @@ -232,6 +237,8 @@ class MyAvatar : public Avatar { Q_PROPERTY(QUuid SELF_ID READ getSelfID CONSTANT) Q_PROPERTY(float walkSpeed READ getWalkSpeed WRITE setWalkSpeed); + Q_PROPERTY(float walkBackwardSpeed READ getWalkBackwardSpeed WRITE setWalkBackwardSpeed); + Q_PROPERTY(float sprintSpeed READ getSprintSpeed WRITE setSprintSpeed); const QString DOMINANT_LEFT_HAND = "left"; const QString DOMINANT_RIGHT_HAND = "right"; @@ -313,6 +320,9 @@ public: // as it moves through the world. void updateFromHMDSensorMatrix(const glm::mat4& hmdSensorMatrix); + // compute the hip to hand average azimuth. + glm::vec2 computeHandAzimuth() const; + // read the location of a hand controller and save the transform void updateJointFromController(controller::Action poseKey, ThreadSafeValueCache& matrixCache); @@ -612,6 +622,8 @@ public: const MyHead* getMyHead() const; + Q_INVOKABLE void toggleSmoothPoleVectors() { _skeletonModel->getRig().toggleSmoothPoleVectors(); }; + /**jsdoc * Get the current position of the avatar's "Head" joint. * @function MyAvatar.getHeadPosition @@ -862,8 +874,6 @@ public: void resetFullAvatarURL(); - virtual void setAttachmentData(const QVector& attachmentData) override; - MyCharacterController* getCharacterController() { return &_characterController; } const MyCharacterController* getCharacterController() const { return &_characterController; } @@ -904,6 +914,10 @@ public: virtual void rebuildCollisionShape() override; + const glm::vec2& getHipToHandController() const { return _hipToHandController; } + void setHipToHandController(glm::vec2 currentHipToHandController) { _hipToHandController = currentHipToHandController; } + const glm::vec2& getHeadControllerFacing() const { return _headControllerFacing; } + void setHeadControllerFacing(glm::vec2 currentHeadControllerFacing) { _headControllerFacing = currentHeadControllerFacing; } const glm::vec2& getHeadControllerFacingMovingAverage() const { return _headControllerFacingMovingAverage; } void setHeadControllerFacingMovingAverage(glm::vec2 currentHeadControllerFacing) { _headControllerFacingMovingAverage = currentHeadControllerFacing; } float getCurrentStandingHeight() const { return _currentStandingHeight; } @@ -926,7 +940,8 @@ public: * @returns {object[]} */ Q_INVOKABLE QVariantList getAvatarEntitiesVariant(); - void removeAvatarEntities(); + void clearAvatarEntities(); + void removeWearableAvatarEntities(); /**jsdoc * @function MyAvatar.isFlying @@ -952,10 +967,34 @@ public: */ Q_INVOKABLE bool getFlyingEnabled(); + /**jsdoc + * @function MyAvatar.setFlyingDesktopPref + * @param {boolean} enabled + */ + Q_INVOKABLE void setFlyingDesktopPref(bool enabled); + + /**jsdoc + * @function MyAvatar.getFlyingDesktopPref + * @returns {boolean} + */ + Q_INVOKABLE bool getFlyingDesktopPref(); + + /**jsdoc + * @function MyAvatar.setFlyingDesktopPref + * @param {boolean} enabled + */ + Q_INVOKABLE void setFlyingHMDPref(bool enabled); + + /**jsdoc + * @function MyAvatar.getFlyingDesktopPref + * @returns {boolean} + */ + Q_INVOKABLE bool getFlyingHMDPref(); + /**jsdoc * @function MyAvatar.getAvatarScale - * @returns {number} + * @returns {number} */ Q_INVOKABLE float getAvatarScale(); @@ -995,7 +1034,7 @@ public: virtual glm::quat getAbsoluteJointRotationInObjectFrame(int index) const override; virtual glm::vec3 getAbsoluteJointTranslationInObjectFrame(int index) const override; - // all calibration matrices are in absolute avatar space. + // all calibration matrices are in absolute sensor space. glm::mat4 getCenterEyeCalibrationMat() const; glm::mat4 getHeadCalibrationMat() const; glm::mat4 getSpine2CalibrationMat() const; @@ -1016,12 +1055,14 @@ public: // results are in sensor frame (-z forward) glm::mat4 deriveBodyFromHMDSensor() const; - glm::vec3 computeCounterBalance() const; + glm::mat4 getSpine2RotationRigSpace() const; + + glm::vec3 computeCounterBalance(); // derive avatar body position and orientation from using the current HMD Sensor location in relation to the previous // location of the base of support of the avatar. // results are in sensor frame (-z foward) - glm::mat4 deriveBodyUsingCgModel() const; + glm::mat4 deriveBodyUsingCgModel(); /**jsdoc * @function MyAvatar.isUp @@ -1047,6 +1088,10 @@ public: void setWalkSpeed(float value); float getWalkSpeed() const; + void setWalkBackwardSpeed(float value); + float getWalkBackwardSpeed() const; + void setSprintSpeed(float value); + float getSprintSpeed() const; QVector getScriptUrls(); @@ -1055,6 +1100,12 @@ public: float computeStandingHeightMode(const controller::Pose& head); glm::quat computeAverageHeadRotation(const controller::Pose& head); + virtual void setAttachmentData(const QVector& attachmentData) override; + virtual QVector getAttachmentData() const override; + + virtual QVariantList getAttachmentsVariant() const override; + virtual void setAttachmentsVariant(const QVariantList& variant) override; + public slots: /**jsdoc @@ -1106,6 +1157,20 @@ public slots: */ float getGravity(); + /**jsdoc + * Move the avatar to a new position and/or orientation in the domain, while taking into account Avatar leg-length. + * @function MyAvatar.goToFeetLocation + * @param {Vec3} position - The new position for the avatar, in world coordinates. + * @param {boolean} [hasOrientation=false] - Set to true to set the orientation of the avatar. + * @param {Quat} [orientation=Quat.IDENTITY] - The new orientation for the avatar. + * @param {boolean} [shouldFaceLocation=false] - Set to true to position the avatar a short distance away from + * the new position and orientate the avatar to face the position. + */ + + void goToFeetLocation(const glm::vec3& newPosition, + bool hasOrientation, const glm::quat& newOrientation, + bool shouldFaceLocation); + /**jsdoc * Move the avatar to a new position and/or orientation in the domain. * @function MyAvatar.goToLocation @@ -1281,7 +1346,6 @@ public slots: */ void setAnimGraphUrl(const QUrl& url); // thread-safe - /**jsdoc * @function MyAvatar.getPositionForAudio * @returns {Vec3} @@ -1294,7 +1358,6 @@ public slots: */ glm::quat getOrientationForAudio(); - /**jsdoc * @function MyAvatar.setModelScale * @param {number} scale @@ -1464,6 +1527,10 @@ private: float getRotationRecenterFilterLength() const { return _rotationRecenterFilterLength; } void setRotationThreshold(float angleRadians); float getRotationThreshold() const { return _rotationThreshold; } + void setEnableStepResetRotation(bool stepReset) { _stepResetRotationEnabled = stepReset; } + bool getEnableStepResetRotation() const { return _stepResetRotationEnabled; } + void setEnableDrawAverageFacing(bool drawAverage) { _drawAverageFacingEnabled = drawAverage; } + bool getEnableDrawAverageFacing() const { return _drawAverageFacingEnabled; } bool isMyAvatar() const override { return true; } virtual int parseDataFromBuffer(const QByteArray& buffer) override; virtual glm::vec3 getSkeletonPosition() const override; @@ -1479,11 +1546,21 @@ private: void setScriptedMotorTimescale(float timescale); void setScriptedMotorFrame(QString frame); void setScriptedMotorMode(QString mode); + + // Attachments virtual void attach(const QString& modelURL, const QString& jointName = QString(), const glm::vec3& translation = glm::vec3(), const glm::quat& rotation = glm::quat(), float scale = 1.0f, bool isSoft = false, bool allowDuplicates = false, bool useSaved = true) override; + virtual void detachOne(const QString& modelURL, const QString& jointName = QString()) override; + virtual void detachAll(const QString& modelURL, const QString& jointName = QString()) override; + + // Attachments/Avatar Entity + void attachmentDataToEntityProperties(const AttachmentData& data, EntityItemProperties& properties); + AttachmentData entityPropertiesToAttachmentData(const EntityItemProperties& properties) const; + bool findAvatarEntity(const QString& modelURL, const QString& jointName, QUuid& entityID); + bool cameraInsideHead(const glm::vec3& cameraPosition) const; void updateEyeContactTarget(float deltaTime); @@ -1502,6 +1579,8 @@ private: std::bitset _disabledDriveKeys; bool _enableFlying { false }; + bool _flyingPrefDesktop { true }; + bool _flyingPrefHMD { false }; bool _wasPushing { false }; bool _isPushing { false }; bool _isBeingPushed { false }; @@ -1574,6 +1653,8 @@ private: std::atomic _hasScriptedBlendShapes { false }; std::atomic _rotationRecenterFilterLength { 4.0f }; std::atomic _rotationThreshold { 0.5235f }; // 30 degrees in radians + std::atomic _stepResetRotationEnabled { true }; + std::atomic _drawAverageFacingEnabled { false }; // working copy -- see AvatarData for thread-safe _sensorToWorldMatrixCache, used for outward facing access glm::mat4 _sensorToWorldMatrix { glm::mat4() }; @@ -1587,6 +1668,8 @@ private: glm::vec2 _headControllerFacingMovingAverage { 0.0f, 0.0f }; // facing vector in xz plane (sensor space) glm::quat _averageHeadRotation { 0.0f, 0.0f, 0.0f, 1.0f }; + glm::vec2 _hipToHandController { 0.0f, -1.0f }; // spine2 facing vector in xz plane (avatar space) + float _currentStandingHeight { 0.0f }; bool _resetMode { true }; RingBufferHistory _recentModeReadings; @@ -1702,6 +1785,8 @@ private: // max unscaled forward movement speed ThreadSafeValueCache _walkSpeed { DEFAULT_AVATAR_MAX_WALKING_SPEED }; + ThreadSafeValueCache _walkBackwardSpeed { DEFAULT_AVATAR_MAX_WALKING_BACKWARD_SPEED }; + ThreadSafeValueCache _sprintSpeed { AVATAR_SPRINT_SPEED_SCALAR }; float _walkSpeedScalar { AVATAR_WALK_SPEED_SCALAR }; // load avatar scripts once when rig is ready @@ -1717,4 +1802,6 @@ void audioListenModeFromScriptValue(const QScriptValue& object, AudioListenerMod QScriptValue driveKeysToScriptValue(QScriptEngine* engine, const MyAvatar::DriveKeys& driveKeys); void driveKeysFromScriptValue(const QScriptValue& object, MyAvatar::DriveKeys& driveKeys); +bool isWearableEntity(const EntityItemPointer& entity); + #endif // hifi_MyAvatar_h diff --git a/interface/src/avatar/MyAvatarHeadTransformNode.cpp b/interface/src/avatar/MyAvatarHeadTransformNode.cpp new file mode 100644 index 0000000000..9c202ba94a --- /dev/null +++ b/interface/src/avatar/MyAvatarHeadTransformNode.cpp @@ -0,0 +1,23 @@ +// +// Created by Sabrina Shanman 8/14/2018 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "MyAvatarHeadTransformNode.h" + +#include "DependencyManager.h" +#include "AvatarManager.h" +#include "MyAvatar.h" + +Transform MyAvatarHeadTransformNode::getTransform() { + auto myAvatar = DependencyManager::get()->getMyAvatar(); + + glm::vec3 pos = myAvatar->getHeadPosition(); + glm::quat headOri = myAvatar->getHeadOrientation(); + glm::quat ori = headOri * glm::angleAxis(-PI / 2.0f, Vectors::RIGHT); + + return Transform(ori, glm::vec3(1.0f), pos); +} \ No newline at end of file diff --git a/interface/src/avatar/MyAvatarHeadTransformNode.h b/interface/src/avatar/MyAvatarHeadTransformNode.h new file mode 100644 index 0000000000..a7d7144521 --- /dev/null +++ b/interface/src/avatar/MyAvatarHeadTransformNode.h @@ -0,0 +1,19 @@ +// +// Created by Sabrina Shanman 8/14/2018 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#ifndef hifi_MyAvatarHeadTransformNode_h +#define hifi_MyAvatarHeadTransformNode_h + +#include "TransformNode.h" + +class MyAvatarHeadTransformNode : public TransformNode { +public: + MyAvatarHeadTransformNode() { } + Transform getTransform() override; +}; + +#endif // hifi_MyAvatarHeadTransformNode_h \ No newline at end of file diff --git a/interface/src/avatar/MySkeletonModel.cpp b/interface/src/avatar/MySkeletonModel.cpp index 0fc5e7521e..77d1a87195 100644 --- a/interface/src/avatar/MySkeletonModel.cpp +++ b/interface/src/avatar/MySkeletonModel.cpp @@ -238,6 +238,35 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { params.primaryControllerPoses[Rig::PrimaryControllerType_Hips] = sensorToRigPose * hips; params.primaryControllerFlags[Rig::PrimaryControllerType_Hips] = (uint8_t)Rig::ControllerFlags::Enabled | (uint8_t)Rig::ControllerFlags::Estimated; + // set spine2 if we have hand controllers + if (myAvatar->getControllerPoseInAvatarFrame(controller::Action::RIGHT_HAND).isValid() && + myAvatar->getControllerPoseInAvatarFrame(controller::Action::LEFT_HAND).isValid() && + !(params.primaryControllerFlags[Rig::PrimaryControllerType_Spine2] & (uint8_t)Rig::ControllerFlags::Enabled)) { + + AnimPose currentSpine2Pose; + AnimPose currentHeadPose; + AnimPose currentHipsPose; + bool spine2Exists = _rig.getAbsoluteJointPoseInRigFrame(_rig.indexOfJoint("Spine2"), currentSpine2Pose); + bool headExists = _rig.getAbsoluteJointPoseInRigFrame(_rig.indexOfJoint("Head"), currentHeadPose); + bool hipsExists = _rig.getAbsoluteJointPoseInRigFrame(_rig.indexOfJoint("Hips"), currentHipsPose); + if (spine2Exists && headExists && hipsExists) { + AnimPose rigSpaceYaw(myAvatar->getSpine2RotationRigSpace()); + glm::vec3 u, v, w; + glm::vec3 fwd = rigSpaceYaw.rot() * glm::vec3(0.0f, 0.0f, 1.0f); + glm::vec3 up = currentHeadPose.trans() - currentHipsPose.trans(); + if (glm::length(up) > 0.0f) { + up = glm::normalize(up); + } else { + up = glm::vec3(0.0f, 1.0f, 0.0f); + } + generateBasisVectors(up, fwd, u, v, w); + AnimPose newSpinePose(glm::mat4(glm::vec4(w, 0.0f), glm::vec4(u, 0.0f), glm::vec4(v, 0.0f), glm::vec4(glm::vec3(0.0f, 0.0f, 0.0f), 1.0f))); + currentSpine2Pose.rot() = newSpinePose.rot(); + params.primaryControllerPoses[Rig::PrimaryControllerType_Spine2] = currentSpine2Pose; + params.primaryControllerFlags[Rig::PrimaryControllerType_Spine2] = (uint8_t)Rig::ControllerFlags::Enabled | (uint8_t)Rig::ControllerFlags::Estimated; + } + } + } else { _prevHipsValid = false; } diff --git a/interface/src/avatar/OtherAvatar.cpp b/interface/src/avatar/OtherAvatar.cpp index 5e51658128..a0fa496c4c 100644 --- a/interface/src/avatar/OtherAvatar.cpp +++ b/interface/src/avatar/OtherAvatar.cpp @@ -9,6 +9,8 @@ #include "OtherAvatar.h" #include "Application.h" +#include "AvatarMotionState.h" + OtherAvatar::OtherAvatar(QThread* thread) : Avatar(thread) { // give the pointer to our head to inherited _headData variable from AvatarData _headData = new Head(this); @@ -27,20 +29,23 @@ OtherAvatar::~OtherAvatar() { } void OtherAvatar::removeOrb() { - if (qApp->getOverlays().isAddedOverlay(_otherAvatarOrbMeshPlaceholderID)) { + if (!_otherAvatarOrbMeshPlaceholderID.isNull()) { qApp->getOverlays().deleteOverlay(_otherAvatarOrbMeshPlaceholderID); + _otherAvatarOrbMeshPlaceholderID = UNKNOWN_OVERLAY_ID; } } void OtherAvatar::updateOrbPosition() { if (_otherAvatarOrbMeshPlaceholder != nullptr) { _otherAvatarOrbMeshPlaceholder->setWorldPosition(getHead()->getPosition()); + if (_otherAvatarOrbMeshPlaceholderID.isNull()) { + _otherAvatarOrbMeshPlaceholderID = qApp->getOverlays().addOverlay(_otherAvatarOrbMeshPlaceholder); + } } } void OtherAvatar::createOrb() { - if (_otherAvatarOrbMeshPlaceholderID == UNKNOWN_OVERLAY_ID || - !qApp->getOverlays().isAddedOverlay(_otherAvatarOrbMeshPlaceholderID)) { + if (_otherAvatarOrbMeshPlaceholderID.isNull()) { _otherAvatarOrbMeshPlaceholder = std::make_shared(); _otherAvatarOrbMeshPlaceholder->setAlpha(1.0f); _otherAvatarOrbMeshPlaceholder->setColor({ 0xFF, 0x00, 0xFF }); @@ -48,7 +53,7 @@ void OtherAvatar::createOrb() { _otherAvatarOrbMeshPlaceholder->setPulseMin(0.5); _otherAvatarOrbMeshPlaceholder->setPulseMax(1.0); _otherAvatarOrbMeshPlaceholder->setColorPulse(1.0); - _otherAvatarOrbMeshPlaceholder->setIgnoreRayIntersection(true); + _otherAvatarOrbMeshPlaceholder->setIgnorePickIntersection(true); _otherAvatarOrbMeshPlaceholder->setDrawInFront(false); _otherAvatarOrbMeshPlaceholderID = qApp->getOverlays().addOverlay(_otherAvatarOrbMeshPlaceholder); // Position focus @@ -58,3 +63,38 @@ void OtherAvatar::createOrb() { _otherAvatarOrbMeshPlaceholder->setVisible(true); } } + +void OtherAvatar::setSpaceIndex(int32_t index) { + assert(_spaceIndex == -1); + _spaceIndex = index; +} + +void OtherAvatar::updateSpaceProxy(workload::Transaction& transaction) const { + if (_spaceIndex > -1) { + float approximateBoundingRadius = glm::length(getTargetScale()); + workload::Sphere sphere(getWorldPosition(), approximateBoundingRadius); + transaction.update(_spaceIndex, sphere); + } +} + +int OtherAvatar::parseDataFromBuffer(const QByteArray& buffer) { + int32_t bytesRead = Avatar::parseDataFromBuffer(buffer); + if (_moving && _motionState) { + _motionState->addDirtyFlags(Simulation::DIRTY_POSITION); + } + return bytesRead; +} + +void OtherAvatar::setWorkloadRegion(uint8_t region) { + _workloadRegion = region; +} + +bool OtherAvatar::shouldBeInPhysicsSimulation() const { + return (_workloadRegion < workload::Region::R3 && !isDead()); +} + +void OtherAvatar::rebuildCollisionShape() { + if (_motionState) { + _motionState->addDirtyFlags(Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS); + } +} diff --git a/interface/src/avatar/OtherAvatar.h b/interface/src/avatar/OtherAvatar.h index f33952b78b..ccfe42dbed 100644 --- a/interface/src/avatar/OtherAvatar.h +++ b/interface/src/avatar/OtherAvatar.h @@ -1,6 +1,6 @@ // -// Created by Bradley Austin Davis on 2017/04/27 -// Copyright 2013-2017 High Fidelity, Inc. +// Created by amantly 2018.06.26 +// Copyright 2018 High Fidelity, Inc. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -9,10 +9,17 @@ #ifndef hifi_OtherAvatar_h #define hifi_OtherAvatar_h +#include + #include +#include + +#include "InterfaceLogging.h" #include "ui/overlays/Overlays.h" #include "ui/overlays/Sphere3DOverlay.h" -#include "InterfaceLogging.h" + +class AvatarManager; +class AvatarMotionState; class OtherAvatar : public Avatar { public: @@ -24,9 +31,28 @@ public: void updateOrbPosition(); void removeOrb(); + void setSpaceIndex(int32_t index); + int32_t getSpaceIndex() const { return _spaceIndex; } + void updateSpaceProxy(workload::Transaction& transaction) const; + + int parseDataFromBuffer(const QByteArray& buffer) override; + + bool isInPhysicsSimulation() const { return _motionState != nullptr; } + void rebuildCollisionShape() override; + + void setWorkloadRegion(uint8_t region); + bool shouldBeInPhysicsSimulation() const; + + friend AvatarManager; + protected: std::shared_ptr _otherAvatarOrbMeshPlaceholder { nullptr }; OverlayID _otherAvatarOrbMeshPlaceholderID { UNKNOWN_OVERLAY_ID }; + AvatarMotionState* _motionState { nullptr }; + int32_t _spaceIndex { -1 }; + uint8_t _workloadRegion { workload::Region::INVALID }; }; +using OtherAvatarPointer = std::shared_ptr; + #endif // hifi_OtherAvatar_h diff --git a/interface/src/commerce/Ledger.cpp b/interface/src/commerce/Ledger.cpp index 702251f867..67303f2a9b 100644 --- a/interface/src/commerce/Ledger.cpp +++ b/interface/src/commerce/Ledger.cpp @@ -31,7 +31,9 @@ QJsonObject Ledger::apiResponse(const QString& label, QNetworkReply* reply) { QByteArray response = reply->readAll(); QJsonObject data = QJsonDocument::fromJson(response).object(); +#if defined(DEV_BUILD) // Don't expose user's personal data in the wild. But during development this can be handy. qInfo(commerce) << label << "response" << QJsonDocument(data).toJson(QJsonDocument::Compact); +#endif return data; } // Non-200 responses are not json: @@ -69,7 +71,9 @@ void Ledger::send(const QString& endpoint, const QString& success, const QString auto accountManager = DependencyManager::get(); const QString URL = "/api/v1/commerce/"; JSONCallbackParameters callbackParams(this, success, fail); +#if defined(DEV_BUILD) // Don't expose user's personal data in the wild. But during development this can be handy. qCInfo(commerce) << "Sending" << endpoint << QJsonDocument(request).toJson(QJsonDocument::Compact); +#endif accountManager->sendRequest(URL + endpoint, authType, method, @@ -117,7 +121,7 @@ void Ledger::buy(const QString& hfc_key, int cost, const QString& asset_id, cons signedSend("transaction", transactionString, hfc_key, "buy", "buySuccess", "buyFailure", controlled_failure); } -bool Ledger::receiveAt(const QString& hfc_key, const QString& signing_key) { +bool Ledger::receiveAt(const QString& hfc_key, const QString& signing_key, const QByteArray& locker) { auto accountManager = DependencyManager::get(); if (!accountManager->isLoggedIn()) { qCWarning(commerce) << "Cannot set receiveAt when not logged in."; @@ -125,11 +129,25 @@ bool Ledger::receiveAt(const QString& hfc_key, const QString& signing_key) { emit receiveAtResult(result); return false; // We know right away that we will fail, so tell the caller. } - - signedSend("public_key", hfc_key.toUtf8(), signing_key, "receive_at", "receiveAtSuccess", "receiveAtFailure"); + QJsonObject transaction; + transaction["public_key"] = hfc_key; + transaction["locker"] = QString::fromUtf8(locker); + QJsonDocument transactionDoc{ transaction }; + auto transactionString = transactionDoc.toJson(QJsonDocument::Compact); + signedSend("text", transactionString, signing_key, "receive_at", "receiveAtSuccess", "receiveAtFailure"); return true; // Note that there may still be an asynchronous signal of failure that callers might be interested in. } +bool Ledger::receiveAt() { + auto wallet = DependencyManager::get(); + auto keys = wallet->listPublicKeys(); + if (keys.isEmpty()) { + return false; + } + auto key = keys.first(); + return receiveAt(key, key, wallet->getWallet()); +} + void Ledger::balance(const QStringList& keys) { keysQuery("balance", "balanceSuccess", "balanceFailure"); } @@ -283,24 +301,30 @@ void Ledger::accountSuccess(QNetworkReply* reply) { auto iv = QByteArray::fromBase64(data["iv"].toString().toUtf8()); auto ckey = QByteArray::fromBase64(data["ckey"].toString().toUtf8()); QString remotePublicKey = data["public_key"].toString(); + const QByteArray locker = data["locker"].toString().toUtf8(); bool isOverride = wallet->wasSoftReset(); wallet->setSalt(salt); wallet->setIv(iv); wallet->setCKey(ckey); + if (!locker.isEmpty()) { + wallet->setWallet(locker); + wallet->setPassphrase("ACCOUNT"); // We only locker wallets that have been converted to account-based auth. + } QString keyStatus = "ok"; QStringList localPublicKeys = wallet->listPublicKeys(); if (remotePublicKey.isEmpty() || isOverride) { - if (!localPublicKeys.isEmpty()) { - QString key = localPublicKeys.first(); - receiveAt(key, key); + if (!localPublicKeys.isEmpty()) { // Let the metaverse know about a local wallet. + receiveAt(); } } else { if (localPublicKeys.isEmpty()) { keyStatus = "preexisting"; } else if (localPublicKeys.first() != remotePublicKey) { keyStatus = "conflicting"; + } else if (locker.isEmpty()) { // Matches metaverse data, but we haven't lockered it yet. + receiveAt(); } } diff --git a/interface/src/commerce/Ledger.h b/interface/src/commerce/Ledger.h index ba2f167f4b..427395ee11 100644 --- a/interface/src/commerce/Ledger.h +++ b/interface/src/commerce/Ledger.h @@ -26,7 +26,8 @@ class Ledger : public QObject, public Dependency { public: void buy(const QString& hfc_key, int cost, const QString& asset_id, const QString& inventory_key, const bool controlled_failure = false); - bool receiveAt(const QString& hfc_key, const QString& signing_key); + bool receiveAt(const QString& hfc_key, const QString& signing_key, const QByteArray& locker); + bool receiveAt(); void balance(const QStringList& keys); void inventory(const QString& editionFilter, const QString& typeFilter, const QString& titleFilter, const int& page, const int& perPage); void history(const QStringList& keys, const int& pageNumber, const int& itemsPerPage); diff --git a/interface/src/commerce/QmlCommerce.cpp b/interface/src/commerce/QmlCommerce.cpp index 1f44343bdc..06da18148f 100644 --- a/interface/src/commerce/QmlCommerce.cpp +++ b/interface/src/commerce/QmlCommerce.cpp @@ -40,11 +40,9 @@ QmlCommerce::QmlCommerce() { connect(ledger.data(), &Ledger::transferAssetToUsernameResult, this, &QmlCommerce::transferAssetToUsernameResult); connect(ledger.data(), &Ledger::availableUpdatesResult, this, &QmlCommerce::availableUpdatesResult); connect(ledger.data(), &Ledger::updateItemResult, this, &QmlCommerce::updateItemResult); - + auto accountManager = DependencyManager::get(); - connect(accountManager.data(), &AccountManager::usernameChanged, this, [&]() { - setPassphrase(""); - }); + connect(accountManager.data(), &AccountManager::usernameChanged, this, [&]() { setPassphrase(""); }); _appsPath = PathUtils::getAppDataPath() + "Apps/"; } @@ -105,7 +103,11 @@ void QmlCommerce::balance() { } } -void QmlCommerce::inventory(const QString& editionFilter, const QString& typeFilter, const QString& titleFilter, const int& page, const int& perPage) { +void QmlCommerce::inventory(const QString& editionFilter, + const QString& typeFilter, + const QString& titleFilter, + const int& page, + const int& perPage) { auto ledger = DependencyManager::get(); auto wallet = DependencyManager::get(); QStringList cachedPublicKeys = wallet->listPublicKeys(); @@ -166,24 +168,30 @@ void QmlCommerce::certificateInfo(const QString& certificateId) { ledger->certificateInfo(certificateId); } -void QmlCommerce::transferAssetToNode(const QString& nodeID, const QString& certificateID, const int& amount, const QString& optionalMessage) { +void QmlCommerce::transferAssetToNode(const QString& nodeID, + const QString& certificateID, + const int& amount, + const QString& optionalMessage) { auto ledger = DependencyManager::get(); auto wallet = DependencyManager::get(); QStringList keys = wallet->listPublicKeys(); if (keys.count() == 0) { - QJsonObject result{ { "status", "fail" },{ "message", "Uninitialized Wallet." } }; + QJsonObject result{ { "status", "fail" }, { "message", "Uninitialized Wallet." } }; return emit transferAssetToNodeResult(result); } QString key = keys[0]; ledger->transferAssetToNode(key, nodeID, certificateID, amount, optionalMessage); } -void QmlCommerce::transferAssetToUsername(const QString& username, const QString& certificateID, const int& amount, const QString& optionalMessage) { +void QmlCommerce::transferAssetToUsername(const QString& username, + const QString& certificateID, + const int& amount, + const QString& optionalMessage) { auto ledger = DependencyManager::get(); auto wallet = DependencyManager::get(); QStringList keys = wallet->listPublicKeys(); if (keys.count() == 0) { - QJsonObject result{ { "status", "fail" },{ "message", "Uninitialized Wallet." } }; + QJsonObject result{ { "status", "fail" }, { "message", "Uninitialized Wallet." } }; return emit transferAssetToUsernameResult(result); } QString key = keys[0]; @@ -194,10 +202,7 @@ void QmlCommerce::replaceContentSet(const QString& itemHref, const QString& cert auto ledger = DependencyManager::get(); ledger->updateLocation(certificateID, DependencyManager::get()->getPlaceName(), true); qApp->replaceDomainContent(itemHref); - QJsonObject messageProperties = { - { "status", "SuccessfulRequestToReplaceContent" }, - { "content_set_url", itemHref } - }; + QJsonObject messageProperties = { { "status", "SuccessfulRequestToReplaceContent" }, { "content_set_url", itemHref } }; UserActivityLogger::getInstance().logAction("replace_domain_content", messageProperties); emit contentSetChanged(itemHref); @@ -214,10 +219,7 @@ QString QmlCommerce::getInstalledApps(const QString& justInstalledAppID) { QDir directory(_appsPath); QStringList apps = directory.entryList(QStringList("*.app.json")); - foreach(QString appFileName, apps) { - installedAppsFromMarketplace += appFileName; - installedAppsFromMarketplace += ","; - + foreach (QString appFileName, apps) { // If we were supplied a "justInstalledAppID" argument, that means we're entering this function // to get the new list of installed apps immediately after installing an app. // In that case, the app we installed may not yet have its associated script running - @@ -243,10 +245,12 @@ QString QmlCommerce::getInstalledApps(const QString& justInstalledAppID) { // delete the .app.json from the user's local disk. if (!runningScripts.contains(scriptURL)) { if (!appFile.remove()) { - qCWarning(commerce) - << "Couldn't delete local .app.json file (app's script isn't running). App filename is:" - << appFileName; + qCWarning(commerce) << "Couldn't delete local .app.json file (app's script isn't running). App filename is:" + << appFileName; } + } else { + installedAppsFromMarketplace += appFileName; + installedAppsFromMarketplace += ","; } } else { qCDebug(commerce) << "Couldn't open local .app.json file for reading."; @@ -317,7 +321,9 @@ bool QmlCommerce::uninstallApp(const QString& itemHref) { // Read from the file to know what .js script to stop QFile appFile(_appsPath + "/" + appHref.fileName()); if (!appFile.open(QIODevice::ReadOnly)) { - qCDebug(commerce) << "Couldn't open local .app.json file for deletion. Cannot continue with app uninstallation. App filename is:" << appHref.fileName(); + qCDebug(commerce) + << "Couldn't open local .app.json file for deletion. Cannot continue with app uninstallation. App filename is:" + << appHref.fileName(); return false; } QJsonDocument appFileJsonDocument = QJsonDocument::fromJson(appFile.readAll()); @@ -325,13 +331,15 @@ bool QmlCommerce::uninstallApp(const QString& itemHref) { QString scriptUrl = appFileJsonObject["scriptURL"].toString(); if (!DependencyManager::get()->stopScript(scriptUrl.trimmed(), false)) { - qCWarning(commerce) << "Couldn't stop script during app uninstall. Continuing anyway. ScriptURL is:" << scriptUrl.trimmed(); + qCWarning(commerce) << "Couldn't stop script during app uninstall. Continuing anyway. ScriptURL is:" + << scriptUrl.trimmed(); } // Delete the .app.json from the filesystem // remove() closes the file first. if (!appFile.remove()) { - qCWarning(commerce) << "Couldn't delete local .app.json file during app uninstall. Continuing anyway. App filename is:" << appHref.fileName(); + qCWarning(commerce) << "Couldn't delete local .app.json file during app uninstall. Continuing anyway. App filename is:" + << appHref.fileName(); } QFileInfo appFileInfo(appFile); @@ -352,8 +360,8 @@ bool QmlCommerce::openApp(const QString& itemHref) { QJsonObject appFileJsonObject = appFileJsonDocument.object(); QString homeUrl = appFileJsonObject["homeURL"].toString(); - auto tabletScriptingInterface = DependencyManager::get(); - auto tablet = dynamic_cast(tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system")); + auto tablet = dynamic_cast( + DependencyManager::get()->getTablet("com.highfidelity.interface.tablet.system")); if (homeUrl.contains(".qml", Qt::CaseInsensitive)) { tablet->loadQMLSource(homeUrl); } else if (homeUrl.contains(".html", Qt::CaseInsensitive)) { @@ -378,7 +386,7 @@ void QmlCommerce::updateItem(const QString& certificateId) { auto wallet = DependencyManager::get(); QStringList keys = wallet->listPublicKeys(); if (keys.count() == 0) { - QJsonObject result{ { "status", "fail" },{ "message", "Uninitialized Wallet." } }; + QJsonObject result{ { "status", "fail" }, { "message", "Uninitialized Wallet." } }; return emit updateItemResult(result); } QString key = keys[0]; diff --git a/interface/src/commerce/Wallet.cpp b/interface/src/commerce/Wallet.cpp index 991f1ebf3f..5b8417be7c 100644 --- a/interface/src/commerce/Wallet.cpp +++ b/interface/src/commerce/Wallet.cpp @@ -131,7 +131,7 @@ bool Wallet::writeBackupInstructions() { QFile outputFile(outputFilename); bool retval = false; - if (getKeyFilePath() == "") + if (getKeyFilePath().isEmpty()) { return false; } @@ -190,6 +190,30 @@ bool writeKeys(const char* filename, EC_KEY* keys) { return retval; } +bool Wallet::setWallet(const QByteArray& wallet) { + QFile file(keyFilePath()); + if (!file.open(QIODevice::WriteOnly)) { + qCCritical(commerce) << "Unable to open wallet for write in" << keyFilePath(); + return false; + } + if (file.write(wallet) != wallet.count()) { + qCCritical(commerce) << "Unable to write wallet in" << keyFilePath(); + return false; + } + file.close(); + return true; +} +QByteArray Wallet::getWallet() { + QFile file(keyFilePath()); + if (!file.open(QIODevice::ReadOnly)) { + qCInfo(commerce) << "No existing wallet in" << keyFilePath(); + return QByteArray(); + } + QByteArray wallet = file.readAll(); + file.close(); + return wallet; +} + QPair generateECKeypair() { EC_KEY* keyPair = EC_KEY_new_by_curve_name(NID_secp256k1); @@ -328,13 +352,13 @@ Wallet::Wallet() { packetReceiver.registerListener(PacketType::ChallengeOwnership, this, "handleChallengeOwnershipPacket"); packetReceiver.registerListener(PacketType::ChallengeOwnershipRequest, this, "handleChallengeOwnershipPacket"); - connect(ledger.data(), &Ledger::accountResult, this, [&](QJsonObject result) { + connect(ledger.data(), &Ledger::accountResult, this, [](QJsonObject result) { auto wallet = DependencyManager::get(); auto walletScriptingInterface = DependencyManager::get(); uint status; QString keyStatus = result.contains("data") ? result["data"].toObject()["keyStatus"].toString() : ""; - if (wallet->getKeyFilePath() == "" || !wallet->getSecurityImage()) { + if (wallet->getKeyFilePath().isEmpty() || !wallet->getSecurityImage()) { if (keyStatus == "preexisting") { status = (uint) WalletStatus::WALLET_STATUS_PREEXISTING; } else{ @@ -524,15 +548,23 @@ bool Wallet::walletIsAuthenticatedWithPassphrase() { // FIXME: initialize OpenSSL elsewhere soon initialize(); + qCDebug(commerce) << "walletIsAuthenticatedWithPassphrase: checking" << (!_passphrase || !_passphrase->isEmpty()); // this should always be false if we don't have a passphrase // cached yet if (!_passphrase || _passphrase->isEmpty()) { - return false; + if (!getKeyFilePath().isEmpty()) { // If file exists, then it is an old school file that has not been lockered. Must get user's passphrase. + qCDebug(commerce) << "walletIsAuthenticatedWithPassphrase: No passphrase, but there is an existing wallet."; + return false; + } else { + qCDebug(commerce) << "walletIsAuthenticatedWithPassphrase: New setup."; + setPassphrase("ACCOUNT"); // Going forward, consider this an account-based client. + } } if (_publicKeys.count() > 0) { // we _must_ be authenticated if the publicKeys are there DependencyManager::get()->setWalletStatus((uint)WalletStatus::WALLET_STATUS_READY); + qCDebug(commerce) << "walletIsAuthenticatedWithPassphrase: wallet was ready"; return true; } @@ -545,10 +577,15 @@ bool Wallet::walletIsAuthenticatedWithPassphrase() { // be sure to add the public key so we don't do this over and over _publicKeys.push_back(publicKey.toBase64()); + + if (*_passphrase != "ACCOUNT") { + changePassphrase("ACCOUNT"); // Rewrites with salt and constant, and will be lockered that way. + } + qCDebug(commerce) << "walletIsAuthenticatedWithPassphrase: wallet now ready"; return true; } } - + qCDebug(commerce) << "walletIsAuthenticatedWithPassphrase: wallet not ready"; return false; } @@ -559,6 +596,7 @@ bool Wallet::generateKeyPair() { qCInfo(commerce) << "Generating keypair."; auto keyPair = generateECKeypair(); if (!keyPair.first) { + qCWarning(commerce) << "Empty keypair"; return false; } @@ -576,7 +614,7 @@ bool Wallet::generateKeyPair() { // 2. It is maximally private, and we can step back from that later if desired. // 3. It maximally exercises all the machinery, so we are most likely to surface issues now. auto ledger = DependencyManager::get(); - return ledger->receiveAt(key, key); + return ledger->receiveAt(key, key, getWallet()); } QStringList Wallet::listPublicKeys() { @@ -666,11 +704,13 @@ void Wallet::chooseSecurityImage(const QString& filename) { // there _is_ a keyfile, we need to update it (similar to changing the // passphrase, we need to do so into a temp file and move it). if (!QFile(keyFilePath()).exists()) { + qCDebug(commerce) << "initial security pic set for empty wallet"; emit securityImageResult(true); return; } bool success = writeWallet(); + qCDebug(commerce) << "updated security pic" << success; emit securityImageResult(success); } @@ -715,6 +755,11 @@ QString Wallet::getKeyFilePath() { bool Wallet::writeWallet(const QString& newPassphrase) { EC_KEY* keys = readKeys(keyFilePath().toStdString().c_str()); + auto ledger = DependencyManager::get(); + // Remove any existing locker, because it will be out of date. + if (!_publicKeys.isEmpty() && !ledger->receiveAt(_publicKeys.first(), _publicKeys.first(), QByteArray())) { + return false; // FIXME: receiveAt could fail asynchronously. + } if (keys) { // we read successfully, so now write to a new temp file QString tempFileName = QString("%1.%2").arg(keyFilePath(), QString("temp")); @@ -722,6 +767,7 @@ bool Wallet::writeWallet(const QString& newPassphrase) { if (!newPassphrase.isEmpty()) { setPassphrase(newPassphrase); } + if (writeKeys(tempFileName.toStdString().c_str(), keys)) { if (writeSecurityImage(_securityImage, tempFileName)) { // ok, now move the temp file to the correct spot @@ -729,6 +775,11 @@ bool Wallet::writeWallet(const QString& newPassphrase) { QFile(tempFileName).rename(QString(keyFilePath())); qCDebug(commerce) << "wallet written successfully"; emit keyFilePathIfExistsResult(getKeyFilePath()); + if (!walletIsAuthenticatedWithPassphrase() || !ledger->receiveAt()) { + // FIXME: Should we fail the whole operation? + // Tricky, because we'll need the the key and file from the TEMP location... + qCWarning(commerce) << "Failed to update locker"; + } return true; } else { qCDebug(commerce) << "couldn't write security image to temp wallet"; diff --git a/interface/src/commerce/Wallet.h b/interface/src/commerce/Wallet.h index 665afd9a23..c096713058 100644 --- a/interface/src/commerce/Wallet.h +++ b/interface/src/commerce/Wallet.h @@ -73,6 +73,7 @@ private slots: void handleChallengeOwnershipPacket(QSharedPointer packet, SharedNodePointer sendingNode); private: + friend class Ledger; QStringList _publicKeys{}; QPixmap* _securityImage { nullptr }; QByteArray _salt; @@ -87,6 +88,9 @@ private: bool readSecurityImage(const QString& inputFilePath, unsigned char** outputBufferPtr, int* outputBufferLen); bool writeBackupInstructions(); + bool setWallet(const QByteArray& wallet); + QByteArray getWallet(); + void account(); }; diff --git a/interface/src/main.cpp b/interface/src/main.cpp index c1ba6f0535..3e3c9da148 100644 --- a/interface/src/main.cpp +++ b/interface/src/main.cpp @@ -42,6 +42,48 @@ extern "C" { int main(int argc, const char* argv[]) { setupHifiApplication(BuildInfo::INTERFACE_NAME); + QStringList arguments; + for (int i = 0; i < argc; ++i) { + arguments << argv[i]; + } + + QCommandLineParser parser; + parser.setApplicationDescription("High Fidelity Interface"); + QCommandLineOption versionOption = parser.addVersionOption(); + QCommandLineOption helpOption = parser.addHelpOption(); + + QCommandLineOption urlOption("url", "", "value"); + QCommandLineOption noUpdaterOption("no-updater", "Do not show auto-updater"); + QCommandLineOption checkMinSpecOption("checkMinSpec", "Check if machine meets minimum specifications"); + QCommandLineOption runServerOption("runServer", "Whether to run the server"); + QCommandLineOption serverContentPathOption("serverContentPath", "Where to find server content", "serverContentPath"); + QCommandLineOption allowMultipleInstancesOption("allowMultipleInstances", "Allow multiple instances to run"); + QCommandLineOption overrideAppLocalDataPathOption("cache", "set test cache ", "dir"); + QCommandLineOption overrideScriptsPathOption(SCRIPTS_SWITCH, "set scripts ", "path"); + + parser.addOption(urlOption); + parser.addOption(noUpdaterOption); + parser.addOption(checkMinSpecOption); + parser.addOption(runServerOption); + parser.addOption(serverContentPathOption); + parser.addOption(overrideAppLocalDataPathOption); + parser.addOption(overrideScriptsPathOption); + parser.addOption(allowMultipleInstancesOption); + + if (!parser.parse(arguments)) { + std::cout << parser.errorText().toStdString() << std::endl; // Avoid Qt log spam + } + + if (parser.isSet(versionOption)) { + parser.showVersion(); + Q_UNREACHABLE(); + } + if (parser.isSet(helpOption)) { + QCoreApplication mockApp(argc, const_cast(argv)); // required for call to showHelp() + parser.showHelp(); + Q_UNREACHABLE(); + } + // Early check for --traceFile argument auto tracer = DependencyManager::set(); const char * traceFile = nullptr; @@ -95,30 +137,6 @@ int main(int argc, const char* argv[]) { qDebug() << "Crash handler started:" << crashHandlerStarted; } - QStringList arguments; - for (int i = 0; i < argc; ++i) { - arguments << argv[i]; - } - - QCommandLineParser parser; - QCommandLineOption urlOption("url", "", "value"); - QCommandLineOption noUpdaterOption("no-updater", "Do not show auto-updater"); - QCommandLineOption checkMinSpecOption("checkMinSpec", "Check if machine meets minimum specifications"); - QCommandLineOption runServerOption("runServer", "Whether to run the server"); - QCommandLineOption serverContentPathOption("serverContentPath", "Where to find server content", "serverContentPath"); - QCommandLineOption allowMultipleInstancesOption("allowMultipleInstances", "Allow multiple instances to run"); - QCommandLineOption overrideAppLocalDataPathOption("cache", "set test cache ", "dir"); - QCommandLineOption overrideScriptsPathOption(SCRIPTS_SWITCH, "set scripts ", "path"); - parser.addOption(urlOption); - parser.addOption(noUpdaterOption); - parser.addOption(checkMinSpecOption); - parser.addOption(runServerOption); - parser.addOption(serverContentPathOption); - parser.addOption(overrideAppLocalDataPathOption); - parser.addOption(overrideScriptsPathOption); - parser.addOption(allowMultipleInstancesOption); - parser.parse(arguments); - const QString& applicationName = getInterfaceSharedMemoryName(); bool instanceMightBeRunning = true; diff --git a/interface/src/octree/OctreePacketProcessor.cpp b/interface/src/octree/OctreePacketProcessor.cpp index 7d38e29710..4bc6817a9e 100644 --- a/interface/src/octree/OctreePacketProcessor.cpp +++ b/interface/src/octree/OctreePacketProcessor.cpp @@ -16,16 +16,21 @@ #include "Application.h" #include "Menu.h" #include "SceneScriptingInterface.h" +#include "SafeLanding.h" -OctreePacketProcessor::OctreePacketProcessor() { +OctreePacketProcessor::OctreePacketProcessor(): + _safeLanding(new SafeLanding()) +{ setObjectName("Octree Packet Processor"); auto& packetReceiver = DependencyManager::get()->getPacketReceiver(); - - packetReceiver.registerDirectListenerForTypes({ PacketType::OctreeStats, PacketType::EntityData, PacketType::EntityErase }, - this, "handleOctreePacket"); + const PacketReceiver::PacketTypeList octreePackets = + { PacketType::OctreeStats, PacketType::EntityData, PacketType::EntityErase, PacketType::EntityQueryInitialResultsComplete }; + packetReceiver.registerDirectListenerForTypes(octreePackets, this, "handleOctreePacket"); } +OctreePacketProcessor::~OctreePacketProcessor() { } + void OctreePacketProcessor::handleOctreePacket(QSharedPointer message, SharedNodePointer senderNode) { queueReceivedPacket(message, senderNode); } @@ -107,12 +112,28 @@ void OctreePacketProcessor::processPacket(QSharedPointer messag auto renderer = qApp->getEntities(); if (renderer) { renderer->processDatagram(*message, sendingNode); + _safeLanding->noteReceivedsequenceNumber(renderer->getLastOctreeMessageSequence()); } } } break; + case PacketType::EntityQueryInitialResultsComplete: { + // Read sequence # + OCTREE_PACKET_SEQUENCE completionNumber; + message->readPrimitive(&completionNumber); + _safeLanding->setCompletionSequenceNumbers(0, completionNumber); + } break; + default: { // nothing to do } break; } } + +void OctreePacketProcessor::startEntitySequence() { + _safeLanding->startEntitySequence(qApp->getEntities()); +} + +bool OctreePacketProcessor::isLoadSequenceComplete() const { + return _safeLanding->isLoadSequenceComplete(); +} diff --git a/interface/src/octree/OctreePacketProcessor.h b/interface/src/octree/OctreePacketProcessor.h index d04cab3584..f9c24ddc51 100644 --- a/interface/src/octree/OctreePacketProcessor.h +++ b/interface/src/octree/OctreePacketProcessor.h @@ -15,12 +15,18 @@ #include #include +class SafeLanding; + /// Handles processing of incoming voxel packets for the interface application. As with other ReceivedPacketProcessor classes /// the user is responsible for reading inbound packets and adding them to the processing queue by calling queueReceivedPacket() class OctreePacketProcessor : public ReceivedPacketProcessor { Q_OBJECT public: OctreePacketProcessor(); + ~OctreePacketProcessor(); + + void startEntitySequence(); + bool isLoadSequenceComplete() const; signals: void packetVersionMismatch(); @@ -30,5 +36,8 @@ protected: private slots: void handleOctreePacket(QSharedPointer message, SharedNodePointer senderNode); + +private: + std::unique_ptr _safeLanding; }; #endif // hifi_OctreePacketProcessor_h diff --git a/interface/src/octree/SafeLanding.cpp b/interface/src/octree/SafeLanding.cpp new file mode 100644 index 0000000000..60b660f66a --- /dev/null +++ b/interface/src/octree/SafeLanding.cpp @@ -0,0 +1,172 @@ +// +// SafeLanding.cpp +// interface/src/octree +// +// Created by Simon Walton. +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "SafeLanding.h" +#include "EntityTreeRenderer.h" +#include "ModelEntityItem.h" +#include "InterfaceLogging.h" + +const int SafeLanding::SEQUENCE_MODULO = std::numeric_limits::max() + 1; + +namespace { + template bool lessThanWraparound(int a, int b) { + constexpr int MAX_T_VALUE = std::numeric_limits::max(); + if (b <= a) { + b += MAX_T_VALUE; + } + return (b - a) < (MAX_T_VALUE / 2); + } +} + +bool SafeLanding::SequenceLessThan::operator()(const int& a, const int& b) const { + return lessThanWraparound(a, b); +} + +void SafeLanding::startEntitySequence(QSharedPointer entityTreeRenderer) { + auto entityTree = entityTreeRenderer->getTree(); + + if (entityTree) { + Locker lock(_lock); + _entityTree = entityTree; + _trackedEntities.clear(); + _trackingEntities = true; + connect(std::const_pointer_cast(_entityTree).get(), + &EntityTree::addingEntity, this, &SafeLanding::addTrackedEntity); + connect(std::const_pointer_cast(_entityTree).get(), + &EntityTree::deletingEntity, this, &SafeLanding::deleteTrackedEntity); + + _sequenceNumbers.clear(); + _initialStart = INVALID_SEQUENCE; + _initialEnd = INVALID_SEQUENCE; + EntityTreeRenderer::setEntityLoadingPriorityFunction(&ElevatedPriority); + } +} + +void SafeLanding::stopEntitySequence() { + Locker lock(_lock); + _trackingEntities = false; + _initialStart = INVALID_SEQUENCE; + _initialEnd = INVALID_SEQUENCE; + _trackedEntities.clear(); + _sequenceNumbers.clear(); +} + +void SafeLanding::addTrackedEntity(const EntityItemID& entityID) { + if (_trackingEntities) { + Locker lock(_lock); + EntityItemPointer entity = _entityTree->findEntityByID(entityID); + + if (entity && !entity->getCollisionless()) { + const auto& entityType = entity->getType(); + if (entityType == EntityTypes::Model) { + ModelEntityItem * modelEntity = std::dynamic_pointer_cast(entity).get(); + static const std::set downloadedCollisionTypes + { SHAPE_TYPE_COMPOUND, SHAPE_TYPE_SIMPLE_COMPOUND, SHAPE_TYPE_STATIC_MESH, SHAPE_TYPE_SIMPLE_HULL }; + bool hasAABox; + entity->getAABox(hasAABox); + if (hasAABox && downloadedCollisionTypes.count(modelEntity->getShapeType()) != 0) { + // Only track entities with downloaded collision bodies. + _trackedEntities.emplace(entityID, entity); + qCDebug(interfaceapp) << "Safe Landing: Tracking entity " << entity->getItemName(); + } + } + } + } +} + +void SafeLanding::deleteTrackedEntity(const EntityItemID& entityID) { + Locker lock(_lock); + _trackedEntities.erase(entityID); +} + +void SafeLanding::setCompletionSequenceNumbers(int first, int last) { + Locker lock(_lock); + if (_initialStart == INVALID_SEQUENCE) { + _initialStart = first; + _initialEnd = last; + } +} + +void SafeLanding::noteReceivedsequenceNumber(int sequenceNumber) { + if (_trackingEntities) { + Locker lock(_lock); + _sequenceNumbers.insert(sequenceNumber); + } +} + +bool SafeLanding::isLoadSequenceComplete() { + if (isEntityPhysicsComplete() && isSequenceNumbersComplete()) { + Locker lock(_lock); + _trackedEntities.clear(); + _initialStart = INVALID_SEQUENCE; + _initialEnd = INVALID_SEQUENCE; + _entityTree = nullptr; + EntityTreeRenderer::setEntityLoadingPriorityFunction(StandardPriority); + qCDebug(interfaceapp) << "Safe Landing: load sequence complete"; + } + + return !_trackingEntities; +} + +bool SafeLanding::isSequenceNumbersComplete() { + if (_initialStart != INVALID_SEQUENCE) { + Locker lock(_lock); + int sequenceSize = _initialStart <= _initialEnd ? _initialEnd - _initialStart: + _initialEnd + SEQUENCE_MODULO - _initialStart; + auto startIter = _sequenceNumbers.find(_initialStart); + auto endIter = _sequenceNumbers.find(_initialEnd - 1); + if (sequenceSize == 0 || + (startIter != _sequenceNumbers.end() + && endIter != _sequenceNumbers.end() + && distance(startIter, endIter) == sequenceSize - 1) ) { + _trackingEntities = false; // Don't track anything else that comes in. + return true; + } + } + return false; +} + +bool SafeLanding::isEntityPhysicsComplete() { + Locker lock(_lock); + for (auto entityMapIter = _trackedEntities.begin(); entityMapIter != _trackedEntities.end(); ++entityMapIter) { + auto entity = entityMapIter->second; + if (!entity->shouldBePhysical() || entity->isReadyToComputeShape()) { + entityMapIter = _trackedEntities.erase(entityMapIter); + if (entityMapIter == _trackedEntities.end()) { + break; + } + } + } + return _trackedEntities.empty(); +} + +float SafeLanding::ElevatedPriority(const EntityItem& entityItem) { + return entityItem.getCollisionless() ? 0.0f : 10.0f; +} + +void SafeLanding::debugDumpSequenceIDs() const { + int p = -1; + qCDebug(interfaceapp) << "Sequence set size:" << _sequenceNumbers.size(); + for (auto s: _sequenceNumbers) { + if (p == -1) { + p = s; + qCDebug(interfaceapp) << "First:" << s; + } else { + if (s != p + 1) { + qCDebug(interfaceapp) << "Gap from" << p << "to" << s << "(exclusive)"; + p = s; + } + } + } + if (p != -1) { + qCDebug(interfaceapp) << "Last:" << p; + } +} diff --git a/interface/src/octree/SafeLanding.h b/interface/src/octree/SafeLanding.h new file mode 100644 index 0000000000..9177930d81 --- /dev/null +++ b/interface/src/octree/SafeLanding.h @@ -0,0 +1,65 @@ +// +// SafeLanding.h +// interface/src/octree +// +// Created by Simon Walton. +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +// Controller for logic to wait for local collision bodies before enabling physics. + +#ifndef hifi_SafeLanding_h +#define hifi_SafeLanding_h + +#include +#include + +#include "EntityItem.h" + +class EntityTreeRenderer; +class EntityItemID; + +class SafeLanding : public QObject { +public: + void startEntitySequence(QSharedPointer entityTreeRenderer); + void stopEntitySequence(); + void setCompletionSequenceNumbers(int first, int last); // 'last' exclusive. + void noteReceivedsequenceNumber(int sequenceNumber); + bool isLoadSequenceComplete(); + +private slots: + void addTrackedEntity(const EntityItemID& entityID); + void deleteTrackedEntity(const EntityItemID& entityID); + +private: + bool isSequenceNumbersComplete(); + void debugDumpSequenceIDs() const; + bool isEntityPhysicsComplete(); + + std::mutex _lock; + using Locker = std::lock_guard; + bool _trackingEntities { false }; + EntityTreePointer _entityTree; + using EntityMap = std::map; + EntityMap _trackedEntities; + + static constexpr int INVALID_SEQUENCE = -1; + int _initialStart { INVALID_SEQUENCE }; + int _initialEnd { INVALID_SEQUENCE }; + + struct SequenceLessThan { + bool operator()(const int& a, const int& b) const; + }; + + std::set _sequenceNumbers; + + static float ElevatedPriority(const EntityItem& entityItem); + static float StandardPriority(const EntityItem&) { return 0.0f; } + + static const int SEQUENCE_MODULO; +}; + +#endif // hifi_SafeLanding_h diff --git a/interface/src/raypick/CollisionPick.cpp b/interface/src/raypick/CollisionPick.cpp new file mode 100644 index 0000000000..7d0276875b --- /dev/null +++ b/interface/src/raypick/CollisionPick.cpp @@ -0,0 +1,425 @@ +// +// Created by Sabrina Shanman 7/16/2018 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "CollisionPick.h" + +#include + +#include + +#include "PhysicsCollisionGroups.h" +#include "ScriptEngineLogging.h" +#include "UUIDHasher.h" + +PickResultPointer CollisionPickResult::compareAndProcessNewResult(const PickResultPointer& newRes) { + const std::shared_ptr newCollisionResult = std::static_pointer_cast(newRes); + + if (entityIntersections.size()) { + entityIntersections.insert(entityIntersections.cend(), newCollisionResult->entityIntersections.begin(), newCollisionResult->entityIntersections.end()); + } else { + entityIntersections = newCollisionResult->entityIntersections; + } + + if (avatarIntersections.size()) { + avatarIntersections.insert(avatarIntersections.cend(), newCollisionResult->avatarIntersections.begin(), newCollisionResult->avatarIntersections.end()); + } else { + avatarIntersections = newCollisionResult->avatarIntersections; + } + + intersects = entityIntersections.size() || avatarIntersections.size(); + + return std::make_shared(*this); +} + +void buildObjectIntersectionsMap(IntersectionType intersectionType, const std::vector& objectIntersections, std::unordered_map& intersections, std::unordered_map& collisionPointPairs) { + for (auto& objectIntersection : objectIntersections) { + auto at = intersections.find(objectIntersection.foundID); + if (at == intersections.end()) { + QVariantMap intersectingObject; + intersectingObject["id"] = objectIntersection.foundID; + intersectingObject["type"] = intersectionType; + intersections[objectIntersection.foundID] = intersectingObject; + + collisionPointPairs[objectIntersection.foundID] = QVariantList(); + } + + QVariantMap collisionPointPair; + collisionPointPair["pointOnPick"] = vec3toVariant(objectIntersection.testCollisionPoint); + collisionPointPair["pointOnObject"] = vec3toVariant(objectIntersection.foundCollisionPoint); + + collisionPointPairs[objectIntersection.foundID].append(collisionPointPair); + } +} + +QVariantMap CollisionPickResult::toVariantMap() const { + QVariantMap variantMap; + + variantMap["intersects"] = intersects; + + std::unordered_map intersections; + std::unordered_map collisionPointPairs; + + buildObjectIntersectionsMap(ENTITY, entityIntersections, intersections, collisionPointPairs); + buildObjectIntersectionsMap(AVATAR, avatarIntersections, intersections, collisionPointPairs); + + QVariantList qIntersectingObjects; + for (auto& intersectionKeyVal : intersections) { + const QUuid& id = intersectionKeyVal.first; + QVariantMap& intersection = intersectionKeyVal.second; + + intersection["collisionContacts"] = collisionPointPairs[id]; + qIntersectingObjects.append(intersection); + } + + variantMap["intersectingObjects"] = qIntersectingObjects; + variantMap["collisionRegion"] = pickVariant; + + return variantMap; +} + +bool CollisionPick::isLoaded() const { + return !_mathPick.shouldComputeShapeInfo() || (_cachedResource && _cachedResource->isLoaded()); +} + +bool CollisionPick::getShapeInfoReady() { + if (_mathPick.shouldComputeShapeInfo()) { + if (_cachedResource && _cachedResource->isLoaded()) { + computeShapeInfo(_mathPick, *_mathPick.shapeInfo, _cachedResource); + _mathPick.loaded = true; + } else { + _mathPick.loaded = false; + } + } else { + computeShapeInfoDimensionsOnly(_mathPick, *_mathPick.shapeInfo, _cachedResource); + _mathPick.loaded = true; + } + + return _mathPick.loaded; +} + +void CollisionPick::computeShapeInfoDimensionsOnly(CollisionRegion& pick, ShapeInfo& shapeInfo, QSharedPointer resource) { + ShapeType type = shapeInfo.getType(); + glm::vec3 dimensions = pick.transform.getScale(); + QString modelURL = (resource ? resource->getURL().toString() : ""); + if (type == SHAPE_TYPE_COMPOUND) { + shapeInfo.setParams(type, dimensions, modelURL); + } else if (type >= SHAPE_TYPE_SIMPLE_HULL && type <= SHAPE_TYPE_STATIC_MESH) { + shapeInfo.setParams(type, 0.5f * dimensions, modelURL); + } else { + shapeInfo.setParams(type, 0.5f * dimensions, modelURL); + } +} + +void CollisionPick::computeShapeInfo(CollisionRegion& pick, ShapeInfo& shapeInfo, QSharedPointer resource) { + // This code was copied and modified from RenderableModelEntityItem::computeShapeInfo + // TODO: Move to some shared code area (in entities-renderer? model-networking?) + // after we verify this is working and do a diff comparison with RenderableModelEntityItem::computeShapeInfo + // to consolidate the code. + // We may also want to make computeShapeInfo always abstract away from the gpu model mesh, like it does here. + const uint32_t TRIANGLE_STRIDE = 3; + const uint32_t QUAD_STRIDE = 4; + + ShapeType type = shapeInfo.getType(); + glm::vec3 dimensions = pick.transform.getScale(); + if (type == SHAPE_TYPE_COMPOUND) { + // should never fall in here when collision model not fully loaded + // TODO: assert that all geometries exist and are loaded + //assert(_model && _model->isLoaded() && _compoundShapeResource && _compoundShapeResource->isLoaded()); + const FBXGeometry& collisionGeometry = resource->getFBXGeometry(); + + ShapeInfo::PointCollection& pointCollection = shapeInfo.getPointCollection(); + pointCollection.clear(); + uint32_t i = 0; + + // the way OBJ files get read, each section under a "g" line is its own meshPart. We only expect + // to find one actual "mesh" (with one or more meshParts in it), but we loop over the meshes, just in case. + foreach (const FBXMesh& mesh, collisionGeometry.meshes) { + // each meshPart is a convex hull + foreach (const FBXMeshPart &meshPart, mesh.parts) { + pointCollection.push_back(QVector()); + ShapeInfo::PointList& pointsInPart = pointCollection[i]; + + // run through all the triangles and (uniquely) add each point to the hull + uint32_t numIndices = (uint32_t)meshPart.triangleIndices.size(); + // TODO: assert rather than workaround after we start sanitizing FBXMesh higher up + //assert(numIndices % TRIANGLE_STRIDE == 0); + numIndices -= numIndices % TRIANGLE_STRIDE; // WORKAROUND lack of sanity checking in FBXReader + + for (uint32_t j = 0; j < numIndices; j += TRIANGLE_STRIDE) { + glm::vec3 p0 = mesh.vertices[meshPart.triangleIndices[j]]; + glm::vec3 p1 = mesh.vertices[meshPart.triangleIndices[j + 1]]; + glm::vec3 p2 = mesh.vertices[meshPart.triangleIndices[j + 2]]; + if (!pointsInPart.contains(p0)) { + pointsInPart << p0; + } + if (!pointsInPart.contains(p1)) { + pointsInPart << p1; + } + if (!pointsInPart.contains(p2)) { + pointsInPart << p2; + } + } + + // run through all the quads and (uniquely) add each point to the hull + numIndices = (uint32_t)meshPart.quadIndices.size(); + // TODO: assert rather than workaround after we start sanitizing FBXMesh higher up + //assert(numIndices % QUAD_STRIDE == 0); + numIndices -= numIndices % QUAD_STRIDE; // WORKAROUND lack of sanity checking in FBXReader + + for (uint32_t j = 0; j < numIndices; j += QUAD_STRIDE) { + glm::vec3 p0 = mesh.vertices[meshPart.quadIndices[j]]; + glm::vec3 p1 = mesh.vertices[meshPart.quadIndices[j + 1]]; + glm::vec3 p2 = mesh.vertices[meshPart.quadIndices[j + 2]]; + glm::vec3 p3 = mesh.vertices[meshPart.quadIndices[j + 3]]; + if (!pointsInPart.contains(p0)) { + pointsInPart << p0; + } + if (!pointsInPart.contains(p1)) { + pointsInPart << p1; + } + if (!pointsInPart.contains(p2)) { + pointsInPart << p2; + } + if (!pointsInPart.contains(p3)) { + pointsInPart << p3; + } + } + + if (pointsInPart.size() == 0) { + qCDebug(scriptengine) << "Warning -- meshPart has no faces"; + pointCollection.pop_back(); + continue; + } + ++i; + } + } + + // We expect that the collision model will have the same units and will be displaced + // from its origin in the same way the visual model is. The visual model has + // been centered and probably scaled. We take the scaling and offset which were applied + // to the visual model and apply them to the collision model (without regard for the + // collision model's extents). + + glm::vec3 scaleToFit = dimensions / resource->getFBXGeometry().getUnscaledMeshExtents().size(); + // multiply each point by scale + for (int32_t i = 0; i < pointCollection.size(); i++) { + for (int32_t j = 0; j < pointCollection[i].size(); j++) { + // back compensate for registration so we can apply that offset to the shapeInfo later + pointCollection[i][j] = scaleToFit * pointCollection[i][j]; + } + } + shapeInfo.setParams(type, dimensions, resource->getURL().toString()); + } else if (type >= SHAPE_TYPE_SIMPLE_HULL && type <= SHAPE_TYPE_STATIC_MESH) { + const FBXGeometry& fbxGeometry = resource->getFBXGeometry(); + int numFbxMeshes = fbxGeometry.meshes.size(); + int totalNumVertices = 0; + for (int i = 0; i < numFbxMeshes; i++) { + const FBXMesh& mesh = fbxGeometry.meshes.at(i); + totalNumVertices += mesh.vertices.size(); + } + const int32_t MAX_VERTICES_PER_STATIC_MESH = 1e6; + if (totalNumVertices > MAX_VERTICES_PER_STATIC_MESH) { + qWarning() << "model" << resource->getURL() << "has too many vertices" << totalNumVertices << "and will collide as a box."; + shapeInfo.setParams(SHAPE_TYPE_BOX, 0.5f * dimensions); + return; + } + + auto& meshes = resource->getFBXGeometry().meshes; + int32_t numMeshes = (int32_t)(meshes.size()); + + const int MAX_ALLOWED_MESH_COUNT = 1000; + if (numMeshes > MAX_ALLOWED_MESH_COUNT) { + // too many will cause the deadlock timer to throw... + shapeInfo.setParams(SHAPE_TYPE_BOX, 0.5f * dimensions); + return; + } + + ShapeInfo::PointCollection& pointCollection = shapeInfo.getPointCollection(); + pointCollection.clear(); + if (type == SHAPE_TYPE_SIMPLE_COMPOUND) { + pointCollection.resize(numMeshes); + } else { + pointCollection.resize(1); + } + + ShapeInfo::TriangleIndices& triangleIndices = shapeInfo.getTriangleIndices(); + triangleIndices.clear(); + + Extents extents; + int32_t meshCount = 0; + int32_t pointListIndex = 0; + for (auto& mesh : meshes) { + if (!mesh.vertices.size()) { + continue; + } + QVector vertices = mesh.vertices; + + ShapeInfo::PointList& points = pointCollection[pointListIndex]; + + // reserve room + int32_t sizeToReserve = (int32_t)(vertices.count()); + if (type == SHAPE_TYPE_SIMPLE_COMPOUND) { + // a list of points for each mesh + pointListIndex++; + } else { + // only one list of points + sizeToReserve += (int32_t)points.size(); + } + points.reserve(sizeToReserve); + + // copy points + const glm::vec3* vertexItr = vertices.cbegin(); + while (vertexItr != vertices.cend()) { + glm::vec3 point = *vertexItr; + points.push_back(point); + extents.addPoint(point); + ++vertexItr; + } + + if (type == SHAPE_TYPE_STATIC_MESH) { + // copy into triangleIndices + size_t triangleIndicesCount = 0; + for (const FBXMeshPart& meshPart : mesh.parts) { + triangleIndicesCount += meshPart.triangleIndices.count(); + } + triangleIndices.reserve((int)triangleIndicesCount); + + for (const FBXMeshPart& meshPart : mesh.parts) { + const int* indexItr = meshPart.triangleIndices.cbegin(); + while (indexItr != meshPart.triangleIndices.cend()) { + triangleIndices.push_back(*indexItr); + ++indexItr; + } + } + } else if (type == SHAPE_TYPE_SIMPLE_COMPOUND) { + // for each mesh copy unique part indices, separated by special bogus (flag) index values + for (const FBXMeshPart& meshPart : mesh.parts) { + // collect unique list of indices for this part + std::set uniqueIndices; + auto numIndices = meshPart.triangleIndices.count(); + // TODO: assert rather than workaround after we start sanitizing FBXMesh higher up + //assert(numIndices% TRIANGLE_STRIDE == 0); + numIndices -= numIndices % TRIANGLE_STRIDE; // WORKAROUND lack of sanity checking in FBXReader + + auto indexItr = meshPart.triangleIndices.cbegin(); + while (indexItr != meshPart.triangleIndices.cend()) { + uniqueIndices.insert(*indexItr); + ++indexItr; + } + + // store uniqueIndices in triangleIndices + triangleIndices.reserve(triangleIndices.size() + (int32_t)uniqueIndices.size()); + for (auto index : uniqueIndices) { + triangleIndices.push_back(index); + } + // flag end of part + triangleIndices.push_back(END_OF_MESH_PART); + } + // flag end of mesh + triangleIndices.push_back(END_OF_MESH); + } + ++meshCount; + } + + // scale and shift + glm::vec3 extentsSize = extents.size(); + glm::vec3 scaleToFit = dimensions / extentsSize; + for (int32_t i = 0; i < 3; ++i) { + if (extentsSize[i] < 1.0e-6f) { + scaleToFit[i] = 1.0f; + } + } + for (auto points : pointCollection) { + for (int32_t i = 0; i < points.size(); ++i) { + points[i] = (points[i] * scaleToFit); + } + } + + shapeInfo.setParams(type, 0.5f * dimensions, resource->getURL().toString()); + } +} + +CollisionPick::CollisionPick(const PickFilter& filter, float maxDistance, bool enabled, CollisionRegion collisionRegion, PhysicsEnginePointer physicsEngine) : + Pick(filter, maxDistance, enabled), + _mathPick(collisionRegion), + _physicsEngine(physicsEngine) { + if (collisionRegion.shouldComputeShapeInfo()) { + _cachedResource = DependencyManager::get()->getCollisionGeometryResource(collisionRegion.modelURL); + } + _mathPick.loaded = isLoaded(); +} + +CollisionRegion CollisionPick::getMathematicalPick() const { + CollisionRegion mathPick = _mathPick; + mathPick.loaded = isLoaded(); + if (!parentTransform) { + return mathPick; + } else { + mathPick.transform = parentTransform->getTransform().worldTransform(mathPick.transform); + return mathPick; + } +} + +void CollisionPick::filterIntersections(std::vector& intersections) const { + const QVector& ignoreItems = getIgnoreItems(); + const QVector& includeItems = getIncludeItems(); + bool isWhitelist = !includeItems.empty(); + + if (!isWhitelist && ignoreItems.empty()) { + return; + } + + std::vector filteredIntersections; + + int n = (int)intersections.size(); + for (int i = 0; i < n; i++) { + auto& intersection = intersections[i]; + const QUuid& id = intersection.foundID; + if (!ignoreItems.contains(id) && (!isWhitelist || includeItems.contains(id))) { + filteredIntersections.push_back(intersection); + } + } + + intersections = filteredIntersections; +} + +PickResultPointer CollisionPick::getEntityIntersection(const CollisionRegion& pick) { + if (!pick.loaded) { + // Cannot compute result + return std::make_shared(pick.toVariantMap(), std::vector(), std::vector()); + } + getShapeInfoReady(); + + auto entityIntersections = _physicsEngine->contactTest(USER_COLLISION_MASK_ENTITIES, *pick.shapeInfo, pick.transform, USER_COLLISION_GROUP_DYNAMIC, pick.threshold); + filterIntersections(entityIntersections); + return std::make_shared(pick, entityIntersections, std::vector()); +} + +PickResultPointer CollisionPick::getOverlayIntersection(const CollisionRegion& pick) { + return std::make_shared(pick, std::vector(), std::vector()); +} + +PickResultPointer CollisionPick::getAvatarIntersection(const CollisionRegion& pick) { + if (!pick.loaded) { + // Cannot compute result + return std::make_shared(pick, std::vector(), std::vector()); + } + getShapeInfoReady(); + + auto avatarIntersections = _physicsEngine->contactTest(USER_COLLISION_MASK_AVATARS, *pick.shapeInfo, pick.transform, USER_COLLISION_GROUP_DYNAMIC, pick.threshold); + filterIntersections(avatarIntersections); + return std::make_shared(pick, std::vector(), avatarIntersections); +} + +PickResultPointer CollisionPick::getHUDIntersection(const CollisionRegion& pick) { + return std::make_shared(pick.toVariantMap(), std::vector(), std::vector()); +} + +Transform CollisionPick::getResultTransform() const { + return Transform(getMathematicalPick().transform); +} \ No newline at end of file diff --git a/interface/src/raypick/CollisionPick.h b/interface/src/raypick/CollisionPick.h new file mode 100644 index 0000000000..ce8b3bd199 --- /dev/null +++ b/interface/src/raypick/CollisionPick.h @@ -0,0 +1,75 @@ +// +// Created by Sabrina Shanman 7/11/2018 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#ifndef hifi_CollisionPick_h +#define hifi_CollisionPick_h + +#include +#include +#include +#include +#include + +class CollisionPickResult : public PickResult { +public: + CollisionPickResult() {} + CollisionPickResult(const QVariantMap& pickVariant) : PickResult(pickVariant) {} + + CollisionPickResult(const CollisionRegion& searchRegion, const std::vector& entityIntersections, const std::vector& avatarIntersections) : + PickResult(searchRegion.toVariantMap()), + intersects(entityIntersections.size() || avatarIntersections.size()), + entityIntersections(entityIntersections), + avatarIntersections(avatarIntersections) + { + } + + CollisionPickResult(const CollisionPickResult& collisionPickResult) : PickResult(collisionPickResult.pickVariant) { + avatarIntersections = collisionPickResult.avatarIntersections; + entityIntersections = collisionPickResult.entityIntersections; + intersects = collisionPickResult.intersects; + } + + bool intersects { false }; + std::vector entityIntersections; + std::vector avatarIntersections; + + QVariantMap toVariantMap() const override; + + bool doesIntersect() const override { return intersects; } + bool checkOrFilterAgainstMaxDistance(float maxDistance) override { return true; } + + PickResultPointer compareAndProcessNewResult(const PickResultPointer& newRes) override; +}; + +class CollisionPick : public Pick { +public: + CollisionPick(const PickFilter& filter, float maxDistance, bool enabled, CollisionRegion collisionRegion, PhysicsEnginePointer physicsEngine); + + CollisionRegion getMathematicalPick() const override; + PickResultPointer getDefaultResult(const QVariantMap& pickVariant) const override { + return std::make_shared(pickVariant, std::vector(), std::vector()); + } + PickResultPointer getEntityIntersection(const CollisionRegion& pick) override; + PickResultPointer getOverlayIntersection(const CollisionRegion& pick) override; + PickResultPointer getAvatarIntersection(const CollisionRegion& pick) override; + PickResultPointer getHUDIntersection(const CollisionRegion& pick) override; + Transform getResultTransform() const override; +protected: + // Returns true if the resource for _mathPick.shapeInfo is loaded or if a resource is not needed. + bool isLoaded() const; + // Returns true if _mathPick.shapeInfo is valid. Otherwise, attempts to get the _mathPick ready for use. + bool getShapeInfoReady(); + void computeShapeInfo(CollisionRegion& pick, ShapeInfo& shapeInfo, QSharedPointer resource); + void computeShapeInfoDimensionsOnly(CollisionRegion& pick, ShapeInfo& shapeInfo, QSharedPointer resource); + void filterIntersections(std::vector& intersections) const; + + CollisionRegion _mathPick; + PhysicsEnginePointer _physicsEngine; + QSharedPointer _cachedResource; +}; + +#endif // hifi_CollisionPick_h \ No newline at end of file diff --git a/interface/src/raypick/JointParabolaPick.cpp b/interface/src/raypick/JointParabolaPick.cpp new file mode 100644 index 0000000000..11a2e90819 --- /dev/null +++ b/interface/src/raypick/JointParabolaPick.cpp @@ -0,0 +1,43 @@ +// +// Created by Sam Gondelman 7/2/2018 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#include "JointParabolaPick.h" + +#include "avatar/AvatarManager.h" + +JointParabolaPick::JointParabolaPick(const std::string& jointName, const glm::vec3& posOffset, const glm::vec3& dirOffset, + float speed, const glm::vec3& accelerationAxis, bool rotateAccelerationWithAvatar, bool scaleWithAvatar, PickFilter& filter, float maxDistance, bool enabled) : + ParabolaPick(speed, accelerationAxis, rotateAccelerationWithAvatar, scaleWithAvatar, filter, maxDistance, enabled), + _jointName(jointName), + _posOffset(posOffset), + _dirOffset(dirOffset) +{ +} + +PickParabola JointParabolaPick::getMathematicalPick() const { + auto myAvatar = DependencyManager::get()->getMyAvatar(); + int jointIndex = myAvatar->getJointIndex(QString::fromStdString(_jointName)); + bool useAvatarHead = _jointName == "Avatar"; + const int INVALID_JOINT = -1; + if (jointIndex != INVALID_JOINT || useAvatarHead) { + glm::vec3 jointPos = useAvatarHead ? myAvatar->getHeadPosition() : myAvatar->getAbsoluteJointTranslationInObjectFrame(jointIndex); + glm::quat jointRot = useAvatarHead ? myAvatar->getHeadOrientation() : myAvatar->getAbsoluteJointRotationInObjectFrame(jointIndex); + glm::vec3 avatarPos = myAvatar->getWorldPosition(); + glm::quat avatarRot = myAvatar->getWorldOrientation(); + + glm::vec3 pos = useAvatarHead ? jointPos : avatarPos + (avatarRot * jointPos); + glm::quat rot = useAvatarHead ? jointRot * glm::angleAxis(-PI / 2.0f, Vectors::RIGHT) : avatarRot * jointRot; + + // Apply offset + pos = pos + (rot * (myAvatar->getSensorToWorldScale() * _posOffset)); + glm::vec3 dir = glm::normalize(rot * glm::normalize(_dirOffset)); + + return PickParabola(pos, getSpeed() * dir, getAcceleration()); + } + + return PickParabola(); +} diff --git a/interface/src/raypick/JointParabolaPick.h b/interface/src/raypick/JointParabolaPick.h new file mode 100644 index 0000000000..aff6bd34d8 --- /dev/null +++ b/interface/src/raypick/JointParabolaPick.h @@ -0,0 +1,32 @@ +// +// Created by Sam Gondelman 7/2/2018 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#ifndef hifi_JointParabolaPick_h +#define hifi_JointParabolaPick_h + +#include "ParabolaPick.h" + +class JointParabolaPick : public ParabolaPick { + +public: + JointParabolaPick(const std::string& jointName, const glm::vec3& posOffset, const glm::vec3& dirOffset, + float speed, const glm::vec3& accelerationAxis, bool rotateAccelerationWithAvatar, bool scaleWithAvatar, + PickFilter& filter, float maxDistance = 0.0f, bool enabled = false); + + PickParabola getMathematicalPick() const override; + + bool isLeftHand() const override { return (_jointName == "_CONTROLLER_LEFTHAND") || (_jointName == "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND"); } + bool isRightHand() const override { return (_jointName == "_CONTROLLER_RIGHTHAND") || (_jointName == "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND"); } + +private: + std::string _jointName; + glm::vec3 _posOffset; + glm::vec3 _dirOffset; + +}; + +#endif // hifi_JointParabolaPick_h diff --git a/interface/src/raypick/JointRayPick.cpp b/interface/src/raypick/JointRayPick.cpp index 62912fdcd6..340014e7d2 100644 --- a/interface/src/raypick/JointRayPick.cpp +++ b/interface/src/raypick/JointRayPick.cpp @@ -36,7 +36,7 @@ PickRay JointRayPick::getMathematicalPick() const { // Apply offset pos = pos + (rot * (myAvatar->getSensorToWorldScale() * _posOffset)); - glm::vec3 dir = rot * glm::normalize(_dirOffset); + glm::vec3 dir = glm::normalize(rot * glm::normalize(_dirOffset)); return PickRay(pos, dir); } diff --git a/interface/src/raypick/LaserPointer.cpp b/interface/src/raypick/LaserPointer.cpp index bd71e47cf0..5fbe3a90b5 100644 --- a/interface/src/raypick/LaserPointer.cpp +++ b/interface/src/raypick/LaserPointer.cpp @@ -14,315 +14,148 @@ #include "avatar/AvatarManager.h" #include -#include -#include "PickScriptingInterface.h" #include "RayPick.h" LaserPointer::LaserPointer(const QVariant& rayProps, const RenderStateMap& renderStates, const DefaultRenderStateMap& defaultRenderStates, bool hover, - const PointerTriggers& triggers, bool faceAvatar, bool centerEndY, bool lockEnd, bool distanceScaleEnd, bool scaleWithAvatar, bool enabled) : - Pointer(DependencyManager::get()->createRayPick(rayProps), enabled, hover), - _triggers(triggers), - _renderStates(renderStates), - _defaultRenderStates(defaultRenderStates), - _faceAvatar(faceAvatar), - _centerEndY(centerEndY), - _lockEnd(lockEnd), - _distanceScaleEnd(distanceScaleEnd), - _scaleWithAvatar(scaleWithAvatar) + const PointerTriggers& triggers, bool faceAvatar, bool followNormal, float followNormalTime, bool centerEndY, bool lockEnd, + bool distanceScaleEnd, bool scaleWithAvatar, bool enabled) : + PathPointer(PickQuery::Ray, rayProps, renderStates, defaultRenderStates, hover, triggers, faceAvatar, followNormal, followNormalTime, + centerEndY, lockEnd, distanceScaleEnd, scaleWithAvatar, enabled) { - for (auto& state : _renderStates) { - if (!enabled || state.first != _currentRenderState) { - disableRenderState(state.second); - } - } - for (auto& state : _defaultRenderStates) { - if (!enabled || state.first != _currentRenderState) { - disableRenderState(state.second.second); - } - } } -LaserPointer::~LaserPointer() { - for (auto& renderState : _renderStates) { - renderState.second.deleteOverlays(); - } - for (auto& renderState : _defaultRenderStates) { - renderState.second.second.deleteOverlays(); - } -} - -void LaserPointer::setRenderState(const std::string& state) { - withWriteLock([&] { - if (!_currentRenderState.empty() && state != _currentRenderState) { - if (_renderStates.find(_currentRenderState) != _renderStates.end()) { - disableRenderState(_renderStates[_currentRenderState]); - } - if (_defaultRenderStates.find(_currentRenderState) != _defaultRenderStates.end()) { - disableRenderState(_defaultRenderStates[_currentRenderState].second); - } - } - _currentRenderState = state; - }); -} - -void LaserPointer::editRenderState(const std::string& state, const QVariant& startProps, const QVariant& pathProps, const QVariant& endProps) { - withWriteLock([&] { - updateRenderStateOverlay(_renderStates[state].getStartID(), startProps); - updateRenderStateOverlay(_renderStates[state].getPathID(), pathProps); - updateRenderStateOverlay(_renderStates[state].getEndID(), endProps); - QVariant endDim = endProps.toMap()["dimensions"]; - if (endDim.isValid()) { - _renderStates[state].setEndDim(vec3FromVariant(endDim)); - } +void LaserPointer::editRenderStatePath(const std::string& state, const QVariant& pathProps) { + auto renderState = std::static_pointer_cast(_renderStates[state]); + if (renderState) { + updateRenderStateOverlay(renderState->getPathID(), pathProps); QVariant lineWidth = pathProps.toMap()["lineWidth"]; if (lineWidth.isValid()) { - _renderStates[state].setLineWidth(lineWidth.toFloat()); + renderState->setLineWidth(lineWidth.toFloat()); } - }); -} - -PickResultPointer LaserPointer::getVisualPickResult(const PickResultPointer& pickResult) { - PickResultPointer visualPickResult = pickResult; - auto rayPickResult = std::static_pointer_cast(visualPickResult); - IntersectionType type = rayPickResult ? rayPickResult->type : IntersectionType::NONE; - - if (type != IntersectionType::HUD) { - glm::vec3 endVec; - PickRay pickRay = rayPickResult ? PickRay(rayPickResult->pickVariant) : PickRay(); - if (!_lockEndObject.id.isNull()) { - glm::vec3 pos; - glm::quat rot; - glm::vec3 dim; - glm::vec3 registrationPoint; - if (_lockEndObject.isOverlay) { - pos = vec3FromVariant(qApp->getOverlays().getProperty(_lockEndObject.id, "position").value); - rot = quatFromVariant(qApp->getOverlays().getProperty(_lockEndObject.id, "rotation").value); - dim = vec3FromVariant(qApp->getOverlays().getProperty(_lockEndObject.id, "dimensions").value); - registrationPoint = glm::vec3(0.5f); - } else { - EntityItemProperties props = DependencyManager::get()->getEntityProperties(_lockEndObject.id); - glm::mat4 entityMat = createMatFromQuatAndPos(props.getRotation(), props.getPosition()); - glm::mat4 finalPosAndRotMat = entityMat * _lockEndObject.offsetMat; - pos = extractTranslation(finalPosAndRotMat); - rot = glmExtractRotation(finalPosAndRotMat); - dim = props.getDimensions(); - registrationPoint = props.getRegistrationPoint(); - } - const glm::vec3 DEFAULT_REGISTRATION_POINT = glm::vec3(0.5f); - endVec = pos + rot * (dim * (DEFAULT_REGISTRATION_POINT - registrationPoint)); - glm::vec3 direction = endVec - pickRay.origin; - float distance = glm::distance(pickRay.origin, endVec); - glm::vec3 normalizedDirection = glm::normalize(direction); - - rayPickResult->type = _lockEndObject.isOverlay ? IntersectionType::OVERLAY : IntersectionType::ENTITY; - rayPickResult->objectID = _lockEndObject.id; - rayPickResult->intersection = endVec; - rayPickResult->distance = distance; - rayPickResult->surfaceNormal = -normalizedDirection; - rayPickResult->pickVariant["direction"] = vec3toVariant(normalizedDirection); - } else if (type != IntersectionType::NONE && _lockEnd) { - if (type == IntersectionType::ENTITY) { - endVec = DependencyManager::get()->getEntityTransform(rayPickResult->objectID)[3]; - } else if (type == IntersectionType::OVERLAY) { - endVec = vec3FromVariant(qApp->getOverlays().getProperty(rayPickResult->objectID, "position").value); - } else if (type == IntersectionType::AVATAR) { - endVec = DependencyManager::get()->getAvatar(rayPickResult->objectID)->getPosition(); - } - glm::vec3 direction = endVec - pickRay.origin; - float distance = glm::distance(pickRay.origin, endVec); - glm::vec3 normalizedDirection = glm::normalize(direction); - rayPickResult->intersection = endVec; - rayPickResult->distance = distance; - rayPickResult->surfaceNormal = -normalizedDirection; - rayPickResult->pickVariant["direction"] = vec3toVariant(normalizedDirection); - } - } - return visualPickResult; -} - -void LaserPointer::updateRenderStateOverlay(const OverlayID& id, const QVariant& props) { - if (!id.isNull() && props.isValid()) { - QVariantMap propMap = props.toMap(); - propMap.remove("visible"); - qApp->getOverlays().editOverlay(id, propMap); } } -void LaserPointer::updateRenderState(const RenderState& renderState, const IntersectionType type, float distance, const QUuid& objectID, const PickRay& pickRay) { - if (!renderState.getStartID().isNull()) { - QVariantMap startProps; - startProps.insert("position", vec3toVariant(pickRay.origin)); - startProps.insert("visible", true); - startProps.insert("ignoreRayIntersection", renderState.doesStartIgnoreRays()); - qApp->getOverlays().editOverlay(renderState.getStartID(), startProps); - } - glm::vec3 endVec = pickRay.origin + pickRay.direction * distance; +QVariantMap LaserPointer::toVariantMap() const { + QVariantMap qVariantMap; - QVariant end = vec3toVariant(endVec); - if (!renderState.getPathID().isNull()) { - QVariantMap pathProps; - pathProps.insert("start", vec3toVariant(pickRay.origin)); - pathProps.insert("end", end); - pathProps.insert("visible", true); - pathProps.insert("ignoreRayIntersection", renderState.doesPathIgnoreRays()); - if (_scaleWithAvatar) { - pathProps.insert("lineWidth", renderState.getLineWidth() * DependencyManager::get()->getMyAvatar()->getSensorToWorldScale()); - } - qApp->getOverlays().editOverlay(renderState.getPathID(), pathProps); + QVariantMap qRenderStates; + for (auto iter = _renderStates.cbegin(); iter != _renderStates.cend(); iter++) { + auto renderState = iter->second; + QVariantMap qRenderState; + qRenderState["start"] = renderState->getStartID(); + qRenderState["path"] = std::static_pointer_cast(renderState)->getPathID(); + qRenderState["end"] = renderState->getEndID(); + qRenderStates[iter->first.c_str()] = qRenderState; } - if (!renderState.getEndID().isNull()) { - QVariantMap endProps; - glm::quat faceAvatarRotation = DependencyManager::get()->getMyAvatar()->getWorldOrientation() * glm::quat(glm::radians(glm::vec3(0.0f, 180.0f, 0.0f))); - glm::vec3 dim = vec3FromVariant(qApp->getOverlays().getProperty(renderState.getEndID(), "dimensions").value); - if (_distanceScaleEnd) { - dim = renderState.getEndDim() * glm::distance(pickRay.origin, endVec); - endProps.insert("dimensions", vec3toVariant(dim)); - } - if (_centerEndY) { - endProps.insert("position", end); - } else { - glm::vec3 currentUpVector = faceAvatarRotation * Vectors::UP; - endProps.insert("position", vec3toVariant(endVec + glm::vec3(currentUpVector.x * 0.5f * dim.y, currentUpVector.y * 0.5f * dim.y, currentUpVector.z * 0.5f * dim.y))); - } - if (_faceAvatar) { - endProps.insert("rotation", quatToVariant(faceAvatarRotation)); - } - endProps.insert("visible", true); - endProps.insert("ignoreRayIntersection", renderState.doesEndIgnoreRays()); - qApp->getOverlays().editOverlay(renderState.getEndID(), endProps); + qVariantMap["renderStates"] = qRenderStates; + + QVariantMap qDefaultRenderStates; + for (auto iter = _defaultRenderStates.cbegin(); iter != _defaultRenderStates.cend(); iter++) { + float distance = iter->second.first; + auto defaultRenderState = iter->second.second; + QVariantMap qDefaultRenderState; + + qDefaultRenderState["distance"] = distance; + qDefaultRenderState["start"] = defaultRenderState->getStartID(); + qDefaultRenderState["path"] = std::static_pointer_cast(defaultRenderState)->getPathID(); + qDefaultRenderState["end"] = defaultRenderState->getEndID(); + qDefaultRenderStates[iter->first.c_str()] = qDefaultRenderState; } + qVariantMap["defaultRenderStates"] = qDefaultRenderStates; + + return qVariantMap; } -void LaserPointer::disableRenderState(const RenderState& renderState) { - if (!renderState.getStartID().isNull()) { - QVariantMap startProps; - startProps.insert("visible", false); - startProps.insert("ignoreRayIntersection", true); - qApp->getOverlays().editOverlay(renderState.getStartID(), startProps); - } - if (!renderState.getPathID().isNull()) { - QVariantMap pathProps; - pathProps.insert("visible", false); - pathProps.insert("ignoreRayIntersection", true); - qApp->getOverlays().editOverlay(renderState.getPathID(), pathProps); - } - if (!renderState.getEndID().isNull()) { - QVariantMap endProps; - endProps.insert("visible", false); - endProps.insert("ignoreRayIntersection", true); - qApp->getOverlays().editOverlay(renderState.getEndID(), endProps); - } +glm::vec3 LaserPointer::getPickOrigin(const PickResultPointer& pickResult) const { + auto rayPickResult = std::static_pointer_cast(pickResult); + return (rayPickResult ? vec3FromVariant(rayPickResult->pickVariant["origin"]) : glm::vec3(0.0f)); } -void LaserPointer::updateVisuals(const PickResultPointer& pickResult) { - auto rayPickResult = std::static_pointer_cast(pickResult); - - IntersectionType type = rayPickResult ? rayPickResult->type : IntersectionType::NONE; - if (_enabled && !_currentRenderState.empty() && _renderStates.find(_currentRenderState) != _renderStates.end() && - (type != IntersectionType::NONE || _laserLength > 0.0f || !_lockEndObject.id.isNull())) { - PickRay pickRay = rayPickResult ? PickRay(rayPickResult->pickVariant): PickRay(); - QUuid uid = rayPickResult->objectID; - float distance = _laserLength > 0.0f ? _laserLength : rayPickResult->distance; - updateRenderState(_renderStates[_currentRenderState], type, distance, uid, pickRay); - disableRenderState(_defaultRenderStates[_currentRenderState].second); - } else if (_enabled && !_currentRenderState.empty() && _defaultRenderStates.find(_currentRenderState) != _defaultRenderStates.end()) { - disableRenderState(_renderStates[_currentRenderState]); - PickRay pickRay = rayPickResult ? PickRay(rayPickResult->pickVariant) : PickRay(); - updateRenderState(_defaultRenderStates[_currentRenderState].second, IntersectionType::NONE, _defaultRenderStates[_currentRenderState].first, QUuid(), pickRay); - } else if (!_currentRenderState.empty()) { - disableRenderState(_renderStates[_currentRenderState]); - disableRenderState(_defaultRenderStates[_currentRenderState].second); - } -} - -Pointer::PickedObject LaserPointer::getHoveredObject(const PickResultPointer& pickResult) { - auto rayPickResult = std::static_pointer_cast(pickResult); +glm::vec3 LaserPointer::getPickEnd(const PickResultPointer& pickResult, float distance) const { + auto rayPickResult = std::static_pointer_cast(pickResult); if (!rayPickResult) { - return PickedObject(); + return glm::vec3(0.0f); + } + if (distance > 0.0f) { + PickRay pick = PickRay(rayPickResult->pickVariant); + return pick.origin + distance * pick.direction; + } else { + return rayPickResult->intersection; } - return PickedObject(rayPickResult->objectID, rayPickResult->type); } -Pointer::Buttons LaserPointer::getPressedButtons(const PickResultPointer& pickResult) { - std::unordered_set toReturn; - auto rayPickResult = std::static_pointer_cast(pickResult); +glm::vec3 LaserPointer::getPickedObjectNormal(const PickResultPointer& pickResult) const { + auto rayPickResult = std::static_pointer_cast(pickResult); + return (rayPickResult ? rayPickResult->surfaceNormal : glm::vec3(0.0f)); +} +IntersectionType LaserPointer::getPickedObjectType(const PickResultPointer& pickResult) const { + auto rayPickResult = std::static_pointer_cast(pickResult); + return (rayPickResult ? rayPickResult->type : IntersectionType::NONE); +} + +QUuid LaserPointer::getPickedObjectID(const PickResultPointer& pickResult) const { + auto rayPickResult = std::static_pointer_cast(pickResult); + return (rayPickResult ? rayPickResult->objectID : QUuid()); +} + +void LaserPointer::setVisualPickResultInternal(PickResultPointer pickResult, IntersectionType type, const QUuid& id, + const glm::vec3& intersection, float distance, const glm::vec3& surfaceNormal) { + auto rayPickResult = std::static_pointer_cast(pickResult); if (rayPickResult) { - for (const PointerTrigger& trigger : _triggers) { - std::string button = trigger.getButton(); - TriggerState& state = _states[button]; - // TODO: right now, LaserPointers don't support axes, only on/off buttons - if (trigger.getEndpoint()->peek() >= 1.0f) { - toReturn.insert(button); - - if (_previousButtons.find(button) == _previousButtons.end()) { - // start triggering for buttons that were just pressed - state.triggeredObject = PickedObject(rayPickResult->objectID, rayPickResult->type); - state.intersection = rayPickResult->intersection; - state.triggerPos2D = findPos2D(state.triggeredObject, rayPickResult->intersection); - state.triggerStartTime = usecTimestampNow(); - state.surfaceNormal = rayPickResult->surfaceNormal; - state.deadspotExpired = false; - state.wasTriggering = true; - state.triggering = true; - _latestState = state; - } - } else { - // stop triggering for buttons that aren't pressed - state.wasTriggering = state.triggering; - state.triggering = false; - _latestState = state; - } - } - _previousButtons = toReturn; + rayPickResult->type = type; + rayPickResult->objectID = id; + rayPickResult->intersection = intersection; + rayPickResult->distance = distance; + rayPickResult->surfaceNormal = surfaceNormal; + rayPickResult->pickVariant["direction"] = vec3toVariant(-surfaceNormal); } - - return toReturn; } -void LaserPointer::setLength(float length) { - withWriteLock([&] { - _laserLength = length; - }); -} - -void LaserPointer::setLockEndUUID(const QUuid& objectID, const bool isOverlay, const glm::mat4& offsetMat) { - withWriteLock([&] { - _lockEndObject.id = objectID; - _lockEndObject.isOverlay = isOverlay; - _lockEndObject.offsetMat = offsetMat; - }); -} - -RenderState::RenderState(const OverlayID& startID, const OverlayID& pathID, const OverlayID& endID) : - _startID(startID), _pathID(pathID), _endID(endID) +LaserPointer::RenderState::RenderState(const OverlayID& startID, const OverlayID& pathID, const OverlayID& endID) : + StartEndRenderState(startID, endID), _pathID(pathID) { - if (!_startID.isNull()) { - _startIgnoreRays = qApp->getOverlays().getProperty(_startID, "ignoreRayIntersection").value.toBool(); - } if (!_pathID.isNull()) { _pathIgnoreRays = qApp->getOverlays().getProperty(_pathID, "ignoreRayIntersection").value.toBool(); _lineWidth = qApp->getOverlays().getProperty(_pathID, "lineWidth").value.toFloat(); } - if (!_endID.isNull()) { - _endDim = vec3FromVariant(qApp->getOverlays().getProperty(_endID, "dimensions").value); - _endIgnoreRays = qApp->getOverlays().getProperty(_endID, "ignoreRayIntersection").value.toBool(); - } } -void RenderState::deleteOverlays() { - if (!_startID.isNull()) { - qApp->getOverlays().deleteOverlay(_startID); - } +void LaserPointer::RenderState::cleanup() { + StartEndRenderState::cleanup(); if (!_pathID.isNull()) { qApp->getOverlays().deleteOverlay(_pathID); } - if (!_endID.isNull()) { - qApp->getOverlays().deleteOverlay(_endID); +} + +void LaserPointer::RenderState::disable() { + StartEndRenderState::disable(); + if (!getPathID().isNull()) { + QVariantMap pathProps; + pathProps.insert("visible", false); + pathProps.insert("ignoreRayIntersection", true); + qApp->getOverlays().editOverlay(getPathID(), pathProps); } } -RenderState LaserPointer::buildRenderState(const QVariantMap& propMap) { +void LaserPointer::RenderState::update(const glm::vec3& origin, const glm::vec3& end, const glm::vec3& surfaceNormal, bool scaleWithAvatar, bool distanceScaleEnd, bool centerEndY, + bool faceAvatar, bool followNormal, float followNormalStrength, float distance, const PickResultPointer& pickResult) { + StartEndRenderState::update(origin, end, surfaceNormal, scaleWithAvatar, distanceScaleEnd, centerEndY, faceAvatar, followNormal, followNormalStrength, distance, pickResult); + QVariant endVariant = vec3toVariant(end); + if (!getPathID().isNull()) { + QVariantMap pathProps; + pathProps.insert("start", vec3toVariant(origin)); + pathProps.insert("end", endVariant); + pathProps.insert("visible", true); + pathProps.insert("ignoreRayIntersection", doesPathIgnoreRays()); + if (scaleWithAvatar) { + pathProps.insert("lineWidth", getLineWidth() * DependencyManager::get()->getMyAvatar()->getSensorToWorldScale()); + } + qApp->getOverlays().editOverlay(getPathID(), pathProps); + } +} + +std::shared_ptr LaserPointer::buildRenderState(const QVariantMap& propMap) { QUuid startID; if (propMap["start"].isValid()) { QVariantMap startMap = propMap["start"].toMap(); @@ -335,7 +168,7 @@ RenderState LaserPointer::buildRenderState(const QVariantMap& propMap) { QUuid pathID; if (propMap["path"].isValid()) { QVariantMap pathMap = propMap["path"].toMap(); - // right now paths must be line3ds + // laser paths must be line3ds if (pathMap["type"].isValid() && pathMap["type"].toString() == "line3d") { pathMap.remove("visible"); pathID = qApp->getOverlays().addOverlay(pathMap["type"].toString(), pathMap); @@ -351,7 +184,7 @@ RenderState LaserPointer::buildRenderState(const QVariantMap& propMap) { } } - return RenderState(startID, pathID, endID); + return std::make_shared(startID, pathID, endID); } PointerEvent LaserPointer::buildPointerEvent(const PickedObject& target, const PickResultPointer& pickResult, const std::string& button, bool hover) { @@ -391,24 +224,11 @@ PointerEvent LaserPointer::buildPointerEvent(const PickedObject& target, const P glm::vec3 LaserPointer::findIntersection(const PickedObject& pickedObject, const glm::vec3& origin, const glm::vec3& direction) { switch (pickedObject.type) { - case ENTITY: - return RayPick::intersectRayWithEntityXYPlane(pickedObject.objectID, origin, direction); - case OVERLAY: - return RayPick::intersectRayWithOverlayXYPlane(pickedObject.objectID, origin, direction); - default: - return glm::vec3(NAN); - } -} - -glm::vec2 LaserPointer::findPos2D(const PickedObject& pickedObject, const glm::vec3& origin) { - switch (pickedObject.type) { - case ENTITY: - return RayPick::projectOntoEntityXYPlane(pickedObject.objectID, origin); - case OVERLAY: - return RayPick::projectOntoOverlayXYPlane(pickedObject.objectID, origin); - case HUD: - return DependencyManager::get()->calculatePos2DFromHUD(origin); - default: - return glm::vec2(NAN); + case ENTITY: + return RayPick::intersectRayWithEntityXYPlane(pickedObject.objectID, origin, direction); + case OVERLAY: + return RayPick::intersectRayWithOverlayXYPlane(pickedObject.objectID, origin, direction); + default: + return glm::vec3(NAN); } } diff --git a/interface/src/raypick/LaserPointer.h b/interface/src/raypick/LaserPointer.h index 964881be42..c0ac3259d9 100644 --- a/interface/src/raypick/LaserPointer.h +++ b/interface/src/raypick/LaserPointer.h @@ -11,117 +11,56 @@ #ifndef hifi_LaserPointer_h #define hifi_LaserPointer_h -#include -#include - -#include "ui/overlays/Overlay.h" - -#include -#include - -struct LockEndObject { - QUuid id { QUuid() }; - bool isOverlay { false }; - glm::mat4 offsetMat { glm::mat4() }; -}; - -class RenderState { +#include "PathPointer.h" +class LaserPointer : public PathPointer { + using Parent = PathPointer; public: - RenderState() {} - RenderState(const OverlayID& startID, const OverlayID& pathID, const OverlayID& endID); + class RenderState : public StartEndRenderState { + public: + RenderState() {} + RenderState(const OverlayID& startID, const OverlayID& pathID, const OverlayID& endID); - const OverlayID& getStartID() const { return _startID; } - const OverlayID& getPathID() const { return _pathID; } - const OverlayID& getEndID() const { return _endID; } - const bool& doesStartIgnoreRays() const { return _startIgnoreRays; } - const bool& doesPathIgnoreRays() const { return _pathIgnoreRays; } - const bool& doesEndIgnoreRays() const { return _endIgnoreRays; } + const OverlayID& getPathID() const { return _pathID; } + const bool& doesPathIgnoreRays() const { return _pathIgnoreRays; } - void setEndDim(const glm::vec3& endDim) { _endDim = endDim; } - const glm::vec3& getEndDim() const { return _endDim; } + void setLineWidth(const float& lineWidth) { _lineWidth = lineWidth; } + const float& getLineWidth() const { return _lineWidth; } - void setLineWidth(const float& lineWidth) { _lineWidth = lineWidth; } - const float& getLineWidth() const { return _lineWidth; } + void cleanup() override; + void disable() override; + void update(const glm::vec3& origin, const glm::vec3& end, const glm::vec3& surfaceNormal, bool scaleWithAvatar, bool distanceScaleEnd, bool centerEndY, + bool faceAvatar, bool followNormal, float followNormalStrength, float distance, const PickResultPointer& pickResult) override; - void deleteOverlays(); + private: + OverlayID _pathID; + bool _pathIgnoreRays; -private: - OverlayID _startID; - OverlayID _pathID; - OverlayID _endID; - bool _startIgnoreRays; - bool _pathIgnoreRays; - bool _endIgnoreRays; - - glm::vec3 _endDim; - float _lineWidth; -}; - -class LaserPointer : public Pointer { - using Parent = Pointer; -public: - typedef std::unordered_map RenderStateMap; - typedef std::unordered_map> DefaultRenderStateMap; - - LaserPointer(const QVariant& rayProps, const RenderStateMap& renderStates, const DefaultRenderStateMap& defaultRenderStates, bool hover, const PointerTriggers& triggers, - bool faceAvatar, bool centerEndY, bool lockEnd, bool distanceScaleEnd, bool scaleWithAvatar, bool enabled); - ~LaserPointer(); - - void setRenderState(const std::string& state) override; - // You cannot use editRenderState to change the overlay type of any part of the laser pointer. You can only edit the properties of the existing overlays. - void editRenderState(const std::string& state, const QVariant& startProps, const QVariant& pathProps, const QVariant& endProps) override; - - void setLength(float length) override; - void setLockEndUUID(const QUuid& objectID, bool isOverlay, const glm::mat4& offsetMat = glm::mat4()) override; - - void updateVisuals(const PickResultPointer& prevRayPickResult) override; - - static RenderState buildRenderState(const QVariantMap& propMap); - -protected: - PointerEvent buildPointerEvent(const PickedObject& target, const PickResultPointer& pickResult, const std::string& button = "", bool hover = true) override; - - PickResultPointer getVisualPickResult(const PickResultPointer& pickResult) override; - PickedObject getHoveredObject(const PickResultPointer& pickResult) override; - Pointer::Buttons getPressedButtons(const PickResultPointer& pickResult) override; - - bool shouldHover(const PickResultPointer& pickResult) override { return _currentRenderState != ""; } - bool shouldTrigger(const PickResultPointer& pickResult) override { return _currentRenderState != ""; } - -private: - PointerTriggers _triggers; - float _laserLength { 0.0f }; - std::string _currentRenderState { "" }; - RenderStateMap _renderStates; - DefaultRenderStateMap _defaultRenderStates; - bool _faceAvatar; - bool _centerEndY; - bool _lockEnd; - bool _distanceScaleEnd; - bool _scaleWithAvatar; - LockEndObject _lockEndObject; - - void updateRenderStateOverlay(const OverlayID& id, const QVariant& props); - void updateRenderState(const RenderState& renderState, const IntersectionType type, float distance, const QUuid& objectID, const PickRay& pickRay); - void disableRenderState(const RenderState& renderState); - - struct TriggerState { - PickedObject triggeredObject; - glm::vec3 intersection { NAN }; - glm::vec3 surfaceNormal { NAN }; - glm::vec2 triggerPos2D { NAN }; - quint64 triggerStartTime { 0 }; - bool deadspotExpired { true }; - bool triggering { false }; - bool wasTriggering { false }; + float _lineWidth; }; - Pointer::Buttons _previousButtons; - std::unordered_map _states; - TriggerState _latestState; + LaserPointer(const QVariant& rayProps, const RenderStateMap& renderStates, const DefaultRenderStateMap& defaultRenderStates, bool hover, const PointerTriggers& triggers, + bool faceAvatar, bool followNormal, float followNormalStrength, bool centerEndY, bool lockEnd, bool distanceScaleEnd, bool scaleWithAvatar, bool enabled); + + QVariantMap toVariantMap() const override; + + static std::shared_ptr buildRenderState(const QVariantMap& propMap); + +protected: + void editRenderStatePath(const std::string& state, const QVariant& pathProps) override; + + glm::vec3 getPickOrigin(const PickResultPointer& pickResult) const override; + glm::vec3 getPickEnd(const PickResultPointer& pickResult, float distance) const override; + glm::vec3 getPickedObjectNormal(const PickResultPointer& pickResult) const override; + IntersectionType getPickedObjectType(const PickResultPointer& pickResult) const override; + QUuid getPickedObjectID(const PickResultPointer& pickResult) const override; + void setVisualPickResultInternal(PickResultPointer pickResult, IntersectionType type, const QUuid& id, + const glm::vec3& intersection, float distance, const glm::vec3& surfaceNormal) override; + + PointerEvent buildPointerEvent(const PickedObject& target, const PickResultPointer& pickResult, const std::string& button = "", bool hover = true) override; + +private: static glm::vec3 findIntersection(const PickedObject& pickedObject, const glm::vec3& origin, const glm::vec3& direction); - static glm::vec2 findPos2D(const PickedObject& pickedObject, const glm::vec3& origin); }; diff --git a/interface/src/raypick/MouseParabolaPick.cpp b/interface/src/raypick/MouseParabolaPick.cpp new file mode 100644 index 0000000000..66351f4520 --- /dev/null +++ b/interface/src/raypick/MouseParabolaPick.cpp @@ -0,0 +1,28 @@ +// +// Created by Sam Gondelman 7/2/2018 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#include "MouseParabolaPick.h" + +#include "Application.h" +#include "display-plugins/CompositorHelper.h" + +MouseParabolaPick::MouseParabolaPick(float speed, const glm::vec3& accelerationAxis, bool rotateAccelerationWithAvatar, + bool scaleWithAvatar, const PickFilter& filter, float maxDistance, bool enabled) : + ParabolaPick(speed, accelerationAxis, rotateAccelerationWithAvatar, scaleWithAvatar, filter, maxDistance, enabled) +{ +} + +PickParabola MouseParabolaPick::getMathematicalPick() const { + QVariant position = qApp->getApplicationCompositor().getReticleInterface()->getPosition(); + if (position.isValid()) { + QVariantMap posMap = position.toMap(); + PickRay pickRay = qApp->getCamera().computePickRay(posMap["x"].toFloat(), posMap["y"].toFloat()); + return PickParabola(pickRay.origin, getSpeed() * pickRay.direction, getAcceleration()); + } + + return PickParabola(); +} diff --git a/interface/src/raypick/MouseParabolaPick.h b/interface/src/raypick/MouseParabolaPick.h new file mode 100644 index 0000000000..cb67c3b361 --- /dev/null +++ b/interface/src/raypick/MouseParabolaPick.h @@ -0,0 +1,24 @@ +// +// Created by Sam Gondelman 7/2/2018 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#ifndef hifi_MouseParabolaPick_h +#define hifi_MouseParabolaPick_h + +#include "ParabolaPick.h" + +class MouseParabolaPick : public ParabolaPick { + +public: + MouseParabolaPick(float speed, const glm::vec3& accelerationAxis, bool rotateAccelerationWithAvatar, bool scaleWithAvatar, + const PickFilter& filter, float maxDistance = 0.0f, bool enabled = false); + + PickParabola getMathematicalPick() const override; + + bool isMouse() const override { return true; } +}; + +#endif // hifi_MouseParabolaPick_h diff --git a/interface/src/raypick/MouseTransformNode.cpp b/interface/src/raypick/MouseTransformNode.cpp new file mode 100644 index 0000000000..9caa4865a2 --- /dev/null +++ b/interface/src/raypick/MouseTransformNode.cpp @@ -0,0 +1,27 @@ +// +// Created by Sabrina Shanman 8/14/2018 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "MouseTransformNode.h" + +#include "Application.h" +#include "display-plugins/CompositorHelper.h" +#include "RayPick.h" + +Transform MouseTransformNode::getTransform() { + QVariant position = qApp->getApplicationCompositor().getReticleInterface()->getPosition(); + if (position.isValid()) { + Transform transform; + QVariantMap posMap = position.toMap(); + PickRay pickRay = qApp->getCamera().computePickRay(posMap["x"].toFloat(), posMap["y"].toFloat()); + transform.setTranslation(pickRay.origin); + transform.setRotation(rotationBetween(Vectors::UP, pickRay.direction)); + return transform; + } + + return Transform(); +} \ No newline at end of file diff --git a/interface/src/raypick/MouseTransformNode.h b/interface/src/raypick/MouseTransformNode.h new file mode 100644 index 0000000000..4f340339e4 --- /dev/null +++ b/interface/src/raypick/MouseTransformNode.h @@ -0,0 +1,18 @@ +// +// Created by Sabrina Shanman 8/14/2018 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#ifndef hifi_MouseTransformNode_h +#define hifi_MouseTransformNode_h + +#include "TransformNode.h" + +class MouseTransformNode : public TransformNode { +public: + Transform getTransform() override; +}; + +#endif // hifi_MouseTransformNode_h \ No newline at end of file diff --git a/interface/src/raypick/ParabolaPick.cpp b/interface/src/raypick/ParabolaPick.cpp new file mode 100644 index 0000000000..4b5eeec828 --- /dev/null +++ b/interface/src/raypick/ParabolaPick.cpp @@ -0,0 +1,85 @@ +// +// Created by Sam Gondelman 7/2/2018 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#include "ParabolaPick.h" + +#include "Application.h" +#include "EntityScriptingInterface.h" +#include "ui/overlays/Overlays.h" +#include "avatar/AvatarManager.h" +#include "scripting/HMDScriptingInterface.h" +#include "DependencyManager.h" +#include "PickManager.h" + +PickResultPointer ParabolaPick::getEntityIntersection(const PickParabola& pick) { + if (glm::length2(pick.acceleration) > EPSILON && glm::length2(pick.velocity) > EPSILON) { + bool precisionPicking = !(getFilter().doesPickCoarse() || DependencyManager::get()->getForceCoarsePicking()); + ParabolaToEntityIntersectionResult entityRes = + DependencyManager::get()->findParabolaIntersectionVector(pick, precisionPicking, + getIncludeItemsAs(), getIgnoreItemsAs(), !getFilter().doesPickInvisible(), !getFilter().doesPickNonCollidable()); + if (entityRes.intersects) { + return std::make_shared(IntersectionType::ENTITY, entityRes.entityID, entityRes.distance, entityRes.parabolicDistance, entityRes.intersection, pick, entityRes.surfaceNormal, entityRes.extraInfo); + } + } + return std::make_shared(pick.toVariantMap()); +} + +PickResultPointer ParabolaPick::getOverlayIntersection(const PickParabola& pick) { + if (glm::length2(pick.acceleration) > EPSILON && glm::length2(pick.velocity) > EPSILON) { + bool precisionPicking = !(getFilter().doesPickCoarse() || DependencyManager::get()->getForceCoarsePicking()); + ParabolaToOverlayIntersectionResult overlayRes = + qApp->getOverlays().findParabolaIntersectionVector(pick, precisionPicking, + getIncludeItemsAs(), getIgnoreItemsAs(), !getFilter().doesPickInvisible(), !getFilter().doesPickNonCollidable()); + if (overlayRes.intersects) { + return std::make_shared(IntersectionType::OVERLAY, overlayRes.overlayID, overlayRes.distance, overlayRes.parabolicDistance, overlayRes.intersection, pick, overlayRes.surfaceNormal, overlayRes.extraInfo); + } + } + return std::make_shared(pick.toVariantMap()); +} + +PickResultPointer ParabolaPick::getAvatarIntersection(const PickParabola& pick) { + if (glm::length2(pick.acceleration) > EPSILON && glm::length2(pick.velocity) > EPSILON) { + ParabolaToAvatarIntersectionResult avatarRes = DependencyManager::get()->findParabolaIntersectionVector(pick, getIncludeItemsAs(), getIgnoreItemsAs()); + if (avatarRes.intersects) { + return std::make_shared(IntersectionType::AVATAR, avatarRes.avatarID, avatarRes.distance, avatarRes.parabolicDistance, avatarRes.intersection, pick, avatarRes.surfaceNormal, avatarRes.extraInfo); + } + } + return std::make_shared(pick.toVariantMap()); +} + +PickResultPointer ParabolaPick::getHUDIntersection(const PickParabola& pick) { + if (glm::length2(pick.acceleration) > EPSILON && glm::length2(pick.velocity) > EPSILON) { + float parabolicDistance; + glm::vec3 hudRes = DependencyManager::get()->calculateParabolaUICollisionPoint(pick.origin, pick.velocity, pick.acceleration, parabolicDistance); + return std::make_shared(IntersectionType::HUD, QUuid(), glm::distance(pick.origin, hudRes), parabolicDistance, hudRes, pick); + } + return std::make_shared(pick.toVariantMap()); +} + +float ParabolaPick::getSpeed() const { + return (_scaleWithAvatar ? DependencyManager::get()->getMyAvatar()->getSensorToWorldScale() * _speed : _speed); +} + +glm::vec3 ParabolaPick::getAcceleration() const { + float scale = (_scaleWithAvatar ? DependencyManager::get()->getMyAvatar()->getSensorToWorldScale() : 1.0f); + if (_rotateAccelerationWithAvatar) { + return scale * (DependencyManager::get()->getMyAvatar()->getWorldOrientation() * _accelerationAxis); + } + return scale * _accelerationAxis; +} + +Transform ParabolaPick::getResultTransform() const { + PickResultPointer result = getPrevPickResult(); + if (!result) { + return Transform(); + } + + auto parabolaResult = std::static_pointer_cast(result); + Transform transform; + transform.setTranslation(parabolaResult->intersection); + return transform; +} \ No newline at end of file diff --git a/interface/src/raypick/ParabolaPick.h b/interface/src/raypick/ParabolaPick.h new file mode 100644 index 0000000000..01454390f9 --- /dev/null +++ b/interface/src/raypick/ParabolaPick.h @@ -0,0 +1,98 @@ +// +// Created by Sam Gondelman 7/2/2018 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#ifndef hifi_ParabolaPick_h +#define hifi_ParabolaPick_h + +#include +#include + +class EntityItemID; +class OverlayID; + +class ParabolaPickResult : public PickResult { +public: + ParabolaPickResult() {} + ParabolaPickResult(const QVariantMap& pickVariant) : PickResult(pickVariant) {} + ParabolaPickResult(const IntersectionType type, const QUuid& objectID, float distance, float parabolicDistance, const glm::vec3& intersection, const PickParabola& parabola, + const glm::vec3& surfaceNormal = glm::vec3(NAN), const QVariantMap& extraInfo = QVariantMap()) : + PickResult(parabola.toVariantMap()), extraInfo(extraInfo), objectID(objectID), intersection(intersection), surfaceNormal(surfaceNormal), type(type), distance(distance), parabolicDistance(parabolicDistance), intersects(type != NONE) { + } + + ParabolaPickResult(const ParabolaPickResult& parabolaPickResult) : PickResult(parabolaPickResult.pickVariant) { + type = parabolaPickResult.type; + intersects = parabolaPickResult.intersects; + objectID = parabolaPickResult.objectID; + distance = parabolaPickResult.distance; + parabolicDistance = parabolaPickResult.parabolicDistance; + intersection = parabolaPickResult.intersection; + surfaceNormal = parabolaPickResult.surfaceNormal; + extraInfo = parabolaPickResult.extraInfo; + } + + QVariantMap extraInfo; + QUuid objectID; + glm::vec3 intersection { NAN }; + glm::vec3 surfaceNormal { NAN }; + IntersectionType type { NONE }; + float distance { FLT_MAX }; + float parabolicDistance { FLT_MAX }; + bool intersects { false }; + + virtual QVariantMap toVariantMap() const override { + QVariantMap toReturn; + toReturn["type"] = type; + toReturn["intersects"] = intersects; + toReturn["objectID"] = objectID; + toReturn["distance"] = distance; + toReturn["parabolicDistance"] = parabolicDistance; + toReturn["intersection"] = vec3toVariant(intersection); + toReturn["surfaceNormal"] = vec3toVariant(surfaceNormal); + toReturn["parabola"] = PickResult::toVariantMap(); + toReturn["extraInfo"] = extraInfo; + return toReturn; + } + + bool doesIntersect() const override { return intersects; } + bool checkOrFilterAgainstMaxDistance(float maxDistance) override { return parabolicDistance < maxDistance; } + + PickResultPointer compareAndProcessNewResult(const PickResultPointer& newRes) override { + auto newParabolaRes = std::static_pointer_cast(newRes); + if (newParabolaRes->parabolicDistance < parabolicDistance) { + return std::make_shared(*newParabolaRes); + } else { + return std::make_shared(*this); + } + } + +}; + +class ParabolaPick : public Pick { + +public: + ParabolaPick(float speed, const glm::vec3& accelerationAxis, bool rotateAccelerationWithAvatar, bool scaleWithAvatar, const PickFilter& filter, float maxDistance, bool enabled) : + Pick(filter, maxDistance, enabled), _speed(speed), _accelerationAxis(accelerationAxis), _rotateAccelerationWithAvatar(rotateAccelerationWithAvatar), + _scaleWithAvatar(scaleWithAvatar) {} + + PickResultPointer getDefaultResult(const QVariantMap& pickVariant) const override { return std::make_shared(pickVariant); } + PickResultPointer getEntityIntersection(const PickParabola& pick) override; + PickResultPointer getOverlayIntersection(const PickParabola& pick) override; + PickResultPointer getAvatarIntersection(const PickParabola& pick) override; + PickResultPointer getHUDIntersection(const PickParabola& pick) override; + Transform getResultTransform() const override; + +protected: + float _speed; + glm::vec3 _accelerationAxis; + bool _rotateAccelerationWithAvatar; + bool _scaleWithAvatar; + + float getSpeed() const; + glm::vec3 getAcceleration() const; +}; + +#endif // hifi_ParabolaPick_h diff --git a/interface/src/raypick/ParabolaPointer.cpp b/interface/src/raypick/ParabolaPointer.cpp new file mode 100644 index 0000000000..888b3ddbe8 --- /dev/null +++ b/interface/src/raypick/ParabolaPointer.cpp @@ -0,0 +1,456 @@ +// +// Created by Sam Gondelman 7/17/2018 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#include "ParabolaPointer.h" + +#include "Application.h" +#include "avatar/AvatarManager.h" +#include + +#include +#include + +#include "ParabolaPick.h" +const glm::vec4 ParabolaPointer::RenderState::ParabolaRenderItem::DEFAULT_PARABOLA_COLOR { 1.0f }; +const float ParabolaPointer::RenderState::ParabolaRenderItem::DEFAULT_PARABOLA_WIDTH { 0.01f }; +const bool ParabolaPointer::RenderState::ParabolaRenderItem::DEFAULT_PARABOLA_ISVISIBLEINSECONDARYCAMERA { false }; + +gpu::PipelinePointer ParabolaPointer::RenderState::ParabolaRenderItem::_parabolaPipeline { nullptr }; +gpu::PipelinePointer ParabolaPointer::RenderState::ParabolaRenderItem::_transparentParabolaPipeline { nullptr }; + +ParabolaPointer::ParabolaPointer(const QVariant& rayProps, const RenderStateMap& renderStates, const DefaultRenderStateMap& defaultRenderStates, bool hover, + const PointerTriggers& triggers, bool faceAvatar, bool followNormal, float followNormalStrength, bool centerEndY, bool lockEnd, bool distanceScaleEnd, + bool scaleWithAvatar, bool enabled) : + PathPointer(PickQuery::Parabola, rayProps, renderStates, defaultRenderStates, hover, triggers, faceAvatar, followNormal, followNormalStrength, + centerEndY, lockEnd, distanceScaleEnd, scaleWithAvatar, enabled) +{ +} + +void ParabolaPointer::editRenderStatePath(const std::string& state, const QVariant& pathProps) { + auto renderState = std::static_pointer_cast(_renderStates[state]); + if (renderState) { + QVariantMap pathMap = pathProps.toMap(); + glm::vec3 color = glm::vec3(RenderState::ParabolaRenderItem::DEFAULT_PARABOLA_COLOR); + float alpha = RenderState::ParabolaRenderItem::DEFAULT_PARABOLA_COLOR.a; + float width = RenderState::ParabolaRenderItem::DEFAULT_PARABOLA_WIDTH; + bool isVisibleInSecondaryCamera = RenderState::ParabolaRenderItem::DEFAULT_PARABOLA_ISVISIBLEINSECONDARYCAMERA; + bool enabled = false; + if (!pathMap.isEmpty()) { + enabled = true; + if (pathMap["color"].isValid()) { + bool valid; + color = toGlm(xColorFromVariant(pathMap["color"], valid)); + } + if (pathMap["alpha"].isValid()) { + alpha = pathMap["alpha"].toFloat(); + } + if (pathMap["width"].isValid()) { + width = pathMap["width"].toFloat(); + renderState->setPathWidth(width); + } + if (pathMap["isVisibleInSecondaryCamera"].isValid()) { + isVisibleInSecondaryCamera = pathMap["isVisibleInSecondaryCamera"].toBool(); + } + } + renderState->editParabola(color, alpha, width, isVisibleInSecondaryCamera, enabled); + } +} + +QVariantMap ParabolaPointer::toVariantMap() const { + QVariantMap qVariantMap; + + QVariantMap qRenderStates; + for (auto iter = _renderStates.cbegin(); iter != _renderStates.cend(); iter++) { + auto renderState = iter->second; + QVariantMap qRenderState; + qRenderState["start"] = renderState->getStartID(); + qRenderState["end"] = renderState->getEndID(); + qRenderStates[iter->first.c_str()] = qRenderState; + } + qVariantMap["renderStates"] = qRenderStates; + + QVariantMap qDefaultRenderStates; + for (auto iter = _defaultRenderStates.cbegin(); iter != _defaultRenderStates.cend(); iter++) { + float distance = iter->second.first; + auto defaultRenderState = iter->second.second; + QVariantMap qDefaultRenderState; + + qDefaultRenderState["distance"] = distance; + qDefaultRenderState["start"] = defaultRenderState->getStartID(); + qDefaultRenderState["end"] = defaultRenderState->getEndID(); + qDefaultRenderStates[iter->first.c_str()] = qDefaultRenderState; + } + qVariantMap["defaultRenderStates"] = qDefaultRenderStates; + + return qVariantMap; +} + +glm::vec3 ParabolaPointer::getPickOrigin(const PickResultPointer& pickResult) const { + auto parabolaPickResult = std::static_pointer_cast(pickResult); + return (parabolaPickResult ? vec3FromVariant(parabolaPickResult->pickVariant["origin"]) : glm::vec3(0.0f)); +} + +glm::vec3 ParabolaPointer::getPickEnd(const PickResultPointer& pickResult, float distance) const { + auto parabolaPickResult = std::static_pointer_cast(pickResult); + if (!parabolaPickResult) { + return glm::vec3(0.0f); + } + if (distance > 0.0f) { + PickParabola pick = PickParabola(parabolaPickResult->pickVariant); + return pick.origin + pick.velocity * distance + 0.5f * pick.acceleration * distance * distance; + } else { + return parabolaPickResult->intersection; + } +} + +glm::vec3 ParabolaPointer::getPickedObjectNormal(const PickResultPointer& pickResult) const { + auto parabolaPickResult = std::static_pointer_cast(pickResult); + return (parabolaPickResult ? parabolaPickResult->surfaceNormal : glm::vec3(0.0f)); +} + +IntersectionType ParabolaPointer::getPickedObjectType(const PickResultPointer& pickResult) const { + auto parabolaPickResult = std::static_pointer_cast(pickResult); + return (parabolaPickResult ? parabolaPickResult->type : IntersectionType::NONE); +} + +QUuid ParabolaPointer::getPickedObjectID(const PickResultPointer& pickResult) const { + auto parabolaPickResult = std::static_pointer_cast(pickResult); + return (parabolaPickResult ? parabolaPickResult->objectID : QUuid()); +} + +void ParabolaPointer::setVisualPickResultInternal(PickResultPointer pickResult, IntersectionType type, const QUuid& id, + const glm::vec3& intersection, float distance, const glm::vec3& surfaceNormal) { + auto parabolaPickResult = std::static_pointer_cast(pickResult); + if (parabolaPickResult) { + parabolaPickResult->type = type; + parabolaPickResult->objectID = id; + parabolaPickResult->intersection = intersection; + parabolaPickResult->distance = distance; + parabolaPickResult->surfaceNormal = surfaceNormal; + PickParabola parabola = PickParabola(parabolaPickResult->pickVariant); + parabolaPickResult->pickVariant["velocity"] = vec3toVariant((intersection - parabola.origin - + 0.5f * parabola.acceleration * parabolaPickResult->parabolicDistance * parabolaPickResult->parabolicDistance) / parabolaPickResult->parabolicDistance); + } +} + +ParabolaPointer::RenderState::RenderState(const OverlayID& startID, const OverlayID& endID, const glm::vec3& pathColor, float pathAlpha, float pathWidth, + bool isVisibleInSecondaryCamera, bool pathEnabled) : + StartEndRenderState(startID, endID) +{ + render::Transaction transaction; + auto scene = qApp->getMain3DScene(); + _pathID = scene->allocateID(); + _pathWidth = pathWidth; + if (render::Item::isValidID(_pathID)) { + auto renderItem = std::make_shared(pathColor, pathAlpha, pathWidth, isVisibleInSecondaryCamera, pathEnabled); + transaction.resetItem(_pathID, std::make_shared(renderItem)); + scene->enqueueTransaction(transaction); + } +} + +void ParabolaPointer::RenderState::cleanup() { + StartEndRenderState::cleanup(); + if (render::Item::isValidID(_pathID)) { + render::Transaction transaction; + auto scene = qApp->getMain3DScene(); + transaction.removeItem(_pathID); + scene->enqueueTransaction(transaction); + } +} + +void ParabolaPointer::RenderState::disable() { + StartEndRenderState::disable(); + if (render::Item::isValidID(_pathID)) { + render::Transaction transaction; + auto scene = qApp->getMain3DScene(); + transaction.updateItem(_pathID, [](ParabolaRenderItem& item) { + item.setVisible(false); + }); + scene->enqueueTransaction(transaction); + } +} + +void ParabolaPointer::RenderState::editParabola(const glm::vec3& color, float alpha, float width, bool isVisibleInSecondaryCamera, bool enabled) { + if (render::Item::isValidID(_pathID)) { + render::Transaction transaction; + auto scene = qApp->getMain3DScene(); + transaction.updateItem(_pathID, [color, alpha, width, isVisibleInSecondaryCamera, enabled](ParabolaRenderItem& item) { + item.setColor(color); + item.setAlpha(alpha); + item.setWidth(width); + item.setIsVisibleInSecondaryCamera(isVisibleInSecondaryCamera); + item.setEnabled(enabled); + item.updateKey(); + }); + scene->enqueueTransaction(transaction); + } +} + +void ParabolaPointer::RenderState::update(const glm::vec3& origin, const glm::vec3& end, const glm::vec3& surfaceNormal, bool scaleWithAvatar, bool distanceScaleEnd, bool centerEndY, + bool faceAvatar, bool followNormal, float followNormalStrength, float distance, const PickResultPointer& pickResult) { + StartEndRenderState::update(origin, end, surfaceNormal, scaleWithAvatar, distanceScaleEnd, centerEndY, faceAvatar, followNormal, followNormalStrength, distance, pickResult); + auto parabolaPickResult = std::static_pointer_cast(pickResult); + if (parabolaPickResult && render::Item::isValidID(_pathID)) { + render::Transaction transaction; + auto scene = qApp->getMain3DScene(); + + PickParabola parabola = PickParabola(parabolaPickResult->pickVariant); + glm::vec3 velocity = parabola.velocity; + glm::vec3 acceleration = parabola.acceleration; + float parabolicDistance = distance > 0.0f ? distance : parabolaPickResult->parabolicDistance; + float width = scaleWithAvatar ? getPathWidth() * DependencyManager::get()->getMyAvatar()->getSensorToWorldScale() : getPathWidth(); + transaction.updateItem(_pathID, [origin, velocity, acceleration, parabolicDistance, width](ParabolaRenderItem& item) { + item.setVisible(true); + item.setOrigin(origin); + item.setVelocity(velocity); + item.setAcceleration(acceleration); + item.setParabolicDistance(parabolicDistance); + item.setWidth(width); + item.updateBounds(); + }); + scene->enqueueTransaction(transaction); + } +} + +std::shared_ptr ParabolaPointer::buildRenderState(const QVariantMap& propMap) { + QUuid startID; + if (propMap["start"].isValid()) { + QVariantMap startMap = propMap["start"].toMap(); + if (startMap["type"].isValid()) { + startMap.remove("visible"); + startID = qApp->getOverlays().addOverlay(startMap["type"].toString(), startMap); + } + } + + glm::vec3 color = glm::vec3(RenderState::ParabolaRenderItem::DEFAULT_PARABOLA_COLOR); + float alpha = RenderState::ParabolaRenderItem::DEFAULT_PARABOLA_COLOR.a; + float width = RenderState::ParabolaRenderItem::DEFAULT_PARABOLA_WIDTH; + bool isVisibleInSecondaryCamera = RenderState::ParabolaRenderItem::DEFAULT_PARABOLA_ISVISIBLEINSECONDARYCAMERA; + bool enabled = false; + if (propMap["path"].isValid()) { + enabled = true; + QVariantMap pathMap = propMap["path"].toMap(); + if (pathMap["color"].isValid()) { + bool valid; + color = toGlm(xColorFromVariant(pathMap["color"], valid)); + } + + if (pathMap["alpha"].isValid()) { + alpha = pathMap["alpha"].toFloat(); + } + + if (pathMap["width"].isValid()) { + width = pathMap["width"].toFloat(); + } + + if (pathMap["isVisibleInSecondaryCamera"].isValid()) { + isVisibleInSecondaryCamera = pathMap["isVisibleInSecondaryCamera"].toBool(); + } + } + + QUuid endID; + if (propMap["end"].isValid()) { + QVariantMap endMap = propMap["end"].toMap(); + if (endMap["type"].isValid()) { + endMap.remove("visible"); + endID = qApp->getOverlays().addOverlay(endMap["type"].toString(), endMap); + } + } + + return std::make_shared(startID, endID, color, alpha, width, isVisibleInSecondaryCamera, enabled); +} + +PointerEvent ParabolaPointer::buildPointerEvent(const PickedObject& target, const PickResultPointer& pickResult, const std::string& button, bool hover) { + QUuid pickedID; + glm::vec3 intersection, surfaceNormal, origin, velocity, acceleration; + auto parabolaPickResult = std::static_pointer_cast(pickResult); + if (parabolaPickResult) { + intersection = parabolaPickResult->intersection; + surfaceNormal = parabolaPickResult->surfaceNormal; + const QVariantMap& parabola = parabolaPickResult->pickVariant; + origin = vec3FromVariant(parabola["origin"]); + velocity = vec3FromVariant(parabola["velocity"]); + acceleration = vec3FromVariant(parabola["acceleration"]); + pickedID = parabolaPickResult->objectID; + } + + if (pickedID != target.objectID) { + intersection = findIntersection(target, origin, velocity, acceleration); + } + glm::vec2 pos2D = findPos2D(target, intersection); + + // If we just started triggering and we haven't moved too much, don't update intersection and pos2D + TriggerState& state = hover ? _latestState : _states[button]; + float sensorToWorldScale = DependencyManager::get()->getMyAvatar()->getSensorToWorldScale(); + float deadspotSquared = TOUCH_PRESS_TO_MOVE_DEADSPOT_SQUARED * sensorToWorldScale * sensorToWorldScale; + bool withinDeadspot = usecTimestampNow() - state.triggerStartTime < POINTER_MOVE_DELAY && glm::distance2(pos2D, state.triggerPos2D) < deadspotSquared; + if ((state.triggering || state.wasTriggering) && !state.deadspotExpired && withinDeadspot) { + pos2D = state.triggerPos2D; + intersection = state.intersection; + surfaceNormal = state.surfaceNormal; + } + if (!withinDeadspot) { + state.deadspotExpired = true; + } + + return PointerEvent(pos2D, intersection, surfaceNormal, velocity); +} + +glm::vec3 ParabolaPointer::findIntersection(const PickedObject& pickedObject, const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration) { + // TODO: implement + switch (pickedObject.type) { + case ENTITY: + //return ParabolaPick::intersectParabolaWithEntityXYPlane(pickedObject.objectID, origin, velocity, acceleration); + case OVERLAY: + //return ParabolaPick::intersectParabolaWithOverlayXYPlane(pickedObject.objectID, origin, velocity, acceleration); + default: + return glm::vec3(NAN); + } +} + +ParabolaPointer::RenderState::ParabolaRenderItem::ParabolaRenderItem(const glm::vec3& color, float alpha, float width, + bool isVisibleInSecondaryCamera, bool enabled) : + _isVisibleInSecondaryCamera(isVisibleInSecondaryCamera), _enabled(enabled) +{ + _uniformBuffer->resize(sizeof(ParabolaData)); + setColor(color); + setAlpha(alpha); + setWidth(width); + updateKey(); +} + +void ParabolaPointer::RenderState::ParabolaRenderItem::setVisible(bool visible) { + if (visible && _enabled) { + _key = render::ItemKey::Builder(_key).withVisible(); + } else { + _key = render::ItemKey::Builder(_key).withInvisible(); + } + _visible = visible; +} + +void ParabolaPointer::RenderState::ParabolaRenderItem::updateKey() { + auto builder = _parabolaData.color.a < 1.0f ? render::ItemKey::Builder::transparentShape() : render::ItemKey::Builder::opaqueShape(); + + // TODO: parabolas should cast shadows, but they're so thin that the cascaded shadow maps make them look pretty bad + //builder.withShadowCaster(); + + if (_enabled && _visible) { + builder.withVisible(); + } else { + builder.withInvisible(); + } + + if (_isVisibleInSecondaryCamera) { + builder.withTagBits(render::hifi::TAG_ALL_VIEWS); + } else { + builder.withTagBits(render::hifi::TAG_MAIN_VIEW); + } + + _key = builder.build(); +} + +void ParabolaPointer::RenderState::ParabolaRenderItem::updateBounds() { + glm::vec3 max = _origin; + glm::vec3 min = _origin; + + glm::vec3 end = _origin + _parabolaData.velocity * _parabolaData.parabolicDistance + + 0.5f * _parabolaData.acceleration * _parabolaData.parabolicDistance * _parabolaData.parabolicDistance; + max = glm::max(max, end); + min = glm::min(min, end); + + for (int i = 0; i < 3; i++) { + if (fabsf(_parabolaData.velocity[i]) > EPSILON && fabsf(_parabolaData.acceleration[i]) > EPSILON) { + float maxT = -_parabolaData.velocity[i] / _parabolaData.acceleration[i]; + if (maxT > 0.0f && maxT < _parabolaData.parabolicDistance) { + glm::vec3 maxPoint = _origin + _parabolaData.velocity * maxT + 0.5f * _parabolaData.acceleration * maxT * maxT; + max = glm::max(max, maxPoint); + min = glm::min(min, maxPoint); + } + } + } + + glm::vec3 halfWidth = glm::vec3(0.5f * _parabolaData.width); + max += halfWidth; + min -= halfWidth; + + _bound = AABox(min, max - min); +} + +const gpu::PipelinePointer ParabolaPointer::RenderState::ParabolaRenderItem::getParabolaPipeline() { + if (!_parabolaPipeline || !_transparentParabolaPipeline) { + gpu::ShaderPointer program = gpu::Shader::createProgram(shader::render_utils::program::parabola); + + { + auto state = std::make_shared(); + state->setDepthTest(true, true, gpu::LESS_EQUAL); + state->setBlendFunction(false, + gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, + gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); + PrepareStencil::testMaskDrawShape(*state); + state->setCullMode(gpu::State::CULL_NONE); + _parabolaPipeline = gpu::Pipeline::create(program, state); + } + + { + auto state = std::make_shared(); + state->setDepthTest(true, true, gpu::LESS_EQUAL); + state->setBlendFunction(true, + gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, + gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); + PrepareStencil::testMask(*state); + state->setCullMode(gpu::State::CULL_NONE); + _transparentParabolaPipeline = gpu::Pipeline::create(program, state); + } + } + return (_parabolaData.color.a < 1.0f ? _transparentParabolaPipeline : _parabolaPipeline); +} + +void ParabolaPointer::RenderState::ParabolaRenderItem::render(RenderArgs* args) { + if (!_visible) { + return; + } + + gpu::Batch& batch = *(args->_batch); + + Transform transform; + transform.setTranslation(_origin); + batch.setModelTransform(transform); + + batch.setPipeline(getParabolaPipeline()); + + const int MAX_SECTIONS = 100; + if (glm::length2(_parabolaData.acceleration) < EPSILON) { + _parabolaData.numSections = 1; + } else { + _parabolaData.numSections = glm::clamp((int)(_parabolaData.parabolicDistance + 1) * 10, 1, MAX_SECTIONS); + } + updateUniformBuffer(); + batch.setUniformBuffer(0, _uniformBuffer); + + // We draw 2 * n + 2 vertices for a triangle strip + batch.draw(gpu::TRIANGLE_STRIP, 2 * _parabolaData.numSections + 2, 0); +} + +namespace render { + template <> const ItemKey payloadGetKey(const ParabolaPointer::RenderState::ParabolaRenderItem::Pointer& payload) { + return payload->getKey(); + } + template <> const Item::Bound payloadGetBound(const ParabolaPointer::RenderState::ParabolaRenderItem::Pointer& payload) { + if (payload) { + return payload->getBound(); + } + return Item::Bound(); + } + template <> void payloadRender(const ParabolaPointer::RenderState::ParabolaRenderItem::Pointer& payload, RenderArgs* args) { + if (payload) { + payload->render(args); + } + } + template <> const ShapeKey shapeGetShapeKey(const ParabolaPointer::RenderState::ParabolaRenderItem::Pointer& payload) { + return ShapeKey::Builder::ownPipeline(); + } +} \ No newline at end of file diff --git a/interface/src/raypick/ParabolaPointer.h b/interface/src/raypick/ParabolaPointer.h new file mode 100644 index 0000000000..526abe3b0d --- /dev/null +++ b/interface/src/raypick/ParabolaPointer.h @@ -0,0 +1,128 @@ +// +// Created by Sam Gondelman 7/17/2018 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#ifndef hifi_ParabolaPointer_h +#define hifi_ParabolaPointer_h + +#include "PathPointer.h" + +class ParabolaPointer : public PathPointer { + using Parent = PathPointer; +public: + class RenderState : public StartEndRenderState { + public: + class ParabolaRenderItem { + public: + using Payload = render::Payload; + using Pointer = Payload::DataPointer; + + ParabolaRenderItem(const glm::vec3& color, float alpha, float width, + bool isVisibleInSecondaryCamera, bool enabled); + ~ParabolaRenderItem() {} + + static gpu::PipelinePointer _parabolaPipeline; + static gpu::PipelinePointer _transparentParabolaPipeline; + const gpu::PipelinePointer getParabolaPipeline(); + + void render(RenderArgs* args); + render::Item::Bound& editBound() { return _bound; } + const render::Item::Bound& getBound() { return _bound; } + render::ItemKey getKey() const { return _key; } + + void setVisible(bool visible); + void updateKey(); + void updateUniformBuffer() { _uniformBuffer->setSubData(0, _parabolaData); } + void updateBounds(); + + void setColor(const glm::vec3& color) { _parabolaData.color = glm::vec4(color, _parabolaData.color.a); } + void setAlpha(const float& alpha) { _parabolaData.color.a = alpha; } + void setWidth(const float& width) { _parabolaData.width = width; } + void setParabolicDistance(const float& parabolicDistance) { _parabolaData.parabolicDistance = parabolicDistance; } + void setVelocity(const glm::vec3& velocity) { _parabolaData.velocity = velocity; } + void setAcceleration(const glm::vec3& acceleration) { _parabolaData.acceleration = acceleration; } + void setOrigin(const glm::vec3& origin) { _origin = origin; } + void setIsVisibleInSecondaryCamera(const bool& isVisibleInSecondaryCamera) { _isVisibleInSecondaryCamera = isVisibleInSecondaryCamera; } + void setEnabled(const bool& enabled) { _enabled = enabled; } + + static const glm::vec4 DEFAULT_PARABOLA_COLOR; + static const float DEFAULT_PARABOLA_WIDTH; + static const bool DEFAULT_PARABOLA_ISVISIBLEINSECONDARYCAMERA; + + private: + render::Item::Bound _bound; + render::ItemKey _key; + + glm::vec3 _origin { 0.0f }; + bool _isVisibleInSecondaryCamera { DEFAULT_PARABOLA_ISVISIBLEINSECONDARYCAMERA }; + bool _visible { false }; + bool _enabled { false }; + + struct ParabolaData { + glm::vec3 velocity { 0.0f }; + float parabolicDistance { 0.0f }; + vec3 acceleration { 0.0f }; + float width { DEFAULT_PARABOLA_WIDTH }; + vec4 color { vec4(DEFAULT_PARABOLA_COLOR)}; + int numSections { 0 }; + ivec3 spare; + }; + + ParabolaData _parabolaData; + gpu::BufferPointer _uniformBuffer { std::make_shared() }; + }; + + RenderState() {} + RenderState(const OverlayID& startID, const OverlayID& endID, const glm::vec3& pathColor, float pathAlpha, float pathWidth, + bool isVisibleInSecondaryCamera, bool pathEnabled); + + void setPathWidth(float width) { _pathWidth = width; } + float getPathWidth() const { return _pathWidth; } + + void cleanup() override; + void disable() override; + void update(const glm::vec3& origin, const glm::vec3& end, const glm::vec3& surfaceNormal, bool scaleWithAvatar, bool distanceScaleEnd, bool centerEndY, + bool faceAvatar, bool followNormal, float followNormalStrength, float distance, const PickResultPointer& pickResult) override; + + void editParabola(const glm::vec3& color, float alpha, float width, bool isVisibleInSecondaryCamera, bool enabled); + + private: + int _pathID; + float _pathWidth; + }; + + ParabolaPointer(const QVariant& rayProps, const RenderStateMap& renderStates, const DefaultRenderStateMap& defaultRenderStates, bool hover, const PointerTriggers& triggers, + bool faceAvatar, bool followNormal, float followNormalStrength, bool centerEndY, bool lockEnd, bool distanceScaleEnd, bool scaleWithAvatar, bool enabled); + + QVariantMap toVariantMap() const override; + + static std::shared_ptr buildRenderState(const QVariantMap& propMap); + +protected: + void editRenderStatePath(const std::string& state, const QVariant& pathProps) override; + + glm::vec3 getPickOrigin(const PickResultPointer& pickResult) const override; + glm::vec3 getPickEnd(const PickResultPointer& pickResult, float distance) const override; + glm::vec3 getPickedObjectNormal(const PickResultPointer& pickResult) const override; + IntersectionType getPickedObjectType(const PickResultPointer& pickResult) const override; + QUuid getPickedObjectID(const PickResultPointer& pickResult) const override; + void setVisualPickResultInternal(PickResultPointer pickResult, IntersectionType type, const QUuid& id, + const glm::vec3& intersection, float distance, const glm::vec3& surfaceNormal) override; + + PointerEvent buildPointerEvent(const PickedObject& target, const PickResultPointer& pickResult, const std::string& button = "", bool hover = true) override; + +private: + static glm::vec3 findIntersection(const PickedObject& pickedObject, const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration); +}; + +namespace render { + template <> const ItemKey payloadGetKey(const ParabolaPointer::RenderState::ParabolaRenderItem::Pointer& payload); + template <> const Item::Bound payloadGetBound(const ParabolaPointer::RenderState::ParabolaRenderItem::Pointer& payload); + template <> void payloadRender(const ParabolaPointer::RenderState::ParabolaRenderItem::Pointer& payload, RenderArgs* args); + template <> const ShapeKey shapeGetShapeKey(const ParabolaPointer::RenderState::ParabolaRenderItem::Pointer& payload); +} + +#endif // hifi_ParabolaPointer_h diff --git a/interface/src/raypick/PathPointer.cpp b/interface/src/raypick/PathPointer.cpp new file mode 100644 index 0000000000..0301136bfa --- /dev/null +++ b/interface/src/raypick/PathPointer.cpp @@ -0,0 +1,362 @@ +// +// Created by Sam Gondelman 7/17/2018 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#include "PathPointer.h" + +#include "Application.h" +#include "avatar/AvatarManager.h" + +#include +#include +#include "PickScriptingInterface.h" +#include "RayPick.h" + +PathPointer::PathPointer(PickQuery::PickType type, const QVariant& rayProps, const RenderStateMap& renderStates, const DefaultRenderStateMap& defaultRenderStates, + bool hover, const PointerTriggers& triggers, bool faceAvatar, bool followNormal, float followNormalStrength, bool centerEndY, bool lockEnd, + bool distanceScaleEnd, bool scaleWithAvatar, bool enabled) : + Pointer(DependencyManager::get()->createPick(type, rayProps), enabled, hover), + _renderStates(renderStates), + _defaultRenderStates(defaultRenderStates), + _triggers(triggers), + _faceAvatar(faceAvatar), + _followNormal(followNormal), + _followNormalStrength(followNormalStrength), + _centerEndY(centerEndY), + _lockEnd(lockEnd), + _distanceScaleEnd(distanceScaleEnd), + _scaleWithAvatar(scaleWithAvatar) +{ + for (auto& state : _renderStates) { + if (!enabled || state.first != _currentRenderState) { + state.second->disable(); + } + } + for (auto& state : _defaultRenderStates) { + if (!enabled || state.first != _currentRenderState) { + state.second.second->disable(); + } + } +} + +PathPointer::~PathPointer() { + for (auto& renderState : _renderStates) { + renderState.second->cleanup(); + } + for (auto& renderState : _defaultRenderStates) { + renderState.second.second->cleanup(); + } +} + +void PathPointer::setRenderState(const std::string& state) { + withWriteLock([&] { + if (!_currentRenderState.empty() && state != _currentRenderState) { + auto renderState = _renderStates.find(_currentRenderState); + if (renderState != _renderStates.end()) { + renderState->second->disable(); + } + auto defaultRenderState = _defaultRenderStates.find(_currentRenderState); + if (defaultRenderState != _defaultRenderStates.end()) { + defaultRenderState->second.second->disable(); + } + } + _currentRenderState = state; + }); +} + +void PathPointer::setLength(float length) { + withWriteLock([&] { + _pathLength = length; + }); +} + +void PathPointer::setLockEndUUID(const QUuid& objectID, const bool isOverlay, const glm::mat4& offsetMat) { + withWriteLock([&] { + _lockEndObject.id = objectID; + _lockEndObject.isOverlay = isOverlay; + _lockEndObject.offsetMat = offsetMat; + }); +} + +PickResultPointer PathPointer::getVisualPickResult(const PickResultPointer& pickResult) { + PickResultPointer visualPickResult = pickResult; + glm::vec3 origin = getPickOrigin(pickResult); + IntersectionType type = getPickedObjectType(pickResult); + QUuid id; + glm::vec3 intersection; + float distance; + glm::vec3 surfaceNormal; + + if (type != IntersectionType::HUD) { + glm::vec3 endVec; + if (!_lockEndObject.id.isNull()) { + glm::vec3 pos; + glm::quat rot; + glm::vec3 dim; + glm::vec3 registrationPoint; + if (_lockEndObject.isOverlay) { + pos = vec3FromVariant(qApp->getOverlays().getProperty(_lockEndObject.id, "position").value); + rot = quatFromVariant(qApp->getOverlays().getProperty(_lockEndObject.id, "rotation").value); + dim = vec3FromVariant(qApp->getOverlays().getProperty(_lockEndObject.id, "dimensions").value); + registrationPoint = glm::vec3(0.5f); + } else { + EntityItemProperties props = DependencyManager::get()->getEntityProperties(_lockEndObject.id); + glm::mat4 entityMat = createMatFromQuatAndPos(props.getRotation(), props.getPosition()); + glm::mat4 finalPosAndRotMat = entityMat * _lockEndObject.offsetMat; + pos = extractTranslation(finalPosAndRotMat); + rot = props.getRotation(); + dim = props.getDimensions(); + registrationPoint = props.getRegistrationPoint(); + } + const glm::vec3 DEFAULT_REGISTRATION_POINT = glm::vec3(0.5f); + endVec = pos + rot * (dim * (DEFAULT_REGISTRATION_POINT - registrationPoint)); + glm::vec3 direction = endVec - origin; + distance = glm::distance(origin, endVec); + glm::vec3 normalizedDirection = glm::normalize(direction); + + type = _lockEndObject.isOverlay ? IntersectionType::OVERLAY : IntersectionType::ENTITY; + id = _lockEndObject.id; + intersection = endVec; + surfaceNormal = -normalizedDirection; + setVisualPickResultInternal(visualPickResult, type, id, intersection, distance, surfaceNormal); + } else if (type != IntersectionType::NONE && _lockEnd) { + id = getPickedObjectID(pickResult); + if (type == IntersectionType::ENTITY) { + endVec = DependencyManager::get()->getEntityTransform(id)[3]; + } else if (type == IntersectionType::OVERLAY) { + endVec = vec3FromVariant(qApp->getOverlays().getProperty(id, "position").value); + } else if (type == IntersectionType::AVATAR) { + endVec = DependencyManager::get()->getAvatar(id)->getPosition(); + } + glm::vec3 direction = endVec - origin; + distance = glm::distance(origin, endVec); + glm::vec3 normalizedDirection = glm::normalize(direction); + intersection = endVec; + surfaceNormal = -normalizedDirection; + setVisualPickResultInternal(visualPickResult, type, id, intersection, distance, surfaceNormal); + } + } + return visualPickResult; +} + +void PathPointer::updateVisuals(const PickResultPointer& pickResult) { + IntersectionType type = getPickedObjectType(pickResult); + auto renderState = _renderStates.find(_currentRenderState); + auto defaultRenderState = _defaultRenderStates.find(_currentRenderState); + if (_enabled && !_currentRenderState.empty() && renderState != _renderStates.end() && + (type != IntersectionType::NONE || _pathLength > 0.0f)) { + glm::vec3 origin = getPickOrigin(pickResult); + glm::vec3 end = getPickEnd(pickResult, _pathLength); + glm::vec3 surfaceNormal = getPickedObjectNormal(pickResult); + renderState->second->update(origin, end, surfaceNormal, _scaleWithAvatar, _distanceScaleEnd, _centerEndY, _faceAvatar, + _followNormal, _followNormalStrength, _pathLength, pickResult); + if (defaultRenderState != _defaultRenderStates.end() && defaultRenderState->second.second->isEnabled()) { + defaultRenderState->second.second->disable(); + } + } else if (_enabled && !_currentRenderState.empty() && defaultRenderState != _defaultRenderStates.end()) { + if (renderState != _renderStates.end() && renderState->second->isEnabled()) { + renderState->second->disable(); + } + glm::vec3 origin = getPickOrigin(pickResult); + glm::vec3 end = getPickEnd(pickResult, defaultRenderState->second.first); + defaultRenderState->second.second->update(origin, end, Vectors::UP, _scaleWithAvatar, _distanceScaleEnd, _centerEndY, + _faceAvatar, _followNormal, _followNormalStrength, defaultRenderState->second.first, pickResult); + } else if (!_currentRenderState.empty()) { + if (renderState != _renderStates.end() && renderState->second->isEnabled()) { + renderState->second->disable(); + } + if (defaultRenderState != _defaultRenderStates.end() && defaultRenderState->second.second->isEnabled()) { + defaultRenderState->second.second->disable(); + } + } +} + +void PathPointer::editRenderState(const std::string& state, const QVariant& startProps, const QVariant& pathProps, const QVariant& endProps) { + withWriteLock([&] { + auto renderState = _renderStates.find(state); + if (renderState != _renderStates.end()) { + updateRenderStateOverlay(renderState->second->getStartID(), startProps); + updateRenderStateOverlay(renderState->second->getEndID(), endProps); + QVariant startDim = startProps.toMap()["dimensions"]; + if (startDim.isValid()) { + renderState->second->setStartDim(vec3FromVariant(startDim)); + } + QVariant endDim = endProps.toMap()["dimensions"]; + if (endDim.isValid()) { + renderState->second->setEndDim(vec3FromVariant(endDim)); + } + QVariant rotation = endProps.toMap()["rotation"]; + if (rotation.isValid()) { + renderState->second->setEndRot(quatFromVariant(rotation)); + } + + editRenderStatePath(state, pathProps); + } + }); +} + +void PathPointer::updateRenderStateOverlay(const OverlayID& id, const QVariant& props) { + if (!id.isNull() && props.isValid()) { + QVariantMap propMap = props.toMap(); + propMap.remove("visible"); + qApp->getOverlays().editOverlay(id, propMap); + } +} + +Pointer::PickedObject PathPointer::getHoveredObject(const PickResultPointer& pickResult) { + return PickedObject(getPickedObjectID(pickResult), getPickedObjectType(pickResult)); +} + +Pointer::Buttons PathPointer::getPressedButtons(const PickResultPointer& pickResult) { + std::unordered_set toReturn; + + for (const PointerTrigger& trigger : _triggers) { + std::string button = trigger.getButton(); + TriggerState& state = _states[button]; + // TODO: right now, LaserPointers don't support axes, only on/off buttons + if (trigger.getEndpoint()->peek() >= 1.0f) { + toReturn.insert(button); + + if (_previousButtons.find(button) == _previousButtons.end()) { + // start triggering for buttons that were just pressed + state.triggeredObject = PickedObject(getPickedObjectID(pickResult), getPickedObjectType(pickResult)); + state.intersection = getPickEnd(pickResult); + state.triggerPos2D = findPos2D(state.triggeredObject, state.intersection); + state.triggerStartTime = usecTimestampNow(); + state.surfaceNormal = getPickedObjectNormal(pickResult); + state.deadspotExpired = false; + state.wasTriggering = true; + state.triggering = true; + _latestState = state; + } + } else { + // stop triggering for buttons that aren't pressed + state.wasTriggering = state.triggering; + state.triggering = false; + _latestState = state; + } + } + _previousButtons = toReturn; + return toReturn; +} + +StartEndRenderState::StartEndRenderState(const OverlayID& startID, const OverlayID& endID) : + _startID(startID), _endID(endID) { + if (!_startID.isNull()) { + _startDim = vec3FromVariant(qApp->getOverlays().getProperty(_startID, "dimensions").value); + _startIgnoreRays = qApp->getOverlays().getProperty(_startID, "ignoreRayIntersection").value.toBool(); + } + if (!_endID.isNull()) { + _endDim = vec3FromVariant(qApp->getOverlays().getProperty(_endID, "dimensions").value); + _endRot = quatFromVariant(qApp->getOverlays().getProperty(_endID, "rotation").value); + _endIgnoreRays = qApp->getOverlays().getProperty(_endID, "ignoreRayIntersection").value.toBool(); + } +} + +void StartEndRenderState::cleanup() { + if (!_startID.isNull()) { + qApp->getOverlays().deleteOverlay(_startID); + } + if (!_endID.isNull()) { + qApp->getOverlays().deleteOverlay(_endID); + } +} + +void StartEndRenderState::disable() { + if (!getStartID().isNull()) { + QVariantMap startProps; + startProps.insert("visible", false); + startProps.insert("ignoreRayIntersection", true); + qApp->getOverlays().editOverlay(getStartID(), startProps); + } + if (!getEndID().isNull()) { + QVariantMap endProps; + endProps.insert("visible", false); + endProps.insert("ignoreRayIntersection", true); + qApp->getOverlays().editOverlay(getEndID(), endProps); + } + _enabled = false; +} + +void StartEndRenderState::update(const glm::vec3& origin, const glm::vec3& end, const glm::vec3& surfaceNormal, bool scaleWithAvatar, bool distanceScaleEnd, bool centerEndY, + bool faceAvatar, bool followNormal, float followNormalStrength, float distance, const PickResultPointer& pickResult) { + if (!getStartID().isNull()) { + QVariantMap startProps; + startProps.insert("position", vec3toVariant(origin)); + startProps.insert("visible", true); + if (scaleWithAvatar) { + startProps.insert("dimensions", vec3toVariant(getStartDim() * DependencyManager::get()->getMyAvatar()->getSensorToWorldScale())); + } + startProps.insert("ignoreRayIntersection", doesStartIgnoreRays()); + qApp->getOverlays().editOverlay(getStartID(), startProps); + } + + if (!getEndID().isNull()) { + QVariantMap endProps; + glm::vec3 dim = vec3FromVariant(qApp->getOverlays().getProperty(getEndID(), "dimensions").value); + if (distanceScaleEnd) { + dim = getEndDim() * glm::distance(origin, end); + endProps.insert("dimensions", vec3toVariant(dim)); + } else if (scaleWithAvatar) { + dim = getEndDim() * DependencyManager::get()->getMyAvatar()->getSensorToWorldScale(); + endProps.insert("dimensions", vec3toVariant(dim)); + } + + glm::quat normalQuat = Quat().lookAtSimple(Vectors::ZERO, surfaceNormal); + normalQuat = normalQuat * glm::quat(glm::vec3(-M_PI_2, 0, 0)); + glm::vec3 avatarUp = DependencyManager::get()->getMyAvatar()->getWorldOrientation() * Vectors::UP; + glm::quat rotation = glm::rotation(Vectors::UP, avatarUp); + glm::vec3 position = end; + if (!centerEndY) { + if (followNormal) { + position = end + 0.5f * dim.y * surfaceNormal; + } else { + position = end + 0.5f * dim.y * avatarUp; + } + } + if (faceAvatar) { + glm::quat orientation = followNormal ? normalQuat : DependencyManager::get()->getMyAvatar()->getWorldOrientation(); + glm::quat lookAtWorld = Quat().lookAt(position, DependencyManager::get()->getMyAvatar()->getWorldPosition(), surfaceNormal); + glm::quat lookAtModel = glm::inverse(orientation) * lookAtWorld; + glm::quat lookAtFlatModel = Quat().cancelOutRollAndPitch(lookAtModel); + glm::quat lookAtFlatWorld = orientation * lookAtFlatModel; + rotation = lookAtFlatWorld; + } else if (followNormal) { + rotation = normalQuat; + } + if (followNormal && followNormalStrength > 0.0f && followNormalStrength < 1.0f) { + if (!_avgEndRotInitialized) { + _avgEndRot = rotation; + _avgEndRotInitialized = true; + } else { + rotation = glm::slerp(_avgEndRot, rotation, followNormalStrength); + if (!centerEndY) { + position = end + 0.5f * dim.y * (rotation * Vectors::UP); + } + _avgEndRot = rotation; + } + } + endProps.insert("position", vec3toVariant(position)); + endProps.insert("rotation", quatToVariant(rotation)); + endProps.insert("visible", true); + endProps.insert("ignoreRayIntersection", doesEndIgnoreRays()); + qApp->getOverlays().editOverlay(getEndID(), endProps); + } + _enabled = true; +} + +glm::vec2 PathPointer::findPos2D(const PickedObject& pickedObject, const glm::vec3& origin) { + switch (pickedObject.type) { + case ENTITY: + return RayPick::projectOntoEntityXYPlane(pickedObject.objectID, origin); + case OVERLAY: + return RayPick::projectOntoOverlayXYPlane(pickedObject.objectID, origin); + case HUD: + return DependencyManager::get()->calculatePos2DFromHUD(origin); + default: + return glm::vec2(NAN); + } +} diff --git a/interface/src/raypick/PathPointer.h b/interface/src/raypick/PathPointer.h new file mode 100644 index 0000000000..b3638d1f7d --- /dev/null +++ b/interface/src/raypick/PathPointer.h @@ -0,0 +1,139 @@ +// +// Created by Sam Gondelman 7/17/2018 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#ifndef hifi_PathPointer_h +#define hifi_PathPointer_h + +#include +#include +#include + +#include "ui/overlays/Overlay.h" + +#include +#include + +struct LockEndObject { + QUuid id { QUuid() }; + bool isOverlay { false }; + glm::mat4 offsetMat { glm::mat4() }; +}; + +class StartEndRenderState { +public: + StartEndRenderState() {} + StartEndRenderState(const OverlayID& startID, const OverlayID& endID); + + const OverlayID& getStartID() const { return _startID; } + const OverlayID& getEndID() const { return _endID; } + const bool& doesStartIgnoreRays() const { return _startIgnoreRays; } + const bool& doesEndIgnoreRays() const { return _endIgnoreRays; } + + void setStartDim(const glm::vec3& startDim) { _startDim = startDim; } + const glm::vec3& getStartDim() const { return _startDim; } + + void setEndDim(const glm::vec3& endDim) { _endDim = endDim; } + const glm::vec3& getEndDim() const { return _endDim; } + + void setEndRot(const glm::quat& endRot) { _endRot = endRot; } + const glm::quat& getEndRot() const { return _endRot; } + + virtual void cleanup(); + virtual void disable(); + virtual void update(const glm::vec3& origin, const glm::vec3& end, const glm::vec3& surfaceNormal, bool scaleWithAvatar, bool distanceScaleEnd, bool centerEndY, + bool faceAvatar, bool followNormal, float followNormalStrength, float distance, const PickResultPointer& pickResult); + + bool isEnabled() const { return _enabled; } + +protected: + OverlayID _startID; + OverlayID _endID; + bool _startIgnoreRays; + bool _endIgnoreRays; + + glm::vec3 _startDim; + glm::vec3 _endDim; + glm::quat _endRot; + + glm::quat _avgEndRot; + bool _avgEndRotInitialized { false }; + + bool _enabled { true }; +}; + +typedef std::unordered_map> RenderStateMap; +typedef std::unordered_map>> DefaultRenderStateMap; + +class PathPointer : public Pointer { + using Parent = Pointer; +public: + PathPointer(PickQuery::PickType type, const QVariant& rayProps, const RenderStateMap& renderStates, const DefaultRenderStateMap& defaultRenderStates, + bool hover, const PointerTriggers& triggers, bool faceAvatar, bool followNormal, float followNormalStrength, bool centerEndY, bool lockEnd, + bool distanceScaleEnd, bool scaleWithAvatar, bool enabled); + virtual ~PathPointer(); + + void setRenderState(const std::string& state) override; + // You cannot use editRenderState to change the type of any part of the pointer. You can only edit the properties of the existing overlays. + void editRenderState(const std::string& state, const QVariant& startProps, const QVariant& pathProps, const QVariant& endProps) override; + + void setLength(float length) override; + void setLockEndUUID(const QUuid& objectID, bool isOverlay, const glm::mat4& offsetMat = glm::mat4()) override; + + void updateVisuals(const PickResultPointer& prevRayPickResult) override; + +protected: + RenderStateMap _renderStates; + DefaultRenderStateMap _defaultRenderStates; + std::string _currentRenderState { "" }; + PointerTriggers _triggers; + float _pathLength { 0.0f }; + bool _faceAvatar; + bool _followNormal; + float _followNormalStrength; + bool _centerEndY; + bool _lockEnd; + bool _distanceScaleEnd; + bool _scaleWithAvatar; + LockEndObject _lockEndObject; + + struct TriggerState { + PickedObject triggeredObject; + glm::vec3 intersection { NAN }; + glm::vec3 surfaceNormal { NAN }; + glm::vec2 triggerPos2D { NAN }; + quint64 triggerStartTime { 0 }; + bool deadspotExpired { true }; + bool triggering { false }; + bool wasTriggering { false }; + }; + + Pointer::Buttons _previousButtons; + std::unordered_map _states; + TriggerState _latestState; + + bool shouldHover(const PickResultPointer& pickResult) override { return _currentRenderState != ""; } + bool shouldTrigger(const PickResultPointer& pickResult) override { return _currentRenderState != ""; } + + void updateRenderStateOverlay(const OverlayID& id, const QVariant& props); + virtual void editRenderStatePath(const std::string& state, const QVariant& pathProps) = 0; + + PickedObject getHoveredObject(const PickResultPointer& pickResult) override; + Pointer::Buttons getPressedButtons(const PickResultPointer& pickResult) override; + + PickResultPointer getVisualPickResult(const PickResultPointer& pickResult) override; + virtual glm::vec3 getPickOrigin(const PickResultPointer& pickResult) const = 0; + virtual glm::vec3 getPickEnd(const PickResultPointer& pickResult, float distance = 0.0f) const = 0; + virtual glm::vec3 getPickedObjectNormal(const PickResultPointer& pickResult) const = 0; + virtual IntersectionType getPickedObjectType(const PickResultPointer& pickResult) const = 0; + virtual QUuid getPickedObjectID(const PickResultPointer& pickResult) const = 0; + virtual void setVisualPickResultInternal(PickResultPointer pickResult, IntersectionType type, const QUuid& id, + const glm::vec3& intersection, float distance, const glm::vec3& surfaceNormal) = 0; + + static glm::vec2 findPos2D(const PickedObject& pickedObject, const glm::vec3& origin); +}; + +#endif // hifi_PathPointer_h diff --git a/interface/src/raypick/PickScriptingInterface.cpp b/interface/src/raypick/PickScriptingInterface.cpp index 74459ca624..6dedf3fca1 100644 --- a/interface/src/raypick/PickScriptingInterface.cpp +++ b/interface/src/raypick/PickScriptingInterface.cpp @@ -11,12 +11,24 @@ #include #include "GLMHelpers.h" +#include "Application.h" #include #include "StaticRayPick.h" #include "JointRayPick.h" #include "MouseRayPick.h" #include "StylusPick.h" +#include "StaticParabolaPick.h" +#include "JointParabolaPick.h" +#include "MouseParabolaPick.h" +#include "CollisionPick.h" + +#include "SpatialParentFinder.h" +#include "NestableTransformNode.h" +#include "PickTransformNode.h" +#include "MouseTransformNode.h" +#include "avatar/MyAvatarHeadTransformNode.h" +#include "avatar/AvatarManager.h" #include @@ -26,6 +38,10 @@ unsigned int PickScriptingInterface::createPick(const PickQuery::PickType type, return createRayPick(properties); case PickQuery::PickType::Stylus: return createStylusPick(properties); + case PickQuery::PickType::Parabola: + return createParabolaPick(properties); + case PickQuery::PickType::Collision: + return createCollisionPick(properties); default: return PickManager::INVALID_PICK_ID; } @@ -134,6 +150,145 @@ unsigned int PickScriptingInterface::createStylusPick(const QVariant& properties return DependencyManager::get()->addPick(PickQuery::Stylus, std::make_shared(side, filter, maxDistance, enabled)); } +/**jsdoc + * A set of properties that can be passed to {@link Picks.createPick} to create a new Parabola Pick. + * @typedef {object} Picks.ParabolaPickProperties + * @property {boolean} [enabled=false] If this Pick should start enabled or not. Disabled Picks do not updated their pick results. + * @property {number} [filter=Picks.PICK_NOTHING] The filter for this Pick to use, constructed using filter flags combined using bitwise OR. + * @property {number} [maxDistance=0.0] The max distance at which this Pick will intersect. 0.0 = no max. < 0.0 is invalid. + * @property {string} [joint] Only for Joint or Mouse Parabola Picks. If "Mouse", it will create a Parabola Pick that follows the system mouse, in desktop or HMD. + * If "Avatar", it will create a Joint Parabola Pick that follows your avatar's head. Otherwise, it will create a Joint Parabola Pick that follows the given joint, if it + * exists on your current avatar. + * @property {Vec3} [posOffset=Vec3.ZERO] Only for Joint Parabola Picks. A local joint position offset, in meters. x = upward, y = forward, z = lateral + * @property {Vec3} [dirOffset=Vec3.UP] Only for Joint Parabola Picks. A local joint direction offset. x = upward, y = forward, z = lateral + * @property {Vec3} [position] Only for Static Parabola Picks. The world-space origin of the parabola segment. + * @property {Vec3} [direction=-Vec3.FRONT] Only for Static Parabola Picks. The world-space direction of the parabola segment. + * @property {number} [speed=1] The initial speed of the parabola, i.e. the initial speed of the projectile whose trajectory defines the parabola. + * @property {Vec3} [accelerationAxis=-Vec3.UP] The acceleration of the parabola, i.e. the acceleration of the projectile whose trajectory defines the parabola, both magnitude and direction. + * @property {boolean} [rotateAccelerationWithAvatar=true] Whether or not the acceleration axis should rotate with your avatar's local Y axis. + * @property {boolean} [scaleWithAvatar=false] If true, the velocity and acceleration of the Pick will scale linearly with your avatar. + */ +unsigned int PickScriptingInterface::createParabolaPick(const QVariant& properties) { + QVariantMap propMap = properties.toMap(); + + bool enabled = false; + if (propMap["enabled"].isValid()) { + enabled = propMap["enabled"].toBool(); + } + + PickFilter filter = PickFilter(); + if (propMap["filter"].isValid()) { + filter = PickFilter(propMap["filter"].toUInt()); + } + + float maxDistance = 0.0f; + if (propMap["maxDistance"].isValid()) { + maxDistance = propMap["maxDistance"].toFloat(); + } + + float speed = 1.0f; + if (propMap["speed"].isValid()) { + speed = propMap["speed"].toFloat(); + } + + glm::vec3 accelerationAxis = -Vectors::UP; + if (propMap["accelerationAxis"].isValid()) { + accelerationAxis = vec3FromVariant(propMap["accelerationAxis"]); + } + + bool rotateAccelerationWithAvatar = true; + if (propMap["rotateAccelerationWithAvatar"].isValid()) { + rotateAccelerationWithAvatar = propMap["rotateAccelerationWithAvatar"].toBool(); + } + + bool scaleWithAvatar = false; + if (propMap["scaleWithAvatar"].isValid()) { + scaleWithAvatar = propMap["scaleWithAvatar"].toBool(); + } + + if (propMap["joint"].isValid()) { + std::string jointName = propMap["joint"].toString().toStdString(); + + if (jointName != "Mouse") { + // x = upward, y = forward, z = lateral + glm::vec3 posOffset = Vectors::ZERO; + if (propMap["posOffset"].isValid()) { + posOffset = vec3FromVariant(propMap["posOffset"]); + } + + glm::vec3 dirOffset = Vectors::UP; + if (propMap["dirOffset"].isValid()) { + dirOffset = vec3FromVariant(propMap["dirOffset"]); + } + + return DependencyManager::get()->addPick(PickQuery::Parabola, std::make_shared(jointName, posOffset, dirOffset, + speed, accelerationAxis, rotateAccelerationWithAvatar, + scaleWithAvatar, filter, maxDistance, enabled)); + + } else { + return DependencyManager::get()->addPick(PickQuery::Parabola, std::make_shared(speed, accelerationAxis, rotateAccelerationWithAvatar, + scaleWithAvatar, filter, maxDistance, enabled)); + } + } else if (propMap["position"].isValid()) { + glm::vec3 position = vec3FromVariant(propMap["position"]); + + glm::vec3 direction = -Vectors::FRONT; + if (propMap["direction"].isValid()) { + direction = vec3FromVariant(propMap["direction"]); + } + + return DependencyManager::get()->addPick(PickQuery::Parabola, std::make_shared(position, direction, speed, accelerationAxis, + rotateAccelerationWithAvatar, scaleWithAvatar, + filter, maxDistance, enabled)); + } + + return PickManager::INVALID_PICK_ID; +} + +/**jsdoc +* A Shape defines a physical volume. +* +* @typedef {object} Shape +* @property {string} shapeType The type of shape to use. Can be one of the following: "box", "sphere", "capsule-x", "capsule-y", "capsule-z", "cylinder-x", "cylinder-y", "cylinder-z" +* @property {Vec3} dimensions - The size to scale the shape to. +*/ + +// TODO: Add this property to the Shape jsdoc above once model picks work properly +// * @property {string} modelURL - If shapeType is one of: "compound", "simple-hull", "simple-compound", or "static-mesh", this defines the model to load to generate the collision volume. + +/**jsdoc +* A set of properties that can be passed to {@link Picks.createPick} to create a new Collision Pick. + +* @typedef {object} Picks.CollisionPickProperties +* @property {Shape} shape - The information about the collision region's size and shape. +* @property {Vec3} position - The position of the collision region. +* @property {Quat} orientation - The orientation of the collision region. +*/ +unsigned int PickScriptingInterface::createCollisionPick(const QVariant& properties) { + QVariantMap propMap = properties.toMap(); + + bool enabled = false; + if (propMap["enabled"].isValid()) { + enabled = propMap["enabled"].toBool(); + } + + PickFilter filter = PickFilter(); + if (propMap["filter"].isValid()) { + filter = PickFilter(propMap["filter"].toUInt()); + } + + float maxDistance = 0.0f; + if (propMap["maxDistance"].isValid()) { + maxDistance = propMap["maxDistance"].toFloat(); + } + + CollisionRegion collisionRegion(propMap); + auto collisionPick = std::make_shared(filter, maxDistance, enabled, collisionRegion, qApp->getPhysicsEngine()); + collisionPick->parentTransform = createTransformNode(propMap); + + return DependencyManager::get()->addPick(PickQuery::Collision, collisionPick); +} + void PickScriptingInterface::enablePick(unsigned int uid) { DependencyManager::get()->enablePick(uid); } @@ -205,3 +360,43 @@ unsigned int PickScriptingInterface::getPerFrameTimeBudget() const { void PickScriptingInterface::setPerFrameTimeBudget(unsigned int numUsecs) { DependencyManager::get()->setPerFrameTimeBudget(numUsecs); } + +std::shared_ptr PickScriptingInterface::createTransformNode(const QVariantMap& propMap) { + if (propMap["parentID"].isValid()) { + QUuid parentUuid = propMap["parentID"].toUuid(); + if (!parentUuid.isNull()) { + // Infer object type from parentID + // For now, assume a QUuuid is a SpatiallyNestable. This should change when picks are converted over to QUuids. + bool success; + std::weak_ptr nestablePointer = DependencyManager::get()->find(parentUuid, success, nullptr); + int parentJointIndex = 0; + if (propMap["parentJointIndex"].isValid()) { + parentJointIndex = propMap["parentJointIndex"].toInt(); + } + auto sharedNestablePointer = nestablePointer.lock(); + if (success && sharedNestablePointer) { + return std::make_shared(nestablePointer, parentJointIndex); + } + } + + unsigned int pickID = propMap["parentID"].toUInt(); + if (pickID != 0) { + return std::make_shared(pickID); + } + } + + if (propMap["joint"].isValid()) { + QString joint = propMap["joint"].toString(); + if (joint == "Mouse") { + return std::make_shared(); + } else if (joint == "Avatar") { + return std::make_shared(); + } else if (!joint.isNull()) { + auto myAvatar = DependencyManager::get()->getMyAvatar(); + int jointIndex = myAvatar->getJointIndex(joint); + return std::make_shared(myAvatar, jointIndex); + } + } + + return std::shared_ptr(); +} \ No newline at end of file diff --git a/interface/src/raypick/PickScriptingInterface.h b/interface/src/raypick/PickScriptingInterface.h index 0ee091716d..4d99309618 100644 --- a/interface/src/raypick/PickScriptingInterface.h +++ b/interface/src/raypick/PickScriptingInterface.h @@ -12,6 +12,7 @@ #include #include +#include #include /**jsdoc @@ -62,6 +63,8 @@ class PickScriptingInterface : public QObject, public Dependency { public: unsigned int createRayPick(const QVariant& properties); unsigned int createStylusPick(const QVariant& properties); + unsigned int createCollisionPick(const QVariant& properties); + unsigned int createParabolaPick(const QVariant& properties); void registerMetaTypes(QScriptEngine* engine); @@ -71,7 +74,7 @@ public: * with PickType.Ray, depending on which optional parameters you pass, you could create a Static Ray Pick, a Mouse Ray Pick, or a Joint Ray Pick. * @function Picks.createPick * @param {PickType} type A PickType that specifies the method of picking to use - * @param {Picks.RayPickProperties|Picks.StylusPickProperties} properties A PickProperties object, containing all the properties for initializing this Pick + * @param {Picks.RayPickProperties|Picks.StylusPickProperties|Picks.ParabolaPickProperties|Picks.CollisionPickProperties} properties A PickProperties object, containing all the properties for initializing this Pick * @returns {number} The ID of the created Pick. Used for managing the Pick. 0 if invalid. */ Q_INVOKABLE unsigned int createPick(const PickQuery::PickType type, const QVariant& properties); @@ -125,11 +128,52 @@ public: * @property {StylusTip} stylusTip The StylusTip that was used. Valid even if there was no intersection. */ + /**jsdoc + * An intersection result for a Parabola Pick. + * + * @typedef {object} ParabolaPickResult + * @property {number} type The intersection type. + * @property {boolean} intersects If there was a valid intersection (type != INTERSECTED_NONE) + * @property {Uuid} objectID The ID of the intersected object. Uuid.NULL for the HUD or invalid intersections. + * @property {number} distance The distance to the intersection point from the origin of the parabola, not along the parabola. + * @property {number} parabolicDistance The distance to the intersection point from the origin of the parabola, along the parabola. + * @property {Vec3} intersection The intersection point in world-space. + * @property {Vec3} surfaceNormal The surface normal at the intersected point. All NANs if type == INTERSECTED_HUD. + * @property {Variant} extraInfo Additional intersection details when available for Model objects. + * @property {PickParabola} parabola The PickParabola that was used. Valid even if there was no intersection. + */ + + /**jsdoc + * An intersection result for a Collision Pick. + * + * @typedef {object} CollisionPickResult + * @property {boolean} intersects If there was at least one valid intersection (intersectingObjects.length > 0) + * @property {IntersectingObject[]} intersectingObjects The collision information of each object which intersect with the CollisionRegion. + * @property {CollisionRegion} collisionRegion The CollisionRegion that was used. Valid even if there was no intersection. + */ + + /**jsdoc + * Information about the Collision Pick's intersection with an object + * + * @typedef {object} IntersectingObject + * @property {QUuid} id The ID of the object. + * @property {number} type The type of the object, either Picks.INTERSECTED_ENTITY() or Picks.INTERSECTED_AVATAR() + * @property {CollisionContact[]} collisionContacts Pairs of points representing penetration information between the pick and the object + */ + + /**jsdoc + * A pair of points that represents part of an overlap between a Collision Pick and an object in the physics engine. Points which are further apart represent deeper overlap + * + * @typedef {object} CollisionContact + * @property {Vec3} pointOnPick A point representing a penetration of the object's surface into the volume of the pick, in world space. + * @property {Vec3} pointOnObject A point representing a penetration of the pick's surface into the volume of the found object, in world space. + */ + /**jsdoc * Get the most recent pick result from this Pick. This will be updated as long as the Pick is enabled. * @function Picks.getPrevPickResult * @param {number} uid The ID of the Pick, as returned by {@link Picks.createPick}. - * @returns {RayPickResult|StylusPickResult} The most recent intersection result. This will be different for different PickTypes. + * @returns {RayPickResult|StylusPickResult|ParabolaPickResult|CollisionPickResult} The most recent intersection result. This will be different for different PickTypes. */ Q_INVOKABLE QVariantMap getPrevPickResult(unsigned int uid); @@ -162,7 +206,7 @@ public: * Check if a Pick is associated with the left hand. * @function Picks.isLeftHand * @param {number} uid The ID of the Pick, as returned by {@link Picks.createPick}. - * @returns {boolean} True if the Pick is a Joint Ray Pick with joint == "_CONTROLLER_LEFTHAND" or "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND", or a Stylus Pick with hand == 0. + * @returns {boolean} True if the Pick is a Joint Ray or Parabola Pick with joint == "_CONTROLLER_LEFTHAND" or "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND", or a Stylus Pick with hand == 0. */ Q_INVOKABLE bool isLeftHand(unsigned int uid); @@ -170,7 +214,7 @@ public: * Check if a Pick is associated with the right hand. * @function Picks.isRightHand * @param {number} uid The ID of the Pick, as returned by {@link Picks.createPick}. - * @returns {boolean} True if the Pick is a Joint Ray Pick with joint == "_CONTROLLER_RIGHTHAND" or "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND", or a Stylus Pick with hand == 1. + * @returns {boolean} True if the Pick is a Joint Ray or Parabola Pick with joint == "_CONTROLLER_RIGHTHAND" or "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND", or a Stylus Pick with hand == 1. */ Q_INVOKABLE bool isRightHand(unsigned int uid); @@ -178,7 +222,7 @@ public: * Check if a Pick is associated with the system mouse. * @function Picks.isMouse * @param {number} uid The ID of the Pick, as returned by {@link Picks.createPick}. - * @returns {boolean} True if the Pick is a Mouse Ray Pick, false otherwise. + * @returns {boolean} True if the Pick is a Mouse Ray or Parabola Pick, false otherwise. */ Q_INVOKABLE bool isMouse(unsigned int uid); @@ -273,6 +317,9 @@ public slots: * @returns {number} */ static constexpr unsigned int INTERSECTED_HUD() { return IntersectionType::HUD; } + +protected: + static std::shared_ptr createTransformNode(const QVariantMap& propMap); }; #endif // hifi_PickScriptingInterface_h diff --git a/interface/src/raypick/PointerScriptingInterface.cpp b/interface/src/raypick/PointerScriptingInterface.cpp index 4e953a5cb8..7209e402a1 100644 --- a/interface/src/raypick/PointerScriptingInterface.cpp +++ b/interface/src/raypick/PointerScriptingInterface.cpp @@ -15,6 +15,7 @@ #include "Application.h" #include "LaserPointer.h" #include "StylusPointer.h" +#include "ParabolaPointer.h" void PointerScriptingInterface::setIgnoreItems(unsigned int uid, const QScriptValue& ignoreItems) const { DependencyManager::get()->setIgnoreItems(uid, qVectorQUuidFromScriptValue(ignoreItems)); @@ -37,6 +38,8 @@ unsigned int PointerScriptingInterface::createPointer(const PickQuery::PickType& return createLaserPointer(properties); case PickQuery::PickType::Stylus: return createStylus(properties); + case PickQuery::PickType::Parabola: + return createParabolaPointer(properties); default: return PointerEvent::INVALID_POINTER_ID; } @@ -73,38 +76,40 @@ unsigned int PointerScriptingInterface::createStylus(const QVariant& properties) * @property {number} distance The distance at which to render the end of this Ray Pointer, if one is defined. */ /**jsdoc - * A set of properties used to define the visual aspect of a Ray Pointer in the case that the Pointer is intersecting something. + * A set of properties which define the visual aspect of a Ray Pointer in the case that the Pointer is intersecting something. * * @typedef {object} Pointers.RayPointerRenderState - * @property {string} name The name of this render state, used by {@link Pointers.setRenderState} and {@link Pointers.editRenderState} - * @property {Overlays.OverlayProperties} [start] All of the properties you would normally pass to {@link Overlays.addOverlay}, plus the type (as a type field). - * An overlay to represent the beginning of the Ray Pointer, if desired. - * @property {Overlays.OverlayProperties} [path] All of the properties you would normally pass to {@link Overlays.addOverlay}, plus the type (as a type field), which must be "line3d". - * An overlay to represent the path of the Ray Pointer, if desired. - * @property {Overlays.OverlayProperties} [end] All of the properties you would normally pass to {@link Overlays.addOverlay}, plus the type (as a type field). - * An overlay to represent the end of the Ray Pointer, if desired. - */ -/**jsdoc - * A trigger mechanism for Ray Pointers. - * - * @typedef {object} Pointers.Trigger - * @property {Controller.Standard|Controller.Actions|function} action This can be a built-in Controller action, like Controller.Standard.LTClick, or a function that evaluates to >= 1.0 when you want to trigger button. - * @property {string} button Which button to trigger. "Primary", "Secondary", "Tertiary", and "Focus" are currently supported. Only "Primary" will trigger clicks on web surfaces. If "Focus" is triggered, - * it will try to set the entity or overlay focus to the object at which the Pointer is aimed. Buttons besides the first three will still trigger events, but event.button will be "None". + * @property {string} name When using {@link Pointers.createPointer}, the name of this render state, used by {@link Pointers.setRenderState} and {@link Pointers.editRenderState} + * @property {Overlays.OverlayProperties|QUuid} [start] When using {@link Pointers.createPointer}, an optionally defined overlay to represent the beginning of the Ray Pointer, + * using the properties you would normally pass to {@link Overlays.addOverlay}, plus the type (as a type field). + * When returned from {@link Pointers.getPointerProperties}, the ID of the created overlay if it exists, or a null ID otherwise. + * @property {Overlays.OverlayProperties|QUuid} [path] When using {@link Pointers.createPointer}, an optionally defined overlay to represent the path of the Ray Pointer, + * using the properties you would normally pass to {@link Overlays.addOverlay}, plus the type (as a type field), which must be "line3d". + * When returned from {@link Pointers.getPointerProperties}, the ID of the created overlay if it exists, or a null ID otherwise. + * @property {Overlays.OverlayProperties|QUuid} [end] When using {@link Pointers.createPointer}, an optionally defined overlay to represent the end of the Ray Pointer, + * using the properties you would normally pass to {@link Overlays.addOverlay}, plus the type (as a type field). + * When returned from {@link Pointers.getPointerProperties}, the ID of the created overlay if it exists, or a null ID otherwise. */ /**jsdoc * A set of properties that can be passed to {@link Pointers.createPointer} to create a new Pointer. Contains the relevant {@link Picks.PickProperties} to define the underlying Pick. * @typedef {object} Pointers.LaserPointerProperties - * @property {boolean} [faceAvatar=false] Ray Pointers only. If true, the end of the Pointer will always rotate to face the avatar. - * @property {boolean} [centerEndY=true] Ray Pointers only. If false, the end of the Pointer will be moved up by half of its height. - * @property {boolean} [lockEnd=false] Ray Pointers only. If true, the end of the Pointer will lock on to the center of the object at which the laser is pointing. - * @property {boolean} [distanceScaleEnd=false] Ray Pointers only. If true, the dimensions of the end of the Pointer will scale linearly with distance. - * @property {boolean} [scaleWithAvatar=false] Ray Pointers only. If true, the width of the Pointer's path will scale linearly with your avatar's scale. + * @property {boolean} [faceAvatar=false] If true, the end of the Pointer will always rotate to face the avatar. + * @property {boolean} [centerEndY=true] If false, the end of the Pointer will be moved up by half of its height. + * @property {boolean} [lockEnd=false] If true, the end of the Pointer will lock on to the center of the object at which the pointer is pointing. + * @property {boolean} [distanceScaleEnd=false] If true, the dimensions of the end of the Pointer will scale linearly with distance. + * @property {boolean} [scaleWithAvatar=false] If true, the width of the Pointer's path will scale linearly with your avatar's scale. + * @property {boolean} [followNormal=false] If true, the end of the Pointer will rotate to follow the normal of the intersected surface. + * @property {number} [followNormalStrength=0.0] The strength of the interpolation between the real normal and the visual normal if followNormal is true. 0-1. If 0 or 1, + * the normal will follow exactly. * @property {boolean} [enabled=false] - * @property {Pointers.RayPointerRenderState[]} [renderStates] Ray Pointers only. A list of different visual states to switch between. - * @property {Pointers.DefaultRayPointerRenderState[]} [defaultRenderStates] Ray Pointers only. A list of different visual states to use if there is no intersection. + * @property {Pointers.RayPointerRenderState[]|Object.} [renderStates] A collection of different visual states to switch between. + * When using {@link Pointers.createPointer}, a list of RayPointerRenderStates. + * When returned from {@link Pointers.getPointerProperties}, a map between render state names and RayPointRenderStates. + * @property {Pointers.DefaultRayPointerRenderState[]|Object.} [defaultRenderStates] A collection of different visual states to use if there is no intersection. + * When using {@link Pointers.createPointer}, a list of DefaultRayPointerRenderStates. + * When returned from {@link Pointers.getPointerProperties}, a map between render state names and DefaultRayPointRenderStates. * @property {boolean} [hover=false] If this Pointer should generate hover events. - * @property {Pointers.Trigger[]} [triggers] Ray Pointers only. A list of different triggers mechanisms that control this Pointer's click event generation. + * @property {Pointers.Trigger[]} [triggers] A list of different triggers mechanisms that control this Pointer's click event generation. */ unsigned int PointerScriptingInterface::createLaserPointer(const QVariant& properties) const { QVariantMap propertyMap = properties.toMap(); @@ -134,12 +139,21 @@ unsigned int PointerScriptingInterface::createLaserPointer(const QVariant& prope scaleWithAvatar = propertyMap["scaleWithAvatar"].toBool(); } + bool followNormal = false; + if (propertyMap["followNormal"].isValid()) { + followNormal = propertyMap["followNormal"].toBool(); + } + float followNormalStrength = 0.0f; + if (propertyMap["followNormalStrength"].isValid()) { + followNormalStrength = propertyMap["followNormalStrength"].toFloat(); + } + bool enabled = false; if (propertyMap["enabled"].isValid()) { enabled = propertyMap["enabled"].toBool(); } - LaserPointer::RenderStateMap renderStates; + RenderStateMap renderStates; if (propertyMap["renderStates"].isValid()) { QList renderStateVariants = propertyMap["renderStates"].toList(); for (const QVariant& renderStateVariant : renderStateVariants) { @@ -153,7 +167,7 @@ unsigned int PointerScriptingInterface::createLaserPointer(const QVariant& prope } } - LaserPointer::DefaultRenderStateMap defaultRenderStates; + DefaultRenderStateMap defaultRenderStates; if (propertyMap["defaultRenderStates"].isValid()) { QList renderStateVariants = propertyMap["defaultRenderStates"].toList(); for (const QVariant& renderStateVariant : renderStateVariants) { @@ -162,7 +176,7 @@ unsigned int PointerScriptingInterface::createLaserPointer(const QVariant& prope if (renderStateMap["name"].isValid() && renderStateMap["distance"].isValid()) { std::string name = renderStateMap["name"].toString().toStdString(); float distance = renderStateMap["distance"].toFloat(); - defaultRenderStates[name] = std::pair(distance, LaserPointer::buildRenderState(renderStateMap)); + defaultRenderStates[name] = std::pair>(distance, LaserPointer::buildRenderState(renderStateMap)); } } } @@ -192,7 +206,159 @@ unsigned int PointerScriptingInterface::createLaserPointer(const QVariant& prope } return DependencyManager::get()->addPointer(std::make_shared(properties, renderStates, defaultRenderStates, hover, triggers, - faceAvatar, centerEndY, lockEnd, distanceScaleEnd, scaleWithAvatar, enabled)); + faceAvatar, followNormal, followNormalStrength, centerEndY, lockEnd, + distanceScaleEnd, scaleWithAvatar, enabled)); +} + +/**jsdoc +* The rendering properties of the parabolic path +* +* @typedef {object} Pointers.ParabolaProperties +* @property {Color} color=255,255,255 The color of the parabola. +* @property {number} alpha=1.0 The alpha of the parabola. +* @property {number} width=0.01 The width of the parabola, in meters. +* @property {boolean} isVisibleInSecondaryCamera=false The width of the parabola, in meters. +*/ +/**jsdoc +* A set of properties used to define the visual aspect of a Parabola Pointer in the case that the Pointer is not intersecting something. Same as a {@link Pointers.ParabolaPointerRenderState}, +* but with an additional distance field. +* +* @typedef {object} Pointers.DefaultParabolaPointerRenderState +* @augments Pointers.ParabolaPointerRenderState +* @property {number} distance The distance along the parabola at which to render the end of this Parabola Pointer, if one is defined. +*/ +/**jsdoc +* A set of properties used to define the visual aspect of a Parabola Pointer in the case that the Pointer is intersecting something. +* +* @typedef {object} Pointers.ParabolaPointerRenderState +* @property {string} name When using {@link Pointers.createPointer}, the name of this render state, used by {@link Pointers.setRenderState} and {@link Pointers.editRenderState} +* @property {Overlays.OverlayProperties|QUuid} [start] When using {@link Pointers.createPointer}, an optionally defined overlay to represent the beginning of the Parabola Pointer, +* using the properties you would normally pass to {@link Overlays.addOverlay}, plus the type (as a type field). +* When returned from {@link Pointers.getPointerProperties}, the ID of the created overlay if it exists, or a null ID otherwise. +* @property {Pointers.ParabolaProperties} [path] When using {@link Pointers.createPointer}, the optionally defined rendering properties of the parabolic path defined by the Parabola Pointer. +* Not defined in {@link Pointers.getPointerProperties}. +* @property {Overlays.OverlayProperties|QUuid} [end] When using {@link Pointers.createPointer}, an optionally defined overlay to represent the end of the Parabola Pointer, +* using the properties you would normally pass to {@link Overlays.addOverlay}, plus the type (as a type field). +* When returned from {@link Pointers.getPointerProperties}, the ID of the created overlay if it exists, or a null ID otherwise. +*/ +/**jsdoc +* A set of properties that can be passed to {@link Pointers.createPointer} to create a new Pointer. Contains the relevant {@link Picks.PickProperties} to define the underlying Pick. +* @typedef {object} Pointers.ParabolaPointerProperties +* @property {boolean} [faceAvatar=false] If true, the end of the Pointer will always rotate to face the avatar. +* @property {boolean} [centerEndY=true] If false, the end of the Pointer will be moved up by half of its height. +* @property {boolean} [lockEnd=false] If true, the end of the Pointer will lock on to the center of the object at which the pointer is pointing. +* @property {boolean} [distanceScaleEnd=false] If true, the dimensions of the end of the Pointer will scale linearly with distance. +* @property {boolean} [scaleWithAvatar=false] If true, the width of the Pointer's path will scale linearly with your avatar's scale. +* @property {boolean} [followNormal=false] If true, the end of the Pointer will rotate to follow the normal of the intersected surface. +* @property {number} [followNormalStrength=0.0] The strength of the interpolation between the real normal and the visual normal if followNormal is true. 0-1. If 0 or 1, +* the normal will follow exactly. +* @property {boolean} [enabled=false] +* @property {Pointers.ParabolaPointerRenderState[]|Object.} [renderStates] A collection of different visual states to switch between. +* When using {@link Pointers.createPointer}, a list of ParabolaPointerRenderStates. +* When returned from {@link Pointers.getPointerProperties}, a map between render state names and ParabolaPointerRenderStates. +* @property {Pointers.DefaultParabolaPointerRenderState[]|Object.} [defaultRenderStates] A collection of different visual states to use if there is no intersection. +* When using {@link Pointers.createPointer}, a list of DefaultParabolaPointerRenderStates. +* When returned from {@link Pointers.getPointerProperties}, a map between render state names and DefaultParabolaPointerRenderStates. +* @property {boolean} [hover=false] If this Pointer should generate hover events. +* @property {Pointers.Trigger[]} [triggers] A list of different triggers mechanisms that control this Pointer's click event generation. +*/ +unsigned int PointerScriptingInterface::createParabolaPointer(const QVariant& properties) const { + QVariantMap propertyMap = properties.toMap(); + + bool faceAvatar = false; + if (propertyMap["faceAvatar"].isValid()) { + faceAvatar = propertyMap["faceAvatar"].toBool(); + } + + bool centerEndY = true; + if (propertyMap["centerEndY"].isValid()) { + centerEndY = propertyMap["centerEndY"].toBool(); + } + + bool lockEnd = false; + if (propertyMap["lockEnd"].isValid()) { + lockEnd = propertyMap["lockEnd"].toBool(); + } + + bool distanceScaleEnd = false; + if (propertyMap["distanceScaleEnd"].isValid()) { + distanceScaleEnd = propertyMap["distanceScaleEnd"].toBool(); + } + + bool scaleWithAvatar = false; + if (propertyMap["scaleWithAvatar"].isValid()) { + scaleWithAvatar = propertyMap["scaleWithAvatar"].toBool(); + } + + bool followNormal = false; + if (propertyMap["followNormal"].isValid()) { + followNormal = propertyMap["followNormal"].toBool(); + } + float followNormalStrength = 0.0f; + if (propertyMap["followNormalStrength"].isValid()) { + followNormalStrength = propertyMap["followNormalStrength"].toFloat(); + } + + bool enabled = false; + if (propertyMap["enabled"].isValid()) { + enabled = propertyMap["enabled"].toBool(); + } + + RenderStateMap renderStates; + if (propertyMap["renderStates"].isValid()) { + QList renderStateVariants = propertyMap["renderStates"].toList(); + for (const QVariant& renderStateVariant : renderStateVariants) { + if (renderStateVariant.isValid()) { + QVariantMap renderStateMap = renderStateVariant.toMap(); + if (renderStateMap["name"].isValid()) { + std::string name = renderStateMap["name"].toString().toStdString(); + renderStates[name] = ParabolaPointer::buildRenderState(renderStateMap); + } + } + } + } + + DefaultRenderStateMap defaultRenderStates; + if (propertyMap["defaultRenderStates"].isValid()) { + QList renderStateVariants = propertyMap["defaultRenderStates"].toList(); + for (const QVariant& renderStateVariant : renderStateVariants) { + if (renderStateVariant.isValid()) { + QVariantMap renderStateMap = renderStateVariant.toMap(); + if (renderStateMap["name"].isValid() && renderStateMap["distance"].isValid()) { + std::string name = renderStateMap["name"].toString().toStdString(); + float distance = renderStateMap["distance"].toFloat(); + defaultRenderStates[name] = std::pair>(distance, ParabolaPointer::buildRenderState(renderStateMap)); + } + } + } + } + + bool hover = false; + if (propertyMap["hover"].isValid()) { + hover = propertyMap["hover"].toBool(); + } + + PointerTriggers triggers; + auto userInputMapper = DependencyManager::get(); + if (propertyMap["triggers"].isValid()) { + QList triggerVariants = propertyMap["triggers"].toList(); + for (const QVariant& triggerVariant : triggerVariants) { + if (triggerVariant.isValid()) { + QVariantMap triggerMap = triggerVariant.toMap(); + if (triggerMap["action"].isValid() && triggerMap["button"].isValid()) { + controller::Endpoint::Pointer endpoint = userInputMapper->endpointFor(controller::Input(triggerMap["action"].toUInt())); + if (endpoint) { + std::string button = triggerMap["button"].toString().toStdString(); + triggers.emplace_back(endpoint, button); + } + } + } + } + } + + return DependencyManager::get()->addPointer(std::make_shared(properties, renderStates, defaultRenderStates, hover, triggers, + faceAvatar, followNormal, followNormalStrength, centerEndY, lockEnd, distanceScaleEnd, + scaleWithAvatar, enabled)); } void PointerScriptingInterface::editRenderState(unsigned int uid, const QString& renderState, const QVariant& properties) const { @@ -223,4 +389,8 @@ QVariantMap PointerScriptingInterface::getPrevPickResult(unsigned int uid) const result = pickResult->toVariantMap(); } return result; +} + +QVariantMap PointerScriptingInterface::getPointerProperties(unsigned int uid) const { + return DependencyManager::get()->getPointerProperties(uid); } \ No newline at end of file diff --git a/interface/src/raypick/PointerScriptingInterface.h b/interface/src/raypick/PointerScriptingInterface.h index 628af84790..94f1d62552 100644 --- a/interface/src/raypick/PointerScriptingInterface.h +++ b/interface/src/raypick/PointerScriptingInterface.h @@ -31,14 +31,23 @@ class PointerScriptingInterface : public QObject, public Dependency { public: unsigned int createLaserPointer(const QVariant& properties) const; unsigned int createStylus(const QVariant& properties) const; + unsigned int createParabolaPointer(const QVariant& properties) const; + /**jsdoc + * A trigger mechanism for Ray and Parabola Pointers. + * + * @typedef {object} Pointers.Trigger + * @property {Controller.Standard|Controller.Actions|function} action This can be a built-in Controller action, like Controller.Standard.LTClick, or a function that evaluates to >= 1.0 when you want to trigger button. + * @property {string} button Which button to trigger. "Primary", "Secondary", "Tertiary", and "Focus" are currently supported. Only "Primary" will trigger clicks on web surfaces. If "Focus" is triggered, + * it will try to set the entity or overlay focus to the object at which the Pointer is aimed. Buttons besides the first three will still trigger events, but event.button will be "None". + */ /**jsdoc * Adds a new Pointer * Different {@link PickType}s use different properties, and within one PickType, the properties you choose can lead to a wide range of behaviors. For example, * with PickType.Ray, depending on which optional parameters you pass, you could create a Static Ray Pointer, a Mouse Ray Pointer, or a Joint Ray Pointer. * @function Pointers.createPointer * @param {PickType} type A PickType that specifies the method of picking to use - * @param {Pointers.LaserPointerProperties|Pointers.StylusPointerProperties} properties A PointerProperties object, containing all the properties for initializing this Pointer and the {@link Picks.PickProperties} for the Pick that + * @param {Pointers.LaserPointerProperties|Pointers.StylusPointerProperties|Pointers.ParabolaPointerProperties} properties A PointerProperties object, containing all the properties for initializing this Pointer and the {@link Picks.PickProperties} for the Pick that * this Pointer will use to do its picking. * @returns {number} The ID of the created Pointer. Used for managing the Pointer. 0 if invalid. * @@ -194,6 +203,14 @@ public: */ Q_INVOKABLE bool isMouse(unsigned int uid) { return DependencyManager::get()->isMouse(uid); } + /**jsdoc + * Returns information about an existing Pointer + * @function Pointers.getPointerState + * @param {number} uid The ID of the Pointer, as returned by {@link Pointers.createPointer}. + * @returns {Pointers.LaserPointerProperties|Pointers.StylusPointerProperties|Pointers.ParabolaPointerProperties} The information about the Pointer. + * Currently only includes renderStates and defaultRenderStates with associated overlay IDs. + */ + Q_INVOKABLE QVariantMap getPointerProperties(unsigned int uid) const; }; #endif // hifi_PointerScriptingInterface_h diff --git a/interface/src/raypick/RayPick.cpp b/interface/src/raypick/RayPick.cpp index 75b5e77fd8..cabfb3cbd5 100644 --- a/interface/src/raypick/RayPick.cpp +++ b/interface/src/raypick/RayPick.cpp @@ -13,10 +13,12 @@ #include "avatar/AvatarManager.h" #include "scripting/HMDScriptingInterface.h" #include "DependencyManager.h" +#include "PickManager.h" PickResultPointer RayPick::getEntityIntersection(const PickRay& pick) { + bool precisionPicking = !(getFilter().doesPickCoarse() || DependencyManager::get()->getForceCoarsePicking()); RayToEntityIntersectionResult entityRes = - DependencyManager::get()->findRayIntersectionVector(pick, !getFilter().doesPickCoarse(), + DependencyManager::get()->findRayIntersectionVector(pick, precisionPicking, getIncludeItemsAs(), getIgnoreItemsAs(), !getFilter().doesPickInvisible(), !getFilter().doesPickNonCollidable()); if (entityRes.intersects) { return std::make_shared(IntersectionType::ENTITY, entityRes.entityID, entityRes.distance, entityRes.intersection, pick, entityRes.surfaceNormal, entityRes.extraInfo); @@ -26,8 +28,9 @@ PickResultPointer RayPick::getEntityIntersection(const PickRay& pick) { } PickResultPointer RayPick::getOverlayIntersection(const PickRay& pick) { + bool precisionPicking = !(getFilter().doesPickCoarse() || DependencyManager::get()->getForceCoarsePicking()); RayToOverlayIntersectionResult overlayRes = - qApp->getOverlays().findRayIntersectionVector(pick, !getFilter().doesPickCoarse(), + qApp->getOverlays().findRayIntersectionVector(pick, precisionPicking, getIncludeItemsAs(), getIgnoreItemsAs(), !getFilter().doesPickInvisible(), !getFilter().doesPickNonCollidable()); if (overlayRes.intersects) { return std::make_shared(IntersectionType::OVERLAY, overlayRes.overlayID, overlayRes.distance, overlayRes.intersection, pick, overlayRes.surfaceNormal, overlayRes.extraInfo); @@ -39,7 +42,7 @@ PickResultPointer RayPick::getOverlayIntersection(const PickRay& pick) { PickResultPointer RayPick::getAvatarIntersection(const PickRay& pick) { RayToAvatarIntersectionResult avatarRes = DependencyManager::get()->findRayIntersectionVector(pick, getIncludeItemsAs(), getIgnoreItemsAs()); if (avatarRes.intersects) { - return std::make_shared(IntersectionType::AVATAR, avatarRes.avatarID, avatarRes.distance, avatarRes.intersection, pick, glm::vec3(NAN), avatarRes.extraInfo); + return std::make_shared(IntersectionType::AVATAR, avatarRes.avatarID, avatarRes.distance, avatarRes.intersection, pick, avatarRes.surfaceNormal, avatarRes.extraInfo); } else { return std::make_shared(pick.toVariantMap()); } @@ -50,6 +53,18 @@ PickResultPointer RayPick::getHUDIntersection(const PickRay& pick) { return std::make_shared(IntersectionType::HUD, QUuid(), glm::distance(pick.origin, hudRes), hudRes, pick); } +Transform RayPick::getResultTransform() const { + PickResultPointer result = getPrevPickResult(); + if (!result) { + return Transform(); + } + + auto rayResult = std::static_pointer_cast(result); + Transform transform; + transform.setTranslation(rayResult->intersection); + return transform; +} + glm::vec3 RayPick::intersectRayWithXYPlane(const glm::vec3& origin, const glm::vec3& direction, const glm::vec3& point, const glm::quat& rotation, const glm::vec3& registration) { // TODO: take into account registration glm::vec3 n = rotation * Vectors::FRONT; diff --git a/interface/src/raypick/RayPick.h b/interface/src/raypick/RayPick.h index 6bdc2cb5b0..11f985cec2 100644 --- a/interface/src/raypick/RayPick.h +++ b/interface/src/raypick/RayPick.h @@ -19,7 +19,7 @@ public: RayPickResult() {} RayPickResult(const QVariantMap& pickVariant) : PickResult(pickVariant) {} RayPickResult(const IntersectionType type, const QUuid& objectID, float distance, const glm::vec3& intersection, const PickRay& searchRay, const glm::vec3& surfaceNormal = glm::vec3(NAN), const QVariantMap& extraInfo = QVariantMap()) : - PickResult(searchRay.toVariantMap()), type(type), intersects(type != NONE), objectID(objectID), distance(distance), intersection(intersection), surfaceNormal(surfaceNormal), extraInfo(extraInfo) { + PickResult(searchRay.toVariantMap()), extraInfo(extraInfo), objectID(objectID), intersection(intersection), surfaceNormal(surfaceNormal), type(type), distance(distance), intersects(type != NONE) { } RayPickResult(const RayPickResult& rayPickResult) : PickResult(rayPickResult.pickVariant) { @@ -32,13 +32,13 @@ public: extraInfo = rayPickResult.extraInfo; } - IntersectionType type { NONE }; - bool intersects { false }; + QVariantMap extraInfo; QUuid objectID; - float distance { FLT_MAX }; glm::vec3 intersection { NAN }; glm::vec3 surfaceNormal { NAN }; - QVariantMap extraInfo; + IntersectionType type { NONE }; + float distance { FLT_MAX }; + bool intersects { false }; virtual QVariantMap toVariantMap() const override { QVariantMap toReturn; @@ -77,6 +77,7 @@ public: PickResultPointer getOverlayIntersection(const PickRay& pick) override; PickResultPointer getAvatarIntersection(const PickRay& pick) override; PickResultPointer getHUDIntersection(const PickRay& pick) override; + Transform getResultTransform() const override; // These are helper functions for projecting and intersecting rays static glm::vec3 intersectRayWithEntityXYPlane(const QUuid& entityID, const glm::vec3& origin, const glm::vec3& direction); diff --git a/interface/src/raypick/StaticParabolaPick.cpp b/interface/src/raypick/StaticParabolaPick.cpp new file mode 100644 index 0000000000..a4e3ccb97f --- /dev/null +++ b/interface/src/raypick/StaticParabolaPick.cpp @@ -0,0 +1,19 @@ +// +// Created by Sam Gondelman 7/2/2018 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#include "StaticParabolaPick.h" + +StaticParabolaPick::StaticParabolaPick(const glm::vec3& position, const glm::vec3& direction, float speed, const glm::vec3& accelerationAxis, + bool scaleWithAvatar, bool rotateAccelerationWithAvatar, const PickFilter& filter, float maxDistance, bool enabled) : + ParabolaPick(speed, accelerationAxis, rotateAccelerationWithAvatar, scaleWithAvatar, filter, maxDistance, enabled), + _position(position), _velocity(direction) +{ +} + +PickParabola StaticParabolaPick::getMathematicalPick() const { + return PickParabola(_position, getSpeed() * _velocity, getAcceleration()); +} \ No newline at end of file diff --git a/interface/src/raypick/StaticParabolaPick.h b/interface/src/raypick/StaticParabolaPick.h new file mode 100644 index 0000000000..df2057a6f0 --- /dev/null +++ b/interface/src/raypick/StaticParabolaPick.h @@ -0,0 +1,26 @@ +// +// Created by Sam Gondelman 7/2/2018 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#ifndef hifi_StaticParabolaPick_h +#define hifi_StaticParabolaPick_h + +#include "ParabolaPick.h" + +class StaticParabolaPick : public ParabolaPick { + +public: + StaticParabolaPick(const glm::vec3& position, const glm::vec3& direction, float speed, const glm::vec3& accelerationAxis, bool rotateAccelerationWithAvatar, + bool scaleWithAvatar, const PickFilter& filter, float maxDistance = 0.0f, bool enabled = false); + + PickParabola getMathematicalPick() const override; + +private: + glm::vec3 _position; + glm::vec3 _velocity; +}; + +#endif // hifi_StaticParabolaPick_h diff --git a/interface/src/raypick/StylusPick.cpp b/interface/src/raypick/StylusPick.cpp index 9021c922a6..69f605e7f9 100644 --- a/interface/src/raypick/StylusPick.cpp +++ b/interface/src/raypick/StylusPick.cpp @@ -225,4 +225,16 @@ PickResultPointer StylusPick::getAvatarIntersection(const StylusTip& pick) { PickResultPointer StylusPick::getHUDIntersection(const StylusTip& pick) { return std::make_shared(pick.toVariantMap()); +} + +Transform StylusPick::getResultTransform() const { + PickResultPointer result = getPrevPickResult(); + if (!result) { + return Transform(); + } + + auto stylusResult = std::static_pointer_cast(result); + Transform transform; + transform.setTranslation(stylusResult->intersection); + return transform; } \ No newline at end of file diff --git a/interface/src/raypick/StylusPick.h b/interface/src/raypick/StylusPick.h index f19e343f8d..ca80e9fbea 100644 --- a/interface/src/raypick/StylusPick.h +++ b/interface/src/raypick/StylusPick.h @@ -66,6 +66,7 @@ public: PickResultPointer getOverlayIntersection(const StylusTip& pick) override; PickResultPointer getAvatarIntersection(const StylusTip& pick) override; PickResultPointer getHUDIntersection(const StylusTip& pick) override; + Transform getResultTransform() const override; bool isLeftHand() const override { return _side == Side::Left; } bool isRightHand() const override { return _side == Side::Right; } diff --git a/interface/src/raypick/StylusPointer.cpp b/interface/src/raypick/StylusPointer.cpp index 8c0fb59106..06e3e52d21 100644 --- a/interface/src/raypick/StylusPointer.cpp +++ b/interface/src/raypick/StylusPointer.cpp @@ -64,7 +64,9 @@ void StylusPointer::updateVisuals(const PickResultPointer& pickResult) { return; } } - hide(); + if (_showing) { + hide(); + } } void StylusPointer::show(const StylusTip& tip) { @@ -80,6 +82,7 @@ void StylusPointer::show(const StylusTip& tip) { props["visible"] = true; qApp->getOverlays().editOverlay(_stylusOverlay, props); } + _showing = true; } void StylusPointer::hide() { @@ -88,6 +91,7 @@ void StylusPointer::hide() { props.insert("visible", false); qApp->getOverlays().editOverlay(_stylusOverlay, props); } + _showing = false; } bool StylusPointer::shouldHover(const PickResultPointer& pickResult) { @@ -203,6 +207,10 @@ void StylusPointer::setRenderState(const std::string& state) { } } +QVariantMap StylusPointer::toVariantMap() const { + return QVariantMap(); +} + glm::vec3 StylusPointer::findIntersection(const PickedObject& pickedObject, const glm::vec3& origin, const glm::vec3& direction) { switch (pickedObject.type) { case ENTITY: diff --git a/interface/src/raypick/StylusPointer.h b/interface/src/raypick/StylusPointer.h index 950b03b7c9..4095acb529 100644 --- a/interface/src/raypick/StylusPointer.h +++ b/interface/src/raypick/StylusPointer.h @@ -33,6 +33,8 @@ public: void setRenderState(const std::string& state) override; void editRenderState(const std::string& state, const QVariant& startProps, const QVariant& pathProps, const QVariant& endProps) override {} + QVariantMap toVariantMap() const override; + static OverlayID buildStylusOverlay(const QVariantMap& properties); protected: @@ -76,6 +78,8 @@ private: static glm::vec3 findIntersection(const PickedObject& pickedObject, const glm::vec3& origin, const glm::vec3& direction); static glm::vec2 findPos2D(const PickedObject& pickedObject, const glm::vec3& origin); + bool _showing { true }; + }; #endif // hifi_StylusPointer_h diff --git a/interface/src/scripting/Audio.cpp b/interface/src/scripting/Audio.cpp index b27cc344c3..524170c7a5 100644 --- a/interface/src/scripting/Audio.cpp +++ b/interface/src/scripting/Audio.cpp @@ -56,26 +56,24 @@ Audio::Audio() : _devices(_contextIsHMD) { connect(client, &AudioClient::inputVolumeChanged, this, &Audio::setInputVolume); connect(this, &Audio::contextChanged, &_devices, &AudioDevices::onContextChanged); enableNoiseReduction(enableNoiseReductionSetting.get()); + onContextChanged(); } bool Audio::startRecording(const QString& filepath) { - auto client = DependencyManager::get().data(); return resultWithWriteLock([&] { - return client->startRecording(filepath); + return DependencyManager::get()->startRecording(filepath); }); } bool Audio::getRecording() { - auto client = DependencyManager::get().data(); return resultWithReadLock([&] { - return client->getRecording(); + return DependencyManager::get()->getRecording(); }); } void Audio::stopRecording() { - auto client = DependencyManager::get().data(); withWriteLock([&] { - client->stopRecording(); + DependencyManager::get()->stopRecording(); }); } diff --git a/interface/src/scripting/Audio.h b/interface/src/scripting/Audio.h index 8d16b06995..4b8eb6aabc 100644 --- a/interface/src/scripting/Audio.h +++ b/interface/src/scripting/Audio.h @@ -26,7 +26,7 @@ class Audio : public AudioScriptingInterface, protected ReadWriteLockable { SINGLETON_DEPENDENCY /**jsdoc - * The Audio API features tools to help control audio contexts and settings. + * The Audio API provides facilities to interact with audio inputs and outputs and to play sounds. * * @namespace Audio * @@ -35,14 +35,23 @@ class Audio : public AudioScriptingInterface, protected ReadWriteLockable { * @hifi-server-entity * @hifi-assignment-client * - * @property {boolean} muted - * @property {boolean} noiseReduction - * @property {number} inputVolume - * @property {number} inputLevel Read-only. - * @property {string} context Read-only. - * @property {} devices Read-only. + * @property {boolean} muted - true if the audio input is muted, otherwise false. + * @property {boolean} noiseReduction - true if noise reduction is enabled, otherwise false. When + * enabled, the input audio signal is blocked (fully attenuated) when it falls below an adaptive threshold set just + * above the noise floor. + * @property {number} inputLevel - The loudness of the audio input, range 0.0 (no sound) – + * 1.0 (the onset of clipping). Read-only. + * @property {number} inputVolume - Adjusts the volume of the input audio; range 0.01.0. + * If set to a value, the resulting value depends on the input device: for example, the volume can't be changed on some + * devices, and others might only support values of 0.0 and 1.0. + * @property {boolean} isStereoInput - true if the input audio is being used in stereo, otherwise + * false. Some devices do not support stereo, in which case the value is always false. + * @property {string} context - The current context of the audio: either "Desktop" or "HMD". + * Read-only. + * @property {object} devices Read-only. Deprecated: This property is deprecated and will be + * removed. */ - + Q_PROPERTY(bool muted READ isMuted WRITE setMuted NOTIFY mutedChanged) Q_PROPERTY(bool noiseReduction READ noiseReductionEnabled WRITE enableNoiseReduction NOTIFY noiseReductionChanged) Q_PROPERTY(float inputVolume READ getInputVolume WRITE setInputVolume NOTIFY inputVolumeChanged) @@ -69,45 +78,91 @@ public: /**jsdoc * @function Audio.setInputDevice - * @param {} device + * @param {object} device * @param {boolean} isHMD + * @deprecated This function is deprecated and will be removed. */ Q_INVOKABLE void setInputDevice(const QAudioDeviceInfo& device, bool isHMD); /**jsdoc * @function Audio.setOutputDevice - * @param {} device + * @param {object} device * @param {boolean} isHMD + * @deprecated This function is deprecated and will be removed. */ Q_INVOKABLE void setOutputDevice(const QAudioDeviceInfo& device, bool isHMD); /**jsdoc + * Enable or disable reverberation. Reverberation is done by the client, on the post-mix audio. The reverberation options + * come from either the domain's audio zone if used — configured on the server — or as scripted by + * {@link Audio.setReverbOptions|setReverbOptions}. * @function Audio.setReverb - * @param {boolean} enable - */ + * @param {boolean} enable - true to enable reverberation, false to disable. + * @example Enable reverberation for a short while. + * var sound = SoundCache.getSound(Script.resourcesPath() + "sounds/sample.wav"); + * var injector; + * var injectorOptions = { + * position: MyAvatar.position + * }; + * + * Script.setTimeout(function () { + * print("Reverb OFF"); + * Audio.setReverb(false); + * injector = Audio.playSound(sound, injectorOptions); + * }, 1000); + * + * Script.setTimeout(function () { + * var reverbOptions = new AudioEffectOptions(); + * reverbOptions.roomSize = 100; + * Audio.setReverbOptions(reverbOptions); + * print("Reverb ON"); + * Audio.setReverb(true); + * }, 4000); + * + * Script.setTimeout(function () { + * print("Reverb OFF"); + * Audio.setReverb(false); + * }, 8000); */ Q_INVOKABLE void setReverb(bool enable); /**jsdoc + * Configure reverberation options. Use {@link Audio.setReverb|setReverb} to enable or disable reverberation. * @function Audio.setReverbOptions - * @param {AudioEffectOptions} options + * @param {AudioEffectOptions} options - The reverberation options. */ Q_INVOKABLE void setReverbOptions(const AudioEffectOptions* options); /**jsdoc + * Starts making an audio recording of the audio being played in-world (i.e., not local-only audio) to a file in WAV format. * @function Audio.startRecording - * @param {string} filename - * @returns {boolean} + * @param {string} filename - The path and name of the file to make the recording in. Should have a .wav + * extension. The file is overwritten if it already exists. + * @returns {boolean} true if the specified file could be opened and audio recording has started, otherwise + * false. + * @example Make a 10 second audio recording. + * var filename = File.getTempDir() + "/audio.wav"; + * if (Audio.startRecording(filename)) { + * Script.setTimeout(function () { + * Audio.stopRecording(); + * print("Audio recording made in: " + filename); + * }, 10000); + * + * } else { + * print("Could not make an audio recording in: " + filename); + * } */ Q_INVOKABLE bool startRecording(const QString& filename); /**jsdoc + * Finish making an audio recording started with {@link Audio.startRecording|startRecording}. * @function Audio.stopRecording */ Q_INVOKABLE void stopRecording(); /**jsdoc + * Check whether an audio recording is currently being made. * @function Audio.getRecording - * @returns {boolean} + * @returns {boolean} true if an audio recording is currently being made, otherwise false. */ Q_INVOKABLE bool getRecording(); @@ -116,40 +171,54 @@ signals: /**jsdoc * @function Audio.nop * @returns {Signal} + * @deprecated This signal is deprecated and will be removed. */ void nop(); /**jsdoc + * Triggered when the audio input is muted or unmuted. * @function Audio.mutedChanged - * @param {boolean} isMuted + * @param {boolean} isMuted - true if the audio input is muted, otherwise false. * @returns {Signal} + * @example Report when audio input is muted or unmuted + * Audio.mutedChanged.connect(function (isMuted) { + * print("Audio muted: " + isMuted); + * }); */ void mutedChanged(bool isMuted); /**jsdoc + * Triggered when the audio input noise reduction is enabled or disabled. * @function Audio.noiseReductionChanged - * @param {boolean} isEnabled + * @param {boolean} isEnabled - true if audio input noise reduction is enabled, otherwise false. * @returns {Signal} */ void noiseReductionChanged(bool isEnabled); /**jsdoc + * Triggered when the input audio volume changes. * @function Audio.inputVolumeChanged - * @param {number} volume + * @param {number} volume - The requested volume to be applied to the audio input, range 0.0 – + * 1.0. The resulting value of Audio.inputVolume depends on the capabilities of the device: + * for example, the volume can't be changed on some devices, and others might only support values of 0.0 + * and 1.0. * @returns {Signal} */ void inputVolumeChanged(float volume); /**jsdoc + * Triggered when the input audio level changes. * @function Audio.inputLevelChanged - * @param {number} level + * @param {number} level - The loudness of the input audio, range 0.0 (no sound) – 1.0 (the + * onset of clipping). * @returns {Signal} */ void inputLevelChanged(float level); /**jsdoc + * Triggered when the current context of the audio changes. * @function Audio.contextChanged - * @param {string} context + * @param {string} context - The current context of the audio: either "Desktop" or "HMD". * @returns {Signal} */ void contextChanged(const QString& context); @@ -158,7 +227,7 @@ public slots: /**jsdoc * @function Audio.onContextChanged - * @returns {Signal} + * @deprecated This function is deprecated and will be removed. */ void onContextChanged(); diff --git a/interface/src/scripting/AudioDevices.cpp b/interface/src/scripting/AudioDevices.cpp index f08a0bf382..ce82786c8d 100644 --- a/interface/src/scripting/AudioDevices.cpp +++ b/interface/src/scripting/AudioDevices.cpp @@ -360,11 +360,11 @@ void AudioInputDeviceList::onPeakValueListChanged(const QList& peakValueL } AudioDevices::AudioDevices(bool& contextIsHMD) : _contextIsHMD(contextIsHMD) { - auto client = DependencyManager::get(); + auto client = DependencyManager::get().data(); - connect(client.data(), &AudioClient::deviceChanged, this, &AudioDevices::onDeviceChanged, Qt::QueuedConnection); - connect(client.data(), &AudioClient::devicesChanged, this, &AudioDevices::onDevicesChanged, Qt::QueuedConnection); - connect(client.data(), &AudioClient::peakValueListChanged, &_inputs, &AudioInputDeviceList::onPeakValueListChanged, Qt::QueuedConnection); + connect(client, &AudioClient::deviceChanged, this, &AudioDevices::onDeviceChanged, Qt::QueuedConnection); + connect(client, &AudioClient::devicesChanged, this, &AudioDevices::onDevicesChanged, Qt::QueuedConnection); + connect(client, &AudioClient::peakValueListChanged, &_inputs, &AudioInputDeviceList::onPeakValueListChanged, Qt::QueuedConnection); _inputs.onDeviceChanged(client->getActiveAudioDevice(QAudio::AudioInput), contextIsHMD); _outputs.onDeviceChanged(client->getActiveAudioDevice(QAudio::AudioOutput), contextIsHMD); @@ -446,7 +446,7 @@ void AudioDevices::onDevicesChanged(QAudio::Mode mode, const QList(); + auto client = DependencyManager::get().data(); _inputs._hmdSavedDeviceName = getTargetDevice(true, QAudio::AudioInput); _inputs._desktopSavedDeviceName = getTargetDevice(false, QAudio::AudioInput); @@ -494,9 +494,9 @@ void AudioDevices::onDevicesChanged(QAudio::Mode mode, const QList(); + auto client = DependencyManager::get().data(); _requestedInputDevice = device; - QMetaObject::invokeMethod(client.data(), "switchAudioDevice", + QMetaObject::invokeMethod(client, "switchAudioDevice", Q_ARG(QAudio::Mode, QAudio::AudioInput), Q_ARG(const QAudioDeviceInfo&, device)); } else { @@ -511,9 +511,9 @@ void AudioDevices::chooseInputDevice(const QAudioDeviceInfo& device, bool isHMD) void AudioDevices::chooseOutputDevice(const QAudioDeviceInfo& device, bool isHMD) { //check if current context equals device to change if (_contextIsHMD == isHMD) { - auto client = DependencyManager::get(); + auto client = DependencyManager::get().data(); _requestedOutputDevice = device; - QMetaObject::invokeMethod(client.data(), "switchAudioDevice", + QMetaObject::invokeMethod(client, "switchAudioDevice", Q_ARG(QAudio::Mode, QAudio::AudioOutput), Q_ARG(const QAudioDeviceInfo&, device)); } else { diff --git a/interface/src/scripting/HMDScriptingInterface.cpp b/interface/src/scripting/HMDScriptingInterface.cpp index 31b8f74e9e..ad8e265a01 100644 --- a/interface/src/scripting/HMDScriptingInterface.cpp +++ b/interface/src/scripting/HMDScriptingInterface.cpp @@ -35,6 +35,12 @@ glm::vec3 HMDScriptingInterface::calculateRayUICollisionPoint(const glm::vec3& p return result; } +glm::vec3 HMDScriptingInterface::calculateParabolaUICollisionPoint(const glm::vec3& position, const glm::vec3& velocity, const glm::vec3& acceleration, float& parabolicDistance) const { + glm::vec3 result; + qApp->getApplicationCompositor().calculateParabolaUICollisionPoint(position, velocity, acceleration, result, parabolicDistance); + return result; +} + glm::vec2 HMDScriptingInterface::overlayFromWorldPoint(const glm::vec3& position) const { return qApp->getApplicationCompositor().overlayFromSphereSurface(position); } diff --git a/interface/src/scripting/HMDScriptingInterface.h b/interface/src/scripting/HMDScriptingInterface.h index d4dba2f0f5..a8fec839eb 100644 --- a/interface/src/scripting/HMDScriptingInterface.h +++ b/interface/src/scripting/HMDScriptingInterface.h @@ -56,8 +56,7 @@ class QScriptEngine; * @property {Uuid} tabletID - The UUID of the tablet body model overlay. * @property {Uuid} tabletScreenID - The UUID of the tablet's screen overlay. * @property {Uuid} homeButtonID - The UUID of the tablet's "home" button overlay. - * @property {Uuid} homeButtonHighlightMaterialID - The UUID of the material entity used to highlight tablet button - * @property {Uuid} homeButtonUnhighlightMaterialID - The UUID of the material entity use to unhighlight the entity + * @property {Uuid} homeButtonHighlightID - The UUID of the tablet's "home" button highlight overlay. */ class HMDScriptingInterface : public AbstractHMDScriptingInterface, public Dependency { Q_OBJECT @@ -69,11 +68,10 @@ class HMDScriptingInterface : public AbstractHMDScriptingInterface, public Depen Q_PROPERTY(QUuid tabletID READ getCurrentTabletFrameID WRITE setCurrentTabletFrameID) Q_PROPERTY(QUuid homeButtonID READ getCurrentHomeButtonID WRITE setCurrentHomeButtonID) Q_PROPERTY(QUuid tabletScreenID READ getCurrentTabletScreenID WRITE setCurrentTabletScreenID) - Q_PROPERTY(QUuid homeButtonHighlightMaterialID READ getCurrentHomeButtonHighlightMaterialID WRITE setCurrentHomeButtonHighlightMaterialID) - Q_PROPERTY(QUuid homeButtonUnhighlightMaterialID READ getCurrentHomeButtonUnhighlightMaterialID WRITE setCurrentHomeButtonUnhighlightMaterialID) + Q_PROPERTY(QUuid homeButtonHighlightID READ getCurrentHomeButtonHighlightID WRITE setCurrentHomeButtonHighlightID) public: - + /**jsdoc * Calculate the intersection of a ray with the HUD overlay. * @function HMD.calculateRayUICollisionPoint @@ -100,6 +98,8 @@ public: */ Q_INVOKABLE glm::vec3 calculateRayUICollisionPoint(const glm::vec3& position, const glm::vec3& direction) const; + glm::vec3 calculateParabolaUICollisionPoint(const glm::vec3& position, const glm::vec3& velocity, const glm::vec3& acceleration, float& parabolicDistance) const; + /**jsdoc * Get the 2D HUD overlay coordinates of a 3D point on the HUD overlay. * 2D HUD overlay coordinates are pixels with the origin at the top left of the overlay. @@ -345,17 +345,6 @@ signals: */ bool shouldShowHandControllersChanged(); - /**jsdoc - * Triggered when the HMD.mounted property value changes. - * @function HMD.mountedChanged - * @returns {Signal} - * @example Report when there's a change in the HMD being worn. - * HMD.mountedChanged.connect(function () { - * print("Mounted changed. HMD is mounted: " + HMD.mounted); - * }); - */ - void mountedChanged(); - public: HMDScriptingInterface(); static QScriptValue getHUDLookAtPosition2D(QScriptContext* context, QScriptEngine* engine); @@ -374,15 +363,12 @@ public: void setCurrentHomeButtonID(QUuid homeButtonID) { _homeButtonID = homeButtonID; } QUuid getCurrentHomeButtonID() const { return _homeButtonID; } + void setCurrentHomeButtonHighlightID(QUuid homeButtonHighlightID) { _homeButtonHighlightID = homeButtonHighlightID; } + QUuid getCurrentHomeButtonHighlightID() const { return _homeButtonHighlightID; } + void setCurrentTabletScreenID(QUuid tabletID) { _tabletScreenID = tabletID; } QUuid getCurrentTabletScreenID() const { return _tabletScreenID; } - void setCurrentHomeButtonHighlightMaterialID(QUuid homeButtonHighlightMaterialID) { _homeButtonHighlightMaterialID = homeButtonHighlightMaterialID; } - QUuid getCurrentHomeButtonHighlightMaterialID() { return _homeButtonHighlightMaterialID; } - - void setCurrentHomeButtonUnhighlightMaterialID(QUuid homeButtonUnhighlightMaterialID) { _homeButtonUnhighlightMaterialID = homeButtonUnhighlightMaterialID; } - QUuid getCurrentHomeButtonUnhighlightMaterialID() { return _homeButtonUnhighlightMaterialID; } - private: bool _showTablet { false }; bool _tabletContextualMode { false }; @@ -390,8 +376,7 @@ private: QUuid _tabletScreenID; // this is the overlayID which is part of (a child of) the tablet-ui. QUuid _homeButtonID; QUuid _tabletEntityID; - QUuid _homeButtonHighlightMaterialID; - QUuid _homeButtonUnhighlightMaterialID; + QUuid _homeButtonHighlightID; // Get the position of the HMD glm::vec3 getPosition() const; diff --git a/interface/src/scripting/TestScriptingInterface.cpp b/interface/src/scripting/TestScriptingInterface.cpp index 430441226f..52f6a3ebc0 100644 --- a/interface/src/scripting/TestScriptingInterface.cpp +++ b/interface/src/scripting/TestScriptingInterface.cpp @@ -190,4 +190,12 @@ void TestScriptingInterface::saveObject(QVariant variant, const QString& filenam void TestScriptingInterface::showMaximized() { qApp->getWindow()->showMaximized(); +} + +void TestScriptingInterface::setOtherAvatarsReplicaCount(int count) { + qApp->setOtherAvatarsReplicaCount(count); +} + +int TestScriptingInterface::getOtherAvatarsReplicaCount() { + return qApp->getOtherAvatarsReplicaCount(); } \ No newline at end of file diff --git a/interface/src/scripting/TestScriptingInterface.h b/interface/src/scripting/TestScriptingInterface.h index c47e39d1f3..4a1d1a3eeb 100644 --- a/interface/src/scripting/TestScriptingInterface.h +++ b/interface/src/scripting/TestScriptingInterface.h @@ -149,6 +149,20 @@ public slots: */ void showMaximized(); + /**jsdoc + * Values higher than 0 will create replicas of other-avatars when entering a domain for testing purpouses + * @function Test.setOtherAvatarsReplicaCount + * @param {number} count - Number of replicas we want to create + */ + Q_INVOKABLE void setOtherAvatarsReplicaCount(int count); + + /**jsdoc + * Return the number of replicas that are being created of other-avatars when entering a domain + * @function Test.getOtherAvatarsReplicaCount + * @returns {number} Current number of replicas of other-avatars. + */ + Q_INVOKABLE int getOtherAvatarsReplicaCount(); + private: bool waitForCondition(qint64 maxWaitMs, std::function condition); QString _testResultsLocation; diff --git a/interface/src/ui/JSConsole.cpp b/interface/src/ui/JSConsole.cpp index 1ca1ac2842..ed4ee97780 100644 --- a/interface/src/ui/JSConsole.cpp +++ b/interface/src/ui/JSConsole.cpp @@ -312,7 +312,7 @@ JSConsole::~JSConsole() { delete _ui; } -void JSConsole::setScriptEngine(const ScriptEnginePointer& scriptEngine) { +void JSConsole::setScriptEngine(const ScriptEnginePointer& scriptEngine) { if (_scriptEngine == scriptEngine && scriptEngine != nullptr) { return; } diff --git a/interface/src/ui/LoginDialog.cpp b/interface/src/ui/LoginDialog.cpp index 9804c6712a..8a40ee2f83 100644 --- a/interface/src/ui/LoginDialog.cpp +++ b/interface/src/ui/LoginDialog.cpp @@ -162,9 +162,7 @@ void LoginDialog::createAccountFromStream(QString username) { } void LoginDialog::openUrl(const QString& url) const { - - auto tabletScriptingInterface = DependencyManager::get(); - auto tablet = dynamic_cast(tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system")); + auto tablet = dynamic_cast(DependencyManager::get()->getTablet("com.highfidelity.interface.tablet.system")); auto hmd = DependencyManager::get(); auto offscreenUi = DependencyManager::get(); diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index d0fe92ea00..951925214c 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -27,28 +27,27 @@ void setupPreferences() { auto preferences = DependencyManager::get(); - auto nodeList = DependencyManager::get(); auto myAvatar = DependencyManager::get()->getMyAvatar(); static const QString AVATAR_BASICS { "Avatar Basics" }; { - auto getter = [=]()->QString { return myAvatar->getDisplayName(); }; - auto setter = [=](const QString& value) { myAvatar->setDisplayName(value); }; + auto getter = [myAvatar]()->QString { return myAvatar->getDisplayName(); }; + auto setter = [myAvatar](const QString& value) { myAvatar->setDisplayName(value); }; auto preference = new EditPreference(AVATAR_BASICS, "Avatar display name (optional)", getter, setter); preference->setPlaceholderText("Not showing a name"); preferences->addPreference(preference); } { - auto getter = [=]()->QString { return myAvatar->getCollisionSoundURL(); }; - auto setter = [=](const QString& value) { myAvatar->setCollisionSoundURL(value); }; + auto getter = [myAvatar]()->QString { return myAvatar->getCollisionSoundURL(); }; + auto setter = [myAvatar](const QString& value) { myAvatar->setCollisionSoundURL(value); }; auto preference = new EditPreference(AVATAR_BASICS, "Avatar collision sound URL (optional)", getter, setter); preference->setPlaceholderText("Enter the URL of a sound to play when you bump into something"); preferences->addPreference(preference); } { - auto getter = [=]()->QString { return myAvatar->getFullAvatarURLFromPreferences().toString(); }; - auto setter = [=](const QString& value) { myAvatar->useFullAvatarURL(value, ""); qApp->clearAvatarOverrideUrl(); }; + auto getter = [myAvatar]()->QString { return myAvatar->getFullAvatarURLFromPreferences().toString(); }; + auto setter = [myAvatar](const QString& value) { myAvatar->useFullAvatarURL(value, ""); qApp->clearAvatarOverrideUrl(); }; auto preference = new AvatarPreference(AVATAR_BASICS, "Appearance", getter, setter); preferences->addPreference(preference); } @@ -56,53 +55,12 @@ void setupPreferences() { // Graphics quality static const QString GRAPHICS_QUALITY { "Graphics Quality" }; { - static const float MAX_DESKTOP_FPS = 60; - static const float MAX_HMD_FPS = 90; - static const float MIN_FPS = 10; - static const float LOW = 0.25f; - static const float MEDIUM = 0.5f; - static const float HIGH = 0.75f; auto getter = []()->float { - auto lodManager = DependencyManager::get(); - bool inHMD = qApp->isHMDMode(); - - float increaseFPS = 0; - if (inHMD) { - increaseFPS = lodManager->getHMDLODDecreaseFPS(); - } else { - increaseFPS = lodManager->getDesktopLODDecreaseFPS(); - } - float maxFPS = inHMD ? MAX_HMD_FPS : MAX_DESKTOP_FPS; - float percentage = increaseFPS / maxFPS; - - if (percentage >= HIGH) { - return LOW; - } else if (percentage >= LOW) { - return MEDIUM; - } - return HIGH; + return DependencyManager::get()->getWorldDetailQuality(); }; auto setter = [](float value) { - static const float THRASHING_DIFFERENCE = 10; - auto lodManager = DependencyManager::get(); - - bool isLowestValue = value == LOW; - bool isHMDMode = qApp->isHMDMode(); - - float maxFPS = isHMDMode ? MAX_HMD_FPS : MAX_DESKTOP_FPS; - float desiredFPS = maxFPS - THRASHING_DIFFERENCE; - - if (!isLowestValue) { - float calculatedFPS = (maxFPS - (maxFPS * value)) - THRASHING_DIFFERENCE; - desiredFPS = calculatedFPS < MIN_FPS ? MIN_FPS : calculatedFPS; - } - - if (isHMDMode) { - lodManager->setHMDLODDecreaseFPS(desiredFPS); - } else { - lodManager->setDesktopLODDecreaseFPS(desiredFPS); - } + DependencyManager::get()->setWorldDetailQuality(value); }; auto wodSlider = new SliderPreference(GRAPHICS_QUALITY, "World Detail", getter, setter); @@ -163,8 +121,8 @@ void setupPreferences() { static const QString VIEW_CATEGORY{ "View" }; { - auto getter = [=]()->float { return myAvatar->getRealWorldFieldOfView(); }; - auto setter = [=](float value) { myAvatar->setRealWorldFieldOfView(value); }; + auto getter = [myAvatar]()->float { return myAvatar->getRealWorldFieldOfView(); }; + auto setter = [myAvatar](float value) { myAvatar->setRealWorldFieldOfView(value); }; auto preference = new SpinnerPreference(VIEW_CATEGORY, "Real world vertical field of view (angular size of monitor)", getter, setter); preference->setMin(1); preference->setMax(180); @@ -219,13 +177,13 @@ void setupPreferences() { static const QString AVATAR_TUNING { "Avatar Tuning" }; { - auto getter = [=]()->QString { return myAvatar->getDominantHand(); }; - auto setter = [=](const QString& value) { myAvatar->setDominantHand(value); }; + auto getter = [myAvatar]()->QString { return myAvatar->getDominantHand(); }; + auto setter = [myAvatar](const QString& value) { myAvatar->setDominantHand(value); }; preferences->addPreference(new PrimaryHandPreference(AVATAR_TUNING, "Dominant Hand", getter, setter)); } { - auto getter = [=]()->float { return myAvatar->getTargetScale(); }; - auto setter = [=](float value) { myAvatar->setTargetScale(value); }; + auto getter = [myAvatar]()->float { return myAvatar->getTargetScale(); }; + auto setter = [myAvatar](float value) { myAvatar->setTargetScale(value); }; auto preference = new SpinnerSliderPreference(AVATAR_TUNING, "Avatar Scale", getter, setter); preference->setMin(0.25); preference->setMax(4); @@ -240,16 +198,16 @@ void setupPreferences() { } { - auto getter = [=]()->QString { return myAvatar->getAnimGraphOverrideUrl().toString(); }; - auto setter = [=](const QString& value) { myAvatar->setAnimGraphOverrideUrl(QUrl(value)); }; + auto getter = [myAvatar]()->QString { return myAvatar->getAnimGraphOverrideUrl().toString(); }; + auto setter = [myAvatar](const QString& value) { myAvatar->setAnimGraphOverrideUrl(QUrl(value)); }; auto preference = new EditPreference(AVATAR_TUNING, "Avatar animation JSON", getter, setter); preference->setPlaceholderText("default"); preferences->addPreference(preference); } { - auto getter = [=]()->bool { return myAvatar->getCollisionsEnabled(); }; - auto setter = [=](bool value) { myAvatar->setCollisionsEnabled(value); }; + auto getter = [myAvatar]()->bool { return myAvatar->getCollisionsEnabled(); }; + auto setter = [myAvatar](bool value) { myAvatar->setCollisionsEnabled(value); }; auto preference = new CheckPreference(AVATAR_TUNING, "Enable Avatar collisions", getter, setter); preferences->addPreference(preference); } @@ -266,25 +224,25 @@ void setupPreferences() { preferences->addPreference(new SliderPreference(FACE_TRACKING, "Eye Deflection", getter, setter)); } - static const QString MOVEMENT{ "VR Movement" }; + static const QString VR_MOVEMENT{ "VR Movement" }; { static const QString movementsControlChannel = QStringLiteral("Hifi-Advanced-Movement-Disabler"); - auto getter = [=]()->bool { return myAvatar->useAdvancedMovementControls(); }; - auto setter = [=](bool value) { myAvatar->setUseAdvancedMovementControls(value); }; - preferences->addPreference(new CheckPreference(MOVEMENT, - QStringLiteral("Advanced movement for hand controllers"), + auto getter = [myAvatar]()->bool { return myAvatar->useAdvancedMovementControls(); }; + auto setter = [myAvatar](bool value) { myAvatar->setUseAdvancedMovementControls(value); }; + preferences->addPreference(new CheckPreference(VR_MOVEMENT, + QStringLiteral("Advanced movement in VR (Teleport movement when unchecked)"), getter, setter)); } { - auto getter = [=]()->bool { return myAvatar->getFlyingEnabled(); }; - auto setter = [=](bool value) { myAvatar->setFlyingEnabled(value); }; - preferences->addPreference(new CheckPreference(MOVEMENT, "Flying & jumping", getter, setter)); + auto getter = [myAvatar]()->bool { return myAvatar->getFlyingHMDPref(); }; + auto setter = [myAvatar](bool value) { myAvatar->setFlyingHMDPref(value); }; + preferences->addPreference(new CheckPreference(VR_MOVEMENT, "Flying & jumping (HMD)", getter, setter)); } { - auto getter = [=]()->int { return myAvatar->getSnapTurn() ? 0 : 1; }; - auto setter = [=](int value) { myAvatar->setSnapTurn(value == 0); }; - auto preference = new RadioButtonsPreference(MOVEMENT, "Snap turn / Smooth turn", getter, setter); + auto getter = [myAvatar]()->int { return myAvatar->getSnapTurn() ? 0 : 1; }; + auto setter = [myAvatar](int value) { myAvatar->setSnapTurn(value == 0); }; + auto preference = new RadioButtonsPreference(VR_MOVEMENT, "Snap turn / Smooth turn", getter, setter); QStringList items; items << "Snap turn" << "Smooth turn"; preference->setItems(items); @@ -293,7 +251,7 @@ void setupPreferences() { { auto getter = [=]()->float { return myAvatar->getUserHeight(); }; auto setter = [=](float value) { myAvatar->setUserHeight(value); }; - auto preference = new SpinnerPreference(MOVEMENT, "User real-world height (meters)", getter, setter); + auto preference = new SpinnerPreference(VR_MOVEMENT, "User real-world height (meters)", getter, setter); preference->setMin(1.0f); preference->setMax(2.2f); preference->setDecimals(3); @@ -301,7 +259,7 @@ void setupPreferences() { preferences->addPreference(preference); } { - auto preference = new ButtonPreference(MOVEMENT, "RESET SENSORS", [] { + auto preference = new ButtonPreference(VR_MOVEMENT, "RESET SENSORS", [] { qApp->resetSensors(); }); preferences->addPreference(preference); @@ -309,8 +267,8 @@ void setupPreferences() { static const QString AVATAR_CAMERA{ "Mouse Sensitivity" }; { - auto getter = [=]()->float { return myAvatar->getPitchSpeed(); }; - auto setter = [=](float value) { myAvatar->setPitchSpeed(value); }; + auto getter = [myAvatar]()->float { return myAvatar->getPitchSpeed(); }; + auto setter = [myAvatar](float value) { myAvatar->setPitchSpeed(value); }; auto preference = new SpinnerSliderPreference(AVATAR_CAMERA, "Y input:", getter, setter); preference->setMin(1.0f); preference->setMax(360.0f); @@ -319,8 +277,8 @@ void setupPreferences() { preferences->addPreference(preference); } { - auto getter = [=]()->float { return myAvatar->getYawSpeed(); }; - auto setter = [=](float value) { myAvatar->setYawSpeed(value); }; + auto getter = [myAvatar]()->float { return myAvatar->getYawSpeed(); }; + auto setter = [myAvatar](float value) { myAvatar->setYawSpeed(value); }; auto preference = new SpinnerSliderPreference(AVATAR_CAMERA, "X input:", getter, setter); preference->setMin(1.0f); preference->setMax(360.0f); @@ -381,12 +339,24 @@ void setupPreferences() { { static const QString NETWORKING("Networking"); - auto nodelist = DependencyManager::get(); + QWeakPointer nodeListWeak = DependencyManager::get(); { static const int MIN_PORT_NUMBER { 0 }; static const int MAX_PORT_NUMBER { 65535 }; - auto getter = [nodelist] { return static_cast(nodelist->getSocketLocalPort()); }; - auto setter = [nodelist](int preset) { nodelist->setSocketLocalPort(static_cast(preset)); }; + auto getter = [nodeListWeak] { + auto nodeList = nodeListWeak.lock(); + if (nodeList) { + return static_cast(nodeList->getSocketLocalPort()); + } else { + return -1; + } + }; + auto setter = [nodeListWeak](int preset) { + auto nodeList = nodeListWeak.lock(); + if (nodeList) { + nodeList->setSocketLocalPort(static_cast(preset)); + } + }; auto preference = new IntSpinnerPreference(NETWORKING, "Listening Port", getter, setter); preference->setMin(MIN_PORT_NUMBER); preference->setMax(MAX_PORT_NUMBER); diff --git a/interface/src/ui/Stats.cpp b/interface/src/ui/Stats.cpp index 6bb615948c..1b26c9b621 100644 --- a/interface/src/ui/Stats.cpp +++ b/interface/src/ui/Stats.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include @@ -121,6 +122,7 @@ void Stats::updateStats(bool force) { auto avatarManager = DependencyManager::get(); // we need to take one avatar out so we don't include ourselves STAT_UPDATE(avatarCount, avatarManager->size() - 1); + STAT_UPDATE(physicsObjectCount, qApp->getNumCollisionObjects()); STAT_UPDATE(updatedAvatarCount, avatarManager->getNumAvatarsUpdated()); STAT_UPDATE(notUpdatedAvatarCount, avatarManager->getNumAvatarsNotUpdated()); STAT_UPDATE(serverCount, (int)nodeList->size()); @@ -146,6 +148,20 @@ void Stats::updateStats(bool force) { } STAT_UPDATE(gameLoopRate, (int)qApp->getGameLoopRate()); + auto pickManager = DependencyManager::get(); + if (pickManager && (_expanded || force)) { + std::vector totalPicks = pickManager->getTotalPickCounts(); + STAT_UPDATE(stylusPicksCount, totalPicks[PickQuery::Stylus]); + STAT_UPDATE(rayPicksCount, totalPicks[PickQuery::Ray]); + STAT_UPDATE(parabolaPicksCount, totalPicks[PickQuery::Parabola]); + STAT_UPDATE(collisionPicksCount, totalPicks[PickQuery::Collision]); + std::vector updatedPicks = pickManager->getUpdatedPickCounts(); + STAT_UPDATE(stylusPicksUpdated, updatedPicks[PickQuery::Stylus]); + STAT_UPDATE(rayPicksUpdated, updatedPicks[PickQuery::Ray]); + STAT_UPDATE(parabolaPicksUpdated, updatedPicks[PickQuery::Parabola]); + STAT_UPDATE(collisionPicksUpdated, updatedPicks[PickQuery::Collision]); + } + auto bandwidthRecorder = DependencyManager::get(); STAT_UPDATE(packetInCount, (int)bandwidthRecorder->getCachedTotalAverageInputPacketsPerSecond()); STAT_UPDATE(packetOutCount, (int)bandwidthRecorder->getCachedTotalAverageOutputPacketsPerSecond()); @@ -174,7 +190,7 @@ void Stats::updateStats(bool force) { int octreeServerCount = 0; int pingOctreeMax = 0; int totalEntityKbps = 0; - nodeList->eachNode([&](const SharedNodePointer& node) { + nodeList->eachNode([&totalPingOctree, &totalEntityKbps, &octreeServerCount, &pingOctreeMax](const SharedNodePointer& node) { // TODO: this should also support entities if (node->getType() == NodeType::EntityServer) { totalPingOctree += node->getPingMs(); @@ -191,6 +207,14 @@ void Stats::updateStats(bool force) { // Third column, avatar stats auto myAvatar = avatarManager->getMyAvatar(); + auto animStack = myAvatar->getSkeletonModel()->getRig().getAnimStack(); + + _animStackNames.clear(); + for (auto animStackIterator = animStack.begin(); animStackIterator != animStack.end(); ++animStackIterator) { + _animStackNames << animStackIterator->first + ": " + QString::number(animStackIterator->second,'f',3); + } + emit animStackNamesChanged(); + glm::vec3 avatarPos = myAvatar->getWorldPosition(); STAT_UPDATE(position, QVector3D(avatarPos.x, avatarPos.y, avatarPos.z)); STAT_UPDATE_FLOAT(speed, glm::length(myAvatar->getWorldVelocity()), 0.01f); @@ -211,7 +235,7 @@ void Stats::updateStats(bool force) { STAT_UPDATE_FLOAT(myAvatarSendRate, avatarManager->getMyAvatarSendRate(), 0.1f); SharedNodePointer audioMixerNode = nodeList->soloNodeOfType(NodeType::AudioMixer); - auto audioClient = DependencyManager::get(); + auto audioClient = DependencyManager::get().data(); if (audioMixerNode || force) { STAT_UPDATE(audioMixerKbps, (int)roundf( bandwidthRecorder->getAverageInputKilobitsPerSecond(NodeType::AudioMixer) + @@ -251,7 +275,6 @@ void Stats::updateStats(bool force) { STAT_UPDATE(downloadsPending, ResourceCache::getPendingRequestCount()); STAT_UPDATE(processing, DependencyManager::get()->getStat("Processing").toInt()); STAT_UPDATE(processingPending, DependencyManager::get()->getStat("PendingProcessing").toInt()); - // See if the active download urls have changed bool shouldUpdateUrls = _downloads != _downloadUrls.size(); @@ -278,7 +301,7 @@ void Stats::updateStats(bool force) { // downloads << (int)(resource->getProgress() * 100.0f) << "% "; //} //downloads << "(" << << " pending)"; - } // expanded avatar column + } // Fourth column, octree stats int serverCount = 0; @@ -346,29 +369,36 @@ void Stats::updateStats(bool force) { auto config = qApp->getRenderEngine()->getConfiguration().get(); STAT_UPDATE(engineFrameTime, (float) config->getCPURunTime()); STAT_UPDATE(avatarSimulationTime, (float)avatarManager->getAvatarSimulationTime()); - - STAT_UPDATE(gpuBuffers, (int)gpu::Context::getBufferGPUCount()); - STAT_UPDATE(gpuBufferMemory, (int)BYTES_TO_MB(gpu::Context::getBufferGPUMemSize())); - STAT_UPDATE(gpuTextures, (int)gpu::Context::getTextureGPUCount()); + if (_expanded) { + STAT_UPDATE(gpuBuffers, (int)gpu::Context::getBufferGPUCount()); + STAT_UPDATE(gpuBufferMemory, (int)BYTES_TO_MB(gpu::Context::getBufferGPUMemSize())); + STAT_UPDATE(gpuTextures, (int)gpu::Context::getTextureGPUCount()); - STAT_UPDATE(glContextSwapchainMemory, (int)BYTES_TO_MB(gl::Context::getSwapchainMemoryUsage())); + STAT_UPDATE(glContextSwapchainMemory, (int)BYTES_TO_MB(gl::Context::getSwapchainMemoryUsage())); - STAT_UPDATE(qmlTextureMemory, (int)BYTES_TO_MB(OffscreenQmlSurface::getUsedTextureMemory())); - STAT_UPDATE(texturePendingTransfers, (int)BYTES_TO_MB(gpu::Context::getTexturePendingGPUTransferMemSize())); - STAT_UPDATE(gpuTextureMemory, (int)BYTES_TO_MB(gpu::Context::getTextureGPUMemSize())); - STAT_UPDATE(gpuTextureResidentMemory, (int)BYTES_TO_MB(gpu::Context::getTextureResidentGPUMemSize())); - STAT_UPDATE(gpuTextureFramebufferMemory, (int)BYTES_TO_MB(gpu::Context::getTextureFramebufferGPUMemSize())); - STAT_UPDATE(gpuTextureResourceMemory, (int)BYTES_TO_MB(gpu::Context::getTextureResourceGPUMemSize())); - STAT_UPDATE(gpuTextureResourceIdealMemory, (int)BYTES_TO_MB(gpu::Context::getTextureResourceIdealGPUMemSize())); - STAT_UPDATE(gpuTextureResourcePopulatedMemory, (int)BYTES_TO_MB(gpu::Context::getTextureResourcePopulatedGPUMemSize())); - STAT_UPDATE(gpuTextureExternalMemory, (int)BYTES_TO_MB(gpu::Context::getTextureExternalGPUMemSize())); + STAT_UPDATE(qmlTextureMemory, (int)BYTES_TO_MB(OffscreenQmlSurface::getUsedTextureMemory())); + STAT_UPDATE(texturePendingTransfers, (int)BYTES_TO_MB(gpu::Context::getTexturePendingGPUTransferMemSize())); + STAT_UPDATE(gpuTextureMemory, (int)BYTES_TO_MB(gpu::Context::getTextureGPUMemSize())); + STAT_UPDATE(gpuTextureResidentMemory, (int)BYTES_TO_MB(gpu::Context::getTextureResidentGPUMemSize())); + STAT_UPDATE(gpuTextureFramebufferMemory, (int)BYTES_TO_MB(gpu::Context::getTextureFramebufferGPUMemSize())); + STAT_UPDATE(gpuTextureResourceMemory, (int)BYTES_TO_MB(gpu::Context::getTextureResourceGPUMemSize())); + STAT_UPDATE(gpuTextureResourceIdealMemory, (int)BYTES_TO_MB(gpu::Context::getTextureResourceIdealGPUMemSize())); + STAT_UPDATE(gpuTextureResourcePopulatedMemory, (int)BYTES_TO_MB(gpu::Context::getTextureResourcePopulatedGPUMemSize())); + STAT_UPDATE(gpuTextureExternalMemory, (int)BYTES_TO_MB(gpu::Context::getTextureExternalGPUMemSize())); #if !defined(Q_OS_ANDROID) - STAT_UPDATE(gpuTextureMemoryPressureState, getTextureMemoryPressureModeString()); + STAT_UPDATE(gpuTextureMemoryPressureState, getTextureMemoryPressureModeString()); #endif - STAT_UPDATE(gpuFreeMemory, (int)BYTES_TO_MB(gpu::Context::getFreeGPUMemSize())); - STAT_UPDATE(rectifiedTextureCount, (int)RECTIFIED_TEXTURE_COUNT.load()); - STAT_UPDATE(decimatedTextureCount, (int)DECIMATED_TEXTURE_COUNT.load()); + STAT_UPDATE(gpuFreeMemory, (int)BYTES_TO_MB(gpu::Context::getFreeGPUMemSize())); + STAT_UPDATE(rectifiedTextureCount, (int)RECTIFIED_TEXTURE_COUNT.load()); + STAT_UPDATE(decimatedTextureCount, (int)DECIMATED_TEXTURE_COUNT.load()); + } + + gpu::ContextStats gpuFrameStats; + gpuContext->getFrameStats(gpuFrameStats); + + STAT_UPDATE(drawcalls, gpuFrameStats._DSNumDrawcalls); + // Incoming packets QLocale locale(QLocale::English); @@ -431,7 +461,7 @@ void Stats::updateStats(bool force) { // a new Map sorted by average time... bool onlyDisplayTopTen = Menu::getInstance()->isOptionChecked(MenuOption::OnlyDisplayTopTen); QMap sortedRecords; - const QMap& allRecords = PerformanceTimer::getAllTimerRecords(); + auto allRecords = PerformanceTimer::getAllTimerRecords(); QMapIterator i(allRecords); while (i.hasNext()) { @@ -479,7 +509,7 @@ void Stats::updateStats(bool force) { bool operator<(const SortableStat& other) const { return priority < other.priority; } }; - const QMap& allRecords = PerformanceTimer::getAllTimerRecords(); + auto allRecords = PerformanceTimer::getAllTimerRecords(); std::priority_queue idleUpdateStats; auto itr = allRecords.find("/idle/update"); if (itr != allRecords.end()) { @@ -496,7 +526,7 @@ void Stats::updateStats(bool force) { }; for (int32_t j = 0; j < categories.size(); ++j) { QString recordKey = "/idle/update/" + categories[j]; - auto record = PerformanceTimer::getTimerRecord(recordKey); + auto& record = allRecords[recordKey]; if (record.getCount()) { float dt = (float) record.getMovingAverage() / (float)USECS_PER_MSEC; QString message = QString("\n %1 = %2").arg(categories[j]).arg(dt); diff --git a/interface/src/ui/Stats.h b/interface/src/ui/Stats.h index f4181f9788..a0453a09c5 100644 --- a/interface/src/ui/Stats.h +++ b/interface/src/ui/Stats.h @@ -22,7 +22,6 @@ public: \ private: \ type _##name{ initialValue }; - /**jsdoc * @namespace Stats * @@ -49,6 +48,7 @@ private: \ * @property {number} presentdroprate - Read-only. * @property {number} gameLoopRate - Read-only. * @property {number} avatarCount - Read-only. + * @property {number} physicsObjectCount - Read-only. * @property {number} updatedAvatarCount - Read-only. * @property {number} notUpdatedAvatarCount - Read-only. * @property {number} packetInCount - Read-only. @@ -93,7 +93,6 @@ private: \ * @property {number} processing - Read-only. * @property {number} processingPending - Read-only. * @property {number} triangles - Read-only. - * @property {number} quads - Read-only. * @property {number} materialSwitches - Read-only. * @property {number} itemConsidered - Read-only. * @property {number} itemOutOfView - Read-only. @@ -135,6 +134,7 @@ private: \ * @property {number} batchFrameTime - Read-only. * @property {number} engineFrameTime - Read-only. * @property {number} avatarSimulationTime - Read-only. + * @property {string[]} animStackNames - Read-only. * * * @property {number} x @@ -167,6 +167,15 @@ private: \ * @property {number} implicitHeight * * @property {object} layer - Read-only. + + * @property {number} stylusPicksCount - Read-only. + * @property {number} rayPicksCount - Read-only. + * @property {number} parabolaPicksCount - Read-only. + * @property {number} collisionPicksCount - Read-only. + * @property {Vec4} stylusPicksUpdated - Read-only. + * @property {Vec4} rayPicksUpdated - Read-only. + * @property {Vec4} parabolaPicksUpdated - Read-only. + * @property {Vec4} collisionPicksUpdated - Read-only. */ // Properties from x onwards are QQuickItem properties. @@ -194,6 +203,7 @@ class Stats : public QQuickItem { STATS_PROPERTY(float, presentdroprate, 0) STATS_PROPERTY(int, gameLoopRate, 0) STATS_PROPERTY(int, avatarCount, 0) + STATS_PROPERTY(int, physicsObjectCount, 0) STATS_PROPERTY(int, updatedAvatarCount, 0) STATS_PROPERTY(int, notUpdatedAvatarCount, 0) STATS_PROPERTY(int, packetInCount, 0) @@ -238,7 +248,7 @@ class Stats : public QQuickItem { STATS_PROPERTY(int, processing, 0) STATS_PROPERTY(int, processingPending, 0) STATS_PROPERTY(int, triangles, 0) - STATS_PROPERTY(int, quads, 0) + STATS_PROPERTY(int, drawcalls, 0) STATS_PROPERTY(int, materialSwitches, 0) STATS_PROPERTY(int, itemConsidered, 0) STATS_PROPERTY(int, itemOutOfView, 0) @@ -282,6 +292,16 @@ class Stats : public QQuickItem { STATS_PROPERTY(float, batchFrameTime, 0) STATS_PROPERTY(float, engineFrameTime, 0) STATS_PROPERTY(float, avatarSimulationTime, 0) + Q_PROPERTY(QStringList animStackNames READ animStackNames NOTIFY animStackNamesChanged) + + STATS_PROPERTY(int, stylusPicksCount, 0) + STATS_PROPERTY(int, rayPicksCount, 0) + STATS_PROPERTY(int, parabolaPicksCount, 0) + STATS_PROPERTY(int, collisionPicksCount, 0) + STATS_PROPERTY(QVector4D, stylusPicksUpdated, QVector4D(0, 0, 0, 0)) + STATS_PROPERTY(QVector4D, rayPicksUpdated, QVector4D(0, 0, 0, 0)) + STATS_PROPERTY(QVector4D, parabolaPicksUpdated, QVector4D(0, 0, 0, 0)) + STATS_PROPERTY(QVector4D, collisionPicksUpdated, QVector4D(0, 0, 0, 0)) public: static Stats* getInstance(); @@ -306,6 +326,7 @@ public: } QStringList downloadUrls () { return _downloadUrls; } + QStringList animStackNames() { return _animStackNames; } public slots: void forceUpdateStats() { updateStats(true); } @@ -403,6 +424,13 @@ signals: */ void gameLoopRateChanged(); + /**jsdoc + * Trigered when + * @function Stats.numPhysicsBodiesChanged + * @returns {Signal} + */ + void physicsObjectCountChanged(); + /**jsdoc * Triggered when the value of the avatarCount property changes. * @function Stats.avatarCountChanged @@ -706,11 +734,12 @@ signals: void trianglesChanged(); /**jsdoc - * Triggered when the value of the quads property changes. - * @function Stats.quadsChanged - * @returns {Signal} - */ - void quadsChanged(); + * Triggered when the value of the drawcalls property changes. + * This + * @function Stats.drawcallsChanged + * @returns {Signal} + */ + void drawcallsChanged(); /**jsdoc * Triggered when the value of the materialSwitches property changes. @@ -999,6 +1028,13 @@ signals: */ void avatarSimulationTimeChanged(); + /**jsdoc + * Triggered when the value of the animStackNames property changes. + * @function Stats.animStackNamesChanged + * @returns {Signal} + */ + void animStackNamesChanged(); + /**jsdoc * Triggered when the value of the rectifiedTextureCount property changes. * @function Stats.rectifiedTextureCountChanged @@ -1235,6 +1271,62 @@ signals: * @function Stats.update */ + /**jsdoc + * Triggered when the value of the stylusPicksCount property changes. + * @function Stats.stylusPicksCountChanged + * @returns {Signal} + */ + void stylusPicksCountChanged(); + + /**jsdoc + * Triggered when the value of the rayPicksCount property changes. + * @function Stats.rayPicksCountChanged + * @returns {Signal} + */ + void rayPicksCountChanged(); + + /**jsdoc + * Triggered when the value of the parabolaPicksCount property changes. + * @function Stats.parabolaPicksCountChanged + * @returns {Signal} + */ + void parabolaPicksCountChanged(); + + /**jsdoc + * Triggered when the value of the collisionPicksCount property changes. + * @function Stats.collisionPicksCountChanged + * @returns {Signal} + */ + void collisionPicksCountChanged(); + + /**jsdoc + * Triggered when the value of the stylusPicksUpdated property changes. + * @function Stats.stylusPicksUpdatedChanged + * @returns {Signal} + */ + void stylusPicksUpdatedChanged(); + + /**jsdoc + * Triggered when the value of the rayPicksUpdated property changes. + * @function Stats.rayPicksUpdatedChanged + * @returns {Signal} + */ + void rayPicksUpdatedChanged(); + + /**jsdoc + * Triggered when the value of the parabolaPicksUpdated property changes. + * @function Stats.parabolaPicksUpdatedChanged + * @returns {Signal} + */ + void parabolaPicksUpdatedChanged(); + + /**jsdoc + * Triggered when the value of the collisionPicksUpdated property changes. + * @function Stats.collisionPicksUpdatedChanged + * @returns {Signal} + */ + void collisionPicksUpdatedChanged(); + private: int _recentMaxPackets{ 0 } ; // recent max incoming voxel packets to process bool _resetRecentMaxPacketsSoon{ true }; @@ -1244,6 +1336,7 @@ private: QString _monospaceFont; const AudioIOStats* _audioStats; QStringList _downloadUrls = QStringList(); + QStringList _animStackNames = QStringList(); }; #endif // hifi_Stats_h diff --git a/interface/src/ui/TestingDialog.cpp b/interface/src/ui/TestingDialog.cpp index 6143f20ee6..5f0b20ca7e 100644 --- a/interface/src/ui/TestingDialog.cpp +++ b/interface/src/ui/TestingDialog.cpp @@ -23,8 +23,7 @@ TestingDialog::TestingDialog(QWidget* parent) : _console->setFixedHeight(TESTING_CONSOLE_HEIGHT); - auto _engines = DependencyManager::get(); - _engine = _engines->loadScript(qApp->applicationDirPath() + testRunnerRelativePath); + _engine = DependencyManager::get()->loadScript(qApp->applicationDirPath() + testRunnerRelativePath); _console->setScriptEngine(_engine); connect(_engine.data(), &ScriptEngine::finished, this, &TestingDialog::onTestingFinished); } diff --git a/interface/src/ui/overlays/Base3DOverlay.cpp b/interface/src/ui/overlays/Base3DOverlay.cpp index 551b352952..1d8db69e26 100644 --- a/interface/src/ui/overlays/Base3DOverlay.cpp +++ b/interface/src/ui/overlays/Base3DOverlay.cpp @@ -23,7 +23,7 @@ Base3DOverlay::Base3DOverlay() : SpatiallyNestable(NestableType::Overlay, QUuid::createUuid()), _isSolid(DEFAULT_IS_SOLID), _isDashedLine(DEFAULT_IS_DASHED_LINE), - _ignoreRayIntersection(false), + _ignorePickIntersection(false), _drawInFront(false), _drawHUDLayer(false) { @@ -34,7 +34,7 @@ Base3DOverlay::Base3DOverlay(const Base3DOverlay* base3DOverlay) : SpatiallyNestable(NestableType::Overlay, QUuid::createUuid()), _isSolid(base3DOverlay->_isSolid), _isDashedLine(base3DOverlay->_isDashedLine), - _ignoreRayIntersection(base3DOverlay->_ignoreRayIntersection), + _ignorePickIntersection(base3DOverlay->_ignorePickIntersection), _drawInFront(base3DOverlay->_drawInFront), _drawHUDLayer(base3DOverlay->_drawHUDLayer), _isGrabbable(base3DOverlay->_isGrabbable), @@ -183,8 +183,10 @@ void Base3DOverlay::setProperties(const QVariantMap& originalProperties) { if (properties["dashed"].isValid()) { setIsDashedLine(properties["dashed"].toBool()); } - if (properties["ignoreRayIntersection"].isValid()) { - setIgnoreRayIntersection(properties["ignoreRayIntersection"].toBool()); + if (properties["ignorePickIntersection"].isValid()) { + setIgnorePickIntersection(properties["ignorePickIntersection"].toBool()); + } else if (properties["ignoreRayIntersection"].isValid()) { + setIgnorePickIntersection(properties["ignoreRayIntersection"].toBool()); } if (properties["parentID"].isValid()) { @@ -224,8 +226,7 @@ void Base3DOverlay::setProperties(const QVariantMap& originalProperties) { * Antonyms: isWire and wire. * @property {boolean} isDashedLine=false - If true, a dashed line is drawn on the overlay's edges. Synonym: * dashed. - * @property {boolean} ignoreRayIntersection=false - If true, - * {@link Overlays.findRayIntersection|findRayIntersection} ignores the overlay. + * @property {boolean} ignorePickIntersection=false - If true, picks ignore the overlay. ignoreRayIntersection is a synonym. * @property {boolean} drawInFront=false - If true, the overlay is rendered in front of other overlays that don't * have drawInFront set to true, and in front of entities. * @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed. @@ -237,7 +238,9 @@ void Base3DOverlay::setProperties(const QVariantMap& originalProperties) { */ QVariant Base3DOverlay::getProperty(const QString& property) { if (property == "name") { - return _name; + return _nameLock.resultWithReadLock([&] { + return _name; + }); } if (property == "position" || property == "start" || property == "p1" || property == "point") { return vec3toVariant(getWorldPosition()); @@ -260,8 +263,8 @@ QVariant Base3DOverlay::getProperty(const QString& property) { if (property == "isDashedLine" || property == "dashed") { return _isDashedLine; } - if (property == "ignoreRayIntersection") { - return _ignoreRayIntersection; + if (property == "ignorePickIntersection" || property == "ignoreRayIntersection") { + return _ignorePickIntersection; } if (property == "drawInFront") { return _drawInFront; @@ -282,11 +285,6 @@ QVariant Base3DOverlay::getProperty(const QString& property) { return Overlay::getProperty(property); } -bool Base3DOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking) { - return false; -} - void Base3DOverlay::locationChanged(bool tellPhysics) { SpatiallyNestable::locationChanged(tellPhysics); @@ -350,6 +348,20 @@ void Base3DOverlay::setVisible(bool visible) { notifyRenderVariableChange(); } +QString Base3DOverlay::getName() const { + return _nameLock.resultWithReadLock([&] { + return QString("Overlay:") + _name; + }); +} + +void Base3DOverlay::setName(QString name) { + _nameLock.withWriteLock([&] { + _name = name; + }); +} + + + render::ItemKey Base3DOverlay::getKey() { auto builder = render::ItemKey::Builder(Overlay::getKey()); @@ -368,4 +380,4 @@ render::ItemKey Base3DOverlay::getKey() { } return builder.build(); -} \ No newline at end of file +} diff --git a/interface/src/ui/overlays/Base3DOverlay.h b/interface/src/ui/overlays/Base3DOverlay.h index 7c5f551e6a..6f6092a42e 100644 --- a/interface/src/ui/overlays/Base3DOverlay.h +++ b/interface/src/ui/overlays/Base3DOverlay.h @@ -29,8 +29,8 @@ public: virtual OverlayID getOverlayID() const override { return OverlayID(getID().toString()); } void setOverlayID(OverlayID overlayID) override { setID(overlayID); } - virtual QString getName() const override { return QString("Overlay:") + _name; } - void setName(QString name) { _name = name; } + virtual QString getName() const override; + void setName(QString name); // getters virtual bool is3D() const override { return true; } @@ -45,7 +45,7 @@ public: bool getIsSolid() const { return _isSolid; } bool getIsDashedLine() const { return _isDashedLine; } bool getIsSolidLine() const { return !_isDashedLine; } - bool getIgnoreRayIntersection() const { return _ignoreRayIntersection; } + bool getIgnorePickIntersection() const { return _ignorePickIntersection; } bool getDrawInFront() const { return _drawInFront; } bool getDrawHUDLayer() const { return _drawHUDLayer; } bool getIsGrabbable() const { return _isGrabbable; } @@ -53,7 +53,7 @@ public: void setIsSolid(bool isSolid) { _isSolid = isSolid; } void setIsDashedLine(bool isDashedLine) { _isDashedLine = isDashedLine; } - void setIgnoreRayIntersection(bool value) { _ignoreRayIntersection = value; } + void setIgnorePickIntersection(bool value) { _ignorePickIntersection = value; } virtual void setDrawInFront(bool value) { _drawInFront = value; } virtual void setDrawHUDLayer(bool value) { _drawHUDLayer = value; } void setIsGrabbable(bool value) { _isGrabbable = value; } @@ -69,13 +69,21 @@ public: virtual QVariant getProperty(const QString& property) override; virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, - BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking = false); + BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking = false) { return false; } virtual bool findRayIntersectionExtraInfo(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking = false) { return findRayIntersection(origin, direction, distance, face, surfaceNormal, precisionPicking); } + virtual bool findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, float& parabolicDistance, + BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking = false) { return false; } + + virtual bool findParabolaIntersectionExtraInfo(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking = false) { + return findParabolaIntersection(origin, velocity, acceleration, parabolicDistance, face, surfaceNormal, precisionPicking); + } + virtual SpatialParentTree* getParentTree() const override; protected: @@ -91,7 +99,7 @@ protected: bool _isSolid; bool _isDashedLine; - bool _ignoreRayIntersection; + bool _ignorePickIntersection; bool _drawInFront; bool _drawHUDLayer; bool _isGrabbable { false }; @@ -99,6 +107,7 @@ protected: mutable bool _renderVariableDirty { true }; QString _name; + mutable ReadWriteLockable _nameLock; }; #endif // hifi_Base3DOverlay_h diff --git a/interface/src/ui/overlays/Circle3DOverlay.cpp b/interface/src/ui/overlays/Circle3DOverlay.cpp index 8b04f17269..2e06229276 100644 --- a/interface/src/ui/overlays/Circle3DOverlay.cpp +++ b/interface/src/ui/overlays/Circle3DOverlay.cpp @@ -397,8 +397,7 @@ void Circle3DOverlay::setProperties(const QVariantMap& properties) { * Antonyms: isWire and wire. * @property {boolean} isDashedLine=false - If true, a dashed line is drawn on the overlay's edges. Synonym: * dashed. - * @property {boolean} ignoreRayIntersection=false - If true, - * {@link Overlays.findRayIntersection|findRayIntersection} ignores the overlay. + * @property {boolean} ignorePickIntersection=false - If true, picks ignore the overlay. ignoreRayIntersection is a synonym. * @property {boolean} drawInFront=false - If true, the overlay is rendered in front of other overlays that don't * have drawInFront set to true, and in front of entities. * @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed. @@ -520,22 +519,66 @@ QVariant Circle3DOverlay::getProperty(const QString& property) { bool Circle3DOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking) { - // Scale the dimensions by the diameter glm::vec2 dimensions = getOuterRadius() * 2.0f * getDimensions(); - bool intersects = findRayRectangleIntersection(origin, direction, getWorldOrientation(), getWorldPosition(), dimensions, distance); + glm::quat rotation = getWorldOrientation(); - if (intersects) { + if (findRayRectangleIntersection(origin, direction, rotation, getWorldPosition(), dimensions, distance)) { glm::vec3 hitPosition = origin + (distance * direction); glm::vec3 localHitPosition = glm::inverse(getWorldOrientation()) * (hitPosition - getWorldPosition()); localHitPosition.x /= getDimensions().x; localHitPosition.y /= getDimensions().y; float distanceToHit = glm::length(localHitPosition); - intersects = getInnerRadius() <= distanceToHit && distanceToHit <= getOuterRadius(); + if (getInnerRadius() <= distanceToHit && distanceToHit <= getOuterRadius()) { + glm::vec3 forward = rotation * Vectors::FRONT; + if (glm::dot(forward, direction) > 0.0f) { + face = MAX_Z_FACE; + surfaceNormal = -forward; + } else { + face = MIN_Z_FACE; + surfaceNormal = forward; + } + return true; + } } - return intersects; + return false; +} + +bool Circle3DOverlay::findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking) { + // Scale the dimensions by the diameter + glm::vec2 xyDimensions = getOuterRadius() * 2.0f * getDimensions(); + glm::quat rotation = getWorldOrientation(); + glm::vec3 position = getWorldPosition(); + + glm::quat inverseRot = glm::inverse(rotation); + glm::vec3 localOrigin = inverseRot * (origin - position); + glm::vec3 localVelocity = inverseRot * velocity; + glm::vec3 localAcceleration = inverseRot * acceleration; + + if (findParabolaRectangleIntersection(localOrigin, localVelocity, localAcceleration, xyDimensions, parabolicDistance)) { + glm::vec3 localHitPosition = localOrigin + localVelocity * parabolicDistance + 0.5f * localAcceleration * parabolicDistance * parabolicDistance; + localHitPosition.x /= getDimensions().x; + localHitPosition.y /= getDimensions().y; + float distanceToHit = glm::length(localHitPosition); + + if (getInnerRadius() <= distanceToHit && distanceToHit <= getOuterRadius()) { + float localIntersectionVelocityZ = localVelocity.z + localAcceleration.z * parabolicDistance; + glm::vec3 forward = rotation * Vectors::FRONT; + if (localIntersectionVelocityZ > 0.0f) { + face = MIN_Z_FACE; + surfaceNormal = forward; + } else { + face = MAX_Z_FACE; + surfaceNormal = -forward; + } + return true; + } + } + + return false; } Circle3DOverlay* Circle3DOverlay::createClone() const { diff --git a/interface/src/ui/overlays/Circle3DOverlay.h b/interface/src/ui/overlays/Circle3DOverlay.h index 0dc0f8b138..b3fa24fb16 100644 --- a/interface/src/ui/overlays/Circle3DOverlay.h +++ b/interface/src/ui/overlays/Circle3DOverlay.h @@ -54,8 +54,10 @@ public: void setMajorTickMarksColor(const xColor& value) { _majorTickMarksColor = value; } void setMinorTickMarksColor(const xColor& value) { _minorTickMarksColor = value; } - virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, + virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking = false) override; + virtual bool findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking = false) override; virtual Circle3DOverlay* createClone() const override; diff --git a/interface/src/ui/overlays/ContextOverlayInterface.cpp b/interface/src/ui/overlays/ContextOverlayInterface.cpp index c6323614c5..ba9a1f9fc9 100644 --- a/interface/src/ui/overlays/ContextOverlayInterface.cpp +++ b/interface/src/ui/overlays/ContextOverlayInterface.cpp @@ -180,7 +180,7 @@ bool ContextOverlayInterface::createOrDestroyContextOverlay(const EntityItemID& float distance; BoxFace face; glm::vec3 normal; - boundingBox.findRayIntersection(cameraPosition, direction, distance, face, normal); + boundingBox.findRayIntersection(cameraPosition, direction, 1.0f / direction, distance, face, normal); float offsetAngle = -CONTEXT_OVERLAY_OFFSET_ANGLE; if (event.getID() == 1) { // "1" is left hand offsetAngle *= -1.0f; @@ -197,7 +197,7 @@ bool ContextOverlayInterface::createOrDestroyContextOverlay(const EntityItemID& _contextOverlay->setPulseMin(CONTEXT_OVERLAY_UNHOVERED_PULSEMIN); _contextOverlay->setPulseMax(CONTEXT_OVERLAY_UNHOVERED_PULSEMAX); _contextOverlay->setColorPulse(CONTEXT_OVERLAY_UNHOVERED_COLORPULSE); - _contextOverlay->setIgnoreRayIntersection(false); + _contextOverlay->setIgnorePickIntersection(false); _contextOverlay->setDrawInFront(true); _contextOverlay->setURL(PathUtils::resourcesUrl() + "images/inspect-icon.png"); _contextOverlay->setIsFacingAvatar(true); diff --git a/interface/src/ui/overlays/Cube3DOverlay.cpp b/interface/src/ui/overlays/Cube3DOverlay.cpp index c98d9330df..38fff5f26f 100644 --- a/interface/src/ui/overlays/Cube3DOverlay.cpp +++ b/interface/src/ui/overlays/Cube3DOverlay.cpp @@ -160,8 +160,7 @@ void Cube3DOverlay::setProperties(const QVariantMap& properties) { * Antonyms: isWire and wire. * @property {boolean} isDashedLine=false - If true, a dashed line is drawn on the overlay's edges. Synonym: * dashed. - * @property {boolean} ignoreRayIntersection=false - If true, - * {@link Overlays.findRayIntersection|findRayIntersection} ignores the overlay. + * @property {boolean} ignorePickIntersection=false - If true, picks ignore the overlay. ignoreRayIntersection is a synonym. * @property {boolean} drawInFront=false - If true, the overlay is rendered in front of other overlays that don't * have drawInFront set to true, and in front of entities. * @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed. diff --git a/interface/src/ui/overlays/Grid3DOverlay.cpp b/interface/src/ui/overlays/Grid3DOverlay.cpp index 621c19944b..15eb9eef76 100644 --- a/interface/src/ui/overlays/Grid3DOverlay.cpp +++ b/interface/src/ui/overlays/Grid3DOverlay.cpp @@ -145,8 +145,7 @@ void Grid3DOverlay::setProperties(const QVariantMap& properties) { * Antonyms: isWire and wire. * @property {boolean} isDashedLine=false - If true, a dashed line is drawn on the overlay's edges. Synonym: * dashed. - * @property {boolean} ignoreRayIntersection=false - If true, - * {@link Overlays.findRayIntersection|findRayIntersection} ignores the overlay. + * @property {boolean} ignorePickIntersection=false - If true, picks ignore the overlay. ignoreRayIntersection is a synonym. * @property {boolean} drawInFront=false - If true, the overlay is rendered in front of other overlays that don't * have drawInFront set to true, and in front of entities. * @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed. diff --git a/interface/src/ui/overlays/Grid3DOverlay.h b/interface/src/ui/overlays/Grid3DOverlay.h index 34fe4dbbb6..64b65b3178 100644 --- a/interface/src/ui/overlays/Grid3DOverlay.h +++ b/interface/src/ui/overlays/Grid3DOverlay.h @@ -35,7 +35,10 @@ public: virtual Grid3DOverlay* createClone() const override; // Grids are UI tools, and may not be intersected (pickable) - virtual bool findRayIntersection(const glm::vec3&, const glm::vec3&, float&, BoxFace&, glm::vec3&, bool precisionPicking = false) override { return false; } + virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face, + glm::vec3& surfaceNormal, bool precisionPicking = false) override { return false; } + virtual bool findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking = false) override { return false; } protected: Transform evalRenderTransform() override; diff --git a/interface/src/ui/overlays/Image3DOverlay.cpp b/interface/src/ui/overlays/Image3DOverlay.cpp index a4ce7f9e0d..608e7eb72f 100644 --- a/interface/src/ui/overlays/Image3DOverlay.cpp +++ b/interface/src/ui/overlays/Image3DOverlay.cpp @@ -216,8 +216,7 @@ void Image3DOverlay::setProperties(const QVariantMap& properties) { * Antonyms: isWire and wire. * @property {boolean} isDashedLine=false - If true, a dashed line is drawn on the overlay's edges. Synonym: * dashed. - * @property {boolean} ignoreRayIntersection=false - If true, - * {@link Overlays.findRayIntersection|findRayIntersection} ignores the overlay. + * @property {boolean} ignorePickIntersection=false - If true, picks ignore the overlay. ignoreRayIntersection is a synonym. * @property {boolean} drawInFront=false - If true, the overlay is rendered in front of other overlays that don't * have drawInFront set to true, and in front of entities. * @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed. @@ -260,10 +259,7 @@ void Image3DOverlay::setURL(const QString& url) { bool Image3DOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking) { if (_texture && _texture->isLoaded()) { - // Make sure position and rotation is updated. Transform transform = getTransform(); - - // Don't call applyTransformTo() or setTransform() here because this code runs too frequently. // Produce the dimensions of the overlay based on the image's aspect ratio and the overlay's scale. bool isNull = _fromImage.isNull(); @@ -271,12 +267,55 @@ bool Image3DOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec float height = isNull ? _texture->getHeight() : _fromImage.height(); float maxSize = glm::max(width, height); glm::vec2 dimensions = _dimensions * glm::vec2(width / maxSize, height / maxSize); + glm::quat rotation = transform.getRotation(); - // FIXME - face and surfaceNormal not being set - return findRayRectangleIntersection(origin, direction, - transform.getRotation(), - transform.getTranslation(), - dimensions, distance); + if (findRayRectangleIntersection(origin, direction, rotation, transform.getTranslation(), dimensions, distance)) { + glm::vec3 forward = rotation * Vectors::FRONT; + if (glm::dot(forward, direction) > 0.0f) { + face = MAX_Z_FACE; + surfaceNormal = -forward; + } else { + face = MIN_Z_FACE; + surfaceNormal = forward; + } + return true; + } + } + + return false; +} + +bool Image3DOverlay::findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking) { + if (_texture && _texture->isLoaded()) { + Transform transform = getTransform(); + + // Produce the dimensions of the overlay based on the image's aspect ratio and the overlay's scale. + bool isNull = _fromImage.isNull(); + float width = isNull ? _texture->getWidth() : _fromImage.width(); + float height = isNull ? _texture->getHeight() : _fromImage.height(); + float maxSize = glm::max(width, height); + glm::vec2 dimensions = _dimensions * glm::vec2(width / maxSize, height / maxSize); + glm::quat rotation = transform.getRotation(); + glm::vec3 position = getWorldPosition(); + + glm::quat inverseRot = glm::inverse(rotation); + glm::vec3 localOrigin = inverseRot * (origin - position); + glm::vec3 localVelocity = inverseRot * velocity; + glm::vec3 localAcceleration = inverseRot * acceleration; + + if (findParabolaRectangleIntersection(localOrigin, localVelocity, localAcceleration, dimensions, parabolicDistance)) { + float localIntersectionVelocityZ = localVelocity.z + localAcceleration.z * parabolicDistance; + glm::vec3 forward = rotation * Vectors::FRONT; + if (localIntersectionVelocityZ > 0.0f) { + face = MIN_Z_FACE; + surfaceNormal = forward; + } else { + face = MAX_Z_FACE; + surfaceNormal = -forward; + } + return true; + } } return false; diff --git a/interface/src/ui/overlays/Image3DOverlay.h b/interface/src/ui/overlays/Image3DOverlay.h index 4432e3b07c..1ffa062d45 100644 --- a/interface/src/ui/overlays/Image3DOverlay.h +++ b/interface/src/ui/overlays/Image3DOverlay.h @@ -42,8 +42,10 @@ public: QVariant getProperty(const QString& property) override; bool isTransparent() override { return Base3DOverlay::isTransparent() || _alphaTexture; } - virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, + virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking = false) override; + virtual bool findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, float& parabolicDistance, + BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking = false) override; virtual Image3DOverlay* createClone() const override; diff --git a/interface/src/ui/overlays/Line3DOverlay.cpp b/interface/src/ui/overlays/Line3DOverlay.cpp index c2e5ad1fb4..af6c3c2472 100644 --- a/interface/src/ui/overlays/Line3DOverlay.cpp +++ b/interface/src/ui/overlays/Line3DOverlay.cpp @@ -288,8 +288,7 @@ void Line3DOverlay::setProperties(const QVariantMap& originalProperties) { * Antonyms: isWire and wire. * @property {boolean} isDashedLine=false - If true, a dashed line is drawn on the overlay's edges. Synonym: * dashed. - * @property {boolean} ignoreRayIntersection=false - If true, - * {@link Overlays.findRayIntersection|findRayIntersection} ignores the overlay. + * @property {boolean} ignorePickIntersection=false - If true, picks ignore the overlay. ignoreRayIntersection is a synonym. * @property {boolean} drawInFront=false - If true, the overlay is rendered in front of other overlays that don't * have drawInFront set to true, and in front of entities. * @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed. diff --git a/interface/src/ui/overlays/ModelOverlay.cpp b/interface/src/ui/overlays/ModelOverlay.cpp index 71354002ea..eee8222051 100644 --- a/interface/src/ui/overlays/ModelOverlay.cpp +++ b/interface/src/ui/overlays/ModelOverlay.cpp @@ -27,10 +27,6 @@ ModelOverlay::ModelOverlay() { _model->setLoadingPriority(_loadPriority); _isLoaded = false; - - // Don't show overlay until textures have loaded - _visible = false; - render::ScenePointer scene = qApp->getMain3DScene(); _model->setVisibleInScene(false, scene); } @@ -136,11 +132,13 @@ void ModelOverlay::update(float deltatime) { } scene->enqueueTransaction(transaction); + if (_texturesDirty && !_modelTextures.isEmpty()) { + _texturesDirty = false; + _model->setTextures(_modelTextures); + } + if (!_texturesLoaded && _model->getGeometry() && _model->getGeometry()->areTexturesLoaded()) { _texturesLoaded = true; - if (!_modelTextures.isEmpty()) { - _model->setTextures(_modelTextures); - } _model->setVisibleInScene(getVisible(), scene); _model->updateRenderItems(); @@ -242,6 +240,7 @@ void ModelOverlay::setProperties(const QVariantMap& properties) { _texturesLoaded = false; QVariantMap textureMap = texturesValue.toMap(); _modelTextures = textureMap; + _texturesDirty = true; } auto groupCulledValue = properties["isGroupCulled"]; @@ -382,8 +381,7 @@ vectorType ModelOverlay::mapJoints(mapFunction function) const { * Antonyms: isWire and wire. * @property {boolean} isDashedLine=false - If true, a dashed line is drawn on the overlay's edges. Synonym: * dashed. - * @property {boolean} ignoreRayIntersection=false - If true, - * {@link Overlays.findRayIntersection|findRayIntersection} ignores the overlay. + * @property {boolean} ignorePickIntersection=false - If true, picks ignore the overlay. ignoreRayIntersection is a synonym. * @property {boolean} drawInFront=false - If true, the overlay is rendered in front of other overlays that don't * have drawInFront set to true, and in front of entities. * @property {boolean} isGroupCulled=false - If true, the mesh parts of the model are LOD culled as a group. @@ -519,17 +517,26 @@ QVariant ModelOverlay::getProperty(const QString& property) { bool ModelOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking) { - QVariantMap extraInfo; return _model->findRayIntersectionAgainstSubMeshes(origin, direction, distance, face, surfaceNormal, extraInfo, precisionPicking); } bool ModelOverlay::findRayIntersectionExtraInfo(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) { - return _model->findRayIntersectionAgainstSubMeshes(origin, direction, distance, face, surfaceNormal, extraInfo, precisionPicking); } +bool ModelOverlay::findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking) { + QVariantMap extraInfo; + return _model->findParabolaIntersectionAgainstSubMeshes(origin, velocity, acceleration, parabolicDistance, face, surfaceNormal, extraInfo, precisionPicking); +} + +bool ModelOverlay::findParabolaIntersectionExtraInfo(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) { + return _model->findParabolaIntersectionAgainstSubMeshes(origin, velocity, acceleration, parabolicDistance, face, surfaceNormal, extraInfo, precisionPicking); +} + ModelOverlay* ModelOverlay::createClone() const { return new ModelOverlay(this); } diff --git a/interface/src/ui/overlays/ModelOverlay.h b/interface/src/ui/overlays/ModelOverlay.h index f7a79c5615..bd922e258a 100644 --- a/interface/src/ui/overlays/ModelOverlay.h +++ b/interface/src/ui/overlays/ModelOverlay.h @@ -47,7 +47,11 @@ public: virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking = false) override; virtual bool findRayIntersectionExtraInfo(const glm::vec3& origin, const glm::vec3& direction, - float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking = false) override; + float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking = false) override; + virtual bool findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, float& parabolicDistance, + BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking = false) override; + virtual bool findParabolaIntersectionExtraInfo(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, float& parabolicDistance, + BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking = false) override; virtual ModelOverlay* createClone() const override; @@ -94,6 +98,7 @@ private: ModelPointer _model; QVariantMap _modelTextures; bool _texturesLoaded { false }; + bool _texturesDirty { false }; render::ItemIDs _subRenderItemIDs; diff --git a/interface/src/ui/overlays/Overlays.cpp b/interface/src/ui/overlays/Overlays.cpp index 5a576c6d78..de4ff94719 100644 --- a/interface/src/ui/overlays/Overlays.cpp +++ b/interface/src/ui/overlays/Overlays.cpp @@ -546,7 +546,7 @@ RayToOverlayIntersectionResult Overlays::findRayIntersectionVector(const PickRay continue; } - if (thisOverlay && thisOverlay->getVisible() && !thisOverlay->getIgnoreRayIntersection() && thisOverlay->isLoaded()) { + if (thisOverlay && thisOverlay->getVisible() && !thisOverlay->getIgnorePickIntersection() && thisOverlay->isLoaded()) { float thisDistance; BoxFace thisFace; glm::vec3 thisSurfaceNormal; @@ -573,76 +573,86 @@ RayToOverlayIntersectionResult Overlays::findRayIntersectionVector(const PickRay return result; } -QScriptValue RayToOverlayIntersectionResultToScriptValue(QScriptEngine* engine, const RayToOverlayIntersectionResult& value) { - auto obj = engine->newObject(); - obj.setProperty("intersects", value.intersects); - obj.setProperty("overlayID", OverlayIDtoScriptValue(engine, value.overlayID)); - obj.setProperty("distance", value.distance); +ParabolaToOverlayIntersectionResult Overlays::findParabolaIntersectionVector(const PickParabola& parabola, bool precisionPicking, + const QVector& overlaysToInclude, + const QVector& overlaysToDiscard, + bool visibleOnly, bool collidableOnly) { + float bestDistance = std::numeric_limits::max(); + bool bestIsFront = false; - QString faceName = ""; - // handle BoxFace - switch (value.face) { - case MIN_X_FACE: - faceName = "MIN_X_FACE"; - break; - case MAX_X_FACE: - faceName = "MAX_X_FACE"; - break; - case MIN_Y_FACE: - faceName = "MIN_Y_FACE"; - break; - case MAX_Y_FACE: - faceName = "MAX_Y_FACE"; - break; - case MIN_Z_FACE: - faceName = "MIN_Z_FACE"; - break; - case MAX_Z_FACE: - faceName = "MAX_Z_FACE"; - break; - default: - case UNKNOWN_FACE: - faceName = "UNKNOWN_FACE"; - break; + QMutexLocker locker(&_mutex); + ParabolaToOverlayIntersectionResult result; + QMapIterator i(_overlaysWorld); + while (i.hasNext()) { + i.next(); + OverlayID thisID = i.key(); + auto thisOverlay = std::dynamic_pointer_cast(i.value()); + + if ((overlaysToDiscard.size() > 0 && overlaysToDiscard.contains(thisID)) || + (overlaysToInclude.size() > 0 && !overlaysToInclude.contains(thisID))) { + continue; + } + + if (thisOverlay && thisOverlay->getVisible() && !thisOverlay->getIgnorePickIntersection() && thisOverlay->isLoaded()) { + float thisDistance; + BoxFace thisFace; + glm::vec3 thisSurfaceNormal; + QVariantMap thisExtraInfo; + if (thisOverlay->findParabolaIntersectionExtraInfo(parabola.origin, parabola.velocity, parabola.acceleration, thisDistance, + thisFace, thisSurfaceNormal, thisExtraInfo, precisionPicking)) { + bool isDrawInFront = thisOverlay->getDrawInFront(); + if ((bestIsFront && isDrawInFront && thisDistance < bestDistance) + || (!bestIsFront && (isDrawInFront || thisDistance < bestDistance))) { + + bestIsFront = isDrawInFront; + bestDistance = thisDistance; + result.intersects = true; + result.parabolicDistance = thisDistance; + result.face = thisFace; + result.surfaceNormal = thisSurfaceNormal; + result.overlayID = thisID; + result.intersection = parabola.origin + parabola.velocity * thisDistance + 0.5f * parabola.acceleration * thisDistance * thisDistance; + result.distance = glm::distance(result.intersection, parabola.origin); + result.extraInfo = thisExtraInfo; + } + } + } } - obj.setProperty("face", faceName); - auto intersection = vec3toScriptValue(engine, value.intersection); + return result; +} + +QScriptValue RayToOverlayIntersectionResultToScriptValue(QScriptEngine* engine, const RayToOverlayIntersectionResult& value) { + QScriptValue obj = engine->newObject(); + obj.setProperty("intersects", value.intersects); + QScriptValue overlayIDValue = quuidToScriptValue(engine, value.overlayID); + obj.setProperty("overlayID", overlayIDValue); + obj.setProperty("distance", value.distance); + obj.setProperty("face", boxFaceToString(value.face)); + + QScriptValue intersection = vec3toScriptValue(engine, value.intersection); obj.setProperty("intersection", intersection); + QScriptValue surfaceNormal = vec3toScriptValue(engine, value.surfaceNormal); + obj.setProperty("surfaceNormal", surfaceNormal); obj.setProperty("extraInfo", engine->toScriptValue(value.extraInfo)); return obj; } -void RayToOverlayIntersectionResultFromScriptValue(const QScriptValue& objectVar, RayToOverlayIntersectionResult& value) { - QVariantMap object = objectVar.toVariant().toMap(); - value.intersects = object["intersects"].toBool(); - value.overlayID = OverlayID(QUuid(object["overlayID"].toString())); - value.distance = object["distance"].toFloat(); +void RayToOverlayIntersectionResultFromScriptValue(const QScriptValue& object, RayToOverlayIntersectionResult& value) { + value.intersects = object.property("intersects").toVariant().toBool(); + QScriptValue overlayIDValue = object.property("overlayID"); + quuidFromScriptValue(overlayIDValue, value.overlayID); + value.distance = object.property("distance").toVariant().toFloat(); + value.face = boxFaceFromString(object.property("face").toVariant().toString()); - QString faceName = object["face"].toString(); - if (faceName == "MIN_X_FACE") { - value.face = MIN_X_FACE; - } else if (faceName == "MAX_X_FACE") { - value.face = MAX_X_FACE; - } else if (faceName == "MIN_Y_FACE") { - value.face = MIN_Y_FACE; - } else if (faceName == "MAX_Y_FACE") { - value.face = MAX_Y_FACE; - } else if (faceName == "MIN_Z_FACE") { - value.face = MIN_Z_FACE; - } else if (faceName == "MAX_Z_FACE") { - value.face = MAX_Z_FACE; - } else { - value.face = UNKNOWN_FACE; - }; - auto intersection = object["intersection"]; + QScriptValue intersection = object.property("intersection"); if (intersection.isValid()) { - bool valid; - auto newIntersection = vec3FromVariant(intersection, valid); - if (valid) { - value.intersection = newIntersection; - } + vec3FromScriptValue(intersection, value.intersection); } - value.extraInfo = object["extraInfo"].toMap(); + QScriptValue surfaceNormal = object.property("surfaceNormal"); + if (surfaceNormal.isValid()) { + vec3FromScriptValue(surfaceNormal, value.surfaceNormal); + } + value.extraInfo = object.property("extraInfo").toVariant().toMap(); } bool Overlays::isLoaded(OverlayID id) { @@ -1046,7 +1056,8 @@ QVector Overlays::findOverlays(const glm::vec3& center, float radius) { i.next(); OverlayID thisID = i.key(); auto overlay = std::dynamic_pointer_cast(i.value()); - if (overlay && overlay->getVisible() && !overlay->getIgnoreRayIntersection() && overlay->isLoaded()) { + // FIXME: this ignores overlays with ignorePickIntersection == true, which seems wrong + if (overlay && overlay->getVisible() && !overlay->getIgnorePickIntersection() && overlay->isLoaded()) { // get AABox in frame of overlay glm::vec3 dimensions = overlay->getDimensions(); glm::vec3 low = dimensions * -0.5f; diff --git a/interface/src/ui/overlays/Overlays.h b/interface/src/ui/overlays/Overlays.h index 3debf74f26..21b9e93648 100644 --- a/interface/src/ui/overlays/Overlays.h +++ b/interface/src/ui/overlays/Overlays.h @@ -59,19 +59,28 @@ class RayToOverlayIntersectionResult { public: bool intersects { false }; OverlayID overlayID { UNKNOWN_OVERLAY_ID }; - float distance { 0 }; + float distance { 0.0f }; BoxFace face { UNKNOWN_FACE }; glm::vec3 surfaceNormal; glm::vec3 intersection; QVariantMap extraInfo; }; - - Q_DECLARE_METATYPE(RayToOverlayIntersectionResult); - QScriptValue RayToOverlayIntersectionResultToScriptValue(QScriptEngine* engine, const RayToOverlayIntersectionResult& value); void RayToOverlayIntersectionResultFromScriptValue(const QScriptValue& object, RayToOverlayIntersectionResult& value); +class ParabolaToOverlayIntersectionResult { +public: + bool intersects { false }; + OverlayID overlayID { UNKNOWN_OVERLAY_ID }; + float distance { 0.0f }; + float parabolicDistance { 0.0f }; + BoxFace face { UNKNOWN_FACE }; + glm::vec3 surfaceNormal; + glm::vec3 intersection; + QVariantMap extraInfo; +}; + /**jsdoc * The Overlays API provides facilities to create and interact with overlays. Overlays are 2D and 3D objects visible only to * yourself and that aren't persisted to the domain. They are used for UI. @@ -110,6 +119,11 @@ public: const QVector& overlaysToDiscard, bool visibleOnly = false, bool collidableOnly = false); + ParabolaToOverlayIntersectionResult findParabolaIntersectionVector(const PickParabola& parabola, bool precisionPicking, + const QVector& overlaysToInclude, + const QVector& overlaysToDiscard, + bool visibleOnly = false, bool collidableOnly = false); + bool mousePressEvent(QMouseEvent* event); bool mouseDoublePressEvent(QMouseEvent* event); bool mouseReleaseEvent(QMouseEvent* event); diff --git a/interface/src/ui/overlays/Planar3DOverlay.cpp b/interface/src/ui/overlays/Planar3DOverlay.cpp index 9a436c7564..cf2691bb13 100644 --- a/interface/src/ui/overlays/Planar3DOverlay.cpp +++ b/interface/src/ui/overlays/Planar3DOverlay.cpp @@ -72,8 +72,48 @@ QVariant Planar3DOverlay::getProperty(const QString& property) { bool Planar3DOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking) { - // FIXME - face and surfaceNormal not being returned - return findRayRectangleIntersection(origin, direction, getWorldOrientation(), getWorldPosition(), getDimensions(), distance); + glm::vec2 xyDimensions = getDimensions(); + glm::quat rotation = getWorldOrientation(); + glm::vec3 position = getWorldPosition(); + + if (findRayRectangleIntersection(origin, direction, rotation, position, xyDimensions, distance)) { + glm::vec3 forward = rotation * Vectors::FRONT; + if (glm::dot(forward, direction) > 0.0f) { + face = MAX_Z_FACE; + surfaceNormal = -forward; + } else { + face = MIN_Z_FACE; + surfaceNormal = forward; + } + return true; + } + return false; +} + +bool Planar3DOverlay::findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking) { + glm::vec2 xyDimensions = getDimensions(); + glm::quat rotation = getWorldOrientation(); + glm::vec3 position = getWorldPosition(); + + glm::quat inverseRot = glm::inverse(rotation); + glm::vec3 localOrigin = inverseRot * (origin - position); + glm::vec3 localVelocity = inverseRot * velocity; + glm::vec3 localAcceleration = inverseRot * acceleration; + + if (findParabolaRectangleIntersection(localOrigin, localVelocity, localAcceleration, xyDimensions, parabolicDistance)) { + float localIntersectionVelocityZ = localVelocity.z + localAcceleration.z * parabolicDistance; + glm::vec3 forward = rotation * Vectors::FRONT; + if (localIntersectionVelocityZ > 0.0f) { + face = MIN_Z_FACE; + surfaceNormal = forward; + } else { + face = MAX_Z_FACE; + surfaceNormal = -forward; + } + return true; + } + return false; } Transform Planar3DOverlay::evalRenderTransform() { diff --git a/interface/src/ui/overlays/Planar3DOverlay.h b/interface/src/ui/overlays/Planar3DOverlay.h index e2a0e1f896..0054b0baf1 100644 --- a/interface/src/ui/overlays/Planar3DOverlay.h +++ b/interface/src/ui/overlays/Planar3DOverlay.h @@ -32,6 +32,8 @@ public: virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking = false) override; + virtual bool findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking = false) override; protected: glm::vec2 _dimensions; diff --git a/interface/src/ui/overlays/Rectangle3DOverlay.cpp b/interface/src/ui/overlays/Rectangle3DOverlay.cpp index e765f3fc18..48d89fab1c 100644 --- a/interface/src/ui/overlays/Rectangle3DOverlay.cpp +++ b/interface/src/ui/overlays/Rectangle3DOverlay.cpp @@ -140,8 +140,7 @@ const render::ShapeKey Rectangle3DOverlay::getShapeKey() { * Antonyms: isWire and wire. * @property {boolean} isDashedLine=false - If true, a dashed line is drawn on the overlay's edges. Synonym: * dashed. - * @property {boolean} ignoreRayIntersection=false - If true, - * {@link Overlays.findRayIntersection|findRayIntersection} ignores the overlay. + * @property {boolean} ignorePickIntersection=false - If true, picks ignore the overlay. ignoreRayIntersection is a synonym. * @property {boolean} drawInFront=false - If true, the overlay is rendered in front of other overlays that don't * have drawInFront set to true, and in front of entities. * @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed. diff --git a/interface/src/ui/overlays/Shape3DOverlay.cpp b/interface/src/ui/overlays/Shape3DOverlay.cpp index c27faf6f0f..b0d3cf32af 100644 --- a/interface/src/ui/overlays/Shape3DOverlay.cpp +++ b/interface/src/ui/overlays/Shape3DOverlay.cpp @@ -160,8 +160,7 @@ void Shape3DOverlay::setProperties(const QVariantMap& properties) { * Antonyms: isWire and wire. * @property {boolean} isDashedLine=false - If true, a dashed line is drawn on the overlay's edges. Synonym: * dashed. - * @property {boolean} ignoreRayIntersection=false - If true, - * {@link Overlays.findRayIntersection|findRayIntersection} ignores the overlay. + * @property {boolean} ignorePickIntersection=false - If true, picks ignore the overlay. ignoreRayIntersection is a synonym. * @property {boolean} drawInFront=false - If true, the overlay is rendered in front of other overlays that don't * have drawInFront set to true, and in front of entities. * @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed. diff --git a/interface/src/ui/overlays/Sphere3DOverlay.cpp b/interface/src/ui/overlays/Sphere3DOverlay.cpp index 4743e1ed3a..00a0dd686c 100644 --- a/interface/src/ui/overlays/Sphere3DOverlay.cpp +++ b/interface/src/ui/overlays/Sphere3DOverlay.cpp @@ -60,8 +60,7 @@ Sphere3DOverlay::Sphere3DOverlay(const Sphere3DOverlay* Sphere3DOverlay) : * Antonyms: isWire and wire. * @property {boolean} isDashedLine=false - If true, a dashed line is drawn on the overlay's edges. Synonym: * dashed. - * @property {boolean} ignoreRayIntersection=false - If true, - * {@link Overlays.findRayIntersection|findRayIntersection} ignores the overlay. + * @property {boolean} ignorePickIntersection=false - If true, picks ignore the overlay. ignoreRayIntersection is a synonym. * @property {boolean} drawInFront=false - If true, the overlay is rendered in front of other overlays that don't * have drawInFront set to true, and in front of entities. * @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed. diff --git a/interface/src/ui/overlays/Text3DOverlay.cpp b/interface/src/ui/overlays/Text3DOverlay.cpp index b128ce7df7..fc4b8b9010 100644 --- a/interface/src/ui/overlays/Text3DOverlay.cpp +++ b/interface/src/ui/overlays/Text3DOverlay.cpp @@ -229,8 +229,7 @@ void Text3DOverlay::setProperties(const QVariantMap& properties) { * Antonyms: isWire and wire. * @property {boolean} isDashedLine=false - If true, a dashed line is drawn on the overlay's edges. Synonym: * dashed. - * @property {boolean} ignoreRayIntersection=false - If true, - * {@link Overlays.findRayIntersection|findRayIntersection} ignores the overlay. + * @property {boolean} ignorePickIntersection=false - If true, picks ignore the overlay. ignoreRayIntersection is a synonym. * @property {boolean} drawInFront=false - If true, the overlay is rendered in front of other overlays that don't * have drawInFront set to true, and in front of entities. * @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed. diff --git a/interface/src/ui/overlays/Volume3DOverlay.cpp b/interface/src/ui/overlays/Volume3DOverlay.cpp index cf1f7f7fcb..a307d445c0 100644 --- a/interface/src/ui/overlays/Volume3DOverlay.cpp +++ b/interface/src/ui/overlays/Volume3DOverlay.cpp @@ -88,7 +88,34 @@ bool Volume3DOverlay::findRayIntersection(const glm::vec3& origin, const glm::ve // we can use the AABox's ray intersection by mapping our origin and direction into the overlays frame // and testing intersection there. - return _localBoundingBox.findRayIntersection(overlayFrameOrigin, overlayFrameDirection, distance, face, surfaceNormal); + bool hit = _localBoundingBox.findRayIntersection(overlayFrameOrigin, overlayFrameDirection, 1.0f / overlayFrameDirection, distance, face, surfaceNormal); + + if (hit) { + surfaceNormal = transform.getRotation() * surfaceNormal; + } + return hit; +} + +bool Volume3DOverlay::findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking) { + // extents is the entity relative, scaled, centered extents of the entity + glm::mat4 worldToEntityMatrix; + Transform transform = getTransform(); + transform.setScale(1.0f); // ignore any inherited scale from SpatiallyNestable + transform.getInverseMatrix(worldToEntityMatrix); + + glm::vec3 overlayFrameOrigin = glm::vec3(worldToEntityMatrix * glm::vec4(origin, 1.0f)); + glm::vec3 overlayFrameVelocity = glm::vec3(worldToEntityMatrix * glm::vec4(velocity, 0.0f)); + glm::vec3 overlayFrameAcceleration = glm::vec3(worldToEntityMatrix * glm::vec4(acceleration, 0.0f)); + + // we can use the AABox's ray intersection by mapping our origin and direction into the overlays frame + // and testing intersection there. + bool hit = _localBoundingBox.findParabolaIntersection(overlayFrameOrigin, overlayFrameVelocity, overlayFrameAcceleration, parabolicDistance, face, surfaceNormal); + + if (hit) { + surfaceNormal = transform.getRotation() * surfaceNormal; + } + return hit; } Transform Volume3DOverlay::evalRenderTransform() { diff --git a/interface/src/ui/overlays/Volume3DOverlay.h b/interface/src/ui/overlays/Volume3DOverlay.h index e9b996a6dd..e4060ae335 100644 --- a/interface/src/ui/overlays/Volume3DOverlay.h +++ b/interface/src/ui/overlays/Volume3DOverlay.h @@ -32,6 +32,8 @@ public: virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking = false) override; + virtual bool findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, float& parabolicDistance, + BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking = false) override; protected: // Centered local bounding box diff --git a/interface/src/ui/overlays/Web3DOverlay.cpp b/interface/src/ui/overlays/Web3DOverlay.cpp index fbea492d1d..c16b4c016d 100644 --- a/interface/src/ui/overlays/Web3DOverlay.cpp +++ b/interface/src/ui/overlays/Web3DOverlay.cpp @@ -55,7 +55,7 @@ #include "scripting/AccountServicesScriptingInterface.h" #include #include "ui/Snapshot.h" -#include "SoundCache.h" +#include "SoundCacheScriptingInterface.h" #include "raypick/PointerScriptingInterface.h" #include #include "AboutUtil.h" @@ -184,9 +184,11 @@ void Web3DOverlay::buildWebSurface() { _webSurface->getRootItem()->setProperty("scriptURL", _scriptURL); } else { _webSurface = QSharedPointer(new OffscreenQmlSurface(), qmlSurfaceDeleter); + connect(_webSurface.data(), &hifi::qml::OffscreenSurface::rootContextCreated, [this](QQmlContext* surfaceContext) { + setupQmlSurface(_url == TabletScriptingInterface::QML); + }); _webSurface->load(_url); _cachedWebSurface = false; - setupQmlSurface(); } _webSurface->getSurfaceContext()->setContextProperty("globalPosition", vec3toVariant(getWorldPosition())); onResizeWebSurface(); @@ -214,7 +216,7 @@ bool Web3DOverlay::isWebContent() const { return false; } -void Web3DOverlay::setupQmlSurface() { +void Web3DOverlay::setupQmlSurface(bool isTablet) { _webSurface->getSurfaceContext()->setContextProperty("Users", DependencyManager::get().data()); _webSurface->getSurfaceContext()->setContextProperty("HMD", DependencyManager::get().data()); _webSurface->getSurfaceContext()->setContextProperty("UserActivityLogger", DependencyManager::get().data()); @@ -225,7 +227,7 @@ void Web3DOverlay::setupQmlSurface() { _webSurface->getSurfaceContext()->setContextProperty("Entities", DependencyManager::get().data()); _webSurface->getSurfaceContext()->setContextProperty("Snapshot", DependencyManager::get().data()); - if (_webSurface->getRootItem() && _webSurface->getRootItem()->objectName() == "tabletRoot") { + if (isTablet) { auto tabletScriptingInterface = DependencyManager::get(); auto flags = tabletScriptingInterface->getFlags(); @@ -253,7 +255,7 @@ void Web3DOverlay::setupQmlSurface() { _webSurface->getSurfaceContext()->setContextProperty("AvatarList", DependencyManager::get().data()); _webSurface->getSurfaceContext()->setContextProperty("DialogsManager", DialogsManagerScriptingInterface::getInstance()); _webSurface->getSurfaceContext()->setContextProperty("InputConfiguration", DependencyManager::get().data()); - _webSurface->getSurfaceContext()->setContextProperty("SoundCache", DependencyManager::get().data()); + _webSurface->getSurfaceContext()->setContextProperty("SoundCache", DependencyManager::get().data()); _webSurface->getSurfaceContext()->setContextProperty("MenuInterface", MenuScriptingInterface::getInstance()); _webSurface->getSurfaceContext()->setContextProperty("Settings", SettingsScriptingInterface::getInstance()); _webSurface->getSurfaceContext()->setContextProperty("AvatarBookmarks", DependencyManager::get().data()); @@ -543,8 +545,7 @@ void Web3DOverlay::setProperties(const QVariantMap& properties) { * Antonyms: isWire and wire. * @property {boolean} isDashedLine=false - If true, a dashed line is drawn on the overlay's edges. Synonym: * dashed. - * @property {boolean} ignoreRayIntersection=false - If true, - * {@link Overlays.findRayIntersection|findRayIntersection} ignores the overlay. + * @property {boolean} ignorePickIntersection=false - If true, picks ignore the overlay. ignoreRayIntersection is a synonym. * @property {boolean} drawInFront=false - If true, the overlay is rendered in front of other overlays that don't * have drawInFront set to true, and in front of entities. * @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed. @@ -627,20 +628,6 @@ void Web3DOverlay::setScriptURL(const QString& scriptURL) { } } -bool Web3DOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking) { - glm::vec2 dimensions = getDimensions(); - glm::quat rotation = getWorldOrientation(); - glm::vec3 position = getWorldPosition(); - - if (findRayRectangleIntersection(origin, direction, rotation, position, dimensions, distance)) { - surfaceNormal = rotation * Vectors::UNIT_Z; - face = glm::dot(surfaceNormal, direction) > 0 ? MIN_Z_FACE : MAX_Z_FACE; - return true; - } else { - return false; - } -} - Web3DOverlay* Web3DOverlay::createClone() const { return new Web3DOverlay(this); } diff --git a/interface/src/ui/overlays/Web3DOverlay.h b/interface/src/ui/overlays/Web3DOverlay.h index 2cf35c0172..4137ed8680 100644 --- a/interface/src/ui/overlays/Web3DOverlay.h +++ b/interface/src/ui/overlays/Web3DOverlay.h @@ -52,9 +52,6 @@ public: void setProperties(const QVariantMap& properties) override; QVariant getProperty(const QString& property) override; - virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, - BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking = false) override; - virtual Web3DOverlay* createClone() const override; enum InputMode { @@ -82,7 +79,7 @@ protected: Transform evalRenderTransform() override; private: - void setupQmlSurface(); + void setupQmlSurface(bool isTablet); void rebuildWebSurface(); bool isWebContent() const; diff --git a/interface/src/workload/GameWorkloadRenderer.cpp b/interface/src/workload/GameWorkloadRenderer.cpp index a8b65492d3..a2fb32d396 100644 --- a/interface/src/workload/GameWorkloadRenderer.cpp +++ b/interface/src/workload/GameWorkloadRenderer.cpp @@ -14,11 +14,7 @@ #include #include - -#include "render-utils/drawWorkloadProxy_vert.h" -#include "render-utils/drawWorkloadView_vert.h" -#include "render-utils/drawWorkloadProxy_frag.h" -#include "render-utils/drawWorkloadView_frag.h" +#include void GameSpaceToRender::configure(const Config& config) { @@ -101,7 +97,8 @@ namespace render { } } -GameWorkloadRenderItem::GameWorkloadRenderItem() : _key(render::ItemKey::Builder::opaqueShape().withTagBits(render::ItemKey::TAG_BITS_0 | render::ItemKey::TAG_BITS_1)) { + +GameWorkloadRenderItem::GameWorkloadRenderItem() : _key(render::ItemKey::Builder::opaqueShape().withShadowCaster().withTagBits(render::ItemKey::TAG_BITS_0 | render::ItemKey::TAG_BITS_1)) { } render::ItemKey GameWorkloadRenderItem::getKey() const { @@ -149,14 +146,7 @@ void GameWorkloadRenderItem::setAllViews(const workload::Views& views) { const gpu::PipelinePointer GameWorkloadRenderItem::getProxiesPipeline() { if (!_drawAllProxiesPipeline) { - auto vs = drawWorkloadProxy_vert::getShader(); - auto ps = drawWorkloadProxy_frag::getShader(); - gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); - - gpu::Shader::BindingSet slotBindings; - slotBindings.insert(gpu::Shader::Binding("workloadProxiesBuffer", 0)); - gpu::Shader::makeProgram(*program, slotBindings); - + gpu::ShaderPointer program = gpu::Shader::createProgram(shader::render_utils::program::drawWorkloadProxy); auto state = std::make_shared(); state->setDepthTest(true, true, gpu::LESS_EQUAL); /* state->setBlendFunction(true, @@ -173,15 +163,7 @@ const gpu::PipelinePointer GameWorkloadRenderItem::getProxiesPipeline() { const gpu::PipelinePointer GameWorkloadRenderItem::getViewsPipeline() { if (!_drawAllViewsPipeline) { - auto vs = drawWorkloadView_vert::getShader(); - auto ps = drawWorkloadView_frag::getShader(); - gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); - - gpu::Shader::BindingSet slotBindings; - slotBindings.insert(gpu::Shader::Binding("workloadViewsBuffer", 1)); - slotBindings.insert(gpu::Shader::Binding("drawMeshBuffer", 0)); - gpu::Shader::makeProgram(*program, slotBindings); - + gpu::ShaderPointer program = gpu::Shader::createProgram(shader::render_utils::program::drawWorkloadView); auto state = std::make_shared(); state->setDepthTest(true, true, gpu::LESS_EQUAL); /* state->setBlendFunction(true, @@ -192,6 +174,7 @@ const gpu::PipelinePointer GameWorkloadRenderItem::getViewsPipeline() { state->setCullMode(gpu::State::CULL_NONE); _drawAllViewsPipeline = gpu::Pipeline::create(program, state); } + return _drawAllViewsPipeline; } diff --git a/interface/src/workload/PhysicsBoundary.cpp b/interface/src/workload/PhysicsBoundary.cpp index 927121ac04..cc78789145 100644 --- a/interface/src/workload/PhysicsBoundary.cpp +++ b/interface/src/workload/PhysicsBoundary.cpp @@ -10,9 +10,13 @@ #include "PhysicsBoundary.h" +#include +#include #include #include +#include "avatar/AvatarManager.h" +#include "avatar/OtherAvatar.h" #include "workload/GameWorkload.h" void PhysicsBoundary::run(const workload::WorkloadContextPointer& context, const Inputs& inputs) { @@ -21,13 +25,27 @@ void PhysicsBoundary::run(const workload::WorkloadContextPointer& context, const return; } GameWorkloadContext* gameContext = static_cast(context.get()); - PhysicalEntitySimulationPointer simulation = gameContext->_simulation; const auto& regionChanges = inputs.get0(); for (uint32_t i = 0; i < (uint32_t)regionChanges.size(); ++i) { const workload::Space::Change& change = regionChanges[i]; - auto entity = space->getOwner(change.proxyId).get(); - if (entity) { - simulation->changeEntity(entity); + auto nestable = space->getOwner(change.proxyId).get(); + if (nestable) { + switch (nestable->getNestableType()) { + case NestableType::Entity: { + gameContext->_simulation->changeEntity(std::static_pointer_cast(nestable)); + } + break; + case NestableType::Avatar: { + auto avatar = std::static_pointer_cast(nestable); + avatar->setWorkloadRegion(change.region); + if (avatar->isInPhysicsSimulation() != avatar->shouldBeInPhysicsSimulation()) { + DependencyManager::get()->queuePhysicsChange(avatar); + } + } + break; + default: + break; + } } } } diff --git a/interface/src/workload/PhysicsBoundary.h b/interface/src/workload/PhysicsBoundary.h index c316fa5686..bc1e851285 100644 --- a/interface/src/workload/PhysicsBoundary.h +++ b/interface/src/workload/PhysicsBoundary.h @@ -7,25 +7,22 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#ifndef hifi_PhysicsGatekeeper_h -#define hifi_PhysicsGatekeeper_h +#ifndef hifi_PhysicsBoundary_h +#define hifi_PhysicsBoundary_h -#include #include #include -#include "PhysicalEntitySimulation.h" - class PhysicsBoundary { public: using Config = workload::Job::Config; using Inputs = workload::RegionTracker::Outputs; using Outputs = bool; - using JobModel = workload::Job::ModelI; // this doesn't work + using JobModel = workload::Job::ModelI; PhysicsBoundary() {} void configure(const Config& config) { } void run(const workload::WorkloadContextPointer& context, const Inputs& inputs); }; -#endif // hifi_PhysicsGatekeeper_h +#endif // hifi_PhysicsBoundary_h diff --git a/libraries/animation/src/AnimBlendLinear.cpp b/libraries/animation/src/AnimBlendLinear.cpp index 936126bf52..e2d79f864d 100644 --- a/libraries/animation/src/AnimBlendLinear.cpp +++ b/libraries/animation/src/AnimBlendLinear.cpp @@ -24,9 +24,10 @@ AnimBlendLinear::~AnimBlendLinear() { } -const AnimPoseVec& AnimBlendLinear::evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, Triggers& triggersOut) { +const AnimPoseVec& AnimBlendLinear::evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut) { _alpha = animVars.lookup(_alphaVar, _alpha); + float parentAlpha = _animStack[_id]; if (_children.size() == 0) { for (auto&& pose : _poses) { @@ -34,15 +35,29 @@ const AnimPoseVec& AnimBlendLinear::evaluate(const AnimVariantMap& animVars, con } } else if (_children.size() == 1) { _poses = _children[0]->evaluate(animVars, context, dt, triggersOut); + _animStack[_children[0]->getID()] = parentAlpha; } else { - float clampedAlpha = glm::clamp(_alpha, 0.0f, (float)(_children.size() - 1)); size_t prevPoseIndex = glm::floor(clampedAlpha); size_t nextPoseIndex = glm::ceil(clampedAlpha); - float alpha = glm::fract(clampedAlpha); - + auto alpha = glm::fract(clampedAlpha); evaluateAndBlendChildren(animVars, context, triggersOut, alpha, prevPoseIndex, nextPoseIndex, dt); + + // weights are for animation stack debug purposes only. + float weight1 = 0.0f; + float weight2 = 0.0f; + if (prevPoseIndex == nextPoseIndex) { + weight2 = 1.0f; + _animStack[_children[nextPoseIndex]->getID()] = weight2 * parentAlpha; + } else { + weight2 = alpha; + weight1 = 1.0f - weight2; + _animStack[_children[prevPoseIndex]->getID()] = weight1 * parentAlpha; + _animStack[_children[nextPoseIndex]->getID()] = weight2 * parentAlpha; + } } + processOutputJoints(triggersOut); + return _poses; } @@ -51,7 +66,7 @@ const AnimPoseVec& AnimBlendLinear::getPosesInternal() const { return _poses; } -void AnimBlendLinear::evaluateAndBlendChildren(const AnimVariantMap& animVars, const AnimContext& context, Triggers& triggersOut, float alpha, +void AnimBlendLinear::evaluateAndBlendChildren(const AnimVariantMap& animVars, const AnimContext& context, AnimVariantMap& triggersOut, float alpha, size_t prevPoseIndex, size_t nextPoseIndex, float dt) { if (prevPoseIndex == nextPoseIndex) { // this can happen if alpha is on an integer boundary diff --git a/libraries/animation/src/AnimBlendLinear.h b/libraries/animation/src/AnimBlendLinear.h index 0dae6aabdb..d0fe2a8503 100644 --- a/libraries/animation/src/AnimBlendLinear.h +++ b/libraries/animation/src/AnimBlendLinear.h @@ -30,7 +30,7 @@ public: AnimBlendLinear(const QString& id, float alpha); virtual ~AnimBlendLinear() override; - virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, Triggers& triggersOut) override; + virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut) override; void setAlphaVar(const QString& alphaVar) { _alphaVar = alphaVar; } @@ -38,7 +38,7 @@ protected: // for AnimDebugDraw rendering virtual const AnimPoseVec& getPosesInternal() const override; - void evaluateAndBlendChildren(const AnimVariantMap& animVars, const AnimContext& context, Triggers& triggersOut, float alpha, + void evaluateAndBlendChildren(const AnimVariantMap& animVars, const AnimContext& context, AnimVariantMap& triggersOut, float alpha, size_t prevPoseIndex, size_t nextPoseIndex, float dt); AnimPoseVec _poses; diff --git a/libraries/animation/src/AnimBlendLinearMove.cpp b/libraries/animation/src/AnimBlendLinearMove.cpp index 40fbb5a6f7..42098eb072 100644 --- a/libraries/animation/src/AnimBlendLinearMove.cpp +++ b/libraries/animation/src/AnimBlendLinearMove.cpp @@ -26,13 +26,46 @@ AnimBlendLinearMove::~AnimBlendLinearMove() { } -const AnimPoseVec& AnimBlendLinearMove::evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, Triggers& triggersOut) { +static float calculateAlpha(const float speed, const std::vector& characteristicSpeeds) { + + assert(characteristicSpeeds.size() > 0); + // calculate alpha from linear combination of referenceSpeeds. + float alpha = 0.0f; + if (speed <= characteristicSpeeds.front()) { + alpha = 0.0f; + } else if (speed > characteristicSpeeds.back()) { + alpha = (float)(characteristicSpeeds.size() - 1); + } else { + for (size_t i = 0; i < characteristicSpeeds.size() - 1; i++) { + if (characteristicSpeeds[i] < speed && speed < characteristicSpeeds[i + 1]) { + alpha = (float)i + ((speed - characteristicSpeeds[i]) / (characteristicSpeeds[i + 1] - characteristicSpeeds[i])); + break; + } + } + } + return alpha; +} + +const AnimPoseVec& AnimBlendLinearMove::evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut) { assert(_children.size() == _characteristicSpeeds.size()); - _alpha = animVars.lookup(_alphaVar, _alpha); _desiredSpeed = animVars.lookup(_desiredSpeedVar, _desiredSpeed); + float speed = 0.0f; + if (_alphaVar.contains("Lateral")) { + speed = animVars.lookup("moveLateralSpeed", speed); + } else if (_alphaVar.contains("Backward")) { + speed = animVars.lookup("moveBackwardSpeed", speed); + } else { + //this is forward movement + speed = animVars.lookup("moveForwardSpeed", speed); + } + _alpha = calculateAlpha(speed, _characteristicSpeeds); + float parentAlpha = _animStack[_id]; + + _animStack["speed"] = speed; + if (_children.size() == 0) { for (auto&& pose : _poses) { pose = AnimPose::identity; @@ -44,8 +77,8 @@ const AnimPoseVec& AnimBlendLinearMove::evaluate(const AnimVariantMap& animVars, float prevDeltaTime, nextDeltaTime; setFrameAndPhase(dt, alpha, prevPoseIndex, nextPoseIndex, &prevDeltaTime, &nextDeltaTime, triggersOut); evaluateAndBlendChildren(animVars, context, triggersOut, alpha, prevPoseIndex, nextPoseIndex, prevDeltaTime, nextDeltaTime); + _animStack[_children[0]->getID()] = parentAlpha; } else { - auto clampedAlpha = glm::clamp(_alpha, 0.0f, (float)(_children.size() - 1)); auto prevPoseIndex = glm::floor(clampedAlpha); auto nextPoseIndex = glm::ceil(clampedAlpha); @@ -53,7 +86,23 @@ const AnimPoseVec& AnimBlendLinearMove::evaluate(const AnimVariantMap& animVars, float prevDeltaTime, nextDeltaTime; setFrameAndPhase(dt, alpha, prevPoseIndex, nextPoseIndex, &prevDeltaTime, &nextDeltaTime, triggersOut); evaluateAndBlendChildren(animVars, context, triggersOut, alpha, prevPoseIndex, nextPoseIndex, prevDeltaTime, nextDeltaTime); + + // weights are for animation stack debug purposes only. + float weight1 = 0.0f; + float weight2 = 0.0f; + if (prevPoseIndex == nextPoseIndex) { + weight2 = 1.0f; + _animStack[_children[nextPoseIndex]->getID()] = weight2 * parentAlpha; + } else { + weight2 = alpha; + weight1 = 1.0f - weight2; + _animStack[_children[prevPoseIndex]->getID()] = weight1 * parentAlpha; + _animStack[_children[nextPoseIndex]->getID()] = weight2 * parentAlpha; + } } + + processOutputJoints(triggersOut); + return _poses; } @@ -62,7 +111,7 @@ const AnimPoseVec& AnimBlendLinearMove::getPosesInternal() const { return _poses; } -void AnimBlendLinearMove::evaluateAndBlendChildren(const AnimVariantMap& animVars, const AnimContext& context, Triggers& triggersOut, float alpha, +void AnimBlendLinearMove::evaluateAndBlendChildren(const AnimVariantMap& animVars, const AnimContext& context, AnimVariantMap& triggersOut, float alpha, size_t prevPoseIndex, size_t nextPoseIndex, float prevDeltaTime, float nextDeltaTime) { if (prevPoseIndex == nextPoseIndex) { @@ -82,7 +131,7 @@ void AnimBlendLinearMove::evaluateAndBlendChildren(const AnimVariantMap& animVar } void AnimBlendLinearMove::setFrameAndPhase(float dt, float alpha, int prevPoseIndex, int nextPoseIndex, - float* prevDeltaTimeOut, float* nextDeltaTimeOut, Triggers& triggersOut) { + float* prevDeltaTimeOut, float* nextDeltaTimeOut, AnimVariantMap& triggersOut) { const float FRAMES_PER_SECOND = 30.0f; auto prevClipNode = std::dynamic_pointer_cast(_children[prevPoseIndex]); @@ -107,9 +156,13 @@ void AnimBlendLinearMove::setFrameAndPhase(float dt, float alpha, int prevPoseIn // integrate phase forward in time. _phase += omega * dt; + if (_phase < 0.0f) { + _phase = 0.0f; + } + // detect loop trigger events if (_phase >= 1.0f) { - triggersOut.push_back(_id + "Loop"); + triggersOut.setTrigger(_id + "Loop"); _phase = glm::fract(_phase); } diff --git a/libraries/animation/src/AnimBlendLinearMove.h b/libraries/animation/src/AnimBlendLinearMove.h index 083858f873..ff2f2d7763 100644 --- a/libraries/animation/src/AnimBlendLinearMove.h +++ b/libraries/animation/src/AnimBlendLinearMove.h @@ -39,7 +39,7 @@ public: AnimBlendLinearMove(const QString& id, float alpha, float desiredSpeed, const std::vector& characteristicSpeeds); virtual ~AnimBlendLinearMove() override; - virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, Triggers& triggersOut) override; + virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut) override; void setAlphaVar(const QString& alphaVar) { _alphaVar = alphaVar; } void setDesiredSpeedVar(const QString& desiredSpeedVar) { _desiredSpeedVar = desiredSpeedVar; } @@ -48,12 +48,12 @@ protected: // for AnimDebugDraw rendering virtual const AnimPoseVec& getPosesInternal() const override; - void evaluateAndBlendChildren(const AnimVariantMap& animVars, const AnimContext& context, Triggers& triggersOut, float alpha, + void evaluateAndBlendChildren(const AnimVariantMap& animVars, const AnimContext& context, AnimVariantMap& triggersOut, float alpha, size_t prevPoseIndex, size_t nextPoseIndex, float prevDeltaTime, float nextDeltaTime); void setFrameAndPhase(float dt, float alpha, int prevPoseIndex, int nextPoseIndex, - float* prevDeltaTimeOut, float* nextDeltaTimeOut, Triggers& triggersOut); + float* prevDeltaTimeOut, float* nextDeltaTimeOut, AnimVariantMap& triggersOut); virtual void setCurrentFrameInternal(float frame) override; diff --git a/libraries/animation/src/AnimChain.h b/libraries/animation/src/AnimChain.h new file mode 100644 index 0000000000..2385e0c16a --- /dev/null +++ b/libraries/animation/src/AnimChain.h @@ -0,0 +1,159 @@ +// +// AnimChain.h +// +// Created by Anthony J. Thibault on 7/16/2018. +// Copyright (c) 2018 High Fidelity, Inc. All rights reserved. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_AnimChain +#define hifi_AnimChain + +#include +#include +#include + +#include + +template +class AnimChainT { + +public: + AnimChainT() {} + + AnimChainT(const AnimChainT& orig) { + _top = orig._top; + for (int i = 0; i < _top; i++) { + _chain[i] = orig._chain[i]; + } + } + + AnimChainT& operator=(const AnimChainT& orig) { + _top = orig._top; + for (int i = 0; i < _top; i++) { + _chain[i] = orig._chain[i]; + } + return *this; + } + + bool buildFromRelativePoses(const AnimSkeleton::ConstPointer& skeleton, const AnimPoseVec& relativePoses, int tipIndex) { + _top = 0; + // iterate through the skeleton parents, from the tip to the base, copying over relativePoses into the chain. + for (int jointIndex = tipIndex; jointIndex != -1; jointIndex = skeleton->getParentIndex(jointIndex)) { + if (_top >= N) { + assert(_top < N); + // stack overflow + return false; + } + _chain[_top].relativePose = relativePoses[jointIndex]; + _chain[_top].jointIndex = jointIndex; + _chain[_top].dirty = true; + _top++; + } + + buildDirtyAbsolutePoses(); + + return true; + } + + const AnimPose& getAbsolutePoseFromJointIndex(int jointIndex) const { + for (int i = 0; i < _top; i++) { + if (_chain[i].jointIndex == jointIndex) { + return _chain[i].absolutePose; + } + } + return AnimPose::identity; + } + + bool setRelativePoseAtJointIndex(int jointIndex, const AnimPose& relativePose) { + bool foundIndex = false; + for (int i = _top - 1; i >= 0; i--) { + if (_chain[i].jointIndex == jointIndex) { + _chain[i].relativePose = relativePose; + foundIndex = true; + } + // all child absolute poses are now dirty + if (foundIndex) { + _chain[i].dirty = true; + } + } + return foundIndex; + } + + void buildDirtyAbsolutePoses() { + // the relative and absolute pose is the same for the base of the chain. + _chain[_top - 1].absolutePose = _chain[_top - 1].relativePose; + _chain[_top - 1].dirty = false; + + // iterate chain from base to tip, concatinating the relative poses to build the absolute poses. + for (int i = _top - 1; i > 0; i--) { + AnimChainElem& parent = _chain[i]; + AnimChainElem& child = _chain[i - 1]; + + if (child.dirty) { + child.absolutePose = parent.absolutePose * child.relativePose; + child.dirty = false; + } + } + } + + void blend(const AnimChainT& srcChain, float alpha) { + // make sure chains have same lengths + assert(srcChain._top == _top); + if (srcChain._top != _top) { + return; + } + + // only blend the relative poses + for (int i = 0; i < _top; i++) { + _chain[i].relativePose.blend(srcChain._chain[i].relativePose, alpha); + _chain[i].dirty = true; + } + } + + int size() const { + return _top; + } + + void outputRelativePoses(AnimPoseVec& relativePoses) { + for (int i = 0; i < _top; i++) { + relativePoses[_chain[i].jointIndex] = _chain[i].relativePose; + } + } + + void debugDraw(const glm::mat4& geomToWorldMat, const glm::vec4& color) const { + for (int i = 1; i < _top; i++) { + glm::vec3 start = transformPoint(geomToWorldMat, _chain[i - 1].absolutePose.trans()); + glm::vec3 end = transformPoint(geomToWorldMat, _chain[i].absolutePose.trans()); + DebugDraw::getInstance().drawRay(start, end, color); + } + } + + void dump() const { + for (int i = 0; i < _top; i++) { + qWarning() << "AJT: AnimPoseElem[" << i << "]"; + qWarning() << "AJT: relPose =" << _chain[i].relativePose; + qWarning() << "AJT: absPose =" << _chain[i].absolutePose; + qWarning() << "AJT: jointIndex =" << _chain[i].jointIndex; + qWarning() << "AJT: dirty =" << _chain[i].dirty; + } + } + +protected: + + struct AnimChainElem { + AnimPose relativePose; + AnimPose absolutePose; + int jointIndex { -1 }; + bool dirty { true }; + }; + + AnimChainElem _chain[N]; + int _top { 0 }; +}; + +using AnimChain = AnimChainT<10>; + +#endif diff --git a/libraries/animation/src/AnimClip.cpp b/libraries/animation/src/AnimClip.cpp index 7d358e85cc..f9195a608b 100644 --- a/libraries/animation/src/AnimClip.cpp +++ b/libraries/animation/src/AnimClip.cpp @@ -30,7 +30,7 @@ AnimClip::~AnimClip() { } -const AnimPoseVec& AnimClip::evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, Triggers& triggersOut) { +const AnimPoseVec& AnimClip::evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut) { // lookup parameters from animVars, using current instance variables as defaults. _startFrame = animVars.lookup(_startFrameVar, _startFrame); @@ -77,6 +77,8 @@ const AnimPoseVec& AnimClip::evaluate(const AnimVariantMap& animVars, const Anim ::blend(_poses.size(), &prevFrame[0], &nextFrame[0], alpha, &_poses[0]); } + processOutputJoints(triggersOut); + return _poses; } @@ -89,7 +91,7 @@ void AnimClip::loadURL(const QString& url) { void AnimClip::setCurrentFrameInternal(float frame) { // because dt is 0, we should not encounter any triggers const float dt = 0.0f; - Triggers triggers; + AnimVariantMap triggers; _frame = ::accumulateTime(_startFrame, _endFrame, _timeScale, frame + _startFrame, dt, _loopFlag, _id, triggers); } diff --git a/libraries/animation/src/AnimClip.h b/libraries/animation/src/AnimClip.h index 717972ca26..eba361fd4c 100644 --- a/libraries/animation/src/AnimClip.h +++ b/libraries/animation/src/AnimClip.h @@ -28,7 +28,7 @@ public: AnimClip(const QString& id, const QString& url, float startFrame, float endFrame, float timeScale, bool loopFlag, bool mirrorFlag); virtual ~AnimClip() override; - virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, Triggers& triggersOut) override; + virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut) override; void setStartFrameVar(const QString& startFrameVar) { _startFrameVar = startFrameVar; } void setEndFrameVar(const QString& endFrameVar) { _endFrameVar = endFrameVar; } diff --git a/libraries/animation/src/AnimDefaultPose.cpp b/libraries/animation/src/AnimDefaultPose.cpp index 70bcbe7c21..3ed2ff6cca 100644 --- a/libraries/animation/src/AnimDefaultPose.cpp +++ b/libraries/animation/src/AnimDefaultPose.cpp @@ -20,12 +20,15 @@ AnimDefaultPose::~AnimDefaultPose() { } -const AnimPoseVec& AnimDefaultPose::evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, Triggers& triggersOut) { +const AnimPoseVec& AnimDefaultPose::evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut) { if (_skeleton) { _poses = _skeleton->getRelativeDefaultPoses(); } else { _poses.clear(); } + + processOutputJoints(triggersOut); + return _poses; } diff --git a/libraries/animation/src/AnimDefaultPose.h b/libraries/animation/src/AnimDefaultPose.h index eefefac7af..13143f8d92 100644 --- a/libraries/animation/src/AnimDefaultPose.h +++ b/libraries/animation/src/AnimDefaultPose.h @@ -21,7 +21,7 @@ public: AnimDefaultPose(const QString& id); virtual ~AnimDefaultPose() override; - virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, Triggers& triggersOut) override; + virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut) override; protected: // for AnimDebugDraw rendering virtual const AnimPoseVec& getPosesInternal() const override; diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index dc004fe60d..71094cc6e1 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -259,14 +259,6 @@ void AnimInverseKinematics::solve(const AnimContext& context, const std::vector< jointChainInfoVec[i].jointInfoVec[j].rot = safeMix(_prevJointChainInfoVec[i].jointInfoVec[j].rot, jointChainInfoVec[i].jointInfoVec[j].rot, alpha); jointChainInfoVec[i].jointInfoVec[j].trans = lerp(_prevJointChainInfoVec[i].jointInfoVec[j].trans, jointChainInfoVec[i].jointInfoVec[j].trans, alpha); } - - // if joint chain was just disabled, ramp the weight toward zero. - if (_prevJointChainInfoVec[i].target.getType() != IKTarget::Type::Unknown && - jointChainInfoVec[i].target.getType() == IKTarget::Type::Unknown) { - IKTarget newTarget = _prevJointChainInfoVec[i].target; - newTarget.setWeight((1.0f - alpha) * _prevJointChainInfoVec[i].target.getWeight()); - jointChainInfoVec[i].target = newTarget; - } } } } @@ -489,29 +481,6 @@ void AnimInverseKinematics::solveTargetWithCCD(const AnimContext& context, const // reduce angle by a flexCoefficient angle *= target.getFlexCoefficient(chainDepth); deltaRotation = glm::angleAxis(angle, axis); - - // The swing will re-orient the tip but there will tend to be be a non-zero delta between the tip's - // new orientation and its target. This is the final parent-relative orientation that the tip joint have - // make to achieve its target orientation. - glm::quat tipRelativeRotation = glm::inverse(deltaRotation * tipParentOrientation) * target.getRotation(); - - // enforce tip's constraint - RotationConstraint* constraint = getConstraint(tipIndex); - if (constraint) { - bool constrained = constraint->apply(tipRelativeRotation); - if (constrained) { - // The tip's final parent-relative rotation would violate its constraint - // so we try to pre-twist this pivot to compensate. - glm::quat constrainedTipRotation = deltaRotation * tipParentOrientation * tipRelativeRotation; - glm::quat missingRotation = target.getRotation() * glm::inverse(constrainedTipRotation); - glm::quat swingPart; - glm::quat twistPart; - glm::vec3 axis = glm::normalize(deltaRotation * leverArm); - swingTwistDecomposition(missingRotation, axis, swingPart, twistPart); - const float LIMIT_LEAK_FRACTION = 0.1f; - deltaRotation = safeLerp(glm::quat(), twistPart, LIMIT_LEAK_FRACTION); - } - } } } } else if (targetType == IKTarget::Type::HmdHead) { @@ -874,14 +843,14 @@ void AnimInverseKinematics::solveTargetWithSpline(const AnimContext& context, co } //virtual -const AnimPoseVec& AnimInverseKinematics::evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimNode::Triggers& triggersOut) { +const AnimPoseVec& AnimInverseKinematics::evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut) { // don't call this function, call overlay() instead assert(false); return _relativePoses; } //virtual -const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars, const AnimContext& context, float dt, Triggers& triggersOut, const AnimPoseVec& underPoses) { +const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut, const AnimPoseVec& underPoses) { #ifdef Q_OS_ANDROID // disable IK on android return underPoses; @@ -961,6 +930,7 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars PROFILE_RANGE_EX(simulation_animation, "ik/shiftHips", 0xffff00ff, 0); if (_hipsTargetIndex >= 0) { + assert(_hipsTargetIndex < (int)targets.size()); // slam the hips to match the _hipsTarget @@ -1045,6 +1015,7 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars PROFILE_RANGE_EX(simulation_animation, "ik/ccd", 0xffff00ff, 0); setSecondaryTargets(context); + preconditionRelativePosesToAvoidLimbLock(context, targets); solve(context, targets, dt, jointChainInfoVec); @@ -1056,6 +1027,8 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars } } + processOutputJoints(triggersOut); + return _relativePoses; } @@ -1259,48 +1232,7 @@ void AnimInverseKinematics::initConstraints() { constraint = static_cast(stConstraint); } else if (0 == baseName.compare("Hand", Qt::CaseSensitive)) { - SwingTwistConstraint* stConstraint = new SwingTwistConstraint(); - stConstraint->setReferenceRotation(_defaultRelativePoses[i].rot()); - stConstraint->setTwistLimits(0.0f, 0.0f); // max == min, disables twist limits - - /* KEEP THIS CODE for future experimentation -- twist limits for hands - const float MAX_HAND_TWIST = 3.0f * PI / 5.0f; - const float MIN_HAND_TWIST = -PI / 2.0f; - if (isLeft) { - stConstraint->setTwistLimits(-MAX_HAND_TWIST, -MIN_HAND_TWIST); - } else { - stConstraint->setTwistLimits(MIN_HAND_TWIST, MAX_HAND_TWIST); - } - */ - - /* KEEP THIS CODE for future experimentation -- non-symmetrical swing limits for wrist - * a more complicated wrist with asymmetric cone - // these directions are approximate swing limits in parent-frame - // NOTE: they don't need to be normalized - std::vector swungDirections; - swungDirections.push_back(glm::vec3(1.0f, 1.0f, 0.0f)); - swungDirections.push_back(glm::vec3(0.75f, 1.0f, -1.0f)); - swungDirections.push_back(glm::vec3(-0.75f, 1.0f, -1.0f)); - swungDirections.push_back(glm::vec3(-1.0f, 1.0f, 0.0f)); - swungDirections.push_back(glm::vec3(-0.75f, 1.0f, 1.0f)); - swungDirections.push_back(glm::vec3(0.75f, 1.0f, 1.0f)); - - // rotate directions into joint-frame - glm::quat invRelativeRotation = glm::inverse(_defaultRelativePoses[i].rot); - int numDirections = (int)swungDirections.size(); - for (int j = 0; j < numDirections; ++j) { - swungDirections[j] = invRelativeRotation * swungDirections[j]; - } - stConstraint->setSwingLimits(swungDirections); - */ - - // simple cone - std::vector minDots; - const float MAX_HAND_SWING = PI / 2.0f; - minDots.push_back(cosf(MAX_HAND_SWING)); - stConstraint->setSwingLimits(minDots); - - constraint = static_cast(stConstraint); + // hand/wrist constraints have been disabled. } else if (baseName.startsWith("Shoulder", Qt::CaseSensitive)) { SwingTwistConstraint* stConstraint = new SwingTwistConstraint(); stConstraint->setReferenceRotation(_defaultRelativePoses[i].rot()); @@ -1750,7 +1682,7 @@ void AnimInverseKinematics::preconditionRelativePosesToAvoidLimbLock(const AnimC const float MIN_AXIS_LENGTH = 1.0e-4f; for (auto& target : targets) { - if (target.getIndex() != -1) { + if (target.getIndex() != -1 && target.getType() == IKTarget::Type::RotationAndPosition) { for (int i = 0; i < NUM_LIMBS; i++) { if (limbs[i].first == target.getIndex()) { int tipIndex = limbs[i].first; @@ -1843,6 +1775,10 @@ void AnimInverseKinematics::initRelativePosesFromSolutionSource(SolutionSource s default: case SolutionSource::RelaxToUnderPoses: blendToPoses(underPoses, underPoses, RELAX_BLEND_FACTOR); + // special case for hips: don't dampen hip motion from underposes + if (_hipsIndex >= 0 && _hipsIndex < (int)_relativePoses.size()) { + _relativePoses[_hipsIndex] = underPoses[_hipsIndex]; + } break; case SolutionSource::RelaxToLimitCenterPoses: blendToPoses(_limitCenterPoses, underPoses, RELAX_BLEND_FACTOR); diff --git a/libraries/animation/src/AnimInverseKinematics.h b/libraries/animation/src/AnimInverseKinematics.h index ee1f9f43ad..0136b7d125 100644 --- a/libraries/animation/src/AnimInverseKinematics.h +++ b/libraries/animation/src/AnimInverseKinematics.h @@ -52,8 +52,8 @@ public: const QString& typeVar, const QString& weightVar, float weight, const std::vector& flexCoefficients, const QString& poleVectorEnabledVar, const QString& poleReferenceVectorVar, const QString& poleVectorVar); - virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimNode::Triggers& triggersOut) override; - virtual const AnimPoseVec& overlay(const AnimVariantMap& animVars, const AnimContext& context, float dt, Triggers& triggersOut, const AnimPoseVec& underPoses) override; + virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut) override; + virtual const AnimPoseVec& overlay(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut, const AnimPoseVec& underPoses) override; void clearIKJointLimitHistory(); diff --git a/libraries/animation/src/AnimManipulator.cpp b/libraries/animation/src/AnimManipulator.cpp index 46b3cf1c28..1146cbb19a 100644 --- a/libraries/animation/src/AnimManipulator.cpp +++ b/libraries/animation/src/AnimManipulator.cpp @@ -32,11 +32,11 @@ AnimManipulator::~AnimManipulator() { } -const AnimPoseVec& AnimManipulator::evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, Triggers& triggersOut) { +const AnimPoseVec& AnimManipulator::evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut) { return overlay(animVars, context, dt, triggersOut, _skeleton->getRelativeDefaultPoses()); } -const AnimPoseVec& AnimManipulator::overlay(const AnimVariantMap& animVars, const AnimContext& context, float dt, Triggers& triggersOut, const AnimPoseVec& underPoses) { +const AnimPoseVec& AnimManipulator::overlay(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut, const AnimPoseVec& underPoses) { _alpha = animVars.lookup(_alphaVar, _alpha); _poses = underPoses; @@ -74,6 +74,8 @@ const AnimPoseVec& AnimManipulator::overlay(const AnimVariantMap& animVars, cons } } + processOutputJoints(triggersOut); + return _poses; } diff --git a/libraries/animation/src/AnimManipulator.h b/libraries/animation/src/AnimManipulator.h index 1134f75da9..96af08a50a 100644 --- a/libraries/animation/src/AnimManipulator.h +++ b/libraries/animation/src/AnimManipulator.h @@ -22,8 +22,8 @@ public: AnimManipulator(const QString& id, float alpha); virtual ~AnimManipulator() override; - virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, Triggers& triggersOut) override; - virtual const AnimPoseVec& overlay(const AnimVariantMap& animVars, const AnimContext& context, float dt, Triggers& triggersOut, const AnimPoseVec& underPoses) override; + virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut) override; + virtual const AnimPoseVec& overlay(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut, const AnimPoseVec& underPoses) override; void setAlphaVar(const QString& alphaVar) { _alphaVar = alphaVar; } diff --git a/libraries/animation/src/AnimNode.cpp b/libraries/animation/src/AnimNode.cpp index ba8e095109..a8e76617ae 100644 --- a/libraries/animation/src/AnimNode.cpp +++ b/libraries/animation/src/AnimNode.cpp @@ -12,6 +12,10 @@ #include +std::map AnimNode::_animStack = { + {"none", 0.0f} +}; + AnimNode::Pointer AnimNode::getParent() { return _parent.lock(); } @@ -59,3 +63,19 @@ void AnimNode::setCurrentFrame(float frame) { child->setCurrentFrameInternal(frame); } } + +void AnimNode::processOutputJoints(AnimVariantMap& triggersOut) const { + if (!_skeleton) { + return; + } + + for (auto&& jointName : _outputJointNames) { + // TODO: cache the jointIndices + int jointIndex = _skeleton->nameToJointIndex(jointName); + if (jointIndex >= 0) { + AnimPose pose = _skeleton->getAbsolutePose(jointIndex, getPosesInternal()); + triggersOut.set(_id + jointName + "Rotation", pose.rot()); + triggersOut.set(_id + jointName + "Position", pose.trans()); + } + } +} diff --git a/libraries/animation/src/AnimNode.h b/libraries/animation/src/AnimNode.h index 6d9d35b19b..1f14889ce4 100644 --- a/libraries/animation/src/AnimNode.h +++ b/libraries/animation/src/AnimNode.h @@ -45,11 +45,12 @@ public: Manipulator, InverseKinematics, DefaultPose, + TwoBoneIK, + PoleVectorConstraint, NumTypes }; using Pointer = std::shared_ptr; using ConstPointer = std::shared_ptr; - using Triggers = std::vector; friend class AnimDebugDraw; friend void buildChildMap(std::map& map, Pointer node); @@ -61,6 +62,8 @@ public: const QString& getID() const { return _id; } Type getType() const { return _type; } + void addOutputJoint(const QString& outputJointName) { _outputJointNames.push_back(outputJointName); } + // hierarchy accessors Pointer getParent(); void addChild(Pointer child); @@ -74,13 +77,14 @@ public: AnimSkeleton::ConstPointer getSkeleton() const { return _skeleton; } - virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, Triggers& triggersOut) = 0; - virtual const AnimPoseVec& overlay(const AnimVariantMap& animVars, const AnimContext& context, float dt, Triggers& triggersOut, + virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut) = 0; + virtual const AnimPoseVec& overlay(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut, const AnimPoseVec& underPoses) { return evaluate(animVars, context, dt, triggersOut); } void setCurrentFrame(float frame); + const std::map getAnimStack() { return _animStack; } template bool traverse(F func) { @@ -114,11 +118,17 @@ protected: // for AnimDebugDraw rendering virtual const AnimPoseVec& getPosesInternal() const = 0; + void processOutputJoints(AnimVariantMap& triggersOut) const; + Type _type; QString _id; std::vector _children; AnimSkeleton::ConstPointer _skeleton; std::weak_ptr _parent; + std::vector _outputJointNames; + + // global available to Stats.h + static std::map _animStack; // no copies AnimNode(const AnimNode&) = delete; diff --git a/libraries/animation/src/AnimNodeLoader.cpp b/libraries/animation/src/AnimNodeLoader.cpp index 4169ff61a7..34305c3ac6 100644 --- a/libraries/animation/src/AnimNodeLoader.cpp +++ b/libraries/animation/src/AnimNodeLoader.cpp @@ -25,6 +25,8 @@ #include "AnimManipulator.h" #include "AnimInverseKinematics.h" #include "AnimDefaultPose.h" +#include "AnimTwoBoneIK.h" +#include "AnimPoleVectorConstraint.h" using NodeLoaderFunc = AnimNode::Pointer (*)(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl); using NodeProcessFunc = bool (*)(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl); @@ -38,6 +40,8 @@ static AnimNode::Pointer loadStateMachineNode(const QJsonObject& jsonObj, const static AnimNode::Pointer loadManipulatorNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl); static AnimNode::Pointer loadInverseKinematicsNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl); static AnimNode::Pointer loadDefaultPoseNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl); +static AnimNode::Pointer loadTwoBoneIKNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl); +static AnimNode::Pointer loadPoleVectorConstraintNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl); static const float ANIM_GRAPH_LOAD_PRIORITY = 10.0f; @@ -56,6 +60,8 @@ static const char* animNodeTypeToString(AnimNode::Type type) { case AnimNode::Type::Manipulator: return "manipulator"; case AnimNode::Type::InverseKinematics: return "inverseKinematics"; case AnimNode::Type::DefaultPose: return "defaultPose"; + case AnimNode::Type::TwoBoneIK: return "twoBoneIK"; + case AnimNode::Type::PoleVectorConstraint: return "poleVectorConstraint"; case AnimNode::Type::NumTypes: return nullptr; }; return nullptr; @@ -116,6 +122,8 @@ static NodeLoaderFunc animNodeTypeToLoaderFunc(AnimNode::Type type) { case AnimNode::Type::Manipulator: return loadManipulatorNode; case AnimNode::Type::InverseKinematics: return loadInverseKinematicsNode; case AnimNode::Type::DefaultPose: return loadDefaultPoseNode; + case AnimNode::Type::TwoBoneIK: return loadTwoBoneIKNode; + case AnimNode::Type::PoleVectorConstraint: return loadPoleVectorConstraintNode; case AnimNode::Type::NumTypes: return nullptr; }; return nullptr; @@ -131,6 +139,8 @@ static NodeProcessFunc animNodeTypeToProcessFunc(AnimNode::Type type) { case AnimNode::Type::Manipulator: return processDoNothing; case AnimNode::Type::InverseKinematics: return processDoNothing; case AnimNode::Type::DefaultPose: return processDoNothing; + case AnimNode::Type::TwoBoneIK: return processDoNothing; + case AnimNode::Type::PoleVectorConstraint: return processDoNothing; case AnimNode::Type::NumTypes: return nullptr; }; return nullptr; @@ -189,6 +199,25 @@ static NodeProcessFunc animNodeTypeToProcessFunc(AnimNode::Type type) { } \ do {} while (0) +#define READ_VEC3(NAME, JSON_OBJ, ID, URL, ERROR_RETURN) \ + auto NAME##_VAL = JSON_OBJ.value(#NAME); \ + if (!NAME##_VAL.isArray()) { \ + qCCritical(animation) << "AnimNodeLoader, error reading vector" \ + << #NAME << "id =" << ID \ + << ", url =" << URL.toDisplayString(); \ + return ERROR_RETURN; \ + } \ + QJsonArray NAME##_ARRAY = NAME##_VAL.toArray(); \ + if (NAME##_ARRAY.size() != 3) { \ + qCCritical(animation) << "AnimNodeLoader, vector size != 3" \ + << #NAME << "id =" << ID \ + << ", url =" << URL.toDisplayString(); \ + return ERROR_RETURN; \ + } \ + glm::vec3 NAME((float)NAME##_ARRAY.at(0).toDouble(), \ + (float)NAME##_ARRAY.at(1).toDouble(), \ + (float)NAME##_ARRAY.at(2).toDouble()) + static AnimNode::Pointer loadNode(const QJsonObject& jsonObj, const QUrl& jsonUrl) { auto idVal = jsonObj.value("id"); if (!idVal.isString()) { @@ -216,6 +245,16 @@ static AnimNode::Pointer loadNode(const QJsonObject& jsonObj, const QUrl& jsonUr } auto dataObj = dataValue.toObject(); + std::vector outputJoints; + + auto outputJoints_VAL = dataObj.value("outputJoints"); + if (outputJoints_VAL.isArray()) { + QJsonArray outputJoints_ARRAY = outputJoints_VAL.toArray(); + for (int i = 0; i < outputJoints_ARRAY.size(); i++) { + outputJoints.push_back(outputJoints_ARRAY.at(i).toString()); + } + } + assert((int)type >= 0 && type < AnimNode::Type::NumTypes); auto node = (animNodeTypeToLoaderFunc(type))(dataObj, id, jsonUrl); if (!node) { @@ -242,6 +281,9 @@ static AnimNode::Pointer loadNode(const QJsonObject& jsonObj, const QUrl& jsonUr } if ((animNodeTypeToProcessFunc(type))(node, dataObj, id, jsonUrl)) { + for (auto&& outputJoint : outputJoints) { + node->addOutputJoint(outputJoint); + } return node; } else { return nullptr; @@ -531,6 +573,41 @@ static AnimNode::Pointer loadDefaultPoseNode(const QJsonObject& jsonObj, const Q return node; } +static AnimNode::Pointer loadTwoBoneIKNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) { + READ_FLOAT(alpha, jsonObj, id, jsonUrl, nullptr); + READ_BOOL(enabled, jsonObj, id, jsonUrl, nullptr); + READ_FLOAT(interpDuration, jsonObj, id, jsonUrl, nullptr); + READ_STRING(baseJointName, jsonObj, id, jsonUrl, nullptr); + READ_STRING(midJointName, jsonObj, id, jsonUrl, nullptr); + READ_STRING(tipJointName, jsonObj, id, jsonUrl, nullptr); + READ_VEC3(midHingeAxis, jsonObj, id, jsonUrl, nullptr); + READ_STRING(alphaVar, jsonObj, id, jsonUrl, nullptr); + READ_STRING(enabledVar, jsonObj, id, jsonUrl, nullptr); + READ_STRING(endEffectorRotationVarVar, jsonObj, id, jsonUrl, nullptr); + READ_STRING(endEffectorPositionVarVar, jsonObj, id, jsonUrl, nullptr); + + auto node = std::make_shared(id, alpha, enabled, interpDuration, + baseJointName, midJointName, tipJointName, midHingeAxis, + alphaVar, enabledVar, + endEffectorRotationVarVar, endEffectorPositionVarVar); + return node; +} + +static AnimNode::Pointer loadPoleVectorConstraintNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) { + READ_VEC3(referenceVector, jsonObj, id, jsonUrl, nullptr); + READ_BOOL(enabled, jsonObj, id, jsonUrl, nullptr); + READ_STRING(baseJointName, jsonObj, id, jsonUrl, nullptr); + READ_STRING(midJointName, jsonObj, id, jsonUrl, nullptr); + READ_STRING(tipJointName, jsonObj, id, jsonUrl, nullptr); + READ_STRING(enabledVar, jsonObj, id, jsonUrl, nullptr); + READ_STRING(poleVectorVar, jsonObj, id, jsonUrl, nullptr); + + auto node = std::make_shared(id, enabled, referenceVector, + baseJointName, midJointName, tipJointName, + enabledVar, poleVectorVar); + return node; +} + void buildChildMap(std::map& map, AnimNode::Pointer node) { for (int i = 0; i < (int)node->getChildCount(); ++i) { map.insert(std::pair(node->getChild(i)->getID(), i)); @@ -584,7 +661,7 @@ bool processStateMachineNode(AnimNode::Pointer node, const QJsonObject& jsonObj, return false; } - AnimStateMachine::InterpType interpTypeEnum = AnimStateMachine::InterpType::SnapshotBoth; // default value + AnimStateMachine::InterpType interpTypeEnum = AnimStateMachine::InterpType::SnapshotPrev; // default value if (!interpType.isEmpty()) { interpTypeEnum = stringToInterpType(interpType); if (interpTypeEnum == AnimStateMachine::InterpType::NumTypes) { @@ -682,7 +759,8 @@ AnimNode::Pointer AnimNodeLoader::load(const QByteArray& contents, const QUrl& j QString version = versionVal.toString(); // check version - if (version != "1.0") { + // AJT: TODO version check + if (version != "1.0" && version != "1.1") { qCCritical(animation) << "AnimNodeLoader, bad version number" << version << "expected \"1.0\", url =" << jsonUrl.toDisplayString(); return nullptr; } diff --git a/libraries/animation/src/AnimOverlay.cpp b/libraries/animation/src/AnimOverlay.cpp index 10594af20a..910f9b37c0 100644 --- a/libraries/animation/src/AnimOverlay.cpp +++ b/libraries/animation/src/AnimOverlay.cpp @@ -41,7 +41,7 @@ void AnimOverlay::buildBoneSet(BoneSet boneSet) { } } -const AnimPoseVec& AnimOverlay::evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, Triggers& triggersOut) { +const AnimPoseVec& AnimOverlay::evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut) { // lookup parameters from animVars, using current instance variables as defaults. // NOTE: switching bonesets can be an expensive operation, let's try to avoid it. @@ -66,6 +66,9 @@ const AnimPoseVec& AnimOverlay::evaluate(const AnimVariantMap& animVars, const A } } } + + processOutputJoints(triggersOut); + return _poses; } diff --git a/libraries/animation/src/AnimOverlay.h b/libraries/animation/src/AnimOverlay.h index 8b6e1529fc..70929bd4e4 100644 --- a/libraries/animation/src/AnimOverlay.h +++ b/libraries/animation/src/AnimOverlay.h @@ -45,7 +45,7 @@ public: AnimOverlay(const QString& id, BoneSet boneSet, float alpha); virtual ~AnimOverlay() override; - virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, Triggers& triggersOut) override; + virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut) override; void setBoneSetVar(const QString& boneSetVar) { _boneSetVar = boneSetVar; } void setAlphaVar(const QString& alphaVar) { _alphaVar = alphaVar; } diff --git a/libraries/animation/src/AnimPoleVectorConstraint.cpp b/libraries/animation/src/AnimPoleVectorConstraint.cpp new file mode 100644 index 0000000000..f017fe2348 --- /dev/null +++ b/libraries/animation/src/AnimPoleVectorConstraint.cpp @@ -0,0 +1,244 @@ +// +// AnimPoleVectorConstraint.cpp +// +// Created by Anthony J. Thibault on 5/12/18. +// Copyright (c) 2018 High Fidelity, Inc. All rights reserved. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "AnimPoleVectorConstraint.h" +#include "AnimationLogging.h" +#include "AnimUtil.h" +#include "GLMHelpers.h" + +const float FRAMES_PER_SECOND = 30.0f; +const float INTERP_DURATION = 6.0f; + +AnimPoleVectorConstraint::AnimPoleVectorConstraint(const QString& id, bool enabled, glm::vec3 referenceVector, + const QString& baseJointName, const QString& midJointName, const QString& tipJointName, + const QString& enabledVar, const QString& poleVectorVar) : + AnimNode(AnimNode::Type::PoleVectorConstraint, id), + _enabled(enabled), + _referenceVector(referenceVector), + _baseJointName(baseJointName), + _midJointName(midJointName), + _tipJointName(tipJointName), + _enabledVar(enabledVar), + _poleVectorVar(poleVectorVar) { + +} + +AnimPoleVectorConstraint::~AnimPoleVectorConstraint() { + +} + +const AnimPoseVec& AnimPoleVectorConstraint::evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut) { + + assert(_children.size() == 1); + if (_children.size() != 1) { + return _poses; + } + + // evalute underPoses + AnimPoseVec underPoses = _children[0]->evaluate(animVars, context, dt, triggersOut); + + // if we don't have a skeleton, or jointName lookup failed. + if (!_skeleton || _baseJointIndex == -1 || _midJointIndex == -1 || _tipJointIndex == -1 || underPoses.size() == 0) { + // pass underPoses through unmodified. + _poses = underPoses; + return _poses; + } + + // guard against size changes + if (underPoses.size() != _poses.size()) { + _poses = underPoses; + } + + // Look up poleVector from animVars, make sure to convert into geom space. + glm::vec3 poleVector = animVars.lookupRigToGeometryVector(_poleVectorVar, Vectors::UNIT_Z); + + // determine if we should interpolate + bool enabled = animVars.lookup(_enabledVar, _enabled); + + const float MIN_LENGTH = 1.0e-4f; + if (glm::length(poleVector) < MIN_LENGTH) { + enabled = false; + } + + if (enabled != _enabled) { + AnimChain poseChain; + poseChain.buildFromRelativePoses(_skeleton, _poses, _tipJointIndex); + if (enabled) { + beginInterp(InterpType::SnapshotToSolve, poseChain); + } else { + beginInterp(InterpType::SnapshotToUnderPoses, poseChain); + } + } + _enabled = enabled; + + // don't build chains or do IK if we are disbled & not interping. + if (_interpType == InterpType::None && !enabled) { + _poses = underPoses; + return _poses; + } + + // compute chain + AnimChain underChain; + underChain.buildFromRelativePoses(_skeleton, underPoses, _tipJointIndex); + AnimChain ikChain = underChain; + + AnimPose baseParentPose = ikChain.getAbsolutePoseFromJointIndex(_baseParentJointIndex); + AnimPose basePose = ikChain.getAbsolutePoseFromJointIndex(_baseJointIndex); + AnimPose midPose = ikChain.getAbsolutePoseFromJointIndex(_midJointIndex); + AnimPose tipPose = ikChain.getAbsolutePoseFromJointIndex(_tipJointIndex); + + // Look up refVector from animVars, make sure to convert into geom space. + glm::vec3 refVector = midPose.xformVectorFast(_referenceVector); + float refVectorLength = glm::length(refVector); + + glm::vec3 axis = basePose.trans() - tipPose.trans(); + float axisLength = glm::length(axis); + glm::vec3 unitAxis = axis / axisLength; + + glm::vec3 sideVector = glm::cross(unitAxis, refVector); + float sideVectorLength = glm::length(sideVector); + + // project refVector onto axis plane + glm::vec3 refVectorProj = refVector - glm::dot(refVector, unitAxis) * unitAxis; + float refVectorProjLength = glm::length(refVectorProj); + + // project poleVector on plane formed by axis. + glm::vec3 poleVectorProj = poleVector - glm::dot(poleVector, unitAxis) * unitAxis; + float poleVectorProjLength = glm::length(poleVectorProj); + + // double check for zero length vectors or vectors parallel to rotaiton axis. + if (axisLength > MIN_LENGTH && refVectorLength > MIN_LENGTH && sideVectorLength > MIN_LENGTH && + refVectorProjLength > MIN_LENGTH && poleVectorProjLength > MIN_LENGTH) { + + float dot = glm::clamp(glm::dot(refVectorProj / refVectorProjLength, poleVectorProj / poleVectorProjLength), 0.0f, 1.0f); + float sideDot = glm::dot(poleVector, sideVector); + float theta = copysignf(1.0f, sideDot) * acosf(dot); + + glm::quat deltaRot = glm::angleAxis(theta, unitAxis); + + // transform result back into parent relative frame. + glm::quat relBaseRot = glm::inverse(baseParentPose.rot()) * deltaRot * basePose.rot(); + ikChain.setRelativePoseAtJointIndex(_baseJointIndex, AnimPose(relBaseRot, underPoses[_baseJointIndex].trans())); + + glm::quat relTipRot = glm::inverse(midPose.rot()) * glm::inverse(deltaRot) * tipPose.rot(); + ikChain.setRelativePoseAtJointIndex(_tipJointIndex, AnimPose(relTipRot, underPoses[_tipJointIndex].trans())); + } + + // start off by initializing output poses with the underPoses + _poses = underPoses; + + // apply smooth interpolation + if (_interpType != InterpType::None) { + _interpAlpha += _interpAlphaVel * dt; + + if (_interpAlpha < 1.0f) { + AnimChain interpChain; + if (_interpType == InterpType::SnapshotToUnderPoses) { + interpChain = underChain; + interpChain.blend(_snapshotChain, _interpAlpha); + } else if (_interpType == InterpType::SnapshotToSolve) { + interpChain = ikChain; + interpChain.blend(_snapshotChain, _interpAlpha); + } + // copy interpChain into _poses + interpChain.outputRelativePoses(_poses); + } else { + // interpolation complete + _interpType = InterpType::None; + } + } + + if (_interpType == InterpType::None) { + if (enabled) { + // copy chain into _poses + ikChain.outputRelativePoses(_poses); + } else { + // copy under chain into _poses + underChain.outputRelativePoses(_poses); + } + } + + if (context.getEnableDebugDrawIKChains()) { + if (_interpType == InterpType::None && enabled) { + const vec4 BLUE(0.0f, 0.0f, 1.0f, 1.0f); + ikChain.debugDraw(context.getRigToWorldMatrix() * context.getGeometryToRigMatrix(), BLUE); + } + } + + if (context.getEnableDebugDrawIKChains()) { + if (enabled) { + const glm::vec4 RED(1.0f, 0.0f, 0.0f, 1.0f); + const glm::vec4 GREEN(0.0f, 1.0f, 0.0f, 1.0f); + const glm::vec4 CYAN(0.0f, 1.0f, 1.0f, 1.0f); + const glm::vec4 YELLOW(1.0f, 0.0f, 1.0f, 1.0f); + const float VECTOR_LENGTH = 0.5f; + + glm::mat4 geomToWorld = context.getRigToWorldMatrix() * context.getGeometryToRigMatrix(); + + // draw the pole + glm::vec3 start = transformPoint(geomToWorld, basePose.trans()); + glm::vec3 end = transformPoint(geomToWorld, tipPose.trans()); + DebugDraw::getInstance().drawRay(start, end, CYAN); + + // draw the poleVector + glm::vec3 midPoint = 0.5f * (start + end); + glm::vec3 poleVectorEnd = midPoint + VECTOR_LENGTH * glm::normalize(transformVectorFast(geomToWorld, poleVector)); + DebugDraw::getInstance().drawRay(midPoint, poleVectorEnd, GREEN); + + // draw the refVector + glm::vec3 refVectorEnd = midPoint + VECTOR_LENGTH * glm::normalize(transformVectorFast(geomToWorld, refVector)); + DebugDraw::getInstance().drawRay(midPoint, refVectorEnd, RED); + + // draw the sideVector + glm::vec3 sideVector = glm::cross(poleVector, refVector); + glm::vec3 sideVectorEnd = midPoint + VECTOR_LENGTH * glm::normalize(transformVectorFast(geomToWorld, sideVector)); + DebugDraw::getInstance().drawRay(midPoint, sideVectorEnd, YELLOW); + } + } + + processOutputJoints(triggersOut); + + return _poses; +} + +// for AnimDebugDraw rendering +const AnimPoseVec& AnimPoleVectorConstraint::getPosesInternal() const { + return _poses; +} + +void AnimPoleVectorConstraint::setSkeletonInternal(AnimSkeleton::ConstPointer skeleton) { + AnimNode::setSkeletonInternal(skeleton); + lookUpIndices(); +} + +void AnimPoleVectorConstraint::lookUpIndices() { + assert(_skeleton); + + // look up bone indices by name + std::vector indices = _skeleton->lookUpJointIndices({_baseJointName, _midJointName, _tipJointName}); + + // cache the results + _baseJointIndex = indices[0]; + _midJointIndex = indices[1]; + _tipJointIndex = indices[2]; + + if (_baseJointIndex != -1) { + _baseParentJointIndex = _skeleton->getParentIndex(_baseJointIndex); + } +} + +void AnimPoleVectorConstraint::beginInterp(InterpType interpType, const AnimChain& chain) { + // capture the current poses in a snapshot. + _snapshotChain = chain; + + _interpType = interpType; + _interpAlphaVel = FRAMES_PER_SECOND / INTERP_DURATION; + _interpAlpha = 0.0f; +} diff --git a/libraries/animation/src/AnimPoleVectorConstraint.h b/libraries/animation/src/AnimPoleVectorConstraint.h new file mode 100644 index 0000000000..44e22671c1 --- /dev/null +++ b/libraries/animation/src/AnimPoleVectorConstraint.h @@ -0,0 +1,74 @@ +// +// AnimPoleVectorConstraint.h +// +// Created by Anthony J. Thibault on 5/25/18. +// Copyright (c) 2018 High Fidelity, Inc. All rights reserved. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_AnimPoleVectorConstraint_h +#define hifi_AnimPoleVectorConstraint_h + +#include "AnimNode.h" +#include "AnimChain.h" + +// Three bone IK chain + +class AnimPoleVectorConstraint : public AnimNode { +public: + friend class AnimTests; + + AnimPoleVectorConstraint(const QString& id, bool enabled, glm::vec3 referenceVector, + const QString& baseJointName, const QString& midJointName, const QString& tipJointName, + const QString& enabledVar, const QString& poleVectorVar); + virtual ~AnimPoleVectorConstraint() override; + + virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut) override; + +protected: + + enum class InterpType { + None = 0, + SnapshotToUnderPoses, + SnapshotToSolve, + NumTypes + }; + + // for AnimDebugDraw rendering + virtual const AnimPoseVec& getPosesInternal() const override; + virtual void setSkeletonInternal(AnimSkeleton::ConstPointer skeleton) override; + + void lookUpIndices(); + void beginInterp(InterpType interpType, const AnimChain& chain); + + AnimPoseVec _poses; + + bool _enabled; + glm::vec3 _referenceVector; + + QString _baseJointName; + QString _midJointName; + QString _tipJointName; + + QString _enabledVar; + QString _poleVectorVar; + + int _baseParentJointIndex { -1 }; + int _baseJointIndex { -1 }; + int _midJointIndex { -1 }; + int _tipJointIndex { -1 }; + + InterpType _interpType { InterpType::None }; + float _interpAlphaVel { 0.0f }; + float _interpAlpha { 0.0f }; + + AnimChain _snapshotChain; + + // no copies + AnimPoleVectorConstraint(const AnimPoleVectorConstraint&) = delete; + AnimPoleVectorConstraint& operator=(const AnimPoleVectorConstraint&) = delete; +}; + +#endif // hifi_AnimPoleVectorConstraint_h diff --git a/libraries/animation/src/AnimPose.cpp b/libraries/animation/src/AnimPose.cpp index a0b8fba1da..d77514e691 100644 --- a/libraries/animation/src/AnimPose.cpp +++ b/libraries/animation/src/AnimPose.cpp @@ -12,6 +12,7 @@ #include #include #include +#include "AnimUtil.h" const AnimPose AnimPose::identity = AnimPose(glm::vec3(1.0f), glm::quat(), @@ -77,4 +78,8 @@ AnimPose::operator glm::mat4() const { glm::vec4(zAxis, 0.0f), glm::vec4(_trans, 1.0f)); } - +void AnimPose::blend(const AnimPose& srcPose, float alpha) { + _scale = lerp(srcPose._scale, _scale, alpha); + _rot = safeLerp(srcPose._rot, _rot, alpha); + _trans = lerp(srcPose._trans, _trans, alpha); +} diff --git a/libraries/animation/src/AnimPose.h b/libraries/animation/src/AnimPose.h index 2df3d1f2e4..1558a6b881 100644 --- a/libraries/animation/src/AnimPose.h +++ b/libraries/animation/src/AnimPose.h @@ -46,6 +46,8 @@ public: const glm::vec3& trans() const { return _trans; } glm::vec3& trans() { return _trans; } + void blend(const AnimPose& srcPose, float alpha); + private: friend QDebug operator<<(QDebug debug, const AnimPose& pose); glm::vec3 _scale { 1.0f }; diff --git a/libraries/animation/src/AnimSkeleton.cpp b/libraries/animation/src/AnimSkeleton.cpp index e00cad9bc7..bed9c590be 100644 --- a/libraries/animation/src/AnimSkeleton.cpp +++ b/libraries/animation/src/AnimSkeleton.cpp @@ -282,3 +282,17 @@ void AnimSkeleton::dump(const AnimPoseVec& poses) const { qCDebug(animation) << "]"; } +std::vector AnimSkeleton::lookUpJointIndices(const std::vector& jointNames) const { + std::vector result; + result.reserve(jointNames.size()); + for (auto& name : jointNames) { + int index = nameToJointIndex(name); + if (index == -1) { + qWarning(animation) << "AnimSkeleton::lookUpJointIndices(): could not find bone with named " << name; + } + result.push_back(index); + } + return result; +} + + diff --git a/libraries/animation/src/AnimSkeleton.h b/libraries/animation/src/AnimSkeleton.h index 27dbf5ea92..2ebf3f4f5d 100644 --- a/libraries/animation/src/AnimSkeleton.h +++ b/libraries/animation/src/AnimSkeleton.h @@ -61,6 +61,8 @@ public: void dump(bool verbose) const; void dump(const AnimPoseVec& poses) const; + std::vector lookUpJointIndices(const std::vector& jointNames) const; + protected: void buildSkeletonFromJoints(const std::vector& joints); diff --git a/libraries/animation/src/AnimStateMachine.cpp b/libraries/animation/src/AnimStateMachine.cpp index 4e86b92c0b..90fd425ae5 100644 --- a/libraries/animation/src/AnimStateMachine.cpp +++ b/libraries/animation/src/AnimStateMachine.cpp @@ -21,7 +21,11 @@ AnimStateMachine::~AnimStateMachine() { } -const AnimPoseVec& AnimStateMachine::evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, Triggers& triggersOut) { +const AnimPoseVec& AnimStateMachine::evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut) { + + if (_id.contains("userAnimStateMachine")) { + _animStack.clear(); + } QString desiredStateID = animVars.lookup(_currentStateVar, _currentState->getID()); if (_currentState->getID() != desiredStateID) { @@ -29,6 +33,8 @@ const AnimPoseVec& AnimStateMachine::evaluate(const AnimVariantMap& animVars, co bool foundState = false; for (auto& state : _states) { if (state->getID() == desiredStateID) { + // parenthesis means previous state, which is a snapshot. + _previousStateID = "(" + _currentState->getID() + ")"; switchState(animVars, context, state); foundState = true; break; @@ -42,6 +48,8 @@ const AnimPoseVec& AnimStateMachine::evaluate(const AnimVariantMap& animVars, co // evaluate currentState transitions auto desiredState = evaluateTransitions(animVars); if (desiredState != _currentState) { + // parenthesis means previous state, which is a snapshot. + _previousStateID = "(" + _currentState->getID() + ")"; switchState(animVars, context, desiredState); } @@ -49,8 +57,17 @@ const AnimPoseVec& AnimStateMachine::evaluate(const AnimVariantMap& animVars, co auto currentStateNode = _children[_currentState->getChildIndex()]; assert(currentStateNode); + if (!_previousStateID.contains("none")) { + _animStack[_previousStateID] = 1.0f - _alpha; + } + if (_duringInterp) { _alpha += _alphaVel * dt; + if (_alpha > 1.0f) { + _animStack[_currentState->getID()] = 1.0f; + } else { + _animStack[_currentState->getID()] = _alpha; + } if (_alpha < 1.0f) { AnimPoseVec* nextPoses = nullptr; AnimPoseVec* prevPoses = nullptr; @@ -68,19 +85,25 @@ const AnimPoseVec& AnimStateMachine::evaluate(const AnimVariantMap& animVars, co } else { assert(false); } - if (_poses.size() > 0 && nextPoses && prevPoses && nextPoses->size() > 0 && prevPoses->size() > 0) { ::blend(_poses.size(), &(prevPoses->at(0)), &(nextPoses->at(0)), _alpha, &_poses[0]); } } else { _duringInterp = false; + if (_animStack.count(_previousStateID) > 0) { + _animStack.erase(_previousStateID); + } + _previousStateID = "none"; _prevPoses.clear(); _nextPoses.clear(); } } if (!_duringInterp) { + _animStack[_currentState->getID()] = 1.0f; _poses = currentStateNode->evaluate(animVars, context, dt, triggersOut); } + processOutputJoints(triggersOut); + return _poses; } @@ -107,7 +130,7 @@ void AnimStateMachine::switchState(const AnimVariantMap& animVars, const AnimCon // because dt is 0, we should not encounter any triggers const float dt = 0.0f; - Triggers triggers; + AnimVariantMap triggers; if (_interpType == InterpType::SnapshotBoth) { // snapshot previous pose. diff --git a/libraries/animation/src/AnimStateMachine.h b/libraries/animation/src/AnimStateMachine.h index 711326a9ae..b20e5203a1 100644 --- a/libraries/animation/src/AnimStateMachine.h +++ b/libraries/animation/src/AnimStateMachine.h @@ -113,7 +113,7 @@ public: explicit AnimStateMachine(const QString& id); virtual ~AnimStateMachine() override; - virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, Triggers& triggersOut) override; + virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut) override; void setCurrentStateVar(QString& currentStateVar) { _currentStateVar = currentStateVar; } @@ -133,11 +133,12 @@ protected: // interpolation state bool _duringInterp = false; - InterpType _interpType { InterpType::SnapshotBoth }; + InterpType _interpType { InterpType::SnapshotPrev }; float _alphaVel = 0.0f; float _alpha = 0.0f; AnimPoseVec _prevPoses; AnimPoseVec _nextPoses; + QString _previousStateID { "none" }; State::Pointer _currentState; std::vector _states; diff --git a/libraries/animation/src/AnimTwoBoneIK.cpp b/libraries/animation/src/AnimTwoBoneIK.cpp new file mode 100644 index 0000000000..d68240d176 --- /dev/null +++ b/libraries/animation/src/AnimTwoBoneIK.cpp @@ -0,0 +1,293 @@ +// +// AnimTwoBoneIK.cpp +// +// Created by Anthony J. Thibault on 5/12/18. +// Copyright (c) 2018 High Fidelity, Inc. All rights reserved. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "AnimTwoBoneIK.h" + +#include + +#include "AnimationLogging.h" +#include "AnimUtil.h" + +const float FRAMES_PER_SECOND = 30.0f; + +AnimTwoBoneIK::AnimTwoBoneIK(const QString& id, float alpha, bool enabled, float interpDuration, + const QString& baseJointName, const QString& midJointName, + const QString& tipJointName, const glm::vec3& midHingeAxis, + const QString& alphaVar, const QString& enabledVar, + const QString& endEffectorRotationVarVar, const QString& endEffectorPositionVarVar) : + AnimNode(AnimNode::Type::TwoBoneIK, id), + _alpha(alpha), + _enabled(enabled), + _interpDuration(interpDuration), + _baseJointName(baseJointName), + _midJointName(midJointName), + _tipJointName(tipJointName), + _midHingeAxis(glm::normalize(midHingeAxis)), + _alphaVar(alphaVar), + _enabledVar(enabledVar), + _endEffectorRotationVarVar(endEffectorRotationVarVar), + _endEffectorPositionVarVar(endEffectorPositionVarVar), + _prevEndEffectorRotationVar(), + _prevEndEffectorPositionVar() +{ + +} + +AnimTwoBoneIK::~AnimTwoBoneIK() { + +} + +const AnimPoseVec& AnimTwoBoneIK::evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut) { + + assert(_children.size() == 1); + if (_children.size() != 1) { + return _poses; + } + + // evalute underPoses + AnimPoseVec underPoses = _children[0]->evaluate(animVars, context, dt, triggersOut); + + // if we don't have a skeleton, or jointName lookup failed. + if (!_skeleton || _baseJointIndex == -1 || _midJointIndex == -1 || _tipJointIndex == -1 || underPoses.size() == 0) { + // pass underPoses through unmodified. + _poses = underPoses; + return _poses; + } + + // guard against size changes + if (underPoses.size() != _poses.size()) { + _poses = underPoses; + } + + const float MIN_ALPHA = 0.0f; + const float MAX_ALPHA = 1.0f; + float alpha = glm::clamp(animVars.lookup(_alphaVar, _alpha), MIN_ALPHA, MAX_ALPHA); + + // don't perform IK if we have bad indices, or alpha is zero + if (_tipJointIndex == -1 || _midJointIndex == -1 || _baseJointIndex == -1 || alpha == 0.0f) { + _poses = underPoses; + return _poses; + } + + // determine if we should interpolate + bool enabled = animVars.lookup(_enabledVar, _enabled); + if (enabled != _enabled) { + AnimChain poseChain; + poseChain.buildFromRelativePoses(_skeleton, _poses, _tipJointIndex); + if (enabled) { + beginInterp(InterpType::SnapshotToSolve, poseChain); + } else { + beginInterp(InterpType::SnapshotToUnderPoses, poseChain); + } + } + _enabled = enabled; + + // don't build chains or do IK if we are disbled & not interping. + if (_interpType == InterpType::None && !enabled) { + _poses = underPoses; + return _poses; + } + + // compute chain + AnimChain underChain; + underChain.buildFromRelativePoses(_skeleton, underPoses, _tipJointIndex); + AnimChain ikChain = underChain; + + AnimPose baseParentPose = ikChain.getAbsolutePoseFromJointIndex(_baseParentJointIndex); + AnimPose basePose = ikChain.getAbsolutePoseFromJointIndex(_baseJointIndex); + AnimPose midPose = ikChain.getAbsolutePoseFromJointIndex(_midJointIndex); + AnimPose tipPose = ikChain.getAbsolutePoseFromJointIndex(_tipJointIndex); + + QString endEffectorRotationVar = animVars.lookup(_endEffectorRotationVarVar, QString("")); + QString endEffectorPositionVar = animVars.lookup(_endEffectorPositionVarVar, QString("")); + + // if either of the endEffectorVars have changed + if ((!_prevEndEffectorRotationVar.isEmpty() && (_prevEndEffectorRotationVar != endEffectorRotationVar)) || + (!_prevEndEffectorPositionVar.isEmpty() && (_prevEndEffectorPositionVar != endEffectorPositionVar))) { + // begin interp to smooth out transition between prev and new end effector. + AnimChain poseChain; + poseChain.buildFromRelativePoses(_skeleton, _poses, _tipJointIndex); + beginInterp(InterpType::SnapshotToSolve, poseChain); + } + + // Look up end effector from animVars, make sure to convert into geom space. + // First look in the triggers then look in the animVars, so we can follow output joints underneath us in the anim graph + AnimPose targetPose(tipPose); + if (triggersOut.hasKey(endEffectorRotationVar)) { + targetPose.rot() = triggersOut.lookupRigToGeometry(endEffectorRotationVar, tipPose.rot()); + } else if (animVars.hasKey(endEffectorRotationVar)) { + targetPose.rot() = animVars.lookupRigToGeometry(endEffectorRotationVar, tipPose.rot()); + } + + if (triggersOut.hasKey(endEffectorPositionVar)) { + targetPose.trans() = triggersOut.lookupRigToGeometry(endEffectorPositionVar, tipPose.trans()); + } else if (animVars.hasKey(endEffectorRotationVar)) { + targetPose.trans() = animVars.lookupRigToGeometry(endEffectorPositionVar, tipPose.trans()); + } + + _prevEndEffectorRotationVar = endEffectorRotationVar; + _prevEndEffectorPositionVar = endEffectorPositionVar; + + glm::vec3 bicepVector = midPose.trans() - basePose.trans(); + float r0 = glm::length(bicepVector); + bicepVector = bicepVector / r0; + + glm::vec3 forearmVector = tipPose.trans() - midPose.trans(); + float r1 = glm::length(forearmVector); + forearmVector = forearmVector / r1; + + float d = glm::length(targetPose.trans() - basePose.trans()); + + // http://mathworld.wolfram.com/Circle-CircleIntersection.html + float midAngle = 0.0f; + if (d < r0 + r1) { + float y = sqrtf((-d + r1 - r0) * (-d - r1 + r0) * (-d + r1 + r0) * (d + r1 + r0)) / (2.0f * d); + midAngle = PI - (acosf(y / r0) + acosf(y / r1)); + } + + // compute midJoint rotation + glm::quat relMidRot = glm::angleAxis(midAngle, _midHingeAxis); + + // insert new relative pose into the chain and rebuild it. + ikChain.setRelativePoseAtJointIndex(_midJointIndex, AnimPose(relMidRot, underPoses[_midJointIndex].trans())); + ikChain.buildDirtyAbsolutePoses(); + + // recompute tip pose after mid joint has been rotated + AnimPose newTipPose = ikChain.getAbsolutePoseFromJointIndex(_tipJointIndex); + + glm::vec3 leverArm = newTipPose.trans() - basePose.trans(); + glm::vec3 targetLine = targetPose.trans() - basePose.trans(); + + // compute delta rotation that brings leverArm parallel to targetLine + glm::vec3 axis = glm::cross(leverArm, targetLine); + float axisLength = glm::length(axis); + const float MIN_AXIS_LENGTH = 1.0e-4f; + if (axisLength > MIN_AXIS_LENGTH) { + axis /= axisLength; + float cosAngle = glm::clamp(glm::dot(leverArm, targetLine) / (glm::length(leverArm) * glm::length(targetLine)), -1.0f, 1.0f); + float angle = acosf(cosAngle); + glm::quat deltaRot = glm::angleAxis(angle, axis); + + // combine deltaRot with basePose. + glm::quat absRot = deltaRot * basePose.rot(); + + // transform result back into parent relative frame. + glm::quat relBaseRot = glm::inverse(baseParentPose.rot()) * absRot; + ikChain.setRelativePoseAtJointIndex(_baseJointIndex, AnimPose(relBaseRot, underPoses[_baseJointIndex].trans())); + } + + // recompute midJoint pose after base has been rotated. + ikChain.buildDirtyAbsolutePoses(); + AnimPose midJointPose = ikChain.getAbsolutePoseFromJointIndex(_midJointIndex); + + // transform target rotation in to parent relative frame. + glm::quat relTipRot = glm::inverse(midJointPose.rot()) * targetPose.rot(); + ikChain.setRelativePoseAtJointIndex(_tipJointIndex, AnimPose(relTipRot, underPoses[_tipJointIndex].trans())); + + // blend with the underChain + ikChain.blend(underChain, alpha); + + // start off by initializing output poses with the underPoses + _poses = underPoses; + + // apply smooth interpolation + if (_interpType != InterpType::None) { + _interpAlpha += _interpAlphaVel * dt; + + if (_interpAlpha < 1.0f) { + AnimChain interpChain; + if (_interpType == InterpType::SnapshotToUnderPoses) { + interpChain = underChain; + interpChain.blend(_snapshotChain, _interpAlpha); + } else if (_interpType == InterpType::SnapshotToSolve) { + interpChain = ikChain; + interpChain.blend(_snapshotChain, _interpAlpha); + } + // copy interpChain into _poses + interpChain.outputRelativePoses(_poses); + } else { + // interpolation complete + _interpType = InterpType::None; + } + } + + if (_interpType == InterpType::None) { + if (enabled) { + // copy chain into _poses + ikChain.outputRelativePoses(_poses); + } else { + // copy under chain into _poses + underChain.outputRelativePoses(_poses); + } + } + + if (context.getEnableDebugDrawIKTargets()) { + const vec4 RED(1.0f, 0.0f, 0.0f, 1.0f); + const vec4 GREEN(0.0f, 1.0f, 0.0f, 1.0f); + glm::mat4 rigToAvatarMat = createMatFromQuatAndPos(Quaternions::Y_180, glm::vec3()); + + glm::mat4 geomTargetMat = createMatFromQuatAndPos(targetPose.rot(), targetPose.trans()); + glm::mat4 avatarTargetMat = rigToAvatarMat * context.getGeometryToRigMatrix() * geomTargetMat; + + QString name = QString("%1_target").arg(_id); + DebugDraw::getInstance().addMyAvatarMarker(name, glmExtractRotation(avatarTargetMat), + extractTranslation(avatarTargetMat), _enabled ? GREEN : RED); + } else if (_lastEnableDebugDrawIKTargets) { + QString name = QString("%1_target").arg(_id); + DebugDraw::getInstance().removeMyAvatarMarker(name); + } + _lastEnableDebugDrawIKTargets = context.getEnableDebugDrawIKTargets(); + + if (context.getEnableDebugDrawIKChains()) { + if (_interpType == InterpType::None && enabled) { + const vec4 CYAN(0.0f, 1.0f, 1.0f, 1.0f); + ikChain.debugDraw(context.getRigToWorldMatrix() * context.getGeometryToRigMatrix(), CYAN); + } + } + + processOutputJoints(triggersOut); + + return _poses; +} + +// for AnimDebugDraw rendering +const AnimPoseVec& AnimTwoBoneIK::getPosesInternal() const { + return _poses; +} + +void AnimTwoBoneIK::setSkeletonInternal(AnimSkeleton::ConstPointer skeleton) { + AnimNode::setSkeletonInternal(skeleton); + lookUpIndices(); +} + +void AnimTwoBoneIK::lookUpIndices() { + assert(_skeleton); + + // look up bone indices by name + std::vector indices = _skeleton->lookUpJointIndices({_baseJointName, _midJointName, _tipJointName}); + + // cache the results + _baseJointIndex = indices[0]; + _midJointIndex = indices[1]; + _tipJointIndex = indices[2]; + + if (_baseJointIndex != -1) { + _baseParentJointIndex = _skeleton->getParentIndex(_baseJointIndex); + } +} + +void AnimTwoBoneIK::beginInterp(InterpType interpType, const AnimChain& chain) { + // capture the current poses in a snapshot. + _snapshotChain = chain; + + _interpType = interpType; + _interpAlphaVel = FRAMES_PER_SECOND / _interpDuration; + _interpAlpha = 0.0f; +} diff --git a/libraries/animation/src/AnimTwoBoneIK.h b/libraries/animation/src/AnimTwoBoneIK.h new file mode 100644 index 0000000000..23bc02a662 --- /dev/null +++ b/libraries/animation/src/AnimTwoBoneIK.h @@ -0,0 +1,83 @@ +// +// AnimTwoBoneIK.h +// +// Created by Anthony J. Thibault on 5/12/18. +// Copyright (c) 2018 High Fidelity, Inc. All rights reserved. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_AnimTwoBoneIK_h +#define hifi_AnimTwoBoneIK_h + +#include "AnimNode.h" +#include "AnimChain.h" + +// Simple two bone IK chain +class AnimTwoBoneIK : public AnimNode { +public: + friend class AnimTests; + + AnimTwoBoneIK(const QString& id, float alpha, bool enabled, float interpDuration, + const QString& baseJointName, const QString& midJointName, + const QString& tipJointName, const glm::vec3& midHingeAxis, + const QString& alphaVar, const QString& enabledVar, + const QString& endEffectorRotationVarVar, const QString& endEffectorPositionVarVar); + virtual ~AnimTwoBoneIK() override; + + virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut) override; + +protected: + + enum class InterpType { + None = 0, + SnapshotToUnderPoses, + SnapshotToSolve, + NumTypes + }; + + // for AnimDebugDraw rendering + virtual const AnimPoseVec& getPosesInternal() const override; + virtual void setSkeletonInternal(AnimSkeleton::ConstPointer skeleton) override; + + void lookUpIndices(); + void beginInterp(InterpType interpType, const AnimChain& chain); + + AnimPoseVec _poses; + + float _alpha; + bool _enabled; + float _interpDuration; // in frames (1/30 sec) + QString _baseJointName; + QString _midJointName; + QString _tipJointName; + glm::vec3 _midHingeAxis; // in baseJoint relative frame, should be normalized + + int _baseParentJointIndex { -1 }; + int _baseJointIndex { -1 }; + int _midJointIndex { -1 }; + int _tipJointIndex { -1 }; + + QString _alphaVar; // float - (0, 1) 0 means underPoses only, 1 means IK only. + QString _enabledVar; // bool + QString _endEffectorRotationVarVar; // string + QString _endEffectorPositionVarVar; // string + + QString _prevEndEffectorRotationVar; + QString _prevEndEffectorPositionVar; + + InterpType _interpType { InterpType::None }; + float _interpAlphaVel { 0.0f }; + float _interpAlpha { 0.0f }; + + AnimChain _snapshotChain; + + bool _lastEnableDebugDrawIKTargets { false }; + + // no copies + AnimTwoBoneIK(const AnimTwoBoneIK&) = delete; + AnimTwoBoneIK& operator=(const AnimTwoBoneIK&) = delete; +}; + +#endif // hifi_AnimTwoBoneIK_h diff --git a/libraries/animation/src/AnimUtil.cpp b/libraries/animation/src/AnimUtil.cpp index acb90126fc..c23e228556 100644 --- a/libraries/animation/src/AnimUtil.cpp +++ b/libraries/animation/src/AnimUtil.cpp @@ -21,14 +21,6 @@ void blend(size_t numPoses, const AnimPose* a, const AnimPose* b, float alpha, A const AnimPose& aPose = a[i]; const AnimPose& bPose = b[i]; - // adjust signs if necessary - const glm::quat& q1 = aPose.rot(); - glm::quat q2 = bPose.rot(); - float dot = glm::dot(q1, q2); - if (dot < 0.0f) { - q2 = -q2; - } - result[i].scale() = lerp(aPose.scale(), bPose.scale(), alpha); result[i].rot() = safeLerp(aPose.rot(), bPose.rot(), alpha); result[i].trans() = lerp(aPose.trans(), bPose.trans(), alpha); @@ -53,7 +45,7 @@ glm::quat averageQuats(size_t numQuats, const glm::quat* quats) { } float accumulateTime(float startFrame, float endFrame, float timeScale, float currentFrame, float dt, bool loopFlag, - const QString& id, AnimNode::Triggers& triggersOut) { + const QString& id, AnimVariantMap& triggersOut) { const float EPSILON = 0.0001f; float frame = currentFrame; @@ -79,12 +71,12 @@ float accumulateTime(float startFrame, float endFrame, float timeScale, float cu if (framesRemaining >= framesTillEnd) { if (loopFlag) { // anim loop - triggersOut.push_back(id + "OnLoop"); + triggersOut.setTrigger(id + "OnLoop"); framesRemaining -= framesTillEnd; frame = clampedStartFrame; } else { // anim end - triggersOut.push_back(id + "OnDone"); + triggersOut.setTrigger(id + "OnDone"); frame = endFrame; framesRemaining = 0.0f; } diff --git a/libraries/animation/src/AnimUtil.h b/libraries/animation/src/AnimUtil.h index 3cd7f4b6fb..9300f1a7a0 100644 --- a/libraries/animation/src/AnimUtil.h +++ b/libraries/animation/src/AnimUtil.h @@ -19,7 +19,7 @@ void blend(size_t numPoses, const AnimPose* a, const AnimPose* b, float alpha, A glm::quat averageQuats(size_t numQuats, const glm::quat* quats); float accumulateTime(float startFrame, float endFrame, float timeScale, float currentFrame, float dt, bool loopFlag, - const QString& id, AnimNode::Triggers& triggersOut); + const QString& id, AnimVariantMap& triggersOut); inline glm::quat safeLerp(const glm::quat& a, const glm::quat& b, float alpha) { // adjust signs if necessary diff --git a/libraries/animation/src/AnimationCache.h b/libraries/animation/src/AnimationCache.h index 4b0a8901f5..ca5ea5b072 100644 --- a/libraries/animation/src/AnimationCache.h +++ b/libraries/animation/src/AnimationCache.h @@ -24,71 +24,12 @@ class Animation; typedef QSharedPointer AnimationPointer; -/// Scriptable interface for FBX animation loading. class AnimationCache : public ResourceCache, public Dependency { Q_OBJECT SINGLETON_DEPENDENCY public: - // Properties are copied over from ResourceCache (see ResourceCache.h for reason). - - /**jsdoc - * API to manage animation cache resources. - * @namespace AnimationCache - * - * @hifi-interface - * @hifi-client-entity - * @hifi-assignment-client - * - * @property {number} numTotal - Total number of total resources. Read-only. - * @property {number} numCached - Total number of cached resource. Read-only. - * @property {number} sizeTotal - Size in bytes of all resources. Read-only. - * @property {number} sizeCached - Size in bytes of all cached resources. Read-only. - */ - - // Functions are copied over from ResourceCache (see ResourceCache.h for reason). - - /**jsdoc - * Get the list of all resource URLs. - * @function AnimationCache.getResourceList - * @returns {string[]} - */ - - /**jsdoc - * @function AnimationCache.dirty - * @returns {Signal} - */ - - /**jsdoc - * @function AnimationCache.updateTotalSize - * @param {number} deltaSize - */ - - /**jsdoc - * Prefetches a resource. - * @function AnimationCache.prefetch - * @param {string} url - URL of the resource to prefetch. - * @param {object} [extra=null] - * @returns {ResourceObject} - */ - - /**jsdoc - * Asynchronously loads a resource from the specified URL and returns it. - * @function AnimationCache.getResource - * @param {string} url - URL of the resource to load. - * @param {string} [fallback=""] - Fallback URL if load of the desired URL fails. - * @param {} [extra=null] - * @returns {object} - */ - - - /**jsdoc - * Returns animation resource for particular animation. - * @function AnimationCache.getAnimation - * @param {string} url - URL to load. - * @returns {AnimationObject} animation - */ Q_INVOKABLE AnimationPointer getAnimation(const QString& url) { return getAnimation(QUrl(url)); } Q_INVOKABLE AnimationPointer getAnimation(const QUrl& url); diff --git a/libraries/animation/src/AnimationCacheScriptingInterface.cpp b/libraries/animation/src/AnimationCacheScriptingInterface.cpp new file mode 100644 index 0000000000..3db1d31901 --- /dev/null +++ b/libraries/animation/src/AnimationCacheScriptingInterface.cpp @@ -0,0 +1,20 @@ +// +// AnimationCacheScriptingInterface.cpp +// libraries/animation/src +// +// Created by David Rowe on 25 Jul 2018. +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "AnimationCacheScriptingInterface.h" + +AnimationCacheScriptingInterface::AnimationCacheScriptingInterface() : + ScriptableResourceCache::ScriptableResourceCache(DependencyManager::get()) +{ } + +AnimationPointer AnimationCacheScriptingInterface::getAnimation(const QString& url) { + return DependencyManager::get()->getAnimation(QUrl(url)); +} diff --git a/libraries/animation/src/AnimationCacheScriptingInterface.h b/libraries/animation/src/AnimationCacheScriptingInterface.h new file mode 100644 index 0000000000..1f5735dd0f --- /dev/null +++ b/libraries/animation/src/AnimationCacheScriptingInterface.h @@ -0,0 +1,58 @@ +// +// AnimationCacheScriptingInterface.h +// libraries/animation/src +// +// Created by David Rowe on 25 Jul 2018. +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#pragma once + +#ifndef hifi_AnimationCacheScriptingInterface_h +#define hifi_AnimationCacheScriptingInterface_h + +#include + +#include + +#include "AnimationCache.h" + +class AnimationCacheScriptingInterface : public ScriptableResourceCache, public Dependency { + Q_OBJECT + + // Properties are copied over from ResourceCache (see ResourceCache.h for reason). + + /**jsdoc + * API to manage animation cache resources. + * @namespace AnimationCache + * + * @hifi-interface + * @hifi-client-entity + * @hifi-assignment-client + * + * @property {number} numTotal - Total number of total resources. Read-only. + * @property {number} numCached - Total number of cached resource. Read-only. + * @property {number} sizeTotal - Size in bytes of all resources. Read-only. + * @property {number} sizeCached - Size in bytes of all cached resources. Read-only. + * + * @borrows ResourceCache.getResourceList as getResourceList + * @borrows ResourceCache.updateTotalSize as updateTotalSize + * @borrows ResourceCache.prefetch as prefetch + * @borrows ResourceCache.dirty as dirty + */ + +public: + AnimationCacheScriptingInterface(); + + /**jsdoc + * Returns animation resource for particular animation. + * @function AnimationCache.getAnimation + * @param {string} url - URL to load. + * @returns {AnimationObject} animation + */ + Q_INVOKABLE AnimationPointer getAnimation(const QString& url); +}; + +#endif // hifi_AnimationCacheScriptingInterface_h diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 549989778e..13cf165dac 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -59,6 +59,21 @@ const glm::vec3 DEFAULT_RIGHT_EYE_POS(-0.3f, 0.9f, 0.0f); const glm::vec3 DEFAULT_LEFT_EYE_POS(0.3f, 0.9f, 0.0f); const glm::vec3 DEFAULT_HEAD_POS(0.0f, 0.75f, 0.0f); +static const QString LEFT_FOOT_POSITION("leftFootPosition"); +static const QString LEFT_FOOT_ROTATION("leftFootRotation"); +static const QString LEFT_FOOT_IK_POSITION_VAR("leftFootIKPositionVar"); +static const QString LEFT_FOOT_IK_ROTATION_VAR("leftFootIKRotationVar"); +static const QString MAIN_STATE_MACHINE_LEFT_FOOT_POSITION("mainStateMachineLeftFootPosition"); +static const QString MAIN_STATE_MACHINE_LEFT_FOOT_ROTATION("mainStateMachineLeftFootRotation"); + +static const QString RIGHT_FOOT_POSITION("rightFootPosition"); +static const QString RIGHT_FOOT_ROTATION("rightFootRotation"); +static const QString RIGHT_FOOT_IK_POSITION_VAR("rightFootIKPositionVar"); +static const QString RIGHT_FOOT_IK_ROTATION_VAR("rightFootIKRotationVar"); +static const QString MAIN_STATE_MACHINE_RIGHT_FOOT_ROTATION("mainStateMachineRightFootRotation"); +static const QString MAIN_STATE_MACHINE_RIGHT_FOOT_POSITION("mainStateMachineRightFootPosition"); + + Rig::Rig() { // Ensure thread-safe access to the rigRegistry. std::lock_guard guard(rigRegistryMutex); @@ -575,28 +590,6 @@ bool Rig::getAbsoluteJointPoseInRigFrame(int jointIndex, AnimPose& returnPose) c } } -void Rig::calcAnimAlpha(float speed, const std::vector& referenceSpeeds, float* alphaOut) const { - - ASSERT(referenceSpeeds.size() > 0); - - // calculate alpha from linear combination of referenceSpeeds. - float alpha = 0.0f; - if (speed <= referenceSpeeds.front()) { - alpha = 0.0f; - } else if (speed > referenceSpeeds.back()) { - alpha = (float)(referenceSpeeds.size() - 1); - } else { - for (size_t i = 0; i < referenceSpeeds.size() - 1; i++) { - if (referenceSpeeds[i] < speed && speed < referenceSpeeds[i + 1]) { - alpha = (float)i + ((speed - referenceSpeeds[i]) / (referenceSpeeds[i + 1] - referenceSpeeds[i])); - break; - } - } - } - - *alphaOut = alpha; -} - void Rig::setEnableInverseKinematics(bool enable) { _enableInverseKinematics = enable; } @@ -636,11 +629,6 @@ bool Rig::getRelativeDefaultJointTranslation(int index, glm::vec3& translationOu } } -// animation reference speeds. -static const std::vector FORWARD_SPEEDS = { 0.4f, 1.4f, 4.5f }; // m/s -static const std::vector BACKWARD_SPEEDS = { 0.6f, 1.45f }; // m/s -static const std::vector LATERAL_SPEEDS = { 0.2f, 0.65f }; // m/s - void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPosition, const glm::vec3& worldVelocity, const glm::quat& worldRotation, CharacterControllerState ccState) { glm::vec3 forward = worldRotation * IDENTITY_FORWARD; @@ -660,24 +648,9 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos // sine wave LFO var for testing. static float t = 0.0f; _animVars.set("sine", 2.0f * 0.5f * sinf(t) + 0.5f); - - float moveForwardAlpha = 0.0f; - float moveBackwardAlpha = 0.0f; - float moveLateralAlpha = 0.0f; - - // calcuate the animation alpha and timeScale values based on current speeds and animation reference speeds. - calcAnimAlpha(_averageForwardSpeed.getAverage(), FORWARD_SPEEDS, &moveForwardAlpha); - calcAnimAlpha(-_averageForwardSpeed.getAverage(), BACKWARD_SPEEDS, &moveBackwardAlpha); - calcAnimAlpha(fabsf(_averageLateralSpeed.getAverage()), LATERAL_SPEEDS, &moveLateralAlpha); - _animVars.set("moveForwardSpeed", _averageForwardSpeed.getAverage()); - _animVars.set("moveForwardAlpha", moveForwardAlpha); - _animVars.set("moveBackwardSpeed", -_averageForwardSpeed.getAverage()); - _animVars.set("moveBackwardAlpha", moveBackwardAlpha); - _animVars.set("moveLateralSpeed", fabsf(_averageLateralSpeed.getAverage())); - _animVars.set("moveLateralAlpha", moveLateralAlpha); const float MOVE_ENTER_SPEED_THRESHOLD = 0.2f; // m/sec const float MOVE_EXIT_SPEED_THRESHOLD = 0.07f; // m/sec @@ -762,6 +735,8 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos _animVars.set("isMovingBackward", false); _animVars.set("isMovingRight", false); _animVars.set("isMovingLeft", false); + _animVars.set("isMovingRightHmd", false); + _animVars.set("isMovingLeftHmd", false); _animVars.set("isNotMoving", false); } else { @@ -770,28 +745,48 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos _animVars.set("isMovingForward", false); _animVars.set("isMovingRight", false); _animVars.set("isMovingLeft", false); + _animVars.set("isMovingRightHmd", false); + _animVars.set("isMovingLeftHmd", false); _animVars.set("isNotMoving", false); } } else { if (lateralSpeed > 0.0f) { // right - _animVars.set("isMovingRight", true); - _animVars.set("isMovingLeft", false); + if (!_headEnabled) { + _animVars.set("isMovingRight", true); + _animVars.set("isMovingLeft", false); + _animVars.set("isMovingRightHmd", false); + _animVars.set("isMovingLeftHmd", false); + } else { + _animVars.set("isMovingRight", false); + _animVars.set("isMovingLeft", false); + _animVars.set("isMovingRightHmd", true); + _animVars.set("isMovingLeftHmd", false); + } _animVars.set("isMovingForward", false); _animVars.set("isMovingBackward", false); _animVars.set("isNotMoving", false); } else { // left - _animVars.set("isMovingLeft", true); - _animVars.set("isMovingRight", false); + if (!_headEnabled) { + _animVars.set("isMovingRight", false); + _animVars.set("isMovingLeft", true); + _animVars.set("isMovingRightHmd", false); + _animVars.set("isMovingLeftHmd", false); + } else { + _animVars.set("isMovingRight", false); + _animVars.set("isMovingLeft", false); + _animVars.set("isMovingRightHmd", false); + _animVars.set("isMovingLeftHmd", true); + } _animVars.set("isMovingForward", false); _animVars.set("isMovingBackward", false); _animVars.set("isNotMoving", false); } } } - _animVars.set("isTurningLeft", false); _animVars.set("isTurningRight", false); + _animVars.set("isTurningLeft", false); _animVars.set("isNotTurning", true); _animVars.set("isFlying", false); _animVars.set("isNotFlying", true); @@ -810,14 +805,16 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos _animVars.set("isNotTurning", false); } else { // turning left - _animVars.set("isTurningLeft", true); _animVars.set("isTurningRight", false); + _animVars.set("isTurningLeft", true); _animVars.set("isNotTurning", false); } _animVars.set("isMovingForward", false); _animVars.set("isMovingBackward", false); _animVars.set("isMovingRight", false); _animVars.set("isMovingLeft", false); + _animVars.set("isMovingRightHmd", false); + _animVars.set("isMovingLeftHmd", false); _animVars.set("isNotMoving", true); _animVars.set("isFlying", false); _animVars.set("isNotFlying", true); @@ -828,15 +825,17 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos _animVars.set("isInAirRun", false); _animVars.set("isNotInAir", true); - } else if (_state == RigRole::Idle ) { + } else if (_state == RigRole::Idle) { // default anim vars to notMoving and notTurning _animVars.set("isMovingForward", false); _animVars.set("isMovingBackward", false); - _animVars.set("isMovingLeft", false); _animVars.set("isMovingRight", false); + _animVars.set("isMovingLeft", false); + _animVars.set("isMovingRightHmd", false); + _animVars.set("isMovingLeftHmd", false); _animVars.set("isNotMoving", true); - _animVars.set("isTurningLeft", false); _animVars.set("isTurningRight", false); + _animVars.set("isTurningLeft", false); _animVars.set("isNotTurning", true); _animVars.set("isFlying", false); _animVars.set("isNotFlying", true); @@ -851,11 +850,13 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos // flying. _animVars.set("isMovingForward", false); _animVars.set("isMovingBackward", false); - _animVars.set("isMovingLeft", false); _animVars.set("isMovingRight", false); + _animVars.set("isMovingLeft", false); + _animVars.set("isMovingRightHmd", false); + _animVars.set("isMovingLeftHmd", false); _animVars.set("isNotMoving", true); - _animVars.set("isTurningLeft", false); _animVars.set("isTurningRight", false); + _animVars.set("isTurningLeft", false); _animVars.set("isNotTurning", true); _animVars.set("isFlying", true); _animVars.set("isNotFlying", false); @@ -870,11 +871,13 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos // jumping in-air _animVars.set("isMovingForward", false); _animVars.set("isMovingBackward", false); - _animVars.set("isMovingLeft", false); _animVars.set("isMovingRight", false); + _animVars.set("isMovingLeft", false); + _animVars.set("isMovingRightHmd", false); + _animVars.set("isMovingLeftHmd", false); _animVars.set("isNotMoving", true); - _animVars.set("isTurningLeft", false); _animVars.set("isTurningRight", false); + _animVars.set("isTurningLeft", false); _animVars.set("isNotTurning", true); _animVars.set("isFlying", false); _animVars.set("isNotFlying", true); @@ -897,11 +900,13 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos // jumping in-air _animVars.set("isMovingForward", false); _animVars.set("isMovingBackward", false); - _animVars.set("isMovingLeft", false); _animVars.set("isMovingRight", false); + _animVars.set("isMovingLeft", false); + _animVars.set("isMovingRightHmd", false); + _animVars.set("isMovingLeftHmd", false); _animVars.set("isNotMoving", true); - _animVars.set("isTurningLeft", false); _animVars.set("isTurningRight", false); + _animVars.set("isTurningLeft", false); _animVars.set("isNotTurning", true); _animVars.set("isFlying", false); _animVars.set("isNotFlying", true); @@ -921,7 +926,7 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos // compute blend based on velocity const float JUMP_SPEED = 3.5f; - float alpha = glm::clamp(-_lastVelocity.y / JUMP_SPEED, -1.0f, 1.0f) + 1.0f; + float alpha = glm::clamp(-workingVelocity.y / JUMP_SPEED, -1.0f, 1.0f) + 1.0f; _animVars.set("inAirAlpha", alpha); } @@ -1049,7 +1054,7 @@ void Rig::updateAnimations(float deltaTime, const glm::mat4& rootTransform, cons getGeometryToRigTransform(), rigToWorldTransform); // evaluate the animation - AnimNode::Triggers triggersOut; + AnimVariantMap triggersOut; _internalPoseSet._relativePoses = _animNode->evaluate(_animVars, context, deltaTime, triggersOut); if ((int)_internalPoseSet._relativePoses.size() != _animSkeleton->getNumJoints()) { @@ -1057,9 +1062,7 @@ void Rig::updateAnimations(float deltaTime, const glm::mat4& rootTransform, cons _internalPoseSet._relativePoses = _animSkeleton->getRelativeDefaultPoses(); } _animVars.clearTriggers(); - for (auto& trigger : triggersOut) { - _animVars.setTrigger(trigger); - } + _animVars = triggersOut; } applyOverridePoses(); buildAbsoluteRigPoses(_internalPoseSet._relativePoses, _internalPoseSet._absolutePoses); @@ -1241,17 +1244,15 @@ glm::vec3 Rig::deflectHandFromTorso(const glm::vec3& handPosition, const FBXJoin } void Rig::updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnabled, bool hipsEstimated, - bool leftArmEnabled, bool rightArmEnabled, float dt, + bool leftArmEnabled, bool rightArmEnabled, bool headEnabled, float dt, const AnimPose& leftHandPose, const AnimPose& rightHandPose, const FBXJointShapeInfo& hipsShapeInfo, const FBXJointShapeInfo& spineShapeInfo, const FBXJointShapeInfo& spine1ShapeInfo, const FBXJointShapeInfo& spine2ShapeInfo, const glm::mat4& rigToSensorMatrix, const glm::mat4& sensorToRigMatrix) { - const bool ENABLE_POLE_VECTORS = false; + const bool ENABLE_POLE_VECTORS = true; const float ELBOW_POLE_VECTOR_BLEND_FACTOR = 0.95f; - int hipsIndex = indexOfJoint("Hips"); - if (leftHandEnabled) { glm::vec3 handPosition = leftHandPose.trans(); @@ -1270,22 +1271,33 @@ void Rig::updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnab int handJointIndex = _animSkeleton->nameToJointIndex("LeftHand"); int armJointIndex = _animSkeleton->nameToJointIndex("LeftArm"); int elbowJointIndex = _animSkeleton->nameToJointIndex("LeftForeArm"); - if (ENABLE_POLE_VECTORS && !leftArmEnabled && handJointIndex >= 0 && armJointIndex >= 0 && elbowJointIndex >= 0) { - glm::vec3 poleVector = calculateElbowPoleVector(handJointIndex, elbowJointIndex, armJointIndex, hipsIndex, true); - glm::vec3 sensorPoleVector = transformVectorFast(rigToSensorMatrix, poleVector); + int oppositeArmJointIndex = _animSkeleton->nameToJointIndex("RightArm"); + if (ENABLE_POLE_VECTORS && handJointIndex >= 0 && armJointIndex >= 0 && elbowJointIndex >= 0 && oppositeArmJointIndex >= 0) { + glm::vec3 poleVector; + bool usePoleVector = calculateElbowPoleVector(handJointIndex, elbowJointIndex, armJointIndex, oppositeArmJointIndex, poleVector); + if (usePoleVector) { + glm::vec3 sensorPoleVector = transformVectorFast(rigToSensorMatrix, poleVector); - // smooth toward desired pole vector from previous pole vector... to reduce jitter - if (!_prevLeftHandPoleVectorValid) { - _prevLeftHandPoleVectorValid = true; - _prevLeftHandPoleVector = sensorPoleVector; + if (_smoothPoleVectors) { + // smooth toward desired pole vector from previous pole vector... to reduce jitter + if (!_prevLeftHandPoleVectorValid) { + _prevLeftHandPoleVectorValid = true; + _prevLeftHandPoleVector = sensorPoleVector; + } + glm::quat deltaRot = rotationBetween(_prevLeftHandPoleVector, sensorPoleVector); + glm::quat smoothDeltaRot = safeMix(deltaRot, Quaternions::IDENTITY, ELBOW_POLE_VECTOR_BLEND_FACTOR); + _prevLeftHandPoleVector = smoothDeltaRot * _prevLeftHandPoleVector; + } else { + _prevLeftHandPoleVector = sensorPoleVector; + } + _animVars.set("leftHandPoleVectorEnabled", true); + _animVars.set("leftHandPoleReferenceVector", Vectors::UNIT_X); + _animVars.set("leftHandPoleVector", transformVectorFast(sensorToRigMatrix, _prevLeftHandPoleVector)); + } else { + _prevLeftHandPoleVectorValid = false; + _animVars.set("leftHandPoleVectorEnabled", false); } - glm::quat deltaRot = rotationBetween(_prevLeftHandPoleVector, sensorPoleVector); - glm::quat smoothDeltaRot = safeMix(deltaRot, Quaternions::IDENTITY, ELBOW_POLE_VECTOR_BLEND_FACTOR); - _prevLeftHandPoleVector = smoothDeltaRot * _prevLeftHandPoleVector; - _animVars.set("leftHandPoleVectorEnabled", true); - _animVars.set("leftHandPoleReferenceVector", Vectors::UNIT_X); - _animVars.set("leftHandPoleVector", transformVectorFast(sensorToRigMatrix, _prevLeftHandPoleVector)); } else { _prevLeftHandPoleVectorValid = false; _animVars.set("leftHandPoleVectorEnabled", false); @@ -1296,8 +1308,13 @@ void Rig::updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnab _animVars.unset("leftHandPosition"); _animVars.unset("leftHandRotation"); - _animVars.set("leftHandType", (int)IKTarget::Type::HipsRelativeRotationAndPosition); + if (headEnabled) { + _animVars.set("leftHandType", (int)IKTarget::Type::HipsRelativeRotationAndPosition); + } else { + // disable hand IK for desktop mode + _animVars.set("leftHandType", (int)IKTarget::Type::Unknown); + } } if (rightHandEnabled) { @@ -1318,22 +1335,34 @@ void Rig::updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnab int handJointIndex = _animSkeleton->nameToJointIndex("RightHand"); int armJointIndex = _animSkeleton->nameToJointIndex("RightArm"); int elbowJointIndex = _animSkeleton->nameToJointIndex("RightForeArm"); - if (ENABLE_POLE_VECTORS && !rightArmEnabled && handJointIndex >= 0 && armJointIndex >= 0 && elbowJointIndex >= 0) { - glm::vec3 poleVector = calculateElbowPoleVector(handJointIndex, elbowJointIndex, armJointIndex, hipsIndex, false); - glm::vec3 sensorPoleVector = transformVectorFast(rigToSensorMatrix, poleVector); + int oppositeArmJointIndex = _animSkeleton->nameToJointIndex("LeftArm"); - // smooth toward desired pole vector from previous pole vector... to reduce jitter - if (!_prevRightHandPoleVectorValid) { - _prevRightHandPoleVectorValid = true; - _prevRightHandPoleVector = sensorPoleVector; + if (ENABLE_POLE_VECTORS && handJointIndex >= 0 && armJointIndex >= 0 && elbowJointIndex >= 0 && oppositeArmJointIndex >= 0) { + glm::vec3 poleVector; + bool usePoleVector = calculateElbowPoleVector(handJointIndex, elbowJointIndex, armJointIndex, oppositeArmJointIndex, poleVector); + if (usePoleVector) { + glm::vec3 sensorPoleVector = transformVectorFast(rigToSensorMatrix, poleVector); + + if (_smoothPoleVectors) { + // smooth toward desired pole vector from previous pole vector... to reduce jitter + if (!_prevRightHandPoleVectorValid) { + _prevRightHandPoleVectorValid = true; + _prevRightHandPoleVector = sensorPoleVector; + } + glm::quat deltaRot = rotationBetween(_prevRightHandPoleVector, sensorPoleVector); + glm::quat smoothDeltaRot = safeMix(deltaRot, Quaternions::IDENTITY, ELBOW_POLE_VECTOR_BLEND_FACTOR); + _prevRightHandPoleVector = smoothDeltaRot * _prevRightHandPoleVector; + } else { + _prevRightHandPoleVector = sensorPoleVector; + } + + _animVars.set("rightHandPoleVectorEnabled", true); + _animVars.set("rightHandPoleReferenceVector", -Vectors::UNIT_X); + _animVars.set("rightHandPoleVector", transformVectorFast(sensorToRigMatrix, _prevRightHandPoleVector)); + } else { + _prevRightHandPoleVectorValid = false; + _animVars.set("rightHandPoleVectorEnabled", false); } - glm::quat deltaRot = rotationBetween(_prevRightHandPoleVector, sensorPoleVector); - glm::quat smoothDeltaRot = safeMix(deltaRot, Quaternions::IDENTITY, ELBOW_POLE_VECTOR_BLEND_FACTOR); - _prevRightHandPoleVector = smoothDeltaRot * _prevRightHandPoleVector; - - _animVars.set("rightHandPoleVectorEnabled", true); - _animVars.set("rightHandPoleReferenceVector", -Vectors::UNIT_X); - _animVars.set("rightHandPoleVector", transformVectorFast(sensorToRigMatrix, _prevRightHandPoleVector)); } else { _prevRightHandPoleVectorValid = false; _animVars.set("rightHandPoleVectorEnabled", false); @@ -1344,21 +1373,41 @@ void Rig::updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnab _animVars.unset("rightHandPosition"); _animVars.unset("rightHandRotation"); - _animVars.set("rightHandType", (int)IKTarget::Type::HipsRelativeRotationAndPosition); + + if (headEnabled) { + _animVars.set("rightHandType", (int)IKTarget::Type::HipsRelativeRotationAndPosition); + } else { + // disable hand IK for desktop mode + _animVars.set("rightHandType", (int)IKTarget::Type::Unknown); + } } } -void Rig::updateFeet(bool leftFootEnabled, bool rightFootEnabled, const AnimPose& leftFootPose, const AnimPose& rightFootPose, +void Rig::updateFeet(bool leftFootEnabled, bool rightFootEnabled, bool headEnabled, + const AnimPose& leftFootPose, const AnimPose& rightFootPose, const glm::mat4& rigToSensorMatrix, const glm::mat4& sensorToRigMatrix) { - const float KNEE_POLE_VECTOR_BLEND_FACTOR = 0.95f; - int hipsIndex = indexOfJoint("Hips"); + const float KNEE_POLE_VECTOR_BLEND_FACTOR = 0.85f; + + if (headEnabled) { + // always do IK if head is enabled + _animVars.set("leftFootIKEnabled", true); + _animVars.set("rightFootIKEnabled", true); + } else { + // only do IK if we have a valid foot. + _animVars.set("leftFootIKEnabled", leftFootEnabled); + _animVars.set("rightFootIKEnabled", rightFootEnabled); + } if (leftFootEnabled) { - _animVars.set("leftFootPosition", leftFootPose.trans()); - _animVars.set("leftFootRotation", leftFootPose.rot()); - _animVars.set("leftFootType", (int)IKTarget::Type::RotationAndPosition); + + _animVars.set(LEFT_FOOT_POSITION, leftFootPose.trans()); + _animVars.set(LEFT_FOOT_ROTATION, leftFootPose.rot()); + + // We want to drive the IK directly from the trackers. + _animVars.set(LEFT_FOOT_IK_POSITION_VAR, LEFT_FOOT_POSITION); + _animVars.set(LEFT_FOOT_IK_ROTATION_VAR, LEFT_FOOT_ROTATION); int footJointIndex = _animSkeleton->nameToJointIndex("LeftFoot"); int kneeJointIndex = _animSkeleton->nameToJointIndex("LeftLeg"); @@ -1376,20 +1425,25 @@ void Rig::updateFeet(bool leftFootEnabled, bool rightFootEnabled, const AnimPose _prevLeftFootPoleVector = smoothDeltaRot * _prevLeftFootPoleVector; _animVars.set("leftFootPoleVectorEnabled", true); - _animVars.set("leftFootPoleReferenceVector", Vectors::UNIT_Z); _animVars.set("leftFootPoleVector", transformVectorFast(sensorToRigMatrix, _prevLeftFootPoleVector)); } else { - _animVars.unset("leftFootPosition"); - _animVars.unset("leftFootRotation"); - _animVars.set("leftFootType", (int)IKTarget::Type::RotationAndPosition); + // We want to drive the IK from the underlying animation. + // This gives us the ability to squat while in the HMD, without the feet from dipping under the floor. + _animVars.set(LEFT_FOOT_IK_POSITION_VAR, MAIN_STATE_MACHINE_LEFT_FOOT_POSITION); + _animVars.set(LEFT_FOOT_IK_ROTATION_VAR, MAIN_STATE_MACHINE_LEFT_FOOT_ROTATION); + + // We want to match the animated knee pose as close as possible, so don't use poleVectors _animVars.set("leftFootPoleVectorEnabled", false); _prevLeftFootPoleVectorValid = false; } if (rightFootEnabled) { - _animVars.set("rightFootPosition", rightFootPose.trans()); - _animVars.set("rightFootRotation", rightFootPose.rot()); - _animVars.set("rightFootType", (int)IKTarget::Type::RotationAndPosition); + _animVars.set(RIGHT_FOOT_POSITION, rightFootPose.trans()); + _animVars.set(RIGHT_FOOT_ROTATION, rightFootPose.rot()); + + // We want to drive the IK directly from the trackers. + _animVars.set(RIGHT_FOOT_IK_POSITION_VAR, RIGHT_FOOT_POSITION); + _animVars.set(RIGHT_FOOT_IK_ROTATION_VAR, RIGHT_FOOT_ROTATION); int footJointIndex = _animSkeleton->nameToJointIndex("RightFoot"); int kneeJointIndex = _animSkeleton->nameToJointIndex("RightLeg"); @@ -1407,13 +1461,16 @@ void Rig::updateFeet(bool leftFootEnabled, bool rightFootEnabled, const AnimPose _prevRightFootPoleVector = smoothDeltaRot * _prevRightFootPoleVector; _animVars.set("rightFootPoleVectorEnabled", true); - _animVars.set("rightFootPoleReferenceVector", Vectors::UNIT_Z); _animVars.set("rightFootPoleVector", transformVectorFast(sensorToRigMatrix, _prevRightFootPoleVector)); } else { - _animVars.unset("rightFootPosition"); - _animVars.unset("rightFootRotation"); + // We want to drive the IK from the underlying animation. + // This gives us the ability to squat while in the HMD, without the feet from dipping under the floor. + _animVars.set(RIGHT_FOOT_IK_POSITION_VAR, MAIN_STATE_MACHINE_RIGHT_FOOT_POSITION); + _animVars.set(RIGHT_FOOT_IK_ROTATION_VAR, MAIN_STATE_MACHINE_RIGHT_FOOT_ROTATION); + + // We want to match the animated knee pose as close as possible, so don't use poleVectors _animVars.set("rightFootPoleVectorEnabled", false); - _animVars.set("rightFootType", (int)IKTarget::Type::RotationAndPosition); + _prevRightFootPoleVectorValid = false; } } @@ -1455,83 +1512,97 @@ void Rig::updateEyeJoint(int index, const glm::vec3& modelTranslation, const glm for (int i = 0; i < (int)children.size(); i++) { int jointIndex = children[i]; int parentIndex = _animSkeleton->getParentIndex(jointIndex); - _internalPoseSet._absolutePoses[jointIndex] = + _internalPoseSet._absolutePoses[jointIndex] = _internalPoseSet._absolutePoses[parentIndex] * _internalPoseSet._relativePoses[jointIndex]; } } } -static glm::quat quatLerp(const glm::quat& q1, const glm::quat& q2, float alpha) { - float dot = glm::dot(q1, q2); - glm::quat temp; - if (dot < 0.0f) { - temp = -q2; - } else { - temp = q2; - } - return glm::normalize(glm::lerp(q1, temp, alpha)); -} +bool Rig::calculateElbowPoleVector(int handIndex, int elbowIndex, int armIndex, int oppositeArmIndex, glm::vec3& poleVector) const { + // The resulting Pole Vector is calculated as the sum of a three vectors. + // The first is the vector with direction shoulder-hand. The module of this vector is inversely proportional to the strength of the resulting Pole Vector. + // The second vector is always perpendicular to previous vector and is part of the plane that contains a point located on the horizontal line, + // pointing forward and with height aprox to the avatar head. The position of the horizontal point will be determined by the hands Y component. + // The third vector apply a weighted correction to the resulting pole vector to avoid interpenetration and force a more natural pose. -glm::vec3 Rig::calculateElbowPoleVector(int handIndex, int elbowIndex, int armIndex, int hipsIndex, bool isLeft) const { - AnimPose hipsPose = _externalPoseSet._absolutePoses[hipsIndex]; + AnimPose oppositeArmPose = _externalPoseSet._absolutePoses[oppositeArmIndex]; AnimPose handPose = _externalPoseSet._absolutePoses[handIndex]; - AnimPose elbowPose = _externalPoseSet._absolutePoses[elbowIndex]; AnimPose armPose = _externalPoseSet._absolutePoses[armIndex]; + AnimPose elbowPose = _externalPoseSet._absolutePoses[elbowIndex]; - // ray from hand to arm. - glm::vec3 d = glm::normalize(handPose.trans() - armPose.trans()); + glm::vec3 armToHand = handPose.trans() - armPose.trans(); + glm::vec3 armToElbow = elbowPose.trans() - armPose.trans(); + glm::vec3 elbowToHand = handPose.trans() - elbowPose.trans(); - float sign = isLeft ? 1.0f : -1.0f; + glm::vec3 backVector = oppositeArmPose.trans() - armPose.trans(); + glm::vec3 backCenter = armPose.trans() + 0.5f * backVector; - // form a plane normal to the hips x-axis. - glm::vec3 n = hipsPose.rot() * Vectors::UNIT_X; - glm::vec3 y = hipsPose.rot() * Vectors::UNIT_Y; + glm::vec3 frontVector = glm::normalize(glm::cross(backVector, Vectors::UNIT_Y)); + glm::vec3 topVector = glm::normalize(glm::cross(frontVector, backVector)); - // project d onto this plane - glm::vec3 dProj = d - glm::dot(d, n) * n; + glm::vec3 centerToHand = handPose.trans() - backCenter; + glm::vec3 headCenter = backCenter + glm::length(backVector) * topVector; - // give dProj a bit of offset away from the body. - float avatarScale = extractUniformScale(_modelOffset); - const float LATERAL_OFFSET = 1.0f * avatarScale; - const float VERTICAL_OFFSET = -0.333f * avatarScale; - glm::vec3 dProjWithOffset = dProj + sign * LATERAL_OFFSET * n + y * VERTICAL_OFFSET; + // Make sure is pointing forward + frontVector = frontVector.z < 0 ? -frontVector : frontVector; - // rotate dProj by 90 degrees to get the poleVector. - glm::vec3 poleVector = glm::angleAxis(PI / 2.0f, n) * dProjWithOffset; + float horizontalModule = glm::dot(centerToHand, -topVector); - // blend the wrist oreintation into the pole vector to reduce the painfully bent wrist problem. - glm::quat elbowToHandDelta = handPose.rot() * glm::inverse(elbowPose.rot()); - const float WRIST_POLE_ADJUST_FACTOR = 0.5f; - glm::quat poleAdjust = quatLerp(Quaternions::IDENTITY, elbowToHandDelta, WRIST_POLE_ADJUST_FACTOR); + glm::vec3 headForward = headCenter + glm::max(0.0f, horizontalModule) * frontVector; + glm::vec3 armToHead = headForward - armPose.trans(); - return glm::normalize(poleAdjust * poleVector); + float armToHandDistance = glm::length(armToHand); + float armToElbowDistance = glm::length(armToElbow); + float elbowToHandDistance = glm::length(elbowToHand); + float armTotalDistance = armToElbowDistance + elbowToHandDistance; + + glm::vec3 armToHandDir = armToHand / armToHandDistance; + glm::vec3 armToHeadPlaneNormal = glm::cross(armToHead, armToHandDir); + + // How much the hand is reaching for the opposite side + float oppositeProjection = glm::dot(armToHandDir, glm::normalize(backVector)); + + bool isCrossed = glm::dot(centerToHand, backVector) > 0; + bool isBehind = glm::dot(frontVector, armToHand) < 0; + // Don't use pole vector when the hands are behind the back and the arms are not crossed + if (isBehind && !isCrossed) { + return false; + } + + // The strenght of the resulting pole determined by the arm flex. + float armFlexCoeficient = armToHandDistance / armTotalDistance; + glm::vec3 attenuationVector = armFlexCoeficient * armToHandDir; + // Pole vector is perpendicular to the shoulder-hand direction and located on the plane that contains the head-forward line + glm::vec3 fullPoleVector = glm::normalize(glm::cross(armToHeadPlaneNormal, armToHandDir)); + + // Push elbow forward when hand reaches opposite side + glm::vec3 correctionVector = glm::vec3(0, 0, 0); + + const float FORWARD_TRIGGER_PERCENTAGE = 0.2f; + const float FORWARD_CORRECTOR_WEIGHT = 2.3f; + + float elbowForwardTrigger = FORWARD_TRIGGER_PERCENTAGE * armToHandDistance; + + if (oppositeProjection > -elbowForwardTrigger) { + float forwardAmount = FORWARD_CORRECTOR_WEIGHT * (elbowForwardTrigger + oppositeProjection); + correctionVector = forwardAmount * frontVector; + } + poleVector = glm::normalize(attenuationVector + fullPoleVector + correctionVector); + return true; } +// returns a poleVector for the knees that is a blend of the foot and the hips. +// targetFootPose is in rig space +// result poleVector is also in rig space. glm::vec3 Rig::calculateKneePoleVector(int footJointIndex, int kneeIndex, int upLegIndex, int hipsIndex, const AnimPose& targetFootPose) const { + const float FOOT_THETA = 0.8969f; // 51.39 degrees + const glm::vec3 localFootForward(0.0f, cosf(FOOT_THETA), sinf(FOOT_THETA)); + glm::vec3 footForward = targetFootPose.rot() * localFootForward; AnimPose hipsPose = _externalPoseSet._absolutePoses[hipsIndex]; - AnimPose footPose = targetFootPose; - AnimPose kneePose = _externalPoseSet._absolutePoses[kneeIndex]; - AnimPose upLegPose = _externalPoseSet._absolutePoses[upLegIndex]; + glm::vec3 hipsForward = hipsPose.rot() * Vectors::UNIT_Z; - // ray from foot to upLeg - glm::vec3 d = glm::normalize(footPose.trans() - upLegPose.trans()); - - // form a plane normal to the hips x-axis - glm::vec3 n = hipsPose.rot() * Vectors::UNIT_X; - - // project d onto this plane - glm::vec3 dProj = d - glm::dot(d, n) * n; - - // rotate dProj by 90 degrees to get the poleVector. - glm::vec3 poleVector = glm::angleAxis(-PI / 2.0f, n) * dProj; - - // blend the foot oreintation into the pole vector - glm::quat kneeToFootDelta = footPose.rot() * glm::inverse(kneePose.rot()); - const float WRIST_POLE_ADJUST_FACTOR = 0.5f; - glm::quat poleAdjust = quatLerp(Quaternions::IDENTITY, kneeToFootDelta, WRIST_POLE_ADJUST_FACTOR); - - return glm::normalize(poleAdjust * poleVector); + return glm::normalize(lerp(hipsForward, footForward, 0.75f)); } void Rig::updateFromControllerParameters(const ControllerParameters& params, float dt) { @@ -1542,7 +1613,7 @@ void Rig::updateFromControllerParameters(const ControllerParameters& params, flo _animVars.set("isTalking", params.isTalking); _animVars.set("notIsTalking", !params.isTalking); - bool headEnabled = params.primaryControllerFlags[PrimaryControllerType_Head] & (uint8_t)ControllerFlags::Enabled; + _headEnabled = params.primaryControllerFlags[PrimaryControllerType_Head] & (uint8_t)ControllerFlags::Enabled; bool leftHandEnabled = params.primaryControllerFlags[PrimaryControllerType_LeftHand] & (uint8_t)ControllerFlags::Enabled; bool rightHandEnabled = params.primaryControllerFlags[PrimaryControllerType_RightHand] & (uint8_t)ControllerFlags::Enabled; bool hipsEnabled = params.primaryControllerFlags[PrimaryControllerType_Hips] & (uint8_t)ControllerFlags::Enabled; @@ -1554,18 +1625,18 @@ void Rig::updateFromControllerParameters(const ControllerParameters& params, flo bool rightArmEnabled = params.secondaryControllerFlags[SecondaryControllerType_RightArm] & (uint8_t)ControllerFlags::Enabled; glm::mat4 sensorToRigMatrix = glm::inverse(params.rigToSensorMatrix); - updateHead(headEnabled, hipsEnabled, params.primaryControllerPoses[PrimaryControllerType_Head]); + updateHead(_headEnabled, hipsEnabled, params.primaryControllerPoses[PrimaryControllerType_Head]); - updateHands(leftHandEnabled, rightHandEnabled, hipsEnabled, hipsEstimated, leftArmEnabled, rightArmEnabled, dt, + updateHands(leftHandEnabled, rightHandEnabled, hipsEnabled, hipsEstimated, leftArmEnabled, rightArmEnabled, _headEnabled, dt, params.primaryControllerPoses[PrimaryControllerType_LeftHand], params.primaryControllerPoses[PrimaryControllerType_RightHand], params.hipsShapeInfo, params.spineShapeInfo, params.spine1ShapeInfo, params.spine2ShapeInfo, params.rigToSensorMatrix, sensorToRigMatrix); - updateFeet(leftFootEnabled, rightFootEnabled, + updateFeet(leftFootEnabled, rightFootEnabled, _headEnabled, params.primaryControllerPoses[PrimaryControllerType_LeftFoot], params.primaryControllerPoses[PrimaryControllerType_RightFoot], params.rigToSensorMatrix, sensorToRigMatrix); - if (headEnabled) { + if (_headEnabled) { // Blend IK chains toward the joint limit centers, this should stablize head and hand ik. _animVars.set("solutionSource", (int)AnimInverseKinematics::SolutionSource::RelaxToLimitCenterPoses); } else { diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index ffa3a128b9..accbcccbbc 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -75,6 +75,10 @@ public: }; struct ControllerParameters { + ControllerParameters() { + memset(primaryControllerFlags, 0, NumPrimaryControllerTypes); + memset(secondaryControllerFlags, 0, NumPrimaryControllerTypes); + } glm::mat4 rigToSensorMatrix; AnimPose primaryControllerPoses[NumPrimaryControllerTypes]; // rig space uint8_t primaryControllerFlags[NumPrimaryControllerTypes]; @@ -218,6 +222,9 @@ public: // input assumed to be in rig space void computeHeadFromHMD(const AnimPose& hmdPose, glm::vec3& headPositionOut, glm::quat& headOrientationOut) const; + const std::map getAnimStack() { return _animNode->getAnimStack(); } + + void toggleSmoothPoleVectors() { _smoothPoleVectors = !_smoothPoleVectors; }; signals: void onLoadComplete(); @@ -229,22 +236,24 @@ protected: void updateHead(bool headEnabled, bool hipsEnabled, const AnimPose& headMatrix); void updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnabled, bool hipsEstimated, - bool leftArmEnabled, bool rightArmEnabled, float dt, + bool leftArmEnabled, bool rightArmEnabled, bool headEnabled, float dt, const AnimPose& leftHandPose, const AnimPose& rightHandPose, const FBXJointShapeInfo& hipsShapeInfo, const FBXJointShapeInfo& spineShapeInfo, const FBXJointShapeInfo& spine1ShapeInfo, const FBXJointShapeInfo& spine2ShapeInfo, const glm::mat4& rigToSensorMatrix, const glm::mat4& sensorToRigMatrix); - void updateFeet(bool leftFootEnabled, bool rightFootEnabled, const AnimPose& leftFootPose, const AnimPose& rightFootPose, + void updateFeet(bool leftFootEnabled, bool rightFootEnabled, bool headEnabled, + const AnimPose& leftFootPose, const AnimPose& rightFootPose, const glm::mat4& rigToSensorMatrix, const glm::mat4& sensorToRigMatrix); void updateEyeJoint(int index, const glm::vec3& modelTranslation, const glm::quat& modelRotation, const glm::vec3& lookAt, const glm::vec3& saccade); void calcAnimAlpha(float speed, const std::vector& referenceSpeeds, float* alphaOut) const; - glm::vec3 calculateElbowPoleVector(int handIndex, int elbowIndex, int armIndex, int hipsIndex, bool isLeft) const; + bool calculateElbowPoleVector(int handIndex, int elbowIndex, int armIndex, int oppositeArmIndex, glm::vec3& poleVector) const; glm::vec3 calculateKneePoleVector(int footJointIndex, int kneeJoint, int upLegIndex, int hipsIndex, const AnimPose& targetFootPose) const; glm::vec3 deflectHandFromTorso(const glm::vec3& handPosition, const FBXJointShapeInfo& hipsShapeInfo, const FBXJointShapeInfo& spineShapeInfo, const FBXJointShapeInfo& spine1ShapeInfo, const FBXJointShapeInfo& spine2ShapeInfo) const; + AnimPose _modelOffset; // model to rig space AnimPose _geometryOffset; // geometry to model space (includes unit offset & fst offsets) AnimPose _invGeometryOffset; @@ -292,6 +301,7 @@ protected: std::shared_ptr _animSkeleton; std::unique_ptr _animLoader; AnimVariantMap _animVars; + enum class RigRole { Idle = 0, Turn, @@ -368,13 +378,16 @@ protected: glm::vec3 _prevLeftFootPoleVector { Vectors::UNIT_Z }; // sensor space bool _prevLeftFootPoleVectorValid { false }; - glm::vec3 _prevRightHandPoleVector { -Vectors::UNIT_Z }; // sensor space - bool _prevRightHandPoleVectorValid { false }; + glm::vec3 _prevRightHandPoleVector{ -Vectors::UNIT_Z }; // sensor space + bool _prevRightHandPoleVectorValid{ false }; - glm::vec3 _prevLeftHandPoleVector { -Vectors::UNIT_Z }; // sensor space - bool _prevLeftHandPoleVectorValid { false }; + glm::vec3 _prevLeftHandPoleVector{ -Vectors::UNIT_Z }; // sensor space + bool _prevLeftHandPoleVectorValid{ false }; + + bool _smoothPoleVectors { false }; int _rigId; + bool _headEnabled { false }; }; #endif /* defined(__hifi__Rig__) */ diff --git a/libraries/audio-client/CMakeLists.txt b/libraries/audio-client/CMakeLists.txt index d419a2fb7a..6ca7962c39 100644 --- a/libraries/audio-client/CMakeLists.txt +++ b/libraries/audio-client/CMakeLists.txt @@ -1,5 +1,8 @@ set(TARGET_NAME audio-client) -setup_hifi_library(Network Multimedia) +if (ANDROID) + set(PLATFORM_QT_COMPONENTS AndroidExtras) +endif () +setup_hifi_library(Network Multimedia ${PLATFORM_QT_COMPONENTS}) link_hifi_libraries(audio plugins) include_hifi_library_headers(shared) include_hifi_library_headers(networking) diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index a6f0416a30..6a9363f309 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -52,6 +52,11 @@ #include "AudioLogging.h" #include "AudioHelpers.h" +#if defined(Q_OS_ANDROID) +#define VOICE_RECOGNITION "voicerecognition" +#include +#endif + const int AudioClient::MIN_BUFFER_FRAMES = 1; const int AudioClient::MAX_BUFFER_FRAMES = 20; @@ -60,7 +65,7 @@ static const int RECEIVED_AUDIO_STREAM_CAPACITY_FRAMES = 100; #if defined(Q_OS_ANDROID) static const int CHECK_INPUT_READS_MSECS = 2000; -static const int MIN_READS_TO_CONSIDER_INPUT_ALIVE = 100; +static const int MIN_READS_TO_CONSIDER_INPUT_ALIVE = 10; #endif static const auto DEFAULT_POSITION_GETTER = []{ return Vectors::ZERO; }; @@ -235,7 +240,7 @@ AudioClient::AudioClient() : // start a thread to detect any device changes _checkDevicesTimer = new QTimer(this); - connect(_checkDevicesTimer, &QTimer::timeout, [this] { + connect(_checkDevicesTimer, &QTimer::timeout, this, [this] { QtConcurrent::run(QThreadPool::globalInstance(), [this] { checkDevices(); }); }); const unsigned long DEVICE_CHECK_INTERVAL_MSECS = 2 * 1000; @@ -243,7 +248,7 @@ AudioClient::AudioClient() : // start a thread to detect peak value changes _checkPeakValuesTimer = new QTimer(this); - connect(_checkPeakValuesTimer, &QTimer::timeout, [this] { + connect(_checkPeakValuesTimer, &QTimer::timeout, this, [this] { QtConcurrent::run(QThreadPool::globalInstance(), [this] { checkPeakValues(); }); }); const unsigned long PEAK_VALUES_CHECK_INTERVAL_MSECS = 50; @@ -269,31 +274,14 @@ AudioClient::~AudioClient() { } void AudioClient::customDeleter() { - deleteLater(); -} - -void AudioClient::cleanupBeforeQuit() { - // FIXME: this should be put in customDeleter, but there is still a reference to this when it is called, - // so this must be explicitly, synchronously stopped - static ConditionalGuard guard; - if (QThread::currentThread() != thread()) { - // This will likely be called from the main thread, but we don't want to do blocking queued calls - // from the main thread, so we use a normal auto-connection invoke, and then use a conditional to wait - // for completion - // The effect is the same, yes, but we actually want to avoid the use of Qt::BlockingQueuedConnection - // in the code - QMetaObject::invokeMethod(this, "cleanupBeforeQuit"); - guard.wait(); - return; - } - #if defined(Q_OS_ANDROID) _shouldRestartInputSetup = false; #endif stop(); _checkDevicesTimer->stop(); _checkPeakValuesTimer->stop(); - guard.trigger(); + + deleteLater(); } void AudioClient::handleMismatchAudioFormat(SharedNodePointer node, const QString& currentCodec, const QString& recievedCodec) { @@ -461,7 +449,16 @@ QAudioDeviceInfo defaultAudioDeviceForMode(QAudio::Mode mode) { return getNamedAudioDeviceForMode(mode, deviceName); #endif - +#if defined (Q_OS_ANDROID) + if (mode == QAudio::AudioInput) { + auto inputDevices = QAudioDeviceInfo::availableDevices(QAudio::AudioInput); + for (auto inputDevice : inputDevices) { + if (inputDevice.deviceName() == VOICE_RECOGNITION) { + return inputDevice; + } + } + } +#endif // fallback for failed lookup is the default device return (mode == QAudio::AudioInput) ? QAudioDeviceInfo::defaultInputDevice() : QAudioDeviceInfo::defaultOutputDevice(); } @@ -635,9 +632,7 @@ void AudioClient::start() { qCDebug(audioclient) << "The closest format available is" << outputDeviceInfo.nearestFormat(_desiredOutputFormat); } #if defined(Q_OS_ANDROID) - connect(&_checkInputTimer, &QTimer::timeout, [this] { - checkInputTimeout(); - }); + connect(&_checkInputTimer, &QTimer::timeout, this, &AudioClient::checkInputTimeout); _checkInputTimer.start(CHECK_INPUT_READS_MSECS); #endif } @@ -651,6 +646,7 @@ void AudioClient::stop() { switchOutputToAudioDevice(QAudioDeviceInfo(), true); #if defined(Q_OS_ANDROID) _checkInputTimer.stop(); + disconnect(&_checkInputTimer, &QTimer::timeout, 0, 0); #endif } @@ -1558,9 +1554,7 @@ bool AudioClient::switchInputToAudioDevice(const QAudioDeviceInfo inputDeviceInf #if defined(Q_OS_ANDROID) if (_audioInput) { _shouldRestartInputSetup = true; - connect(_audioInput, &QAudioInput::stateChanged, [this](QAudio::State state) { - audioInputStateChanged(state); - }); + connect(_audioInput, &QAudioInput::stateChanged, this, &AudioClient::audioInputStateChanged); } #endif _inputDevice = _audioInput->start(); @@ -1764,7 +1758,7 @@ bool AudioClient::switchOutputToAudioDevice(const QAudioDeviceInfo outputDeviceI _outputScratchBuffer = new int16_t[_outputPeriod]; // size local output mix buffer based on resampled network frame size - int networkPeriod = _localToOutputResampler->getMaxOutput(AudioConstants::NETWORK_FRAME_SAMPLES_STEREO); + int networkPeriod = _localToOutputResampler ? _localToOutputResampler->getMaxOutput(AudioConstants::NETWORK_FRAME_SAMPLES_STEREO) : AudioConstants::NETWORK_FRAME_SAMPLES_STEREO; _localOutputMixBuffer = new float[networkPeriod]; // local period should be at least twice the output period, @@ -1838,7 +1832,9 @@ const float AudioClient::CALLBACK_ACCELERATOR_RATIO = IsWindows8OrGreater() ? 1. const float AudioClient::CALLBACK_ACCELERATOR_RATIO = 2.0f; #endif -#ifdef Q_OS_LINUX +#ifdef Q_OS_ANDROID +const float AudioClient::CALLBACK_ACCELERATOR_RATIO = 0.5f; +#elif defined(Q_OS_LINUX) const float AudioClient::CALLBACK_ACCELERATOR_RATIO = 2.0f; #endif diff --git a/libraries/audio-client/src/AudioClient.h b/libraries/audio-client/src/AudioClient.h index 9ee7bcfeba..8599c990a3 100644 --- a/libraries/audio-client/src/AudioClient.h +++ b/libraries/audio-client/src/AudioClient.h @@ -171,7 +171,6 @@ public: public slots: void start(); void stop(); - void cleanupBeforeQuit(); void handleAudioEnvironmentDataPacket(QSharedPointer message); void handleAudioDataPacket(QSharedPointer message); diff --git a/libraries/audio-client/src/AudioIOStats.cpp b/libraries/audio-client/src/AudioIOStats.cpp index 1717ad1f9c..1e45622fdc 100644 --- a/libraries/audio-client/src/AudioIOStats.cpp +++ b/libraries/audio-client/src/AudioIOStats.cpp @@ -95,8 +95,6 @@ void AudioIOStats::processStreamStatsPacket(QSharedPointer mess } void AudioIOStats::publish() { - auto audioIO = DependencyManager::get(); - // call _receivedAudioStream's per-second callback _receivedAudioStream->perSecondCallbackForUpdatingStats(); diff --git a/libraries/audio/src/AudioEffectOptions.cpp b/libraries/audio/src/AudioEffectOptions.cpp index edb0ff52ae..873ce7df87 100644 --- a/libraries/audio/src/AudioEffectOptions.cpp +++ b/libraries/audio/src/AudioEffectOptions.cpp @@ -59,28 +59,29 @@ static void setOption(QScriptValue arguments, const QString name, float defaultV } /**jsdoc + * Reverberation options that can be used to initialize an {@link AudioEffectOptions} object when created. * @typedef {object} AudioEffectOptions.ReverbOptions - * @property {number} bandwidth - * @property {number} preDelay - * @property {number} lateDelay - * @property {number} reverbTime - * @property {number} earlyDiffusion - * @property {number} lateDiffusion - * @property {number} roomSize - * @property {number} density - * @property {number} bassMult - * @property {number} bassFreq - * @property {number} highGain - * @property {number} highFreq - * @property {number} modRate - * @property {number} modDepth - * @property {number} earlyGain - * @property {number} lateGain - * @property {number} earlyMixLeft - * @property {number} earlyMixRight - * @property {number} lateMixLeft - * @property {number} lateMixRight - * @property {number} wetDryMix + * @property {number} bandwidth=10000 - The corner frequency (Hz) of the low-pass filter at reverb input. + * @property {number} preDelay=20 - The delay (milliseconds) between dry signal and the onset of early reflections. + * @property {number} lateDelay=0 - The delay (milliseconds) between early reflections and the onset of reverb tail. + * @property {number} reverbTime=2 - The time (seconds) for the reverb tail to decay by 60dB, also known as RT60. + * @property {number} earlyDiffusion=100 - Adjusts the buildup of echo density in the early reflections, normally 100%. + * @property {number} lateDiffusion=100 - Adjusts the buildup of echo density in the reverb tail, normally 100%. + * @property {number} roomSize=50 - The apparent room size, from small (0%) to large (100%). + * @property {number} density=100 - Adjusts the echo density in the reverb tail, normally 100%. + * @property {number} bassMult=1.5 - Adjusts the bass-frequency reverb time, as multiple of reverbTime. + * @property {number} bassFreq=250 - The crossover frequency (Hz) for the onset of bassMult. + * @property {number} highGain=-6 - Reduces the high-frequency reverb time, as attenuation (dB). + * @property {number} highFreq=3000 - The crossover frequency (Hz) for the onset of highGain. + * @property {number} modRate=2.3 - The rate of modulation (Hz) of the LFO-modulated delay lines. + * @property {number} modDepth=50 - The depth of modulation (percent) of the LFO-modulated delay lines. + * @property {number} earlyGain=0 - Adjusts the relative level (dB) of the early reflections. + * @property {number} lateGain=0 - Adjusts the relative level (dB) of the reverb tail. + * @property {number} earlyMixLeft=20 - The apparent distance of the source (percent) in the early reflections. + * @property {number} earlyMixRight=20 - The apparent distance of the source (percent) in the early reflections. + * @property {number} lateMixLeft=90 - The apparent distance of the source (percent) in the reverb tail. + * @property {number} lateMixRight=90 - The apparent distance of the source (percent) in the reverb tail. + * @property {number} wetDryMix=50 - Adjusts the wet/dry ratio, from completely dry (0%) to completely wet (100%). */ AudioEffectOptions::AudioEffectOptions(QScriptValue arguments) { setOption(arguments, BANDWIDTH_HANDLE, BANDWIDTH_DEFAULT, _bandwidth); diff --git a/libraries/audio/src/AudioEffectOptions.h b/libraries/audio/src/AudioEffectOptions.h index 1afd4e21be..4bc7957142 100644 --- a/libraries/audio/src/AudioEffectOptions.h +++ b/libraries/audio/src/AudioEffectOptions.h @@ -16,35 +16,39 @@ #include /**jsdoc + * Audio effect options used by the {@link Audio} API. + * + *

Create using new AudioEffectOptions(reverbOptions).

+ * * @class AudioEffectOptions - * @param {AudioEffectOptions.ReverbOptions} [reverbOptions=null] + * @param {AudioEffectOptions.ReverbOptions} [reverbOptions=null] - Reverberation options. * * @hifi-interface * @hifi-client-entity * @hifi-server-entity * @hifi-assignment-client * - * @property {number} bandwidth=10000 - * @property {number} preDelay=20 - * @property {number} lateDelay=0 - * @property {number} reverbTime=2 - * @property {number} earlyDiffusion=100 - * @property {number} lateDiffusion=100 - * @property {number} roomSize=50 - * @property {number} density=100 - * @property {number} bassMult=1.5 - * @property {number} bassFreq=250 - * @property {number} highGain=-6 - * @property {number} highFreq=3000 - * @property {number} modRate=2.3 - * @property {number} modDepth=50 - * @property {number} earlyGain=0 - * @property {number} lateGain=0 - * @property {number} earlyMixLeft=20 - * @property {number} earlyMixRight=20 - * @property {number} lateMixLeft=90 - * @property {number} lateMixRight=90 - * @property {number} wetDryMix=50 + * @property {number} bandwidth=10000 - The corner frequency (Hz) of the low-pass filter at reverb input. + * @property {number} preDelay=20 - The delay (milliseconds) between dry signal and the onset of early reflections. + * @property {number} lateDelay=0 - The delay (milliseconds) between early reflections and the onset of reverb tail. + * @property {number} reverbTime=2 - The time (seconds) for the reverb tail to decay by 60dB, also known as RT60. + * @property {number} earlyDiffusion=100 - Adjusts the buildup of echo density in the early reflections, normally 100%. + * @property {number} lateDiffusion=100 - Adjusts the buildup of echo density in the reverb tail, normally 100%. + * @property {number} roomSize=50 - The apparent room size, from small (0%) to large (100%). + * @property {number} density=100 - Adjusts the echo density in the reverb tail, normally 100%. + * @property {number} bassMult=1.5 - Adjusts the bass-frequency reverb time, as multiple of reverbTime. + * @property {number} bassFreq=250 - The crossover frequency (Hz) for the onset of bassMult. + * @property {number} highGain=-6 - Reduces the high-frequency reverb time, as attenuation (dB). + * @property {number} highFreq=3000 - The crossover frequency (Hz) for the onset of highGain. + * @property {number} modRate=2.3 - The rate of modulation (Hz) of the LFO-modulated delay lines. + * @property {number} modDepth=50 - The depth of modulation (percent) of the LFO-modulated delay lines. + * @property {number} earlyGain=0 - Adjusts the relative level (dB) of the early reflections. + * @property {number} lateGain=0 - Adjusts the relative level (dB) of the reverb tail. + * @property {number} earlyMixLeft=20 - The apparent distance of the source (percent) in the early reflections. + * @property {number} earlyMixRight=20 - The apparent distance of the source (percent) in the early reflections. + * @property {number} lateMixLeft=90 - The apparent distance of the source (percent) in the reverb tail. + * @property {number} lateMixRight=90 - The apparent distance of the source (percent) in the reverb tail. + * @property {number} wetDryMix=50 - Adjusts the wet/dry ratio, from completely dry (0%) to completely wet (100%). */ class AudioEffectOptions : public QObject { diff --git a/libraries/audio/src/AudioHRTF.cpp b/libraries/audio/src/AudioHRTF.cpp index c0751c6a20..b639301404 100644 --- a/libraries/audio/src/AudioHRTF.cpp +++ b/libraries/audio/src/AudioHRTF.cpp @@ -240,7 +240,7 @@ static void FIR_1x4_SSE(float* src, float* dst0, float* dst1, float* dst2, float float* ps = &src[i - HRTF_TAPS + 1]; // process forwards - assert(HRTF_TAPS % 4 == 0); + static_assert(HRTF_TAPS % 4 == 0, "HRTF_TAPS must be a multiple of 4"); for (int k = 0; k < HRTF_TAPS; k += 4) { @@ -276,23 +276,8 @@ static void FIR_1x4_SSE(float* src, float* dst0, float* dst1, float* dst2, float } } -// -// Runtime CPU dispatch -// - -#include "CPUDetect.h" - -void FIR_1x4_AVX2(float* src, float* dst0, float* dst1, float* dst2, float* dst3, float coef[4][HRTF_TAPS], int numFrames); -void FIR_1x4_AVX512(float* src, float* dst0, float* dst1, float* dst2, float* dst3, float coef[4][HRTF_TAPS], int numFrames); - -static void FIR_1x4(float* src, float* dst0, float* dst1, float* dst2, float* dst3, float coef[4][HRTF_TAPS], int numFrames) { - - static auto f = cpuSupportsAVX512() ? FIR_1x4_AVX512 : (cpuSupportsAVX2() ? FIR_1x4_AVX2 : FIR_1x4_SSE); - (*f)(src, dst0, dst1, dst2, dst3, coef, numFrames); // dispatch -} - // 4 channel planar to interleaved -static void interleave_4x4(float* src0, float* src1, float* src2, float* src3, float* dst, int numFrames) { +static void interleave_4x4_SSE(float* src0, float* src1, float* src2, float* src3, float* dst, int numFrames) { assert(numFrames % 4 == 0); @@ -323,7 +308,7 @@ static void interleave_4x4(float* src0, float* src1, float* src2, float* src3, f // process 2 cascaded biquads on 4 channels (interleaved) // biquads computed in parallel, by adding one sample of delay -static void biquad2_4x4(float* src, float* dst, float coef[5][8], float state[3][8], int numFrames) { +static void biquad2_4x4_SSE(float* src, float* dst, float coef[5][8], float state[3][8], int numFrames) { // enable flush-to-zero mode to prevent denormals unsigned int ftz = _MM_GET_FLUSH_ZERO_MODE(); @@ -388,7 +373,7 @@ static void biquad2_4x4(float* src, float* dst, float coef[5][8], float state[3] } // crossfade 4 inputs into 2 outputs with accumulation (interleaved) -static void crossfade_4x2(float* src, float* dst, const float* win, int numFrames) { +static void crossfade_4x2_SSE(float* src, float* dst, const float* win, int numFrames) { assert(numFrames % 4 == 0); @@ -435,12 +420,12 @@ static void crossfade_4x2(float* src, float* dst, const float* win, int numFrame } // linear interpolation with gain -static void interpolate(float* dst, const float* src0, const float* src1, float frac, float gain) { +static void interpolate_SSE(const float* src0, const float* src1, float* dst, float frac, float gain) { __m128 f0 = _mm_set1_ps(gain * (1.0f - frac)); __m128 f1 = _mm_set1_ps(gain * frac); - assert(HRTF_TAPS % 4 == 0); + static_assert(HRTF_TAPS % 4 == 0, "HRTF_TAPS must be a multiple of 4"); for (int k = 0; k < HRTF_TAPS; k += 4) { @@ -453,6 +438,44 @@ static void interpolate(float* dst, const float* src0, const float* src1, float } } +// +// Runtime CPU dispatch +// + +#include "CPUDetect.h" + +void FIR_1x4_AVX2(float* src, float* dst0, float* dst1, float* dst2, float* dst3, float coef[4][HRTF_TAPS], int numFrames); +void FIR_1x4_AVX512(float* src, float* dst0, float* dst1, float* dst2, float* dst3, float coef[4][HRTF_TAPS], int numFrames); +void interleave_4x4_AVX2(float* src0, float* src1, float* src2, float* src3, float* dst, int numFrames); +void biquad2_4x4_AVX2(float* src, float* dst, float coef[5][8], float state[3][8], int numFrames); +void crossfade_4x2_AVX2(float* src, float* dst, const float* win, int numFrames); +void interpolate_AVX2(const float* src0, const float* src1, float* dst, float frac, float gain); + +static void FIR_1x4(float* src, float* dst0, float* dst1, float* dst2, float* dst3, float coef[4][HRTF_TAPS], int numFrames) { + static auto f = cpuSupportsAVX512() ? FIR_1x4_AVX512 : (cpuSupportsAVX2() ? FIR_1x4_AVX2 : FIR_1x4_SSE); + (*f)(src, dst0, dst1, dst2, dst3, coef, numFrames); // dispatch +} + +static void interleave_4x4(float* src0, float* src1, float* src2, float* src3, float* dst, int numFrames) { + static auto f = cpuSupportsAVX2() ? interleave_4x4_AVX2 : interleave_4x4_SSE; + (*f)(src0, src1, src2, src3, dst, numFrames); // dispatch +} + +static void biquad2_4x4(float* src, float* dst, float coef[5][8], float state[3][8], int numFrames) { + static auto f = cpuSupportsAVX2() ? biquad2_4x4_AVX2 : biquad2_4x4_SSE; + (*f)(src, dst, coef, state, numFrames); // dispatch +} + +static void crossfade_4x2(float* src, float* dst, const float* win, int numFrames) { + static auto f = cpuSupportsAVX2() ? crossfade_4x2_AVX2 : crossfade_4x2_SSE; + (*f)(src, dst, win, numFrames); // dispatch +} + +static void interpolate(const float* src0, const float* src1, float* dst, float frac, float gain) { + static auto f = cpuSupportsAVX2() ? interpolate_AVX2 : interpolate_SSE; + (*f)(src0, src1, dst, frac, gain); // dispatch +} + #else // portable reference code // 1 channel input, 4 channel output @@ -489,7 +512,7 @@ static void FIR_1x4(float* src, float* dst0, float* dst1, float* dst2, float* ds float* ps = &src[i - HRTF_TAPS + 1]; // process forwards - assert(HRTF_TAPS % 4 == 0); + static_assert(HRTF_TAPS % 4 == 0, "HRTF_TAPS must be a multiple of 4"); for (int k = 0; k < HRTF_TAPS; k += 4) { @@ -715,7 +738,7 @@ static void crossfade_4x2(float* src, float* dst, const float* win, int numFrame } // linear interpolation with gain -static void interpolate(float* dst, const float* src0, const float* src1, float frac, float gain) { +static void interpolate(const float* src0, const float* src1, float* dst, float frac, float gain) { float f0 = gain * (1.0f - frac); float f1 = gain * frac; @@ -840,7 +863,7 @@ static void nearFieldGainCorrection(float azimuth, float distance, float& gainL, // normalized distance factor = [0,1] as distance = [HRTF_NEARFIELD_MAX,HRTF_HEAD_RADIUS] assert(distance < HRTF_NEARFIELD_MAX); assert(distance > HRTF_HEAD_RADIUS); - float d = (HRTF_NEARFIELD_MAX - distance) * ( 1.0f / (HRTF_NEARFIELD_MAX - HRTF_HEAD_RADIUS)); + float d = (HRTF_NEARFIELD_MAX - distance) * (1.0f / (HRTF_NEARFIELD_MAX - HRTF_HEAD_RADIUS)); // angle of incidence at each ear float angleL = azimuth + HALFPI; @@ -919,6 +942,9 @@ static void azimuthToIndex(float azimuth, int& index0, int& index1, float& frac) index1 = index0 + 1; frac = azimuth - (float)index0; + if (index0 >= HRTF_AZIMUTHS) { + index0 -= HRTF_AZIMUTHS; + } if (index1 >= HRTF_AZIMUTHS) { index1 -= HRTF_AZIMUTHS; } @@ -964,8 +990,8 @@ static void setFilters(float firCoef[4][HRTF_TAPS], float bqCoef[5][8], int dela azimuthToIndex(azimuth, az0, az1, frac); // interpolate FIR - interpolate(firCoef[channel+0], ir_table_table[index][azL0][0], ir_table_table[index][azL1][0], fracL, gain * gainL); - interpolate(firCoef[channel+1], ir_table_table[index][azR0][1], ir_table_table[index][azR1][1], fracR, gain * gainR); + interpolate(ir_table_table[index][azL0][0], ir_table_table[index][azL1][0], firCoef[channel+0], fracL, gain * gainL); + interpolate(ir_table_table[index][azR0][1], ir_table_table[index][azR1][1], firCoef[channel+1], fracR, gain * gainR); // interpolate ITD float itd = (1.0f - frac) * itd_table_table[index][az0] + frac * itd_table_table[index][az1]; diff --git a/libraries/audio/src/AudioInjectorOptions.cpp b/libraries/audio/src/AudioInjectorOptions.cpp index 2f82cae137..0f4ab7ff42 100644 --- a/libraries/audio/src/AudioInjectorOptions.cpp +++ b/libraries/audio/src/AudioInjectorOptions.cpp @@ -45,6 +45,23 @@ QScriptValue injectorOptionsToScriptValue(QScriptEngine* engine, const AudioInje return obj; } +/**jsdoc + * Configures how an audio injector plays its audio. + * @typedef {object} AudioInjector.AudioInjectorOptions + * @property {Vec3} position=Vec3.ZERO - The position in the domain to play the sound. + * @property {Quat} orientation=Quat.IDENTITY - The orientation in the domain to play the sound in. + * @property {number} volume=1.0 - Playback volume, between 0.0 and 1.0. + * @property {number} pitch=1.0 - Alter the pitch of the sound, within +/- 2 octaves. The value is the relative sample rate to + * resample the sound at, range 0.062516.0. A value of 0.0625 lowers the + * pitch by 2 octaves; 1.0 is no change in pitch; 16.0 raises the pitch by 2 octaves. + * @property {boolean} loop=false - If true, the sound is played repeatedly until playback is stopped. + * @property {number} secondOffset=0 - Starts playback from a specified time (seconds) within the sound file, ≥ + * 0. + * @property {boolean} localOnly=false - IF true, the sound is played back locally on the client rather than to + * others via the audio mixer. + * @property {boolean} ignorePenumbra=false - Deprecated: This property is deprecated and will be + * removed. + */ void injectorOptionsFromScriptValue(const QScriptValue& object, AudioInjectorOptions& injectorOptions) { if (!object.isObject()) { qWarning() << "Audio injector options is not an object."; diff --git a/libraries/audio/src/Sound.h b/libraries/audio/src/Sound.h index 061c4a2417..348600e4ae 100644 --- a/libraries/audio/src/Sound.h +++ b/libraries/audio/src/Sound.h @@ -79,6 +79,14 @@ private: typedef QSharedPointer SharedSoundPointer; /**jsdoc + * An audio resource, created by {@link SoundCache.getSound}, to be played back using {@link Audio.playSound}. + *

Supported formats:

+ *
    + *
  • WAV: 16-bit uncompressed WAV at any sample rate, with 1 (mono), 2(stereo), or 4 (ambisonic) channels.
  • + *
  • MP3: Mono or stereo, at any sample rate.
  • + *
  • RAW: 48khz 16-bit mono or stereo. Filename must include ".stereo" to be interpreted as stereo.
  • + *
+ * * @class SoundObject * * @hifi-interface @@ -86,8 +94,9 @@ typedef QSharedPointer SharedSoundPointer; * @hifi-server-entity * @hifi-assignment-client * - * @property {boolean} downloaded - * @property {number} duration + * @property {boolean} downloaded - true if the sound has been downloaded and is ready to be played, otherwise + * false. + * @property {number} duration - The duration of the sound, in seconds. */ class SoundScriptingInterface : public QObject { Q_OBJECT @@ -103,6 +112,7 @@ public: float getDuration() { return _sound->getDuration(); } /**jsdoc + * Triggered when the sound has been downloaded and is ready to be played. * @function SoundObject.ready * @returns {Signal} */ diff --git a/libraries/audio/src/SoundCache.h b/libraries/audio/src/SoundCache.h index 4352b1d459..64d392a41d 100644 --- a/libraries/audio/src/SoundCache.h +++ b/libraries/audio/src/SoundCache.h @@ -16,73 +16,13 @@ #include "Sound.h" -/// Scriptable interface for sound loading. class SoundCache : public ResourceCache, public Dependency { Q_OBJECT SINGLETON_DEPENDENCY public: - - // Properties are copied over from ResourceCache (see ResourceCache.h for reason). - - /**jsdoc - * API to manage sound cache resources. - * @namespace SoundCache - * - * @hifi-interface - * @hifi-client-entity - * @hifi-server-entity - * @hifi-assignment-client - * - * @property {number} numTotal - Total number of total resources. Read-only. - * @property {number} numCached - Total number of cached resource. Read-only. - * @property {number} sizeTotal - Size in bytes of all resources. Read-only. - * @property {number} sizeCached - Size in bytes of all cached resources. Read-only. - */ - - - // Functions are copied over from ResourceCache (see ResourceCache.h for reason). - - /**jsdoc - * Get the list of all resource URLs. - * @function SoundCache.getResourceList - * @returns {string[]} - */ - - /**jsdoc - * @function SoundCache.dirty - * @returns {Signal} - */ - - /**jsdoc - * @function SoundCache.updateTotalSize - * @param {number} deltaSize - */ - - /**jsdoc - * Prefetches a resource. - * @function SoundCache.prefetch - * @param {string} url - URL of the resource to prefetch. - * @param {object} [extra=null] - * @returns {ResourceObject} - */ - - /**jsdoc - * Asynchronously loads a resource from the specified URL and returns it. - * @function SoundCache.getResource - * @param {string} url - URL of the resource to load. - * @param {string} [fallback=""] - Fallback URL if load of the desired URL fails. - * @param {} [extra=null] - * @returns {object} - */ - - - /**jsdoc - * @function SoundCache.getSound - * @param {string} url - * @returns {SoundObject} - */ Q_INVOKABLE SharedSoundPointer getSound(const QUrl& url); + protected: virtual QSharedPointer createResource(const QUrl& url, const QSharedPointer& fallback, const void* extra) override; diff --git a/libraries/audio/src/SoundCacheScriptingInterface.cpp b/libraries/audio/src/SoundCacheScriptingInterface.cpp new file mode 100644 index 0000000000..94bb12be8c --- /dev/null +++ b/libraries/audio/src/SoundCacheScriptingInterface.cpp @@ -0,0 +1,20 @@ +// +// SoundCacheScriptingInterface.cpp +// libraries/audio/src +// +// Created by David Rowe on 25 Jul 2018. +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "SoundCacheScriptingInterface.h" + +SoundCacheScriptingInterface::SoundCacheScriptingInterface() : + ScriptableResourceCache::ScriptableResourceCache(DependencyManager::get()) +{ } + +SharedSoundPointer SoundCacheScriptingInterface::getSound(const QUrl& url) { + return DependencyManager::get()->getSound(url); +} diff --git a/libraries/audio/src/SoundCacheScriptingInterface.h b/libraries/audio/src/SoundCacheScriptingInterface.h new file mode 100644 index 0000000000..c985e8c211 --- /dev/null +++ b/libraries/audio/src/SoundCacheScriptingInterface.h @@ -0,0 +1,60 @@ +// +// SoundCacheScriptingInterface.h +// libraries/audio/src +// +// Created by David Rowe on 25 Jul 2018. +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#pragma once + +#ifndef hifi_SoundCacheScriptingInterface_h +#define hifi_SoundCacheScriptingInterface_h + +#include + +#include + +#include "SoundCache.h" + +class SoundCacheScriptingInterface : public ScriptableResourceCache, public Dependency { + Q_OBJECT + + // Properties are copied over from ResourceCache (see ResourceCache.h for reason). + + /**jsdoc + * API to manage sound cache resources. + * @namespace SoundCache + * + * @hifi-interface + * @hifi-client-entity + * @hifi-server-entity + * @hifi-assignment-client + * + * @property {number} numTotal - Total number of total resources. Read-only. + * @property {number} numCached - Total number of cached resource. Read-only. + * @property {number} sizeTotal - Size in bytes of all resources. Read-only. + * @property {number} sizeCached - Size in bytes of all cached resources. Read-only. + * + * @borrows ResourceCache.getResourceList as getResourceList + * @borrows ResourceCache.updateTotalSize as updateTotalSize + * @borrows ResourceCache.prefetch as prefetch + * @borrows ResourceCache.dirty as dirty + */ + +public: + SoundCacheScriptingInterface(); + + /**jsdoc + * Loads the content of an audio file into a {@link SoundObject}, ready for playback by {@link Audio.playSound}. + * @function SoundCache.getSound + * @param {string} url - The URL of the audio file to load — Web, ATP, or file. See {@link SoundObject} for supported + * formats. + * @returns {SoundObject} The sound ready for playback. + */ + Q_INVOKABLE SharedSoundPointer getSound(const QUrl& url); +}; + +#endif // hifi_SoundCacheScriptingInterface_h diff --git a/libraries/audio/src/avx2/AudioHRTF_avx2.cpp b/libraries/audio/src/avx2/AudioHRTF_avx2.cpp index e89128b173..aadbb2d0cd 100644 --- a/libraries/audio/src/avx2/AudioHRTF_avx2.cpp +++ b/libraries/audio/src/avx2/AudioHRTF_avx2.cpp @@ -44,7 +44,7 @@ void FIR_1x4_AVX2(float* src, float* dst0, float* dst1, float* dst2, float* dst3 float* ps = &src[i - HRTF_TAPS + 1]; // process forwards - assert(HRTF_TAPS % 4 == 0); + static_assert(HRTF_TAPS % 4 == 0, "HRTF_TAPS must be a multiple of 4"); for (int k = 0; k < HRTF_TAPS; k += 4) { @@ -87,4 +87,165 @@ void FIR_1x4_AVX2(float* src, float* dst0, float* dst1, float* dst2, float* dst3 _mm256_zeroupper(); } +// 4 channel planar to interleaved +void interleave_4x4_AVX2(float* src0, float* src1, float* src2, float* src3, float* dst, int numFrames) { + + assert(numFrames % 8 == 0); + + for (int i = 0; i < numFrames; i += 8) { + + __m256 x0 = _mm256_loadu_ps(&src0[i]); + __m256 x1 = _mm256_loadu_ps(&src1[i]); + __m256 x2 = _mm256_loadu_ps(&src2[i]); + __m256 x3 = _mm256_loadu_ps(&src3[i]); + + // interleave (4x4 matrix transpose) + __m256 t0 = _mm256_unpacklo_ps(x0, x1); + __m256 t1 = _mm256_unpackhi_ps(x0, x1); + __m256 t2 = _mm256_unpacklo_ps(x2, x3); + __m256 t3 = _mm256_unpackhi_ps(x2, x3); + + x0 = _mm256_shuffle_ps(t0, t2, _MM_SHUFFLE(1,0,1,0)); + x1 = _mm256_shuffle_ps(t0, t2, _MM_SHUFFLE(3,2,3,2)); + x2 = _mm256_shuffle_ps(t1, t3, _MM_SHUFFLE(1,0,1,0)); + x3 = _mm256_shuffle_ps(t1, t3, _MM_SHUFFLE(3,2,3,2)); + + t0 = _mm256_permute2f128_ps(x0, x1, 0x20); + t1 = _mm256_permute2f128_ps(x2, x3, 0x20); + t2 = _mm256_permute2f128_ps(x0, x1, 0x31); + t3 = _mm256_permute2f128_ps(x2, x3, 0x31); + + _mm256_storeu_ps(&dst[4*i+0], t0); + _mm256_storeu_ps(&dst[4*i+8], t1); + _mm256_storeu_ps(&dst[4*i+16], t2); + _mm256_storeu_ps(&dst[4*i+24], t3); + } + + _mm256_zeroupper(); +} + +// process 2 cascaded biquads on 4 channels (interleaved) +// biquads are computed in parallel, by adding one sample of delay +void biquad2_4x4_AVX2(float* src, float* dst, float coef[5][8], float state[3][8], int numFrames) { + + // enable flush-to-zero mode to prevent denormals + unsigned int ftz = _MM_GET_FLUSH_ZERO_MODE(); + _MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_ON); + + // restore state + __m256 x0 = _mm256_setzero_ps(); + __m256 y0 = _mm256_loadu_ps(state[0]); + __m256 w1 = _mm256_loadu_ps(state[1]); + __m256 w2 = _mm256_loadu_ps(state[2]); + + // biquad coefs + __m256 b0 = _mm256_loadu_ps(coef[0]); + __m256 b1 = _mm256_loadu_ps(coef[1]); + __m256 b2 = _mm256_loadu_ps(coef[2]); + __m256 a1 = _mm256_loadu_ps(coef[3]); + __m256 a2 = _mm256_loadu_ps(coef[4]); + + for (int i = 0; i < numFrames; i++) { + + // x0 = (first biquad output << 128) | input + x0 = _mm256_insertf128_ps(_mm256_permute2f128_ps(y0, y0, 0x01), _mm_loadu_ps(&src[4*i]), 0); + + // transposed Direct Form II + y0 = _mm256_fmadd_ps(x0, b0, w1); + w1 = _mm256_fmadd_ps(x0, b1, w2); + w2 = _mm256_mul_ps(x0, b2); + w1 = _mm256_fnmadd_ps(y0, a1, w1); + w2 = _mm256_fnmadd_ps(y0, a2, w2); + + _mm_storeu_ps(&dst[4*i], _mm256_extractf128_ps(y0, 1)); // second biquad output + } + + // save state + _mm256_storeu_ps(state[0], y0); + _mm256_storeu_ps(state[1], w1); + _mm256_storeu_ps(state[2], w2); + + _MM_SET_FLUSH_ZERO_MODE(ftz); + _mm256_zeroupper(); +} + +// crossfade 4 inputs into 2 outputs with accumulation (interleaved) +void crossfade_4x2_AVX2(float* src, float* dst, const float* win, int numFrames) { + + assert(numFrames % 8 == 0); + + for (int i = 0; i < numFrames; i += 8) { + + __m256 f0 = _mm256_loadu_ps(&win[i]); + + __m256 x0 = _mm256_castps128_ps256(_mm_loadu_ps(&src[4*i+0])); + __m256 x1 = _mm256_castps128_ps256(_mm_loadu_ps(&src[4*i+4])); + __m256 x2 = _mm256_castps128_ps256(_mm_loadu_ps(&src[4*i+8])); + __m256 x3 = _mm256_castps128_ps256(_mm_loadu_ps(&src[4*i+12])); + + x0 = _mm256_insertf128_ps(x0, _mm_loadu_ps(&src[4*i+16]), 1); + x1 = _mm256_insertf128_ps(x1, _mm_loadu_ps(&src[4*i+20]), 1); + x2 = _mm256_insertf128_ps(x2, _mm_loadu_ps(&src[4*i+24]), 1); + x3 = _mm256_insertf128_ps(x3, _mm_loadu_ps(&src[4*i+28]), 1); + + __m256 y0 = _mm256_loadu_ps(&dst[2*i+0]); + __m256 y1 = _mm256_loadu_ps(&dst[2*i+8]); + + // deinterleave (4x4 matrix transpose) + __m256 t0 = _mm256_unpacklo_ps(x0, x1); + __m256 t1 = _mm256_unpackhi_ps(x0, x1); + __m256 t2 = _mm256_unpacklo_ps(x2, x3); + __m256 t3 = _mm256_unpackhi_ps(x2, x3); + + x0 = _mm256_shuffle_ps(t0, t2, _MM_SHUFFLE(1,0,1,0)); + x1 = _mm256_shuffle_ps(t0, t2, _MM_SHUFFLE(3,2,3,2)); + x2 = _mm256_shuffle_ps(t1, t3, _MM_SHUFFLE(1,0,1,0)); + x3 = _mm256_shuffle_ps(t1, t3, _MM_SHUFFLE(3,2,3,2)); + + // crossfade + x0 = _mm256_sub_ps(x0, x2); + x1 = _mm256_sub_ps(x1, x3); + x2 = _mm256_fmadd_ps(f0, x0, x2); + x3 = _mm256_fmadd_ps(f0, x1, x3); + + // interleave + t0 = _mm256_unpacklo_ps(x2, x3); + t1 = _mm256_unpackhi_ps(x2, x3); + + x0 = _mm256_permute2f128_ps(t0, t1, 0x20); + x1 = _mm256_permute2f128_ps(t0, t1, 0x31); + + // accumulate + y0 = _mm256_add_ps(y0, x0); + y1 = _mm256_add_ps(y1, x1); + + _mm256_storeu_ps(&dst[2*i+0], y0); + _mm256_storeu_ps(&dst[2*i+8], y1); + } + + _mm256_zeroupper(); +} + +// linear interpolation with gain +void interpolate_AVX2(const float* src0, const float* src1, float* dst, float frac, float gain) { + + __m256 f0 = _mm256_set1_ps(gain * (1.0f - frac)); + __m256 f1 = _mm256_set1_ps(gain * frac); + + static_assert(HRTF_TAPS % 8 == 0, "HRTF_TAPS must be a multiple of 8"); + + for (int k = 0; k < HRTF_TAPS; k += 8) { + + __m256 x0 = _mm256_loadu_ps(&src0[k]); + __m256 x1 = _mm256_loadu_ps(&src1[k]); + + x0 = _mm256_mul_ps(f0, x0); + x0 = _mm256_fmadd_ps(f1, x1, x0); + + _mm256_storeu_ps(&dst[k], x0); + } + + _mm256_zeroupper(); +} + #endif diff --git a/libraries/audio/src/avx512/AudioHRTF_avx512.cpp b/libraries/audio/src/avx512/AudioHRTF_avx512.cpp index a8bb62be35..fcec81fa4c 100644 --- a/libraries/audio/src/avx512/AudioHRTF_avx512.cpp +++ b/libraries/audio/src/avx512/AudioHRTF_avx512.cpp @@ -44,7 +44,7 @@ void FIR_1x4_AVX512(float* src, float* dst0, float* dst1, float* dst2, float* ds float* ps = &src[i - HRTF_TAPS + 1]; // process forwards - assert(HRTF_TAPS % 4 == 0); + static_assert(HRTF_TAPS % 4 == 0, "HRTF_TAPS must be a multiple of 4"); for (int k = 0; k < HRTF_TAPS; k += 4) { diff --git a/libraries/avatars-renderer/CMakeLists.txt b/libraries/avatars-renderer/CMakeLists.txt index a70c8294d5..e6b6986e7b 100644 --- a/libraries/avatars-renderer/CMakeLists.txt +++ b/libraries/avatars-renderer/CMakeLists.txt @@ -1,5 +1,4 @@ set(TARGET_NAME avatars-renderer) -AUTOSCRIBE_SHADER_LIB(gpu graphics render render-utils) setup_hifi_library(Network Script) link_hifi_libraries(shared gpu graphics animation model-networking script-engine render render-utils image trackers entities-renderer) include_hifi_library_headers(avatars) diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index dd2828eb25..4ffccefe61 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -139,7 +139,6 @@ Avatar::~Avatar() { } }); } - auto geometryCache = DependencyManager::get(); if (geometryCache) { geometryCache->releaseID(_nameRectGeometryID); @@ -224,14 +223,24 @@ void Avatar::setAvatarEntityDataChanged(bool value) { void Avatar::updateAvatarEntities() { PerformanceTimer perfTimer("attachments"); + + // AVATAR ENTITY UPDATE FLOW // - if queueEditEntityMessage sees clientOnly flag it does _myAvatar->updateAvatarEntity() - // - updateAvatarEntity saves the bytes and sets _avatarEntityDataLocallyEdited - // - MyAvatar::update notices _avatarEntityDataLocallyEdited and calls sendIdentityPacket - // - sendIdentityPacket sends the entity bytes to the server which relays them to other interfaces - // - AvatarHashMap::processAvatarIdentityPacket on other interfaces call avatar->setAvatarEntityData() - // - setAvatarEntityData saves the bytes and sets _avatarEntityDataChanged = true + // - updateAvatarEntity saves the bytes and flags the trait instance for the entity as updated + // - ClientTraitsHandler::sendChangedTraitsToMixer sends the entity bytes to the mixer which relays them to other interfaces + // - AvatarHashMap::processBulkAvatarTraits on other interfaces calls avatar->processTraitInstace + // - AvatarData::processTraitInstance calls updateAvatarEntity, which sets _avatarEntityDataChanged = true // - (My)Avatar::simulate notices _avatarEntityDataChanged and here we are... + // AVATAR ENTITY DELETE FLOW + // - EntityScriptingInterface::deleteEntity calls _myAvatar->clearAvatarEntity() for deleted avatar entities + // - clearAvatarEntity removes the avatar entity and flags the trait instance for the entity as deleted + // - ClientTraitsHandler::sendChangedTraitsToMixer sends a deletion to the mixer which relays to other interfaces + // - AvatarHashMap::processBulkAvatarTraits on other interfaces calls avatar->processDeletedTraitInstace + // - AvatarData::processDeletedTraitInstance calls clearAvatarEntity + // - AvatarData::clearAvatarEntity sets _avatarEntityDataChanged = true and adds the ID to the detached list + // - Avatar::simulate notices _avatarEntityDataChanged and here we are... + if (!_avatarEntityDataChanged) { return; } @@ -345,27 +354,30 @@ void Avatar::updateAvatarEntities() { stateItr.value().success = success; } - AvatarEntityIDs recentlyDettachedAvatarEntities = getAndClearRecentlyDetachedIDs(); - if (!recentlyDettachedAvatarEntities.empty()) { + AvatarEntityIDs recentlyDetachedAvatarEntities = getAndClearRecentlyDetachedIDs(); + if (!recentlyDetachedAvatarEntities.empty()) { // only lock this thread when absolutely necessary AvatarEntityMap avatarEntityData; _avatarEntitiesLock.withReadLock([&] { avatarEntityData = _avatarEntityData; }); - foreach (auto entityID, recentlyDettachedAvatarEntities) { + foreach (auto entityID, recentlyDetachedAvatarEntities) { if (!avatarEntityData.contains(entityID)) { entityTree->deleteEntity(entityID, true, true); } } // remove stale data hashes - foreach (auto entityID, recentlyDettachedAvatarEntities) { + foreach (auto entityID, recentlyDetachedAvatarEntities) { MapOfAvatarEntityDataHashes::iterator stateItr = _avatarEntityDataHashes.find(entityID); if (stateItr != _avatarEntityDataHashes.end()) { _avatarEntityDataHashes.erase(stateItr); } } } + if (avatarEntities.size() != _avatarEntityForRecording.size()) { + createRecordingIDs(); + } }); setAvatarEntityDataChanged(false); @@ -1184,6 +1196,15 @@ glm::quat Avatar::getAbsoluteJointRotationInObjectFrame(int index) const { } return Quaternions::Y_180 * rotation * Quaternions::Y_180; } + case FARGRAB_RIGHTHAND_INDEX: { + return extractRotation(_farGrabRightMatrixCache.get()); + } + case FARGRAB_LEFTHAND_INDEX: { + return extractRotation(_farGrabLeftMatrixCache.get()); + } + case FARGRAB_MOUSE_INDEX: { + return extractRotation(_farGrabMouseMatrixCache.get()); + } default: { glm::quat rotation; _skeletonModel->getAbsoluteJointRotationInRigFrame(index, rotation); @@ -1224,6 +1245,15 @@ glm::vec3 Avatar::getAbsoluteJointTranslationInObjectFrame(int index) const { } return Quaternions::Y_180 * translation * Quaternions::Y_180; } + case FARGRAB_RIGHTHAND_INDEX: { + return extractTranslation(_farGrabRightMatrixCache.get()); + } + case FARGRAB_LEFTHAND_INDEX: { + return extractTranslation(_farGrabLeftMatrixCache.get()); + } + case FARGRAB_MOUSE_INDEX: { + return extractTranslation(_farGrabMouseMatrixCache.get()); + } default: { glm::vec3 translation; _skeletonModel->getAbsoluteJointTranslationInRigFrame(index, translation); @@ -1452,9 +1482,6 @@ int Avatar::parseDataFromBuffer(const QByteArray& buffer) { const float MOVE_DISTANCE_THRESHOLD = 0.001f; _moving = glm::distance(oldPosition, getWorldPosition()) > MOVE_DISTANCE_THRESHOLD; - if (_moving) { - addPhysicsFlags(Simulation::DIRTY_POSITION); - } if (_moving || _hasNewJointData) { locationChanged(); } @@ -1568,6 +1595,7 @@ void Avatar::computeShapeInfo(ShapeInfo& shapeInfo) { } void Avatar::getCapsule(glm::vec3& start, glm::vec3& end, float& radius) { + // FIXME: this doesn't take into account Avatar rotation ShapeInfo shapeInfo; computeShapeInfo(shapeInfo); glm::vec3 halfExtents = shapeInfo.getHalfExtents(); // x = radius, y = halfHeight @@ -1576,6 +1604,14 @@ void Avatar::getCapsule(glm::vec3& start, glm::vec3& end, float& radius) { radius = halfExtents.x; } +glm::vec3 Avatar::getWorldFeetPosition() { + ShapeInfo shapeInfo; + computeShapeInfo(shapeInfo); + glm::vec3 halfExtents = shapeInfo.getHalfExtents(); // x = radius, y = halfHeight + glm::vec3 localFeet(0.0f, shapeInfo.getOffset().y - halfExtents.y - halfExtents.x, 0.0f); + return getWorldOrientation() * localFeet + getWorldPosition(); +} + float Avatar::computeMass() { float radius; glm::vec3 start, end; @@ -1587,20 +1623,6 @@ float Avatar::computeMass() { return _density * TWO_PI * radius * radius * (glm::length(end - start) + 2.0f * radius / 3.0f); } -void Avatar::rebuildCollisionShape() { - addPhysicsFlags(Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS); -} - -void Avatar::setPhysicsCallback(AvatarPhysicsCallback cb) { - _physicsCallback = cb; -} - -void Avatar::addPhysicsFlags(uint32_t flags) { - if (_physicsCallback) { - _physicsCallback(flags); - } -} - // thread-safe glm::vec3 Avatar::getLeftPalmPosition() const { return _leftPalmPositionCache.get(); diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h index 157f7b2ec6..37c3d08c6c 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h @@ -50,8 +50,6 @@ enum ScreenTintLayer { class Texture; -using AvatarPhysicsCallback = std::function; - class Avatar : public AvatarData, public scriptable::ModelProvider { Q_OBJECT @@ -244,11 +242,17 @@ public: // (otherwise floating point error will cause problems at large positions). void applyPositionDelta(const glm::vec3& delta); - virtual void rebuildCollisionShape(); + virtual void rebuildCollisionShape() = 0; virtual void computeShapeInfo(ShapeInfo& shapeInfo); void getCapsule(glm::vec3& start, glm::vec3& end, float& radius); float computeMass(); + /**jsdoc + * Get the position of the current avatar's feet (or rather, bottom of its collision capsule) in world coordinates. + * @function MyAvatar.getWorldFeetPosition + * @returns {Vec3} The position of the avatar's feet in world coordinates. + */ + Q_INVOKABLE glm::vec3 getWorldFeetPosition(); void setPositionViaScript(const glm::vec3& position) override; void setOrientationViaScript(const glm::quat& orientation) override; @@ -326,10 +330,6 @@ public: render::ItemID getRenderItemID() { return _renderItemID; } bool isMoving() const { return _moving; } - void setPhysicsCallback(AvatarPhysicsCallback cb); - void addPhysicsFlags(uint32_t flags); - bool isInPhysicsSimulation() const { return _physicsCallback != nullptr; } - void fadeIn(render::ScenePointer scene); void fadeOut(render::ScenePointer scene, KillAvatarReason reason); bool isFading() const { return _isFading; } @@ -458,7 +458,6 @@ protected: glm::vec3 _lastAngularVelocity; glm::vec3 _angularAcceleration; glm::quat _lastOrientation; - glm::vec3 _worldUpDirection { Vectors::UP }; bool _moving { false }; ///< set when position is changing @@ -524,8 +523,6 @@ protected: int _voiceSphereID; - AvatarPhysicsCallback _physicsCallback { nullptr }; - float _displayNameTargetAlpha { 1.0f }; float _displayNameAlpha { 1.0f }; diff --git a/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp b/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp index de8c02f10e..1ec58fd704 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp @@ -294,6 +294,15 @@ bool SkeletonModel::getEyeModelPositions(glm::vec3& firstEyePosition, glm::vec3& firstEyePosition = baseEyePosition + headRotation * glm::vec3(EYE_SEPARATION, 0.0f, EYES_FORWARD) * headHeight; secondEyePosition = baseEyePosition + headRotation * glm::vec3(-EYE_SEPARATION, 0.0f, EYES_FORWARD) * headHeight; return true; + } else if (getJointPosition(geometry.headJointIndex, headPosition)) { + glm::vec3 baseEyePosition = headPosition; + glm::quat headRotation; + getJointRotation(geometry.headJointIndex, headRotation); + const float EYES_FORWARD_HEAD_ONLY = 0.30f; + const float EYE_SEPARATION = 0.1f; + firstEyePosition = baseEyePosition + headRotation * glm::vec3(EYE_SEPARATION, 0.0f, EYES_FORWARD_HEAD_ONLY); + secondEyePosition = baseEyePosition + headRotation * glm::vec3(-EYE_SEPARATION, 0.0f, EYES_FORWARD_HEAD_ONLY); + return true; } return false; } diff --git a/libraries/avatars/src/AssociatedTraitValues.h b/libraries/avatars/src/AssociatedTraitValues.h new file mode 100644 index 0000000000..cf1edef7f7 --- /dev/null +++ b/libraries/avatars/src/AssociatedTraitValues.h @@ -0,0 +1,158 @@ +// +// AssociatedTraitValues.h +// libraries/avatars/src +// +// Created by Stephen Birarda on 8/8/18. +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_AssociatedTraitValues_h +#define hifi_AssociatedTraitValues_h + +#include "AvatarTraits.h" + +namespace AvatarTraits { + template + class AssociatedTraitValues { + public: + AssociatedTraitValues() : _simpleTypes(FirstInstancedTrait, defaultValue) {} + + void insert(TraitType type, T value) { _simpleTypes[type] = value; } + void erase(TraitType type) { _simpleTypes[type] = defaultValue; } + + T& getInstanceValueRef(TraitType traitType, TraitInstanceID instanceID); + void instanceInsert(TraitType traitType, TraitInstanceID instanceID, T value); + + struct InstanceIDValuePair { + TraitInstanceID id; + T value; + + InstanceIDValuePair(TraitInstanceID id, T value) : id(id), value(value) {}; + }; + + using InstanceIDValuePairs = std::vector; + + InstanceIDValuePairs& getInstanceIDValuePairs(TraitType traitType); + + void instanceErase(TraitType traitType, TraitInstanceID instanceID); + void eraseAllInstances(TraitType traitType); + + // will return defaultValue for instanced traits + T operator[](TraitType traitType) const { return _simpleTypes[traitType]; } + T& operator[](TraitType traitType) { return _simpleTypes[traitType]; } + + void reset() { + std::fill(_simpleTypes.begin(), _simpleTypes.end(), defaultValue); + _instancedTypes.clear(); + } + + typename std::vector::const_iterator simpleCBegin() const { return _simpleTypes.cbegin(); } + typename std::vector::const_iterator simpleCEnd() const { return _simpleTypes.cend(); } + + typename std::vector::iterator simpleBegin() { return _simpleTypes.begin(); } + typename std::vector::iterator simpleEnd() { return _simpleTypes.end(); } + + struct TraitWithInstances { + TraitType traitType; + InstanceIDValuePairs instances; + + TraitWithInstances(TraitType traitType) : traitType(traitType) {}; + TraitWithInstances(TraitType traitType, TraitInstanceID instanceID, T value) : + traitType(traitType), instances({{ instanceID, value }}) {}; + }; + + typename std::vector::const_iterator instancedCBegin() const { return _instancedTypes.cbegin(); } + typename std::vector::const_iterator instancedCEnd() const { return _instancedTypes.cend(); } + + typename std::vector::iterator instancedBegin() { return _instancedTypes.begin(); } + typename std::vector::iterator instancedEnd() { return _instancedTypes.end(); } + + private: + std::vector _simpleTypes; + + typename std::vector::iterator instancesForTrait(TraitType traitType) { + return std::find_if(_instancedTypes.begin(), _instancedTypes.end(), + [traitType](TraitWithInstances& traitWithInstances){ + return traitWithInstances.traitType == traitType; + }); + } + + std::vector _instancedTypes; + }; + + template + inline typename AssociatedTraitValues::InstanceIDValuePairs& + AssociatedTraitValues::getInstanceIDValuePairs(TraitType traitType) { + auto it = instancesForTrait(traitType); + + if (it != _instancedTypes.end()) { + return it->instances; + } else { + _instancedTypes.emplace_back(traitType); + return _instancedTypes.back().instances; + } + } + + template + inline T& AssociatedTraitValues::getInstanceValueRef(TraitType traitType, TraitInstanceID instanceID) { + auto it = instancesForTrait(traitType); + + if (it != _instancedTypes.end()) { + auto& instancesVector = it->instances; + auto instanceIt = std::find_if(instancesVector.begin(), instancesVector.end(), + [instanceID](InstanceIDValuePair& idValuePair){ + return idValuePair.id == instanceID; + }); + if (instanceIt != instancesVector.end()) { + return instanceIt->value; + } else { + instancesVector.emplace_back(instanceID, defaultValue); + return instancesVector.back().value; + } + } else { + _instancedTypes.emplace_back(traitType, instanceID, defaultValue); + return _instancedTypes.back().instances.back().value; + } + } + + template + inline void AssociatedTraitValues::instanceInsert(TraitType traitType, TraitInstanceID instanceID, T value) { + auto it = instancesForTrait(traitType); + + if (it != _instancedTypes.end()) { + auto& instancesVector = it->instances; + auto instanceIt = std::find_if(instancesVector.begin(), instancesVector.end(), + [instanceID](InstanceIDValuePair& idValuePair){ + return idValuePair.id == instanceID; + }); + if (instanceIt != instancesVector.end()) { + instanceIt->value = value; + } else { + instancesVector.emplace_back(instanceID, value); + } + } else { + _instancedTypes.emplace_back(traitType, instanceID, value); + } + } + + template + inline void AssociatedTraitValues::instanceErase(TraitType traitType, TraitInstanceID instanceID) { + auto it = instancesForTrait(traitType); + + if (it != _instancedTypes.end()) { + auto& instancesVector = it->instances; + instancesVector.erase(std::remove_if(instancesVector.begin(), + instancesVector.end(), + [&instanceID](InstanceIDValuePair& idValuePair){ + return idValuePair.id == instanceID; + })); + } + } + + using TraitVersions = AssociatedTraitValues; +}; + +#endif // hifi_AssociatedTraitValues_h diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index fc72f34304..e7aaa18576 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -42,6 +42,8 @@ #include #include "AvatarLogging.h" +#include "AvatarTraits.h" +#include "ClientTraitsHandler.h" //#define WANT_DEBUG @@ -62,7 +64,7 @@ size_t AvatarDataPacket::maxFaceTrackerInfoSize(size_t numBlendshapeCoefficients return FACE_TRACKER_INFO_SIZE + numBlendshapeCoefficients * sizeof(float); } -size_t AvatarDataPacket::maxJointDataSize(size_t numJoints) { +size_t AvatarDataPacket::maxJointDataSize(size_t numJoints, bool hasGrabJoints) { const size_t validityBitsSize = (size_t)std::ceil(numJoints / (float)BITS_IN_BYTE); size_t totalSize = sizeof(uint8_t); // numJoints @@ -73,7 +75,8 @@ size_t AvatarDataPacket::maxJointDataSize(size_t numJoints) { totalSize += numJoints * sizeof(SixByteTrans); // Translations size_t NUM_FAUX_JOINT = 2; - totalSize += NUM_FAUX_JOINT * (sizeof(SixByteQuat) + sizeof(SixByteTrans)); // faux joints + size_t num_grab_joints = (hasGrabJoints ? 2 : 0); + totalSize += (NUM_FAUX_JOINT + num_grab_joints) * (sizeof(SixByteQuat) + sizeof(SixByteTrans)); // faux joints return totalSize; } @@ -227,7 +230,8 @@ QByteArray AvatarData::toByteArrayStateful(AvatarDataDetail dataDetail, bool dro &_outboundDataRate); } -QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSentTime, const QVector& lastSentJointData, +QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSentTime, + const QVector& lastSentJointData, AvatarDataPacket::HasFlags& hasFlagsOut, bool dropFaceTracking, bool distanceAdjust, glm::vec3 viewerPosition, QVector* sentJointDataOut, AvatarDataRate* outboundDataRateOut) const { @@ -284,6 +288,11 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent bool hasFaceTrackerInfo = false; bool hasJointData = false; bool hasJointDefaultPoseFlags = false; + bool hasGrabJoints = false; + + glm::mat4 leftFarGrabMatrix; + glm::mat4 rightFarGrabMatrix; + glm::mat4 mouseFarGrabMatrix; if (sendPALMinimum) { hasAudioLoudness = true; @@ -304,12 +313,30 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent (sendAll || faceTrackerInfoChangedSince(lastSentTime)); hasJointData = sendAll || !sendMinimum; hasJointDefaultPoseFlags = hasJointData; + if (hasJointData) { + bool leftValid; + leftFarGrabMatrix = _farGrabLeftMatrixCache.get(leftValid); + if (!leftValid) { + leftFarGrabMatrix = glm::mat4(); + } + bool rightValid; + rightFarGrabMatrix = _farGrabRightMatrixCache.get(rightValid); + if (!rightValid) { + rightFarGrabMatrix = glm::mat4(); + } + bool mouseValid; + mouseFarGrabMatrix = _farGrabMouseMatrixCache.get(mouseValid); + if (!mouseValid) { + mouseFarGrabMatrix = glm::mat4(); + } + hasGrabJoints = (leftValid || rightValid || mouseValid); + } } const size_t byteArraySize = AvatarDataPacket::MAX_CONSTANT_HEADER_SIZE + (hasFaceTrackerInfo ? AvatarDataPacket::maxFaceTrackerInfoSize(_headData->getBlendshapeCoefficients().size()) : 0) + - (hasJointData ? AvatarDataPacket::maxJointDataSize(_jointData.size()) : 0) + + (hasJointData ? AvatarDataPacket::maxJointDataSize(_jointData.size(), hasGrabJoints) : 0) + (hasJointDefaultPoseFlags ? AvatarDataPacket::maxJointDefaultPoseFlagsSize(_jointData.size()) : 0); QByteArray avatarDataByteArray((int)byteArraySize, 0); @@ -330,7 +357,8 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent | (hasAvatarLocalPosition ? AvatarDataPacket::PACKET_HAS_AVATAR_LOCAL_POSITION : 0) | (hasFaceTrackerInfo ? AvatarDataPacket::PACKET_HAS_FACE_TRACKER_INFO : 0) | (hasJointData ? AvatarDataPacket::PACKET_HAS_JOINT_DATA : 0) - | (hasJointDefaultPoseFlags ? AvatarDataPacket::PACKET_HAS_JOINT_DEFAULT_POSE_FLAGS : 0); + | (hasJointDefaultPoseFlags ? AvatarDataPacket::PACKET_HAS_JOINT_DEFAULT_POSE_FLAGS : 0) + | (hasGrabJoints ? AvatarDataPacket::PACKET_HAS_GRAB_JOINTS : 0); memcpy(destinationBuffer, &packetStateFlags, sizeof(packetStateFlags)); destinationBuffer += sizeof(packetStateFlags); @@ -668,6 +696,53 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent destinationBuffer += packFloatVec3ToSignedTwoByteFixed(destinationBuffer, controllerRightHandTransform.getTranslation(), TRANSLATION_COMPRESSION_RADIX); + if (hasGrabJoints) { + // the far-grab joints may range further than 3 meters, so we can't use packFloatVec3ToSignedTwoByteFixed etc + auto startSection = destinationBuffer; + auto data = reinterpret_cast(destinationBuffer); + glm::vec3 leftFarGrabPosition = extractTranslation(leftFarGrabMatrix); + glm::quat leftFarGrabRotation = extractRotation(leftFarGrabMatrix); + glm::vec3 rightFarGrabPosition = extractTranslation(rightFarGrabMatrix); + glm::quat rightFarGrabRotation = extractRotation(rightFarGrabMatrix); + glm::vec3 mouseFarGrabPosition = extractTranslation(mouseFarGrabMatrix); + glm::quat mouseFarGrabRotation = extractRotation(mouseFarGrabMatrix); + + data->leftFarGrabPosition[0] = leftFarGrabPosition.x; + data->leftFarGrabPosition[1] = leftFarGrabPosition.y; + data->leftFarGrabPosition[2] = leftFarGrabPosition.z; + + data->leftFarGrabRotation[0] = leftFarGrabRotation.w; + data->leftFarGrabRotation[1] = leftFarGrabRotation.x; + data->leftFarGrabRotation[2] = leftFarGrabRotation.y; + data->leftFarGrabRotation[3] = leftFarGrabRotation.z; + + data->rightFarGrabPosition[0] = rightFarGrabPosition.x; + data->rightFarGrabPosition[1] = rightFarGrabPosition.y; + data->rightFarGrabPosition[2] = rightFarGrabPosition.z; + + data->rightFarGrabRotation[0] = rightFarGrabRotation.w; + data->rightFarGrabRotation[1] = rightFarGrabRotation.x; + data->rightFarGrabRotation[2] = rightFarGrabRotation.y; + data->rightFarGrabRotation[3] = rightFarGrabRotation.z; + + data->mouseFarGrabPosition[0] = mouseFarGrabPosition.x; + data->mouseFarGrabPosition[1] = mouseFarGrabPosition.y; + data->mouseFarGrabPosition[2] = mouseFarGrabPosition.z; + + data->mouseFarGrabRotation[0] = mouseFarGrabRotation.w; + data->mouseFarGrabRotation[1] = mouseFarGrabRotation.x; + data->mouseFarGrabRotation[2] = mouseFarGrabRotation.y; + data->mouseFarGrabRotation[3] = mouseFarGrabRotation.z; + + destinationBuffer += sizeof(AvatarDataPacket::FarGrabJoints); + + int numBytes = destinationBuffer - startSection; + + if (outboundDataRateOut) { + outboundDataRateOut->farGrabJointRate.increment(numBytes); + } + } + #ifdef WANT_DEBUG if (sendAll) { qCDebug(avatars) << "AvatarData::toByteArray" << cullSmallChanges << sendAll @@ -834,6 +909,7 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { bool hasFaceTrackerInfo = HAS_FLAG(packetStateFlags, AvatarDataPacket::PACKET_HAS_FACE_TRACKER_INFO); bool hasJointData = HAS_FLAG(packetStateFlags, AvatarDataPacket::PACKET_HAS_JOINT_DATA); bool hasJointDefaultPoseFlags = HAS_FLAG(packetStateFlags, AvatarDataPacket::PACKET_HAS_JOINT_DEFAULT_POSE_FLAGS); + bool hasGrabJoints = HAS_FLAG(packetStateFlags, AvatarDataPacket::PACKET_HAS_GRAB_JOINTS); quint64 now = usecTimestampNow(); @@ -842,7 +918,18 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { PACKET_READ_CHECK(AvatarGlobalPosition, sizeof(AvatarDataPacket::AvatarGlobalPosition)); auto data = reinterpret_cast(sourceBuffer); - auto newValue = glm::vec3(data->globalPosition[0], data->globalPosition[1], data->globalPosition[2]); + + glm::vec3 offset = glm::vec3(0.0f, 0.0f, 0.0f); + + if (_replicaIndex > 0) { + const float SPACE_BETWEEN_AVATARS = 2.0f; + const int AVATARS_PER_ROW = 3; + int row = _replicaIndex % AVATARS_PER_ROW; + int col = floor(_replicaIndex / AVATARS_PER_ROW); + offset = glm::vec3(row * SPACE_BETWEEN_AVATARS, 0.0f, col * SPACE_BETWEEN_AVATARS); + } + + auto newValue = glm::vec3(data->globalPosition[0], data->globalPosition[1], data->globalPosition[2]) + offset; if (_globalPosition != newValue) { _globalPosition = newValue; _globalPositionChanged = usecTimestampNow(); @@ -1195,6 +1282,34 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { int numBytesRead = sourceBuffer - startSection; _jointDataRate.increment(numBytesRead); _jointDataUpdateRate.increment(); + + if (hasGrabJoints) { + auto startSection = sourceBuffer; + + PACKET_READ_CHECK(FarGrabJoints, sizeof(AvatarDataPacket::FarGrabJoints)); + auto data = reinterpret_cast(sourceBuffer); + glm::vec3 leftFarGrabPosition = glm::vec3(data->leftFarGrabPosition[0], data->leftFarGrabPosition[1], + data->leftFarGrabPosition[2]); + glm::quat leftFarGrabRotation = glm::quat(data->leftFarGrabRotation[0], data->leftFarGrabRotation[1], + data->leftFarGrabRotation[2], data->leftFarGrabRotation[3]); + glm::vec3 rightFarGrabPosition = glm::vec3(data->rightFarGrabPosition[0], data->rightFarGrabPosition[1], + data->rightFarGrabPosition[2]); + glm::quat rightFarGrabRotation = glm::quat(data->rightFarGrabRotation[0], data->rightFarGrabRotation[1], + data->rightFarGrabRotation[2], data->rightFarGrabRotation[3]); + glm::vec3 mouseFarGrabPosition = glm::vec3(data->mouseFarGrabPosition[0], data->mouseFarGrabPosition[1], + data->mouseFarGrabPosition[2]); + glm::quat mouseFarGrabRotation = glm::quat(data->mouseFarGrabRotation[0], data->mouseFarGrabRotation[1], + data->mouseFarGrabRotation[2], data->mouseFarGrabRotation[3]); + + _farGrabLeftMatrixCache.set(createMatFromQuatAndPos(leftFarGrabRotation, leftFarGrabPosition)); + _farGrabRightMatrixCache.set(createMatFromQuatAndPos(rightFarGrabRotation, rightFarGrabPosition)); + _farGrabMouseMatrixCache.set(createMatFromQuatAndPos(mouseFarGrabRotation, mouseFarGrabPosition)); + + sourceBuffer += sizeof(AvatarDataPacket::AvatarGlobalPosition); + int numBytesRead = sourceBuffer - startSection; + _farGrabJointRate.increment(numBytesRead); + _farGrabJointUpdateRate.increment(); + } } if (hasJointDefaultPoseFlags) { @@ -1261,6 +1376,8 @@ float AvatarData::getDataRate(const QString& rateName) const { return _jointDataRate.rate() / BYTES_PER_KILOBIT; } else if (rateName == "jointDefaultPoseFlagsRate") { return _jointDefaultPoseFlagsRate.rate() / BYTES_PER_KILOBIT; + } else if (rateName == "farGrabJointRate") { + return _farGrabJointRate.rate() / BYTES_PER_KILOBIT; } else if (rateName == "globalPositionOutbound") { return _outboundDataRate.globalPositionRate.rate() / BYTES_PER_KILOBIT; } else if (rateName == "localPositionOutbound") { @@ -1318,6 +1435,8 @@ float AvatarData::getUpdateRate(const QString& rateName) const { return _faceTrackerUpdateRate.rate(); } else if (rateName == "jointData") { return _jointDataUpdateRate.rate(); + } else if (rateName == "farGrabJointData") { + return _farGrabJointUpdateRate.rate(); } return 0.0f; } @@ -1344,7 +1463,7 @@ void AvatarData::setRawJointData(QVector data) { } void AvatarData::setJointData(int index, const glm::quat& rotation, const glm::vec3& translation) { - if (index == -1) { + if (index < 0 || index >= LOWEST_PSEUDO_JOINT_INDEX) { return; } QWriteLocker writeLock(&_jointDataLock); @@ -1359,7 +1478,7 @@ void AvatarData::setJointData(int index, const glm::quat& rotation, const glm::v } void AvatarData::clearJointData(int index) { - if (index == -1) { + if (index < 0 || index >= LOWEST_PSEUDO_JOINT_INDEX) { return; } QWriteLocker writeLock(&_jointDataLock); @@ -1371,27 +1490,72 @@ void AvatarData::clearJointData(int index) { } bool AvatarData::isJointDataValid(int index) const { - if (index == -1) { - return false; + switch (index) { + case FARGRAB_RIGHTHAND_INDEX: { + bool valid; + _farGrabRightMatrixCache.get(valid); + return valid; + } + case FARGRAB_LEFTHAND_INDEX: { + bool valid; + _farGrabLeftMatrixCache.get(valid); + return valid; + } + case FARGRAB_MOUSE_INDEX: { + bool valid; + _farGrabMouseMatrixCache.get(valid); + return valid; + } + default: { + if (index < 0 || index >= LOWEST_PSEUDO_JOINT_INDEX) { + return false; + } + QReadLocker readLock(&_jointDataLock); + return index < _jointData.size(); + } } - QReadLocker readLock(&_jointDataLock); - return index < _jointData.size(); } glm::quat AvatarData::getJointRotation(int index) const { - if (index == -1) { - return glm::quat(); + switch (index) { + case FARGRAB_RIGHTHAND_INDEX: { + return extractRotation(_farGrabRightMatrixCache.get()); + } + case FARGRAB_LEFTHAND_INDEX: { + return extractRotation(_farGrabLeftMatrixCache.get()); + } + case FARGRAB_MOUSE_INDEX: { + return extractRotation(_farGrabMouseMatrixCache.get()); + } + default: { + if (index < 0 || index >= LOWEST_PSEUDO_JOINT_INDEX) { + return glm::quat(); + } + QReadLocker readLock(&_jointDataLock); + return index < _jointData.size() ? _jointData.at(index).rotation : glm::quat(); + } } - QReadLocker readLock(&_jointDataLock); - return index < _jointData.size() ? _jointData.at(index).rotation : glm::quat(); } glm::vec3 AvatarData::getJointTranslation(int index) const { - if (index == -1) { - return glm::vec3(); + switch (index) { + case FARGRAB_RIGHTHAND_INDEX: { + return extractTranslation(_farGrabRightMatrixCache.get()); + } + case FARGRAB_LEFTHAND_INDEX: { + return extractTranslation(_farGrabLeftMatrixCache.get()); + } + case FARGRAB_MOUSE_INDEX: { + return extractTranslation(_farGrabMouseMatrixCache.get()); + } + default: { + if (index < 0 || index >= LOWEST_PSEUDO_JOINT_INDEX) { + return glm::vec3(); + } + QReadLocker readLock(&_jointDataLock); + return index < _jointData.size() ? _jointData.at(index).translation : glm::vec3(); + } } - QReadLocker readLock(&_jointDataLock); - return index < _jointData.size() ? _jointData.at(index).translation : glm::vec3(); } glm::vec3 AvatarData::getJointTranslation(const QString& name) const { @@ -1400,6 +1564,7 @@ glm::vec3 AvatarData::getJointTranslation(const QString& name) const { // return getJointTranslation(getJointIndex(name)); return readLockWithNamedJointIndex(name, [this](int index) { return _jointData.at(index).translation; + return getJointTranslation(index); }); } @@ -1437,7 +1602,7 @@ void AvatarData::setJointTranslation(const QString& name, const glm::vec3& trans } void AvatarData::setJointRotation(int index, const glm::quat& rotation) { - if (index == -1) { + if (index < 0 || index >= LOWEST_PSEUDO_JOINT_INDEX) { return; } QWriteLocker writeLock(&_jointDataLock); @@ -1450,7 +1615,7 @@ void AvatarData::setJointRotation(int index, const glm::quat& rotation) { } void AvatarData::setJointTranslation(int index, const glm::vec3& translation) { - if (index == -1) { + if (index < 0 || index >= LOWEST_PSEUDO_JOINT_INDEX) { return; } QWriteLocker writeLock(&_jointDataLock); @@ -1567,6 +1732,15 @@ int AvatarData::getFauxJointIndex(const QString& name) const { if (name == "_CAMERA_MATRIX") { return CAMERA_MATRIX_INDEX; } + if (name == "_FARGRAB_RIGHTHAND") { + return FARGRAB_RIGHTHAND_INDEX; + } + if (name == "_FARGRAB_LEFTHAND") { + return FARGRAB_LEFTHAND_INDEX; + } + if (name == "_FARGRAB_MOUSE") { + return FARGRAB_MOUSE_INDEX; + } return -1; } @@ -1588,14 +1762,8 @@ glm::quat AvatarData::getOrientationOutbound() const { return (getLocalOrientation()); } -static const QUrl emptyURL(""); -QUrl AvatarData::cannonicalSkeletonModelURL(const QUrl& emptyURL) const { - // We don't put file urls on the wire, but instead convert to empty. - return _skeletonModelURL.scheme() == "file" ? emptyURL : _skeletonModelURL; -} - void AvatarData::processAvatarIdentity(const QByteArray& identityData, bool& identityChanged, - bool& displayNameChanged, bool& skeletonModelUrlChanged) { + bool& displayNameChanged) { QDataStream packetStream(identityData); @@ -1616,28 +1784,17 @@ void AvatarData::processAvatarIdentity(const QByteArray& identityData, bool& ide if (incomingSequenceNumber > _identitySequenceNumber) { Identity identity; - packetStream >> identity.skeletonModelURL + packetStream >> identity.attachmentData >> identity.displayName >> identity.sessionDisplayName >> identity.isReplicated - >> identity.avatarEntityData >> identity.lookAtSnappingEnabled ; // set the store identity sequence number to match the incoming identity _identitySequenceNumber = incomingSequenceNumber; - if (_firstSkeletonCheck || (identity.skeletonModelURL != cannonicalSkeletonModelURL(emptyURL))) { - setSkeletonModelURL(identity.skeletonModelURL); - identityChanged = true; - skeletonModelUrlChanged = true; - if (_firstSkeletonCheck) { - displayNameChanged = true; - } - _firstSkeletonCheck = false; - } - if (identity.displayName != _displayName) { _displayName = identity.displayName; identityChanged = true; @@ -1655,16 +1812,6 @@ void AvatarData::processAvatarIdentity(const QByteArray& identityData, bool& ide identityChanged = true; } - bool avatarEntityDataChanged = false; - _avatarEntitiesLock.withReadLock([&] { - avatarEntityDataChanged = (identity.avatarEntityData != _avatarEntityData); - }); - - if (avatarEntityDataChanged) { - setAvatarEntityData(identity.avatarEntityData); - identityChanged = true; - } - if (identity.lookAtSnappingEnabled != _lookAtSnappingEnabled) { setProperty("lookAtSnappingEnabled", identity.lookAtSnappingEnabled); identityChanged = true; @@ -1673,7 +1820,6 @@ void AvatarData::processAvatarIdentity(const QByteArray& identityData, bool& ide #ifdef WANT_DEBUG qCDebug(avatars) << __FUNCTION__ << "identity.uuid:" << identity.uuid - << "identity.skeletonModelURL:" << identity.skeletonModelURL << "identity.displayName:" << identity.displayName << "identity.sessionDisplayName:" << identity.sessionDisplayName; } else { @@ -1685,26 +1831,121 @@ void AvatarData::processAvatarIdentity(const QByteArray& identityData, bool& ide } } +QUrl AvatarData::getWireSafeSkeletonModelURL() const { + if (_skeletonModelURL.scheme() != "file" && _skeletonModelURL.scheme() != "qrc") { + return _skeletonModelURL; + } else { + return QUrl(); + } +} + +qint64 AvatarData::packTrait(AvatarTraits::TraitType traitType, ExtendedIODevice& destination, + AvatarTraits::TraitVersion traitVersion) { + qint64 bytesWritten = 0; + bytesWritten += destination.writePrimitive(traitType); + + if (traitVersion > AvatarTraits::DEFAULT_TRAIT_VERSION) { + bytesWritten += destination.writePrimitive(traitVersion); + } + + if (traitType == AvatarTraits::SkeletonModelURL) { + QByteArray encodedSkeletonURL = getWireSafeSkeletonModelURL().toEncoded(); + + AvatarTraits::TraitWireSize encodedURLSize = encodedSkeletonURL.size(); + bytesWritten += destination.writePrimitive(encodedURLSize); + + bytesWritten += destination.write(encodedSkeletonURL); + } + + return bytesWritten; +} + +qint64 AvatarData::packTraitInstance(AvatarTraits::TraitType traitType, AvatarTraits::TraitInstanceID traitInstanceID, + ExtendedIODevice& destination, AvatarTraits::TraitVersion traitVersion, + AvatarTraits::TraitInstanceID wireInstanceID) { + + qint64 bytesWritten = 0; + + bytesWritten += destination.writePrimitive(traitType); + + if (traitVersion > AvatarTraits::DEFAULT_TRAIT_VERSION) { + bytesWritten += destination.writePrimitive(traitVersion); + } + + if (!wireInstanceID.isNull()) { + bytesWritten += destination.write(wireInstanceID.toRfc4122()); + } else { + bytesWritten += destination.write(traitInstanceID.toRfc4122()); + } + + if (traitType == AvatarTraits::AvatarEntity) { + // grab a read lock on the avatar entities and check for entity data for the given ID + QByteArray entityBinaryData; + + _avatarEntitiesLock.withReadLock([this, &entityBinaryData, &traitInstanceID] { + if (_avatarEntityData.contains(traitInstanceID)) { + entityBinaryData = _avatarEntityData[traitInstanceID]; + } + }); + + if (!entityBinaryData.isNull()) { + AvatarTraits::TraitWireSize entityBinarySize = entityBinaryData.size(); + + bytesWritten += destination.writePrimitive(entityBinarySize); + bytesWritten += destination.write(entityBinaryData); + } else { + bytesWritten += destination.writePrimitive(AvatarTraits::DELETED_TRAIT_SIZE); + } + } + + return bytesWritten; +} + +void AvatarData::prepareResetTraitInstances() { + if (_clientTraitsHandler) { + _avatarEntitiesLock.withReadLock([this]{ + foreach (auto entityID, _avatarEntityData.keys()) { + _clientTraitsHandler->markInstancedTraitUpdated(AvatarTraits::AvatarEntity, entityID); + } + }); + } +} + +void AvatarData::processTrait(AvatarTraits::TraitType traitType, QByteArray traitBinaryData) { + if (traitType == AvatarTraits::SkeletonModelURL) { + // get the URL from the binary data + auto skeletonModelURL = QUrl::fromEncoded(traitBinaryData); + setSkeletonModelURL(skeletonModelURL); + } +} + +void AvatarData::processTraitInstance(AvatarTraits::TraitType traitType, + AvatarTraits::TraitInstanceID instanceID, QByteArray traitBinaryData) { + if (traitType == AvatarTraits::AvatarEntity) { + updateAvatarEntity(instanceID, traitBinaryData); + } +} + +void AvatarData::processDeletedTraitInstance(AvatarTraits::TraitType traitType, AvatarTraits::TraitInstanceID instanceID) { + if (traitType == AvatarTraits::AvatarEntity) { + clearAvatarEntity(instanceID); + } +} + QByteArray AvatarData::identityByteArray(bool setIsReplicated) const { QByteArray identityData; QDataStream identityStream(&identityData, QIODevice::Append); - const QUrl& urlToSend = cannonicalSkeletonModelURL(emptyURL); // depends on _skeletonModelURL // when mixers send identity packets to agents, they simply forward along the last incoming sequence number they received // whereas agents send a fresh outgoing sequence number when identity data has changed - _avatarEntitiesLock.withReadLock([&] { - identityStream << getSessionUUID() - << (udt::SequenceNumber::Type) _identitySequenceNumber - << urlToSend - << _attachmentData - << _displayName - << getSessionDisplayNameForTransport() // depends on _sessionDisplayName - << (_isReplicated || setIsReplicated) - << _avatarEntityData - << _lookAtSnappingEnabled - ; - }); + identityStream << getSessionUUID() + << (udt::SequenceNumber::Type) _identitySequenceNumber + << _attachmentData + << _displayName + << getSessionDisplayNameForTransport() // depends on _sessionDisplayName + << (_isReplicated || setIsReplicated) + << _lookAtSnappingEnabled; return identityData; } @@ -1718,11 +1959,17 @@ void AvatarData::setSkeletonModelURL(const QUrl& skeletonModelURL) { if (expanded == _skeletonModelURL) { return; } + _skeletonModelURL = expanded; qCDebug(avatars) << "Changing skeleton model for avatar" << getSessionUUID() << "to" << _skeletonModelURL.toString(); updateJointMappings(); - markIdentityDataChanged(); + + if (_clientTraitsHandler) { + _clientTraitsHandler->markTraitUpdated(AvatarTraits::SkeletonModelURL); + } + + emit skeletonModelURLChanged(); } void AvatarData::setDisplayName(const QString& displayName) { @@ -1817,6 +2064,13 @@ void AvatarData::setJointMappingsFromNetworkReply() { QNetworkReply* networkReply = static_cast(sender()); + // before we process this update, make sure that the skeleton model URL hasn't changed + // since we made the FST request + if (networkReply->url() != _skeletonModelURL) { + qCDebug(avatars) << "Refusing to set joint mappings for FST URL that does not match the current URL"; + return; + } + { QWriteLocker writeLock(&_jointDataLock); QByteArray line; @@ -1908,14 +2162,13 @@ void AvatarData::sendIdentityPacket() { auto packetList = NLPacketList::create(PacketType::AvatarIdentity, QByteArray(), true, true); packetList->write(identityData); nodeList->eachMatchingNode( - [&](const SharedNodePointer& node)->bool { + [](const SharedNodePointer& node)->bool { return node->getType() == NodeType::AvatarMixer && node->getActiveSocket(); }, [&](const SharedNodePointer& node) { nodeList->sendPacketList(std::move(packetList), *node); }); - _avatarEntityDataLocallyEdited = false; _identityDataChanged = false; } @@ -2082,6 +2335,15 @@ void AvatarData::setRecordingBasis(std::shared_ptr recordingBasis) { _recordingBasis = recordingBasis; } +void AvatarData::createRecordingIDs() { + _avatarEntitiesLock.withReadLock([&] { + _avatarEntityForRecording.clear(); + for (int i = 0; i < _avatarEntityData.size(); i++) { + _avatarEntityForRecording.insert(QUuid::createUuid()); + } + }); +} + void AvatarData::clearRecordingBasis() { _recordingBasis.reset(); } @@ -2142,21 +2404,15 @@ QJsonObject AvatarData::toJson() const { if (!getDisplayName().isEmpty()) { root[JSON_AVATAR_DISPLAY_NAME] = getDisplayName(); } - if (!getAttachmentData().isEmpty()) { - QJsonArray attachmentsJson; - for (auto attachment : getAttachmentData()) { - attachmentsJson.push_back(attachment.toJson()); - } - root[JSON_AVATAR_ATTACHMENTS] = attachmentsJson; - } - _avatarEntitiesLock.withReadLock([&] { if (!_avatarEntityData.empty()) { QJsonArray avatarEntityJson; + int entityCount = 0; for (auto entityID : _avatarEntityData.keys()) { QVariantMap entityData; - entityData.insert("id", entityID); - entityData.insert("properties", _avatarEntityData.value(entityID)); + QUuid newId = _avatarEntityForRecording.size() == _avatarEntityData.size() ? _avatarEntityForRecording.values()[entityCount++] : entityID; + entityData.insert("id", newId); + entityData.insert("properties", _avatarEntityData.value(entityID).toBase64()); avatarEntityJson.push_back(QVariant(entityData).toJsonObject()); } root[JSON_AVATAR_ENTITIES] = avatarEntityJson; @@ -2278,12 +2534,17 @@ void AvatarData::fromJson(const QJsonObject& json, bool useFrameSkeleton) { setAttachmentData(attachments); } - // if (json.contains(JSON_AVATAR_ENTITIES) && json[JSON_AVATAR_ENTITIES].isArray()) { - // QJsonArray attachmentsJson = json[JSON_AVATAR_ATTACHMENTS].toArray(); - // for (auto attachmentJson : attachmentsJson) { - // // TODO -- something - // } - // } + if (json.contains(JSON_AVATAR_ENTITIES) && json[JSON_AVATAR_ENTITIES].isArray()) { + QJsonArray attachmentsJson = json[JSON_AVATAR_ENTITIES].toArray(); + for (auto attachmentJson : attachmentsJson) { + if (attachmentJson.isObject()) { + QVariantMap entityData = attachmentJson.toObject().toVariantMap(); + QUuid entityID = entityData.value("id").toUuid(); + QByteArray properties = QByteArray::fromBase64(entityData.value("properties").toByteArray()); + updateAvatarEntity(entityID, properties); + } + } + } if (json.contains(JSON_AVATAR_JOINT_ARRAY)) { if (version == (int)JsonAvatarFrameVersion::JointRotationsInRelativeFrame) { @@ -2470,23 +2731,36 @@ void AvatarData::updateAvatarEntity(const QUuid& entityID, const QByteArray& ent if (itr == _avatarEntityData.end()) { if (_avatarEntityData.size() < MAX_NUM_AVATAR_ENTITIES) { _avatarEntityData.insert(entityID, entityData); - _avatarEntityDataLocallyEdited = true; - markIdentityDataChanged(); } } else { itr.value() = entityData; - _avatarEntityDataLocallyEdited = true; - markIdentityDataChanged(); } }); + + _avatarEntityDataChanged = true; + + if (_clientTraitsHandler) { + // we have a client traits handler, so we need to mark this instanced trait as changed + // so that changes will be sent next frame + _clientTraitsHandler->markInstancedTraitUpdated(AvatarTraits::AvatarEntity, entityID); + } } -void AvatarData::clearAvatarEntity(const QUuid& entityID) { - _avatarEntitiesLock.withWriteLock([&] { - _avatarEntityData.remove(entityID); - _avatarEntityDataLocallyEdited = true; - markIdentityDataChanged(); +void AvatarData::clearAvatarEntity(const QUuid& entityID, bool requiresRemovalFromTree) { + + bool removedEntity = false; + + _avatarEntitiesLock.withWriteLock([this, &removedEntity, &entityID] { + removedEntity = _avatarEntityData.remove(entityID); }); + + insertDetachedEntityID(entityID); + + if (removedEntity && _clientTraitsHandler) { + // we have a client traits handler, so we need to mark this removed instance trait as deleted + // so that changes are sent next frame + _clientTraitsHandler->markInstancedTraitDeleted(AvatarTraits::AvatarEntity, entityID); + } } AvatarEntityMap AvatarData::getAvatarEntityData() const { @@ -2501,6 +2775,8 @@ void AvatarData::insertDetachedEntityID(const QUuid entityID) { _avatarEntitiesLock.withWriteLock([&] { _avatarEntityDetached.insert(entityID); }); + + _avatarEntityDataChanged = true; } void AvatarData::setAvatarEntityData(const AvatarEntityMap& avatarEntityData) { @@ -2520,6 +2796,20 @@ void AvatarData::setAvatarEntityData(const AvatarEntityMap& avatarEntityData) { foreach (auto entityID, previousAvatarEntityIDs) { if (!_avatarEntityData.contains(entityID)) { _avatarEntityDetached.insert(entityID); + + if (_clientTraitsHandler) { + // we have a client traits handler, so we flag this removed entity as deleted + // so that changes are sent next frame + _clientTraitsHandler->markInstancedTraitDeleted(AvatarTraits::AvatarEntity, entityID); + } + } + } + + if (_clientTraitsHandler) { + // if we have a client traits handler, flag any updated or created entities + // so that we send changes for them next frame + foreach (auto entityID, _avatarEntityData.keys()) { + _clientTraitsHandler->markInstancedTraitUpdated(AvatarTraits::AvatarEntity, entityID); } } } @@ -2555,15 +2845,18 @@ glm::mat4 AvatarData::getControllerRightHandMatrix() const { return _controllerRightHandMatrixCache.get(); } - QScriptValue RayToAvatarIntersectionResultToScriptValue(QScriptEngine* engine, const RayToAvatarIntersectionResult& value) { QScriptValue obj = engine->newObject(); obj.setProperty("intersects", value.intersects); QScriptValue avatarIDValue = quuidToScriptValue(engine, value.avatarID); obj.setProperty("avatarID", avatarIDValue); obj.setProperty("distance", value.distance); + obj.setProperty("face", boxFaceToString(value.face)); + QScriptValue intersection = vec3toScriptValue(engine, value.intersection); obj.setProperty("intersection", intersection); + QScriptValue surfaceNormal = vec3toScriptValue(engine, value.surfaceNormal); + obj.setProperty("surfaceNormal", surfaceNormal); obj.setProperty("extraInfo", engine->toScriptValue(value.extraInfo)); return obj; } @@ -2573,10 +2866,16 @@ void RayToAvatarIntersectionResultFromScriptValue(const QScriptValue& object, Ra QScriptValue avatarIDValue = object.property("avatarID"); quuidFromScriptValue(avatarIDValue, value.avatarID); value.distance = object.property("distance").toVariant().toFloat(); + value.face = boxFaceFromString(object.property("face").toVariant().toString()); + QScriptValue intersection = object.property("intersection"); if (intersection.isValid()) { vec3FromScriptValue(intersection, value.intersection); } + QScriptValue surfaceNormal = object.property("surfaceNormal"); + if (surfaceNormal.isValid()) { + vec3FromScriptValue(surfaceNormal, value.surfaceNormal); + } value.extraInfo = object.property("extraInfo").toVariant().toMap(); } diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index b462e8a546..e9f1f5f6c3 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -51,6 +51,7 @@ #include #include "AABox.h" +#include "AvatarTraits.h" #include "HeadData.h" #include "PathUtils.h" @@ -138,6 +139,7 @@ namespace AvatarDataPacket { const HasFlags PACKET_HAS_FACE_TRACKER_INFO = 1U << 10; const HasFlags PACKET_HAS_JOINT_DATA = 1U << 11; const HasFlags PACKET_HAS_JOINT_DEFAULT_POSE_FLAGS = 1U << 12; + const HasFlags PACKET_HAS_GRAB_JOINTS = 1U << 13; const size_t AVATAR_HAS_FLAGS_SIZE = 2; using SixByteQuat = uint8_t[6]; @@ -273,7 +275,7 @@ namespace AvatarDataPacket { SixByteTrans rightHandControllerTranslation; }; */ - size_t maxJointDataSize(size_t numJoints); + size_t maxJointDataSize(size_t numJoints, bool hasGrabJoints); /* struct JointDefaultPoseFlags { @@ -283,6 +285,17 @@ namespace AvatarDataPacket { }; */ size_t maxJointDefaultPoseFlagsSize(size_t numJoints); + + PACKED_BEGIN struct FarGrabJoints { + float leftFarGrabPosition[3]; // left controller far-grab joint position + float leftFarGrabRotation[4]; // left controller far-grab joint rotation + float rightFarGrabPosition[3]; // right controller far-grab joint position + float rightFarGrabRotation[4]; // right controller far-grab joint rotation + float mouseFarGrabPosition[3]; // mouse far-grab joint position + float mouseFarGrabRotation[4]; // mouse far-grab joint rotation + } PACKED_END; + const size_t FAR_GRAB_JOINTS_SIZE = 84; + static_assert(sizeof(FarGrabJoints) == FAR_GRAB_JOINTS_SIZE, "AvatarDataPacket::FarGrabJoints size doesn't match."); } const float MAX_AUDIO_LOUDNESS = 1000.0f; // close enough for mouth animation @@ -324,6 +337,7 @@ enum KillAvatarReason : uint8_t { TheirAvatarEnteredYourBubble, YourAvatarEnteredTheirBubble }; + Q_DECLARE_METATYPE(KillAvatarReason); class QDataStream; @@ -347,6 +361,7 @@ public: RateCounter<> faceTrackerRate; RateCounter<> jointDataRate; RateCounter<> jointDefaultPoseFlagsRate; + RateCounter<> farGrabJointRate; }; class AvatarPriority { @@ -358,12 +373,14 @@ public: bool operator<(const AvatarPriority& other) const { return priority < other.priority; } }; +class ClientTraitsHandler; + class AvatarData : public QObject, public SpatiallyNestable { Q_OBJECT // The following properties have JSDoc in MyAvatar.h and ScriptableAvatar.h Q_PROPERTY(glm::vec3 position READ getWorldPosition WRITE setPositionViaScript) - Q_PROPERTY(float scale READ getTargetScale WRITE setTargetScale) + Q_PROPERTY(float scale READ getDomainLimitedScale WRITE setTargetScale) Q_PROPERTY(float density READ getDensity) Q_PROPERTY(glm::vec3 handPosition READ getHandPosition WRITE setHandPosition) Q_PROPERTY(float bodyYaw READ getBodyYaw WRITE setBodyYaw) @@ -412,7 +429,6 @@ public: virtual ~AvatarData(); static const QUrl& defaultFullAvatarModelUrl(); - QUrl cannonicalSkeletonModelURL(const QUrl& empty) const; virtual bool isMyAvatar() const { return false; } @@ -895,14 +911,14 @@ public: * @returns {object} */ // FIXME: Can this name be improved? Can it be deprecated? - Q_INVOKABLE QVariantList getAttachmentsVariant() const; + Q_INVOKABLE virtual QVariantList getAttachmentsVariant() const; /**jsdoc * @function MyAvatar.setAttachmentsVariant * @param {object} variant */ // FIXME: Can this name be improved? Can it be deprecated? - Q_INVOKABLE void setAttachmentsVariant(const QVariantList& variant); + Q_INVOKABLE virtual void setAttachmentsVariant(const QVariantList& variant); /**jsdoc @@ -911,11 +927,12 @@ public: * @param {string} entityData */ Q_INVOKABLE void updateAvatarEntity(const QUuid& entityID, const QByteArray& entityData); + /**jsdoc * @function MyAvatar.clearAvatarEntity * @param {Uuid} entityID */ - Q_INVOKABLE void clearAvatarEntity(const QUuid& entityID); + Q_INVOKABLE void clearAvatarEntity(const QUuid& entityID, bool requiresRemovalFromTree = true); /**jsdoc @@ -931,23 +948,35 @@ public: const HeadData* getHeadData() const { return _headData; } struct Identity { - QUrl skeletonModelURL; QVector attachmentData; QString displayName; QString sessionDisplayName; bool isReplicated; - AvatarEntityMap avatarEntityData; bool lookAtSnappingEnabled; }; // identityChanged returns true if identity has changed, false otherwise. // identityChanged returns true if identity has changed, false otherwise. Similarly for displayNameChanged and skeletonModelUrlChange. - void processAvatarIdentity(const QByteArray& identityData, bool& identityChanged, - bool& displayNameChanged, bool& skeletonModelUrlChanged); + void processAvatarIdentity(const QByteArray& identityData, bool& identityChanged, bool& displayNameChanged); + + qint64 packTrait(AvatarTraits::TraitType traitType, ExtendedIODevice& destination, + AvatarTraits::TraitVersion traitVersion = AvatarTraits::NULL_TRAIT_VERSION); + qint64 packTraitInstance(AvatarTraits::TraitType traitType, AvatarTraits::TraitInstanceID instanceID, + ExtendedIODevice& destination, AvatarTraits::TraitVersion traitVersion = AvatarTraits::NULL_TRAIT_VERSION, + AvatarTraits::TraitInstanceID wireInstanceID = AvatarTraits::TraitInstanceID()); + + void prepareResetTraitInstances(); + + void processTrait(AvatarTraits::TraitType traitType, QByteArray traitBinaryData); + void processTraitInstance(AvatarTraits::TraitType traitType, + AvatarTraits::TraitInstanceID instanceID, QByteArray traitBinaryData); + void processDeletedTraitInstance(AvatarTraits::TraitType traitType, AvatarTraits::TraitInstanceID instanceID); QByteArray identityByteArray(bool setIsReplicated = false) const; + QUrl getWireSafeSkeletonModelURL() const; const QUrl& getSkeletonModelURL() const { return _skeletonModelURL; } + const QString& getDisplayName() const { return _displayName; } const QString& getSessionDisplayName() const { return _sessionDisplayName; } bool getLookAtSnappingEnabled() const { return _lookAtSnappingEnabled; } @@ -969,7 +998,7 @@ public: * print (attachments[i].modelURL); * } */ - Q_INVOKABLE QVector getAttachmentData() const; + Q_INVOKABLE virtual QVector getAttachmentData() const; /**jsdoc * Set all models currently attached to your avatar. For example, if you retrieve attachment data using @@ -1040,7 +1069,7 @@ public: * @param {string} [jointName=""] - The name of the joint to detach the model from. If "", then the most * recently attached model is removed from which ever joint it was attached to. */ - Q_INVOKABLE void detachOne(const QString& modelURL, const QString& jointName = QString()); + Q_INVOKABLE virtual void detachOne(const QString& modelURL, const QString& jointName = QString()); /**jsdoc * Detach all instances of a particular model from either a specific joint or all joints. @@ -1049,7 +1078,7 @@ public: * @param {string} [jointName=""] - The name of the joint to detach the model from. If "", then the model is * detached from all joints. */ - Q_INVOKABLE void detachAll(const QString& modelURL, const QString& jointName = QString()); + Q_INVOKABLE virtual void detachAll(const QString& modelURL, const QString& jointName = QString()); QString getSkeletonModelURLFromScript() const { return _skeletonModelURL.toString(); } void setSkeletonModelURLFromScript(const QString& skeletonModelString) { setSkeletonModelURL(QUrl(skeletonModelString)); } @@ -1064,6 +1093,7 @@ public: void clearRecordingBasis(); TransformPointer getRecordingBasis() const; void setRecordingBasis(TransformPointer recordingBasis = TransformPointer()); + void createRecordingIDs(); QJsonObject toJson() const; void fromJson(const QJsonObject& json, bool useFrameSkeleton = true); @@ -1160,6 +1190,11 @@ public: virtual void addMaterial(graphics::MaterialLayer material, const std::string& parentMaterialName) {} virtual void removeMaterial(graphics::MaterialPointer material, const std::string& parentMaterialName) {} + void setReplicaIndex(int replicaIndex) { _replicaIndex = replicaIndex; } + int getReplicaIndex() { return _replicaIndex; } + + const AvatarTraits::TraitInstanceID getTraitInstanceXORID() const { return _traitInstanceXORID; } + void cycleTraitInstanceXORID() { _traitInstanceXORID = QUuid::createUuid(); } signals: @@ -1314,9 +1349,9 @@ protected: mutable HeadData* _headData { nullptr }; QUrl _skeletonModelURL; - bool _firstSkeletonCheck { true }; QUrl _skeletonFBXURL; QVector _attachmentData; + QVector _oldAttachmentData; QString _displayName; QString _sessionDisplayName { }; bool _lookAtSnappingEnabled { true }; @@ -1369,6 +1404,7 @@ protected: RateCounter<> _faceTrackerRate; RateCounter<> _jointDataRate; RateCounter<> _jointDefaultPoseFlagsRate; + RateCounter<> _farGrabJointRate; // Some rate data for incoming data updates RateCounter<> _parseBufferUpdateRate; @@ -1385,6 +1421,7 @@ protected: RateCounter<> _faceTrackerUpdateRate; RateCounter<> _jointDataUpdateRate; RateCounter<> _jointDefaultPoseFlagsUpdateRate; + RateCounter<> _farGrabJointUpdateRate; // Some rate data for outgoing data AvatarDataRate _outboundDataRate; @@ -1394,8 +1431,8 @@ protected: mutable ReadWriteLockable _avatarEntitiesLock; AvatarEntityIDs _avatarEntityDetached; // recently detached from this avatar + AvatarEntityIDs _avatarEntityForRecording; // create new entities id for avatar recording AvatarEntityMap _avatarEntityData; - bool _avatarEntityDataLocallyEdited { false }; bool _avatarEntityDataChanged { false }; // used to transform any sensor into world space, including the _hmdSensorMat, or hand controllers. @@ -1403,6 +1440,10 @@ protected: ThreadSafeValueCache _controllerLeftHandMatrixCache { glm::mat4() }; ThreadSafeValueCache _controllerRightHandMatrixCache { glm::mat4() }; + ThreadSafeValueCache _farGrabRightMatrixCache { glm::mat4() }; + ThreadSafeValueCache _farGrabLeftMatrixCache { glm::mat4() }; + ThreadSafeValueCache _farGrabMouseMatrixCache { glm::mat4() }; + int getFauxJointIndex(const QString& name) const; float _audioLoudness { 0.0f }; @@ -1413,6 +1454,10 @@ protected: udt::SequenceNumber _identitySequenceNumber { 0 }; bool _hasProcessedFirstIdentity { false }; float _density; + int _replicaIndex { 0 }; + + // null unless MyAvatar or ScriptableAvatar sending traits data to mixer + std::unique_ptr _clientTraitsHandler; template T readLockWithNamedJointIndex(const QString& name, const T& defaultValue, F f) const { @@ -1457,6 +1502,8 @@ private: // privatize the copy constructor and assignment operator so they cannot be called AvatarData(const AvatarData&); AvatarData& operator= (const AvatarData&); + + AvatarTraits::TraitInstanceID _traitInstanceXORID { QUuid::createUuid() }; }; Q_DECLARE_METATYPE(AvatarData*) @@ -1524,19 +1571,30 @@ void registerAvatarTypes(QScriptEngine* engine); class RayToAvatarIntersectionResult { public: -RayToAvatarIntersectionResult() : intersects(false), avatarID(), distance(0) {} - bool intersects; + bool intersects { false }; QUuid avatarID; - float distance; + float distance { FLT_MAX }; + BoxFace face; glm::vec3 intersection; + glm::vec3 surfaceNormal; QVariantMap extraInfo; }; - Q_DECLARE_METATYPE(RayToAvatarIntersectionResult) - QScriptValue RayToAvatarIntersectionResultToScriptValue(QScriptEngine* engine, const RayToAvatarIntersectionResult& results); void RayToAvatarIntersectionResultFromScriptValue(const QScriptValue& object, RayToAvatarIntersectionResult& results); +class ParabolaToAvatarIntersectionResult { +public: + bool intersects { false }; + QUuid avatarID; + float distance { 0.0f }; + float parabolicDistance { 0.0f }; + BoxFace face; + glm::vec3 intersection; + glm::vec3 surfaceNormal; + QVariantMap extraInfo; +}; + Q_DECLARE_METATYPE(AvatarEntityMap) QScriptValue AvatarEntityMapToScriptValue(QScriptEngine* engine, const AvatarEntityMap& value); @@ -1549,5 +1607,11 @@ const int CONTROLLER_LEFTHAND_INDEX = 65532; // -4 const int CAMERA_RELATIVE_CONTROLLER_RIGHTHAND_INDEX = 65531; // -5 const int CAMERA_RELATIVE_CONTROLLER_LEFTHAND_INDEX = 65530; // -6 const int CAMERA_MATRIX_INDEX = 65529; // -7 +const int FARGRAB_RIGHTHAND_INDEX = 65528; // -8 +const int FARGRAB_LEFTHAND_INDEX = 65527; // -9 +const int FARGRAB_MOUSE_INDEX = 65526; // -10 + +const int LOWEST_PSEUDO_JOINT_INDEX = 65526; + #endif // hifi_AvatarData_h diff --git a/libraries/avatars/src/AvatarHashMap.cpp b/libraries/avatars/src/AvatarHashMap.cpp index 174e81bb31..c1246866dc 100644 --- a/libraries/avatars/src/AvatarHashMap.cpp +++ b/libraries/avatars/src/AvatarHashMap.cpp @@ -19,10 +19,98 @@ #include #include "AvatarLogging.h" +#include "AvatarTraits.h" + + +void AvatarReplicas::addReplica(const QUuid& parentID, AvatarSharedPointer replica) { + if (parentID == QUuid()) { + return; + } + if (_replicasMap.find(parentID) == _replicasMap.end()) { + std::vector emptyReplicas = std::vector(); + _replicasMap.insert(std::pair>(parentID, emptyReplicas)); + } + auto &replicas = _replicasMap[parentID]; + replica->setReplicaIndex((int)replicas.size() + 1); + replicas.push_back(replica); +} + +std::vector AvatarReplicas::getReplicaIDs(const QUuid& parentID) { + std::vector ids; + if (_replicasMap.find(parentID) != _replicasMap.end()) { + auto &replicas = _replicasMap[parentID]; + for (int i = 0; i < (int)replicas.size(); i++) { + ids.push_back(replicas[i]->getID()); + } + } else if (_replicaCount > 0) { + for (int i = 0; i < _replicaCount; i++) { + ids.push_back(QUuid::createUuid()); + } + } + return ids; +} + +void AvatarReplicas::parseDataFromBuffer(const QUuid& parentID, const QByteArray& buffer) { + if (_replicasMap.find(parentID) != _replicasMap.end()) { + auto &replicas = _replicasMap[parentID]; + for (auto avatar : replicas) { + avatar->parseDataFromBuffer(buffer); + } + } +} + +void AvatarReplicas::removeReplicas(const QUuid& parentID) { + if (_replicasMap.find(parentID) != _replicasMap.end()) { + _replicasMap.erase(parentID); + } +} + +void AvatarReplicas::processAvatarIdentity(const QUuid& parentID, const QByteArray& identityData, bool& identityChanged, bool& displayNameChanged) { + if (_replicasMap.find(parentID) != _replicasMap.end()) { + auto &replicas = _replicasMap[parentID]; + for (auto avatar : replicas) { + avatar->processAvatarIdentity(identityData, identityChanged, displayNameChanged); + } + } +} +void AvatarReplicas::processTrait(const QUuid& parentID, AvatarTraits::TraitType traitType, QByteArray traitBinaryData) { + if (_replicasMap.find(parentID) != _replicasMap.end()) { + auto &replicas = _replicasMap[parentID]; + for (auto avatar : replicas) { + avatar->processTrait(traitType, traitBinaryData); + } + } +} +void AvatarReplicas::processDeletedTraitInstance(const QUuid& parentID, AvatarTraits::TraitType traitType, AvatarTraits::TraitInstanceID instanceID) { + if (_replicasMap.find(parentID) != _replicasMap.end()) { + auto &replicas = _replicasMap[parentID]; + for (auto avatar : replicas) { + avatar->processDeletedTraitInstance(traitType, + AvatarTraits::xoredInstanceID(instanceID, avatar->getTraitInstanceXORID())); + } + } +} +void AvatarReplicas::processTraitInstance(const QUuid& parentID, AvatarTraits::TraitType traitType, + AvatarTraits::TraitInstanceID instanceID, QByteArray traitBinaryData) { + if (_replicasMap.find(parentID) != _replicasMap.end()) { + auto &replicas = _replicasMap[parentID]; + for (auto avatar : replicas) { + avatar->processTraitInstance(traitType, + AvatarTraits::xoredInstanceID(instanceID, avatar->getTraitInstanceXORID()), + traitBinaryData); + } + } +} AvatarHashMap::AvatarHashMap() { auto nodeList = DependencyManager::get(); + auto& packetReceiver = nodeList->getPacketReceiver(); + packetReceiver.registerListener(PacketType::BulkAvatarData, this, "processAvatarDataPacket"); + packetReceiver.registerListener(PacketType::KillAvatar, this, "processKillAvatar"); + packetReceiver.registerListener(PacketType::AvatarIdentity, this, "processAvatarIdentityPacket"); + packetReceiver.registerListener(PacketType::BulkAvatarTraits, this, "processBulkAvatarTraits"); + connect(nodeList.data(), &NodeList::uuidChanged, this, &AvatarHashMap::sessionUUIDChanged); } @@ -57,6 +145,21 @@ bool AvatarHashMap::isAvatarInRange(const glm::vec3& position, const float range return false; } +void AvatarHashMap::setReplicaCount(int count) { + _replicas.setReplicaCount(count); + auto avatars = getAvatarIdentifiers(); + for (int i = 0; i < avatars.size(); i++) { + KillAvatarReason reason = KillAvatarReason::NoReason; + if (avatars[i] != QUuid()) { + removeAvatar(avatars[i], reason); + auto replicaIDs = _replicas.getReplicaIDs(avatars[i]); + for (auto id : replicaIDs) { + removeAvatar(id, reason); + } + } + } +} + int AvatarHashMap::numberOfAvatarsInRange(const glm::vec3& position, float rangeMeters) { auto hashCopy = getHashCopy(); auto rangeMeters2 = rangeMeters * rangeMeters; @@ -128,18 +231,25 @@ AvatarSharedPointer AvatarHashMap::parseAvatarData(QSharedPointer(); - bool isNewAvatar; if (sessionUUID != _lastOwnerSessionUUID && (!nodeList->isIgnoringNode(sessionUUID) || nodeList->getRequestsDomainListData())) { auto avatar = newOrExistingAvatar(sessionUUID, sendingNode, isNewAvatar); + if (isNewAvatar) { QWriteLocker locker(&_hashLock); _pendingAvatars.insert(sessionUUID, { std::chrono::steady_clock::now(), 0, avatar }); - } + auto replicaIDs = _replicas.getReplicaIDs(sessionUUID); + for (auto replicaID : replicaIDs) { + auto replicaAvatar = addAvatar(replicaID, sendingNode); + _replicas.addReplica(sessionUUID, replicaAvatar); + } + } // have the matching (or new) avatar parse the data from the packet int bytesRead = avatar->parseDataFromBuffer(byteArray); message->seek(positionBeforeRead + bytesRead); + _replicas.parseDataFromBuffer(sessionUUID, byteArray); + return avatar; } else { // create a dummy AvatarData class to throw this data on the ground @@ -182,9 +292,90 @@ void AvatarHashMap::processAvatarIdentityPacket(QSharedPointer auto avatar = newOrExistingAvatar(identityUUID, sendingNode, isNewAvatar); bool identityChanged = false; bool displayNameChanged = false; - bool skeletonModelUrlChanged = false; // In this case, the "sendingNode" is the Avatar Mixer. - avatar->processAvatarIdentity(message->getMessage(), identityChanged, displayNameChanged, skeletonModelUrlChanged); + avatar->processAvatarIdentity(message->getMessage(), identityChanged, displayNameChanged); + _replicas.processAvatarIdentity(identityUUID, message->getMessage(), identityChanged, displayNameChanged); + + } +} + +void AvatarHashMap::processBulkAvatarTraits(QSharedPointer message, SharedNodePointer sendingNode) { + + while (message->getBytesLeftToRead()) { + // read the avatar ID to figure out which avatar this is for + auto avatarID = QUuid::fromRfc4122(message->readWithoutCopy(NUM_BYTES_RFC4122_UUID)); + + // grab the avatar so we can ask it to process trait data + bool isNewAvatar; + auto avatar = newOrExistingAvatar(avatarID, sendingNode, isNewAvatar); + // read the first trait type for this avatar + AvatarTraits::TraitType traitType; + message->readPrimitive(&traitType); + + // grab the last trait versions for this avatar + auto& lastProcessedVersions = _processedTraitVersions[avatarID]; + + while (traitType != AvatarTraits::NullTrait) { + AvatarTraits::TraitVersion packetTraitVersion; + message->readPrimitive(&packetTraitVersion); + + AvatarTraits::TraitWireSize traitBinarySize; + bool skipBinaryTrait = false; + + if (AvatarTraits::isSimpleTrait(traitType)) { + message->readPrimitive(&traitBinarySize); + + // check if this trait version is newer than what we already have for this avatar + if (packetTraitVersion > lastProcessedVersions[traitType]) { + auto traitData = message->read(traitBinarySize); + avatar->processTrait(traitType, traitData); + _replicas.processTrait(avatarID, traitType, traitData); + lastProcessedVersions[traitType] = packetTraitVersion; + } else { + skipBinaryTrait = true; + } + } else { + AvatarTraits::TraitInstanceID traitInstanceID = + QUuid::fromRfc4122(message->readWithoutCopy(NUM_BYTES_RFC4122_UUID)); + + // XOR the incoming trait instance ID with this avatar object's personal XOR ID + + // this ensures that we have separate entity instances in the local tree + // if we briefly end up with two Avatar objects for this node + + // (which can occur if the shared pointer for the + // previous instance of an avatar hasn't yet gone out of scope before the + // new instance is created) + + auto xoredInstanceID = AvatarTraits::xoredInstanceID(traitInstanceID, avatar->getTraitInstanceXORID()); + + message->readPrimitive(&traitBinarySize); + + auto& processedInstanceVersion = lastProcessedVersions.getInstanceValueRef(traitType, traitInstanceID); + if (packetTraitVersion > processedInstanceVersion) { + // in order to handle re-connections to the avatar mixer when the other + if (traitBinarySize == AvatarTraits::DELETED_TRAIT_SIZE) { + avatar->processDeletedTraitInstance(traitType, xoredInstanceID); + _replicas.processDeletedTraitInstance(avatarID, traitType, traitInstanceID); + } else { + auto traitData = message->read(traitBinarySize); + avatar->processTraitInstance(traitType, xoredInstanceID, traitData); + _replicas.processTraitInstance(avatarID, traitType, traitInstanceID, traitData); + } + processedInstanceVersion = packetTraitVersion; + } else { + skipBinaryTrait = true; + } + } + + if (skipBinaryTrait) { + // we didn't read this trait because it was older or because we didn't have an avatar to process it for + message->seek(message->getPosition() + traitBinarySize); + } + + // read the next trait type, which is null if there are no more traits for this avatar + message->readPrimitive(&traitType); + } } } @@ -195,20 +386,37 @@ void AvatarHashMap::processKillAvatar(QSharedPointer message, S KillAvatarReason reason; message->readPrimitive(&reason); removeAvatar(sessionUUID, reason); + auto replicaIDs = _replicas.getReplicaIDs(sessionUUID); + for (auto id : replicaIDs) { + removeAvatar(id, reason); + } } void AvatarHashMap::removeAvatar(const QUuid& sessionUUID, KillAvatarReason removalReason) { QWriteLocker locker(&_hashLock); + auto replicaIDs = _replicas.getReplicaIDs(sessionUUID); + _replicas.removeReplicas(sessionUUID); + for (auto id : replicaIDs) { + auto removedReplica = _avatarHash.take(id); + if (removedReplica) { + handleRemovedAvatar(removedReplica, removalReason); + } + } + _pendingAvatars.remove(sessionUUID); auto removedAvatar = _avatarHash.take(sessionUUID); if (removedAvatar) { handleRemovedAvatar(removedAvatar, removalReason); } + } void AvatarHashMap::handleRemovedAvatar(const AvatarSharedPointer& removedAvatar, KillAvatarReason removalReason) { + // remove any information about processed traits for this avatar + _processedTraitVersions.erase(removedAvatar->getID()); + qCDebug(avatars) << "Removed avatar with UUID" << uuidStringWithoutCurlyBraces(removedAvatar->getSessionUUID()) << "from AvatarHashMap" << removalReason; emit avatarRemovedEvent(removedAvatar->getSessionUUID()); diff --git a/libraries/avatars/src/AvatarHashMap.h b/libraries/avatars/src/AvatarHashMap.h index fd2cd76fbf..0f847b2a61 100644 --- a/libraries/avatars/src/AvatarHashMap.h +++ b/libraries/avatars/src/AvatarHashMap.h @@ -30,6 +30,7 @@ #include "ScriptAvatarData.h" #include "AvatarData.h" +#include "AssociatedTraitValues.h" /**jsdoc * Note: An AvatarList API is also provided for Interface and client entity scripts: it is a @@ -40,6 +41,27 @@ * @hifi-assignment-client */ +class AvatarReplicas { +public: + AvatarReplicas() {} + void addReplica(const QUuid& parentID, AvatarSharedPointer replica); + std::vector getReplicaIDs(const QUuid& parentID); + void parseDataFromBuffer(const QUuid& parentID, const QByteArray& buffer); + void processAvatarIdentity(const QUuid& parentID, const QByteArray& identityData, bool& identityChanged, bool& displayNameChanged); + void removeReplicas(const QUuid& parentID); + void processTrait(const QUuid& parentID, AvatarTraits::TraitType traitType, QByteArray traitBinaryData); + void processDeletedTraitInstance(const QUuid& parentID, AvatarTraits::TraitType traitType, AvatarTraits::TraitInstanceID instanceID); + void processTraitInstance(const QUuid& parentID, AvatarTraits::TraitType traitType, + AvatarTraits::TraitInstanceID instanceID, QByteArray traitBinaryData); + void setReplicaCount(int count) { _replicaCount = count; } + int getReplicaCount() { return _replicaCount; } + +private: + std::map> _replicasMap; + int _replicaCount { 0 }; +}; + + class AvatarHashMap : public QObject, public Dependency { Q_OBJECT SINGLETON_DEPENDENCY @@ -76,6 +98,9 @@ public: virtual AvatarSharedPointer getAvatarBySessionID(const QUuid& sessionID) const { return findAvatar(sessionID); } int numberOfAvatarsInRange(const glm::vec3& position, float rangeMeters); + void setReplicaCount(int count); + int getReplicaCount() { return _replicas.getReplicaCount(); }; + signals: /**jsdoc @@ -133,6 +158,8 @@ protected slots: */ void processAvatarIdentityPacket(QSharedPointer message, SharedNodePointer sendingNode); + void processBulkAvatarTraits(QSharedPointer message, SharedNodePointer sendingNode); + /**jsdoc * @function AvatarList.processKillAvatar * @param {} message @@ -152,7 +179,7 @@ protected: virtual void removeAvatar(const QUuid& sessionUUID, KillAvatarReason removalReason = KillAvatarReason::NoReason); virtual void handleRemovedAvatar(const AvatarSharedPointer& removedAvatar, KillAvatarReason removalReason = KillAvatarReason::NoReason); - + AvatarHash _avatarHash; struct PendingAvatar { std::chrono::steady_clock::time_point creationTime; @@ -163,6 +190,9 @@ protected: AvatarPendingHash _pendingAvatars; mutable QReadWriteLock _hashLock; + std::unordered_map _processedTraitVersions; + AvatarReplicas _replicas; + private: QUuid _lastOwnerSessionUUID; }; diff --git a/libraries/avatars/src/AvatarTraits.h b/libraries/avatars/src/AvatarTraits.h new file mode 100644 index 0000000000..47be0d6111 --- /dev/null +++ b/libraries/avatars/src/AvatarTraits.h @@ -0,0 +1,78 @@ +// +// AvatarTraits.h +// libraries/avatars/src +// +// Created by Stephen Birarda on 7/30/18. +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_AvatarTraits_h +#define hifi_AvatarTraits_h + +#include +#include +#include + +#include + +namespace AvatarTraits { + enum TraitType : int8_t { + NullTrait = -1, + SkeletonModelURL, + FirstInstancedTrait, + AvatarEntity = FirstInstancedTrait, + TotalTraitTypes + }; + + using TraitInstanceID = QUuid; + + inline bool isSimpleTrait(TraitType traitType) { + return traitType > NullTrait && traitType < FirstInstancedTrait; + } + + using TraitVersion = int32_t; + const TraitVersion DEFAULT_TRAIT_VERSION = 0; + const TraitVersion NULL_TRAIT_VERSION = -1; + + using TraitWireSize = int16_t; + const TraitWireSize DELETED_TRAIT_SIZE = -1; + + inline qint64 packInstancedTraitDelete(TraitType traitType, TraitInstanceID instanceID, ExtendedIODevice& destination, + TraitVersion traitVersion = NULL_TRAIT_VERSION, + TraitInstanceID xoredInstanceID = TraitInstanceID()) { + qint64 bytesWritten = 0; + + bytesWritten += destination.writePrimitive(traitType); + + if (traitVersion > DEFAULT_TRAIT_VERSION) { + bytesWritten += destination.writePrimitive(traitVersion); + } + + if (xoredInstanceID.isNull()) { + bytesWritten += destination.write(instanceID.toRfc4122()); + } else { + bytesWritten += destination.write(xoredInstanceID.toRfc4122()); + } + + bytesWritten += destination.writePrimitive(DELETED_TRAIT_SIZE); + + return bytesWritten; + } + + inline TraitInstanceID xoredInstanceID(TraitInstanceID localInstanceID, TraitInstanceID xorKeyID) { + QByteArray xoredInstanceID { NUM_BYTES_RFC4122_UUID, 0 }; + auto xorKeyIDBytes = xorKeyID.toRfc4122(); + auto localInstanceIDBytes = localInstanceID.toRfc4122(); + + for (auto i = 0; i < localInstanceIDBytes.size(); ++i) { + xoredInstanceID[i] = localInstanceIDBytes[i] ^ xorKeyIDBytes[i]; + } + + return QUuid::fromRfc4122(xoredInstanceID); + } +}; + +#endif // hifi_AvatarTraits_h diff --git a/libraries/avatars/src/ClientTraitsHandler.cpp b/libraries/avatars/src/ClientTraitsHandler.cpp new file mode 100644 index 0000000000..479852cf9a --- /dev/null +++ b/libraries/avatars/src/ClientTraitsHandler.cpp @@ -0,0 +1,160 @@ +// +// ClientTraitsHandler.cpp +// libraries/avatars/src +// +// Created by Stephen Birarda on 7/30/18. +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "ClientTraitsHandler.h" + +#include + +#include +#include + +#include "AvatarData.h" + +ClientTraitsHandler::ClientTraitsHandler(AvatarData* owningAvatar) : + _owningAvatar(owningAvatar) +{ + auto nodeList = DependencyManager::get(); + QObject::connect(nodeList.data(), &NodeList::nodeAdded, [this](SharedNodePointer addedNode){ + if (addedNode->getType() == NodeType::AvatarMixer) { + resetForNewMixer(); + } + }); + + nodeList->getPacketReceiver().registerListener(PacketType::SetAvatarTraits, this, "processTraitOverride"); +} + +void ClientTraitsHandler::resetForNewMixer() { + // re-set the current version to 0 + _currentTraitVersion = AvatarTraits::DEFAULT_TRAIT_VERSION; + + // mark that all traits should be sent next time + _shouldPerformInitialSend = true; + + // reset the trait statuses + _traitStatuses.reset(); + + // pre-fill the instanced statuses that we will need to send next frame + _owningAvatar->prepareResetTraitInstances(); + + // reset the trait XOR ID since we're resetting for a new avatar mixer + _owningAvatar->cycleTraitInstanceXORID(); +} + +void ClientTraitsHandler::sendChangedTraitsToMixer() { + if (hasChangedTraits() || _shouldPerformInitialSend) { + // we have at least one changed trait to send + + auto nodeList = DependencyManager::get(); + auto avatarMixer = nodeList->soloNodeOfType(NodeType::AvatarMixer); + if (!avatarMixer || !avatarMixer->getActiveSocket()) { + // we don't have an avatar mixer with an active socket, we can't send changed traits at this time + return; + } + + // we have a mixer to send to, setup our set traits packet + auto traitsPacketList = NLPacketList::create(PacketType::SetAvatarTraits, QByteArray(), true, true); + + // bump and write the current trait version to an extended header + // the trait version is the same for all traits in this packet list + traitsPacketList->writePrimitive(++_currentTraitVersion); + + // take a copy of the set of changed traits and clear the stored set + auto traitStatusesCopy { _traitStatuses }; + _traitStatuses.reset(); + _hasChangedTraits = false; + + auto simpleIt = traitStatusesCopy.simpleCBegin(); + while (simpleIt != traitStatusesCopy.simpleCEnd()) { + // because the vector contains all trait types (for access using trait type as index) + // we double check that it is a simple iterator here + auto traitType = static_cast(std::distance(traitStatusesCopy.simpleCBegin(), simpleIt)); + + if (_shouldPerformInitialSend || *simpleIt == Updated) { + if (traitType == AvatarTraits::SkeletonModelURL) { + _owningAvatar->packTrait(traitType, *traitsPacketList); + + // keep track of our skeleton version in case we get an override back + _currentSkeletonVersion = _currentTraitVersion; + } + } + + ++simpleIt; + } + + auto instancedIt = traitStatusesCopy.instancedCBegin(); + while (instancedIt != traitStatusesCopy.instancedCEnd()) { + for (auto& instanceIDValuePair : instancedIt->instances) { + if ((_shouldPerformInitialSend && instanceIDValuePair.value != Deleted) + || instanceIDValuePair.value == Updated) { + // this is a changed trait we need to send or we haven't send out trait information yet + // ask the owning avatar to pack it + + // since this is going to the mixer, use the XORed instance ID (to anonymize trait instance IDs + // that would typically persist across sessions) + _owningAvatar->packTraitInstance(instancedIt->traitType, instanceIDValuePair.id, *traitsPacketList, + AvatarTraits::NULL_TRAIT_VERSION, + AvatarTraits::xoredInstanceID(instanceIDValuePair.id, + _owningAvatar->getTraitInstanceXORID())); + } else if (!_shouldPerformInitialSend && instanceIDValuePair.value == Deleted) { + // pack delete for this trait instance + AvatarTraits::packInstancedTraitDelete(instancedIt->traitType, instanceIDValuePair.id, + *traitsPacketList, AvatarTraits::NULL_TRAIT_VERSION, + AvatarTraits::xoredInstanceID(instanceIDValuePair.id, + _owningAvatar->getTraitInstanceXORID())); + } + } + + ++instancedIt; + } + + nodeList->sendPacketList(std::move(traitsPacketList), *avatarMixer); + + // if this was an initial send of all traits, consider it completed + _shouldPerformInitialSend = false; + } +} + +void ClientTraitsHandler::processTraitOverride(QSharedPointer message, SharedNodePointer sendingNode) { + if (sendingNode->getType() == NodeType::AvatarMixer) { + while (message->getBytesLeftToRead()) { + AvatarTraits::TraitType traitType; + message->readPrimitive(&traitType); + + AvatarTraits::TraitVersion traitVersion; + message->readPrimitive(&traitVersion); + + AvatarTraits::TraitWireSize traitBinarySize; + message->readPrimitive(&traitBinarySize); + + // only accept an override if this is for a trait type we override + // and the version matches what we last sent for skeleton + if (traitType == AvatarTraits::SkeletonModelURL + && traitVersion == _currentSkeletonVersion + && _traitStatuses[AvatarTraits::SkeletonModelURL] != Updated) { + + // override the skeleton URL but do not mark the trait as having changed + // so that we don't unecessarily send a new trait packet to the mixer with the overriden URL + auto encodedSkeletonURL = QUrl::fromEncoded(message->readWithoutCopy(traitBinarySize)); + + auto hasChangesBefore = _hasChangedTraits; + + _owningAvatar->setSkeletonModelURL(encodedSkeletonURL); + + // setSkeletonModelURL will flag us for changes to the SkeletonModelURL so we reset some state here to + // avoid unnecessarily sending the overriden skeleton model URL back to the mixer + _traitStatuses.erase(AvatarTraits::SkeletonModelURL); + _hasChangedTraits = hasChangesBefore; + } else { + message->seek(message->getPosition() + traitBinarySize); + } + } + } +} diff --git a/libraries/avatars/src/ClientTraitsHandler.h b/libraries/avatars/src/ClientTraitsHandler.h new file mode 100644 index 0000000000..6d1592ba74 --- /dev/null +++ b/libraries/avatars/src/ClientTraitsHandler.h @@ -0,0 +1,61 @@ +// +// ClientTraitsHandler.h +// libraries/avatars/src +// +// Created by Stephen Birarda on 7/30/18. +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_ClientTraitsHandler_h +#define hifi_ClientTraitsHandler_h + +#include + +#include "AssociatedTraitValues.h" +#include "Node.h" + +class AvatarData; + +class ClientTraitsHandler : public QObject { + Q_OBJECT +public: + ClientTraitsHandler(AvatarData* owningAvatar); + + void sendChangedTraitsToMixer(); + + bool hasChangedTraits() { return _hasChangedTraits; } + + void markTraitUpdated(AvatarTraits::TraitType updatedTrait) + { _traitStatuses[updatedTrait] = Updated; _hasChangedTraits = true; } + void markInstancedTraitUpdated(AvatarTraits::TraitType traitType, AvatarTraits::TraitInstanceID updatedInstanceID) + { _traitStatuses.instanceInsert(traitType, updatedInstanceID, Updated); _hasChangedTraits = true; } + void markInstancedTraitDeleted(AvatarTraits::TraitType traitType, AvatarTraits::TraitInstanceID deleteInstanceID) + { _traitStatuses.instanceInsert(traitType, deleteInstanceID, Deleted); _hasChangedTraits = true; } + + void resetForNewMixer(); + +public slots: + void processTraitOverride(QSharedPointer message, SharedNodePointer sendingNode); + +private: + enum ClientTraitStatus { + Unchanged, + Updated, + Deleted + }; + + AvatarData* _owningAvatar; + + AvatarTraits::AssociatedTraitValues _traitStatuses; + AvatarTraits::TraitVersion _currentTraitVersion { AvatarTraits::DEFAULT_TRAIT_VERSION }; + + AvatarTraits::TraitVersion _currentSkeletonVersion { AvatarTraits::NULL_TRAIT_VERSION }; + + bool _shouldPerformInitialSend { false }; + bool _hasChangedTraits { false }; +}; + +#endif // hifi_ClientTraitsHandler_h diff --git a/libraries/avatars/src/ScriptAvatarData.cpp b/libraries/avatars/src/ScriptAvatarData.cpp index 8491e5368b..a716a40ad8 100644 --- a/libraries/avatars/src/ScriptAvatarData.cpp +++ b/libraries/avatars/src/ScriptAvatarData.cpp @@ -269,6 +269,26 @@ QVector ScriptAvatarData::getAttachmentData() const { // END // +#if PR_BUILD || DEV_BUILD +// +// ENTITY PROPERTIES +// START +// +AvatarEntityMap ScriptAvatarData::getAvatarEntities() const { + AvatarEntityMap scriptEntityData; + + if (AvatarSharedPointer sharedAvatarData = _avatarData.lock()) { + return sharedAvatarData->getAvatarEntityData(); + } + + return scriptEntityData; +} +// +// ENTITY PROPERTIES +// END +// +#endif + // // AUDIO PROPERTIES diff --git a/libraries/avatars/src/ScriptAvatarData.h b/libraries/avatars/src/ScriptAvatarData.h index 13713ff15f..91bac61728 100644 --- a/libraries/avatars/src/ScriptAvatarData.h +++ b/libraries/avatars/src/ScriptAvatarData.h @@ -116,6 +116,10 @@ public: Q_INVOKABLE QStringList getJointNames() const; Q_INVOKABLE QVector getAttachmentData() const; +#if DEV_BUILD || PR_BUILD + Q_INVOKABLE AvatarEntityMap getAvatarEntities() const; +#endif + // // AUDIO PROPERTIES // diff --git a/libraries/baking/src/TextureBaker.cpp b/libraries/baking/src/TextureBaker.cpp index ecfe724441..2516323c37 100644 --- a/libraries/baking/src/TextureBaker.cpp +++ b/libraries/baking/src/TextureBaker.cpp @@ -138,7 +138,7 @@ void TextureBaker::processTexture() { // IMPORTANT: _originalTexture is empty past this point _originalTexture.clear(); _outputFiles.push_back(originalCopyFilePath); - meta.original = _metaTexturePathPrefix +_textureURL.fileName(); + meta.original = _metaTexturePathPrefix + _textureURL.fileName(); } auto buffer = std::static_pointer_cast(std::make_shared(originalCopyFilePath)); @@ -149,49 +149,56 @@ void TextureBaker::processTexture() { // Compressed KTX if (_compressionEnabled) { - auto processedTexture = image::processImage(buffer, _textureURL.toString().toStdString(), - ABSOLUTE_MAX_TEXTURE_NUM_PIXELS, _textureType, true, _abortProcessing); - if (!processedTexture) { - handleError("Could not process texture " + _textureURL.toString()); - return; - } - processedTexture->setSourceHash(hash); + constexpr std::array BACKEND_TARGETS {{ + gpu::BackendTarget::GL45, + gpu::BackendTarget::GLES32 + }}; + for (auto target : BACKEND_TARGETS) { + auto processedTexture = image::processImage(buffer, _textureURL.toString().toStdString(), + ABSOLUTE_MAX_TEXTURE_NUM_PIXELS, _textureType, true, + target, _abortProcessing); + if (!processedTexture) { + handleError("Could not process texture " + _textureURL.toString()); + return; + } + processedTexture->setSourceHash(hash); - if (shouldStop()) { - return; - } + if (shouldStop()) { + return; + } - auto memKTX = gpu::Texture::serialize(*processedTexture); - if (!memKTX) { - handleError("Could not serialize " + _textureURL.toString() + " to KTX"); - return; - } + auto memKTX = gpu::Texture::serialize(*processedTexture); + if (!memKTX) { + handleError("Could not serialize " + _textureURL.toString() + " to KTX"); + return; + } - const char* name = khronos::gl::texture::toString(memKTX->_header.getGLInternaFormat()); - if (name == nullptr) { - handleError("Could not determine internal format for compressed KTX: " + _textureURL.toString()); - return; - } + const char* name = khronos::gl::texture::toString(memKTX->_header.getGLInternaFormat()); + if (name == nullptr) { + handleError("Could not determine internal format for compressed KTX: " + _textureURL.toString()); + return; + } - const char* data = reinterpret_cast(memKTX->_storage->data()); - const size_t length = memKTX->_storage->size(); + const char* data = reinterpret_cast(memKTX->_storage->data()); + const size_t length = memKTX->_storage->size(); - auto fileName = _baseFilename + "_" + name + ".ktx"; - auto filePath = _outputDirectory.absoluteFilePath(fileName); - QFile bakedTextureFile { filePath }; - if (!bakedTextureFile.open(QIODevice::WriteOnly) || bakedTextureFile.write(data, length) == -1) { - handleError("Could not write baked texture for " + _textureURL.toString()); - return; + auto fileName = _baseFilename + "_" + name + ".ktx"; + auto filePath = _outputDirectory.absoluteFilePath(fileName); + QFile bakedTextureFile { filePath }; + if (!bakedTextureFile.open(QIODevice::WriteOnly) || bakedTextureFile.write(data, length) == -1) { + handleError("Could not write baked texture for " + _textureURL.toString()); + return; + } + _outputFiles.push_back(filePath); + meta.availableTextureTypes[memKTX->_header.getGLInternaFormat()] = _metaTexturePathPrefix + fileName; } - _outputFiles.push_back(filePath); - meta.availableTextureTypes[memKTX->_header.getGLInternaFormat()] = _metaTexturePathPrefix + fileName; } // Uncompressed KTX if (_textureType == image::TextureUsage::Type::CUBE_TEXTURE) { buffer->reset(); auto processedTexture = image::processImage(std::move(buffer), _textureURL.toString().toStdString(), - ABSOLUTE_MAX_TEXTURE_NUM_PIXELS, _textureType, false, _abortProcessing); + ABSOLUTE_MAX_TEXTURE_NUM_PIXELS, _textureType, false, gpu::BackendTarget::GL45, _abortProcessing); if (!processedTexture) { handleError("Could not process texture " + _textureURL.toString()); return; diff --git a/libraries/controllers/src/controllers/Input.h b/libraries/controllers/src/controllers/Input.h index 3ca4076de2..3c01ee0942 100644 --- a/libraries/controllers/src/controllers/Input.h +++ b/libraries/controllers/src/controllers/Input.h @@ -19,16 +19,16 @@ struct InputCalibrationData { glm::mat4 sensorToWorldMat; // sensor to world glm::mat4 avatarMat; // avatar to world glm::mat4 hmdSensorMat; // hmd pos and orientation in sensor space - glm::mat4 defaultCenterEyeMat; // default pose for the center of the eyes in avatar space. - glm::mat4 defaultHeadMat; // default pose for head joint in avatar space - glm::mat4 defaultSpine2; // default pose for spine2 joint in avatar space - glm::mat4 defaultHips; // default pose for hips joint in avatar space - glm::mat4 defaultLeftFoot; // default pose for leftFoot joint in avatar space - glm::mat4 defaultRightFoot; // default pose for rightFoot joint in avatar space - glm::mat4 defaultRightArm; // default pose for rightArm joint in avatar space - glm::mat4 defaultLeftArm; // default pose for leftArm joint in avatar space - glm::mat4 defaultRightHand; // default pose for rightHand joint in avatar space - glm::mat4 defaultLeftHand; // default pose for leftHand joint in avatar space + glm::mat4 defaultCenterEyeMat; // default pose for the center of the eyes in sensor space. + glm::mat4 defaultHeadMat; // default pose for head joint in sensor space + glm::mat4 defaultSpine2; // default pose for spine2 joint in sensor space + glm::mat4 defaultHips; // default pose for hips joint in sensor space + glm::mat4 defaultLeftFoot; // default pose for leftFoot joint in sensor space + glm::mat4 defaultRightFoot; // default pose for rightFoot joint in sensor space + glm::mat4 defaultRightArm; // default pose for rightArm joint in sensor space + glm::mat4 defaultLeftArm; // default pose for leftArm joint in sensor space + glm::mat4 defaultRightHand; // default pose for rightHand joint in sensor space + glm::mat4 defaultLeftHand; // default pose for leftHand joint in sensor space }; enum class ChannelType { diff --git a/libraries/controllers/src/controllers/UserInputMapper.cpp b/libraries/controllers/src/controllers/UserInputMapper.cpp index 371deec7d5..307064c073 100755 --- a/libraries/controllers/src/controllers/UserInputMapper.cpp +++ b/libraries/controllers/src/controllers/UserInputMapper.cpp @@ -527,8 +527,8 @@ bool UserInputMapper::applyRoute(const Route::Pointer& route, bool force) { } // If the source hasn't been written yet, defer processing of this route - auto source = route->source; - auto sourceInput = source->getInput(); + auto& source = route->source; + auto& sourceInput = source->getInput(); if (sourceInput.device == STANDARD_DEVICE && !force && source->writeable()) { if (debugRoutes && route->debug) { qCDebug(controllers) << "Source not yet written, deferring"; @@ -559,7 +559,7 @@ bool UserInputMapper::applyRoute(const Route::Pointer& route, bool force) { return true; } - auto destination = route->destination; + auto& destination = route->destination; // THis could happen if the route destination failed to create // FIXME: Maybe do not create the route if the destination failed and avoid this case ? if (!destination) { diff --git a/libraries/display-plugins/CMakeLists.txt b/libraries/display-plugins/CMakeLists.txt index 7d34258c96..0674c9fd92 100644 --- a/libraries/display-plugins/CMakeLists.txt +++ b/libraries/display-plugins/CMakeLists.txt @@ -1,7 +1,6 @@ set(TARGET_NAME display-plugins) -AUTOSCRIBE_SHADER_LIB(gpu display-plugins) setup_hifi_library(Gui) -link_hifi_libraries(shared plugins ui-plugins gl ui render-utils ${PLATFORM_GL_BACKEND}) +link_hifi_libraries(shared shaders plugins ui-plugins gl ui render-utils ${PLATFORM_GL_BACKEND}) include_hifi_library_headers(gpu) include_hifi_library_headers(model-networking) include_hifi_library_headers(networking) diff --git a/libraries/display-plugins/src/display-plugins/AbstractHMDScriptingInterface.h b/libraries/display-plugins/src/display-plugins/AbstractHMDScriptingInterface.h index 392fa7e2a2..7fe58618bc 100644 --- a/libraries/display-plugins/src/display-plugins/AbstractHMDScriptingInterface.h +++ b/libraries/display-plugins/src/display-plugins/AbstractHMDScriptingInterface.h @@ -54,6 +54,17 @@ signals: */ void displayModeChanged(bool isHMDMode); + /**jsdoc + * Triggered when the HMD.mounted property value changes. + * @function HMD.mountedChanged + * @returns {Signal} + * @example Report when there's a change in the HMD being worn. + * HMD.mountedChanged.connect(function () { + * print("Mounted changed. HMD is mounted: " + HMD.mounted); + * }); + */ + void mountedChanged(); + private: float _IPDScale{ 1.0 }; }; diff --git a/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp b/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp index efa4859b42..a0d5cb0920 100644 --- a/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp +++ b/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp @@ -29,6 +29,8 @@ #include #include +#include "GeometryUtil.h" + // Used to animate the magnification windows //static const quint64 TOOLTIP_DELAY = 500 * MSECS_TO_USECS; @@ -357,9 +359,9 @@ bool CompositorHelper::calculateRayUICollisionPoint(const glm::vec3& position, c glm::vec3 localDirection = glm::normalize(transformVectorFast(worldToUi, direction)); const float UI_RADIUS = 1.0f; - float instersectionDistance; - if (raySphereIntersect(localDirection, localPosition, UI_RADIUS, &instersectionDistance)) { - result = transformPoint(uiToWorld, localPosition + localDirection * instersectionDistance); + float intersectionDistance; + if (raySphereIntersect(localDirection, localPosition, UI_RADIUS, &intersectionDistance)) { + result = transformPoint(uiToWorld, localPosition + localDirection * intersectionDistance); #ifdef WANT_DEBUG DebugDraw::getInstance().drawRay(position, result, glm::vec4(0.0f, 1.0f, 0.0f, 1.0f)); #endif @@ -372,6 +374,23 @@ bool CompositorHelper::calculateRayUICollisionPoint(const glm::vec3& position, c return false; } +bool CompositorHelper::calculateParabolaUICollisionPoint(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, glm::vec3& result, float& parabolicDistance) const { + glm::mat4 uiToWorld = getUiTransform(); + glm::mat4 worldToUi = glm::inverse(uiToWorld); + glm::vec3 localOrigin = transformPoint(worldToUi, origin); + glm::vec3 localVelocity = glm::normalize(transformVectorFast(worldToUi, velocity)); + glm::vec3 localAcceleration = glm::normalize(transformVectorFast(worldToUi, acceleration)); + + const float UI_RADIUS = 1.0f; + float intersectionDistance; + if (findParabolaSphereIntersection(localOrigin, localVelocity, localAcceleration, glm::vec3(0.0f), UI_RADIUS, intersectionDistance)) { + result = origin + velocity * intersectionDistance + 0.5f * acceleration * intersectionDistance * intersectionDistance; + parabolicDistance = intersectionDistance; + return true; + } + return false; +} + glm::vec2 CompositorHelper::sphericalToOverlay(const glm::vec2& sphericalPos) const { glm::vec2 result = sphericalPos; result.x *= -1.0f; diff --git a/libraries/display-plugins/src/display-plugins/CompositorHelper.h b/libraries/display-plugins/src/display-plugins/CompositorHelper.h index fb712c26fa..e25d30109f 100644 --- a/libraries/display-plugins/src/display-plugins/CompositorHelper.h +++ b/libraries/display-plugins/src/display-plugins/CompositorHelper.h @@ -52,6 +52,7 @@ public: void setRenderingWidget(QWidget* widget) { _renderingWidget = widget; } bool calculateRayUICollisionPoint(const glm::vec3& position, const glm::vec3& direction, glm::vec3& result) const; + bool calculateParabolaUICollisionPoint(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, glm::vec3& result, float& parabolicDistance) const; bool isHMD() const; bool fakeEventActive() const { return _fakeMouseEvent; } diff --git a/libraries/display-plugins/src/display-plugins/InterleavedSrgbToLinear.slf b/libraries/display-plugins/src/display-plugins/InterleavedSrgbToLinear.slf new file mode 100644 index 0000000000..5f7b3f3411 --- /dev/null +++ b/libraries/display-plugins/src/display-plugins/InterleavedSrgbToLinear.slf @@ -0,0 +1,21 @@ +struct TextureData { + ivec2 textureSize; +}; + +layout(std140, binding=0) uniform textureDataBuffer { + TextureData textureData; +}; + +layout(binding=0) uniform sampler2D colorMap; +layout(location=0) in vec2 varTexCoord0; +layout(location=0) out vec4 outFragColor; + +void main(void) { + ivec2 texCoord = ivec2(floor(varTexCoord0 * textureData.textureSize)); + texCoord.x /= 2; + int row = int(floor(gl_FragCoord.y)); + if (row % 2 > 0) { + texCoord.x += (textureData.textureSize.x / 2); + } + outFragColor = vec4(pow(texelFetch(colorMap, texCoord, 0).rgb, vec3(2.2)), 1.0); +} diff --git a/libraries/display-plugins/src/display-plugins/InterleavedSrgbToLinear.slp b/libraries/display-plugins/src/display-plugins/InterleavedSrgbToLinear.slp new file mode 100644 index 0000000000..c2c4bfbebd --- /dev/null +++ b/libraries/display-plugins/src/display-plugins/InterleavedSrgbToLinear.slp @@ -0,0 +1 @@ +VERTEX gpu::vertex::DrawUnitQuadTexcoord diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp index 9200843cf8..580bea254a 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp @@ -30,7 +30,7 @@ #include #include -#include +#include #include #include #include @@ -44,33 +44,6 @@ #include "CompositorHelper.h" #include "Logging.h" -const char* SRGB_TO_LINEAR_FRAG = R"SCRIBE( - -// OpenGLDisplayPlugin_present.frag - -uniform sampler2D colorMap; - -in vec2 varTexCoord0; - -out vec4 outFragColor; - -float sRGBFloatToLinear(float value) { - const float SRGB_ELBOW = 0.04045; - - return (value <= SRGB_ELBOW) ? value / 12.92 : pow((value + 0.055) / 1.055, 2.4); -} - -vec3 colorToLinearRGB(vec3 srgb) { - return vec3(sRGBFloatToLinear(srgb.r), sRGBFloatToLinear(srgb.g), sRGBFloatToLinear(srgb.b)); -} - -void main(void) { - outFragColor.a = 1.0; - outFragColor.rgb = colorToLinearRGB(texture(colorMap, varTexCoord0).rgb); -} - -)SCRIBE"; - extern QThread* RENDER_THREAD; class PresentThread : public QThread, public Dependency { @@ -391,10 +364,7 @@ void OpenGLDisplayPlugin::customizeContext() { if (!_presentPipeline) { { - auto vs = gpu::StandardShaderLib::getDrawUnitQuadTexcoordVS(); - auto ps = gpu::StandardShaderLib::getDrawTexturePS(); - gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); - gpu::Shader::makeProgram(*program); + gpu::ShaderPointer program = gpu::Shader::createProgram(shader::gpu::program::drawTexture); gpu::StatePointer state = gpu::StatePointer(new gpu::State()); state->setDepthTest(gpu::State::DepthTest(false)); state->setScissorEnable(true); @@ -402,10 +372,7 @@ void OpenGLDisplayPlugin::customizeContext() { } { - auto vs = gpu::StandardShaderLib::getDrawUnitQuadTexcoordVS(); - auto ps = gpu::Shader::createPixel(std::string(SRGB_TO_LINEAR_FRAG)); - gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); - gpu::Shader::makeProgram(*program); + gpu::ShaderPointer program = gpu::Shader::createProgram(shader::display_plugins::program::SrgbToLinear); gpu::StatePointer state = gpu::StatePointer(new gpu::State()); state->setDepthTest(gpu::State::DepthTest(false)); state->setScissorEnable(true); @@ -413,10 +380,9 @@ void OpenGLDisplayPlugin::customizeContext() { } { - auto vs = gpu::StandardShaderLib::getDrawUnitQuadTexcoordVS(); - auto ps = gpu::StandardShaderLib::getDrawTexturePS(); + auto vs = gpu::Shader::createVertex(shader::gpu::vertex::DrawUnitQuadTexcoord); + auto ps = gpu::Shader::createPixel(shader::gpu::fragment::DrawTexture); gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); - gpu::Shader::makeProgram(*program); gpu::StatePointer state = gpu::StatePointer(new gpu::State()); state->setDepthTest(gpu::State::DepthTest(false)); state->setBlendFunction(true, @@ -426,10 +392,9 @@ void OpenGLDisplayPlugin::customizeContext() { } { - auto vs = gpu::StandardShaderLib::getDrawUnitQuadTexcoordVS(); - auto ps = gpu::StandardShaderLib::getDrawTextureMirroredXPS(); + auto vs = gpu::Shader::createVertex(shader::gpu::vertex::DrawUnitQuadTexcoord); + auto ps = gpu::Shader::createPixel(shader::gpu::fragment::DrawTextureMirroredX); gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); - gpu::Shader::makeProgram(*program); gpu::StatePointer state = gpu::StatePointer(new gpu::State()); state->setDepthTest(gpu::State::DepthTest(false)); state->setBlendFunction(true, @@ -439,10 +404,9 @@ void OpenGLDisplayPlugin::customizeContext() { } { - auto vs = gpu::StandardShaderLib::getDrawTransformUnitQuadVS(); - auto ps = gpu::StandardShaderLib::getDrawTexturePS(); + auto vs = gpu::Shader::createVertex(shader::gpu::vertex::DrawTransformUnitQuad); + auto ps = gpu::Shader::createPixel(shader::gpu::fragment::DrawTexture); gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); - gpu::Shader::makeProgram(*program); gpu::StatePointer state = gpu::StatePointer(new gpu::State()); state->setDepthTest(gpu::State::DepthTest(false)); state->setBlendFunction(true, diff --git a/libraries/display-plugins/src/display-plugins/SrgbToLinear.slf b/libraries/display-plugins/src/display-plugins/SrgbToLinear.slf new file mode 100644 index 0000000000..c2bcfb5cb3 --- /dev/null +++ b/libraries/display-plugins/src/display-plugins/SrgbToLinear.slf @@ -0,0 +1,22 @@ +// OpenGLDisplayPlugin_present.frag + +layout(binding = 0) uniform sampler2D colorMap; + +layout(location = 0) in vec2 varTexCoord0; + +layout(location = 0) out vec4 outFragColor; + +float sRGBFloatToLinear(float value) { + const float SRGB_ELBOW = 0.04045; + + return (value <= SRGB_ELBOW) ? value / 12.92 : pow((value + 0.055) / 1.055, 2.4); +} + +vec3 colorToLinearRGB(vec3 srgb) { + return vec3(sRGBFloatToLinear(srgb.r), sRGBFloatToLinear(srgb.g), sRGBFloatToLinear(srgb.b)); +} + +void main(void) { + outFragColor.a = 1.0; + outFragColor.rgb = colorToLinearRGB(texture(colorMap, varTexCoord0).rgb); +} diff --git a/libraries/display-plugins/src/display-plugins/SrgbToLinear.slp b/libraries/display-plugins/src/display-plugins/SrgbToLinear.slp new file mode 100644 index 0000000000..c2c4bfbebd --- /dev/null +++ b/libraries/display-plugins/src/display-plugins/SrgbToLinear.slp @@ -0,0 +1 @@ +VERTEX gpu::vertex::DrawUnitQuadTexcoord diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp index 98283b0ef6..d76b211ede 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp @@ -24,8 +24,8 @@ #include #include #include -#include #include +#include #include #include @@ -34,8 +34,6 @@ #include "../CompositorHelper.h" #include "DesktopPreviewProvider.h" -#include "render-utils/hmd_ui_vert.h" -#include "render-utils/hmd_ui_frag.h" static const QString MONO_PREVIEW = "Mono Preview"; static const QString DISABLE_PREVIEW = "Disable Preview"; @@ -409,12 +407,7 @@ void HmdDisplayPlugin::HUDRenderer::build() { void HmdDisplayPlugin::HUDRenderer::updatePipeline() { if (!pipeline) { - auto vs = hmd_ui_vert::getShader(); - auto ps = hmd_ui_frag::getShader(); - auto program = gpu::Shader::createProgram(vs, ps); - gpu::Shader::makeProgram(*program, gpu::Shader::BindingSet()); - uniformsLocation = program->getUniformBuffers().findLocation("hudBuffer"); - + auto program = gpu::Shader::createProgram(shader::render_utils::program::hmd_ui); gpu::StatePointer state = gpu::StatePointer(new gpu::State()); state->setDepthTest(gpu::State::DepthTest(true, true, gpu::LESS_EQUAL)); state->setBlendFunction(true, @@ -437,9 +430,8 @@ std::function HmdDis batch.setInputBuffer(gpu::Stream::POSITION, posView); batch.setInputBuffer(gpu::Stream::TEXCOORD, uvView); batch.setIndexBuffer(gpu::UINT16, indices, 0); - uniformsBuffer->setSubData(0, uniforms); - batch.setUniformBuffer(uniformsLocation, uniformsBuffer); + batch.setUniformBuffer(0, uniformsBuffer); auto compositorHelper = DependencyManager::get(); glm::mat4 modelTransform = compositorHelper->getUiTransform(); diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h index 3639952524..ea11832e94 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h @@ -21,6 +21,7 @@ #include "../OpenGLDisplayPlugin.h" class HmdDisplayPlugin : public OpenGLDisplayPlugin { + Q_OBJECT using Parent = OpenGLDisplayPlugin; public: ~HmdDisplayPlugin(); @@ -45,6 +46,9 @@ public: virtual bool onDisplayTextureReset() override { _clearPreviewFlag = true; return true; }; +signals: + void hmdMountedChanged(); + protected: virtual void hmdPresent() = 0; virtual bool isHmdMounted() const = 0; @@ -98,7 +102,6 @@ private: gpu::BufferPointer indices; uint32_t indexCount { 0 }; gpu::PipelinePointer pipeline; - int32_t uniformsLocation { -1 }; gpu::BufferPointer uniformsBuffer; diff --git a/libraries/display-plugins/src/display-plugins/stereo/InterleavedStereoDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/stereo/InterleavedStereoDisplayPlugin.cpp index 0b20d0bf30..0ae0f9b1b6 100644 --- a/libraries/display-plugins/src/display-plugins/stereo/InterleavedStereoDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/stereo/InterleavedStereoDisplayPlugin.cpp @@ -8,48 +8,17 @@ #include "InterleavedStereoDisplayPlugin.h" -#include #include #include #include - -static const char* INTERLEAVED_SRGB_TO_LINEAR_FRAG = R"SCRIBE( - -struct TextureData { - ivec2 textureSize; -}; - -layout(std140) uniform textureDataBuffer { - TextureData textureData; -}; - -uniform sampler2D colorMap; - -in vec2 varTexCoord0; - -out vec4 outFragColor; - -void main(void) { - ivec2 texCoord = ivec2(floor(varTexCoord0 * textureData.textureSize)); - texCoord.x /= 2; - int row = int(floor(gl_FragCoord.y)); - if (row % 2 > 0) { - texCoord.x += (textureData.textureSize.x / 2); - } - outFragColor = vec4(pow(texelFetch(colorMap, texCoord, 0).rgb, vec3(2.2)), 1.0); -} - -)SCRIBE"; +#include const QString InterleavedStereoDisplayPlugin::NAME("3D TV - Interleaved"); void InterleavedStereoDisplayPlugin::customizeContext() { StereoDisplayPlugin::customizeContext(); if (!_interleavedPresentPipeline) { - auto vs = gpu::StandardShaderLib::getDrawUnitQuadTexcoordVS(); - auto ps = gpu::Shader::createPixel(std::string(INTERLEAVED_SRGB_TO_LINEAR_FRAG)); - gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); - gpu::Shader::makeProgram(*program); + gpu::ShaderPointer program = gpu::Shader::createProgram(shader::display_plugins::program::InterleavedSrgbToLinear); gpu::StatePointer state = gpu::StatePointer(new gpu::State()); state->setDepthTest(gpu::State::DepthTest(false)); _interleavedPresentPipeline = gpu::Pipeline::create(program, state); diff --git a/libraries/entities-renderer/CMakeLists.txt b/libraries/entities-renderer/CMakeLists.txt index 12b9b3dea5..cf887bfeff 100644 --- a/libraries/entities-renderer/CMakeLists.txt +++ b/libraries/entities-renderer/CMakeLists.txt @@ -1,7 +1,6 @@ set(TARGET_NAME entities-renderer) -AUTOSCRIBE_SHADER_LIB(gpu graphics procedural render render-utils) setup_hifi_library(Network Script) -link_hifi_libraries(shared workload gpu procedural graphics model-networking script-engine render render-utils image qml ui pointers) +link_hifi_libraries(shared workload gpu shaders procedural graphics model-networking script-engine render render-utils image qml ui pointers) include_hifi_library_headers(networking) include_hifi_library_headers(gl) include_hifi_library_headers(ktx) @@ -18,3 +17,4 @@ include_hifi_library_headers(graphics-scripting) # for Forward.h target_bullet() target_polyvox() + diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index 129391e43a..3d782f69a7 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -40,8 +40,7 @@ #include -size_t std::hash::operator()(const EntityItemID& id) const { return qHash(id); } -std::function EntityTreeRenderer::_entitiesShouldFadeFunction; +std::function EntityTreeRenderer::_entitiesShouldFadeFunction = []() { return true; }; QString resolveScriptURL(const QString& scriptUrl) { auto normalizedScriptUrl = DependencyManager::get()->normalizeURL(scriptUrl); @@ -296,7 +295,8 @@ void EntityTreeRenderer::addPendingEntities(const render::ScenePointer& scene, r auto spaceIndex = _space->allocateID(); workload::Sphere sphere(entity->getWorldPosition(), entity->getBoundingRadius()); workload::Transaction transaction; - transaction.reset(spaceIndex, sphere, workload::Owner(entity)); + SpatiallyNestablePointer nestable = std::static_pointer_cast(entity); + transaction.reset(spaceIndex, sphere, workload::Owner(nestable)); _space->enqueueTransaction(transaction); entity->setSpaceIndex(spaceIndex); connect(entity.get(), &EntityItem::spaceUpdate, this, &EntityTreeRenderer::handleSpaceUpdate, Qt::QueuedConnection); @@ -382,6 +382,7 @@ void EntityTreeRenderer::updateChangedEntities(const render::ScenePointer& scene const auto& views = _viewState->getConicalViews(); PrioritySortUtil::PriorityQueue sortedRenderables(views); + sortedRenderables.reserve(_renderablesToUpdate.size()); { PROFILE_RANGE_EX(simulation_physics, "SortRenderables", 0xffff00ff, (uint64_t)_renderablesToUpdate.size()); std::unordered_map::iterator itr = _renderablesToUpdate.begin(); @@ -405,11 +406,14 @@ void EntityTreeRenderer::updateChangedEntities(const render::ScenePointer& scene // process the sorted renderables size_t numSorted = sortedRenderables.size(); - while (!sortedRenderables.empty() && usecTimestampNow() < expiry) { - const auto renderable = sortedRenderables.top().getRenderer(); + const auto& sortedRenderablesVector = sortedRenderables.getSortedVector(); + for (const auto& sortedRenderable : sortedRenderablesVector) { + if (usecTimestampNow() > expiry) { + break; + } + const auto& renderable = sortedRenderable.getRenderer(); renderable->updateInScene(scene, transaction); _renderablesToUpdate.erase(renderable->getEntity()->getID()); - sortedRenderables.pop(); } // compute average per-renderable update cost @@ -643,6 +647,14 @@ bool EntityTreeRenderer::applyLayeredZones() { } void EntityTreeRenderer::processEraseMessage(ReceivedMessage& message, const SharedNodePointer& sourceNode) { + OCTREE_PACKET_FLAGS flags; + message.readPrimitive(&flags); + + OCTREE_PACKET_SEQUENCE sequence; + message.readPrimitive(&sequence); + + _lastOctreeMessageSequence = sequence; + message.seek(0); std::static_pointer_cast(_tree)->processEraseMessage(message, sourceNode); } diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.h b/libraries/entities-renderer/src/EntityTreeRenderer.h index f810aa64b6..4ba1a0060b 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.h +++ b/libraries/entities-renderer/src/EntityTreeRenderer.h @@ -40,9 +40,6 @@ namespace render { namespace entities { } } -// Allow the use of std::unordered_map with QUuid keys -namespace std { template<> struct hash { size_t operator()(const EntityItemID& id) const; }; } - using EntityRenderer = render::entities::EntityRenderer; using EntityRendererPointer = render::entities::EntityRendererPointer; using EntityRendererWeakPointer = render::entities::EntityRendererWeakPointer; diff --git a/libraries/entities-renderer/src/RenderableEntityItem.cpp b/libraries/entities-renderer/src/RenderableEntityItem.cpp index 78801df715..a183101fff 100644 --- a/libraries/entities-renderer/src/RenderableEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableEntityItem.cpp @@ -14,7 +14,6 @@ #include -#include "EntityTreeRenderer.h" #include "RenderableLightEntityItem.h" #include "RenderableLineEntityItem.h" #include "RenderableModelEntityItem.h" @@ -44,8 +43,6 @@ enum class RenderItemStatusIcon { NONE = 255 }; -std::function EntityRenderer::_entitiesShouldFadeFunction = []() { return true; }; - void EntityRenderer::initEntityRenderers() { REGISTER_ENTITY_TYPE_WITH_FACTORY(Model, RenderableModelEntityItem::factory) REGISTER_ENTITY_TYPE_WITH_FACTORY(PolyVox, RenderablePolyVoxEntityItem::factory) @@ -336,6 +333,11 @@ bool EntityRenderer::needsRenderUpdate() const { if (_needsRenderUpdate) { return true; } + + if (isFading()) { + return true; + } + if (_prevIsTransparent != isTransparent()) { return true; } @@ -381,8 +383,12 @@ void EntityRenderer::doRenderUpdateSynchronous(const ScenePointer& scene, Transa DETAILED_PROFILE_RANGE(simulation_physics, __FUNCTION__); withWriteLock([&] { auto transparent = isTransparent(); - if (_prevIsTransparent && !transparent) { - _isFading = false; + auto fading = isFading(); + if (fading || _prevIsTransparent != transparent) { + emit requestRenderUpdate(); + } + if (fading) { + _isFading = Interpolate::calculateFadeRatio(_fadeStartTime) < 1.0f; } _prevIsTransparent = transparent; diff --git a/libraries/entities-renderer/src/RenderableEntityItem.h b/libraries/entities-renderer/src/RenderableEntityItem.h index 496649eb5f..9c4d10190c 100644 --- a/libraries/entities-renderer/src/RenderableEntityItem.h +++ b/libraries/entities-renderer/src/RenderableEntityItem.h @@ -19,6 +19,7 @@ #include "EntitiesRendererLogging.h" #include #include +#include "EntityTreeRenderer.h" class EntityTreeRenderer; @@ -96,7 +97,7 @@ protected: // Called by the `render` method after `needsRenderUpdate` virtual void doRender(RenderArgs* args) = 0; - bool isFading() const { return _isFading; } + virtual bool isFading() const { return _isFading; } void updateModelTransformAndBound(); virtual bool isTransparent() const { return _isFading ? Interpolate::calculateFadeRatio(_fadeStartTime) < 1.0f : false; } inline bool isValidRenderItem() const { return _renderItemID != Item::INVALID_ITEM_ID; } @@ -121,7 +122,6 @@ protected: static void makeStatusGetters(const EntityItemPointer& entity, Item::Status::Getters& statusGetters); - static std::function _entitiesShouldFadeFunction; const Transform& getModelTransform() const; Item::Bound _bound; @@ -131,7 +131,7 @@ protected: ItemIDs _subRenderItemIDs; uint64_t _fadeStartTime{ usecTimestampNow() }; uint64_t _updateTime{ usecTimestampNow() }; // used when sorting/throttling render updates - bool _isFading{ _entitiesShouldFadeFunction() }; + bool _isFading { EntityTreeRenderer::getEntitiesShouldFadeFunction()() }; bool _prevIsTransparent { false }; bool _visible { false }; bool _isVisibleInSecondaryCamera { false }; diff --git a/libraries/entities-renderer/src/RenderableMaterialEntityItem.cpp b/libraries/entities-renderer/src/RenderableMaterialEntityItem.cpp index eabcb68e4f..d7a0cfd18d 100644 --- a/libraries/entities-renderer/src/RenderableMaterialEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableMaterialEntityItem.cpp @@ -112,11 +112,14 @@ void MaterialEntityRenderer::doRender(RenderArgs* args) { } batch.setModelTransform(renderTransform); - drawMaterial->setTextureTransforms(textureTransform); - // bind the material - RenderPipelines::bindMaterial(drawMaterial, batch, args->_enableTexturing); - args->_details._materialSwitches++; + if (args->_renderMode != render::Args::RenderMode::SHADOW_RENDER_MODE) { + drawMaterial->setTextureTransforms(textureTransform); + + // bind the material + RenderPipelines::bindMaterial(drawMaterial, batch, args->_enableTexturing); + args->_details._materialSwitches++; + } // Draw! DependencyManager::get()->renderSphere(batch); diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index d8ac3dc63e..34936c2c48 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -278,7 +278,7 @@ EntityItemProperties RenderableModelEntityItem::getProperties(EntityPropertyFlag return properties; } -bool RenderableModelEntityItem::supportsDetailedRayIntersection() const { +bool RenderableModelEntityItem::supportsDetailedIntersection() const { return true; } @@ -294,6 +294,18 @@ bool RenderableModelEntityItem::findDetailedRayIntersection(const glm::vec3& ori face, surfaceNormal, extraInfo, precisionPicking, false); } +bool RenderableModelEntityItem::findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, + const glm::vec3& acceleration, OctreeElementPointer& element, float& parabolicDistance, BoxFace& face, + glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) const { + auto model = getModel(); + if (!model || !isModelLoaded()) { + return false; + } + + return model->findParabolaIntersectionAgainstSubMeshes(origin, velocity, acceleration, parabolicDistance, + face, surfaceNormal, extraInfo, precisionPicking, false); +} + void RenderableModelEntityItem::getCollisionGeometryResource() { QUrl hullURL(getCompoundShapeURL()); QUrlQuery queryArgs(hullURL); @@ -699,14 +711,6 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& shapeInfo) { adjustShapeInfoByRegistration(shapeInfo); } -void RenderableModelEntityItem::setCollisionShape(const btCollisionShape* shape) { - const void* key = static_cast(shape); - if (_collisionMeshKey != key) { - _collisionMeshKey = key; - emit requestCollisionGeometryUpdate(); - } -} - void RenderableModelEntityItem::setJointMap(std::vector jointMap) { if (jointMap.size() > 0) { _jointMap = jointMap; @@ -1278,10 +1282,6 @@ bool ModelEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPoin return false; } -void ModelEntityRenderer::setCollisionMeshKey(const void*key) { - _collisionMeshKey = key; -} - void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) { DETAILED_PROFILE_RANGE(simulation_physics, __FUNCTION__); if (_hasModel != entity->hasModel()) { diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.h b/libraries/entities-renderer/src/RenderableModelEntityItem.h index 91e5496b97..45892fdd7f 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.h +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.h @@ -66,11 +66,15 @@ public: void doInitialModelSimulation(); void updateModelBounds(); - virtual bool supportsDetailedRayIntersection() const override; + virtual bool supportsDetailedIntersection() const override; virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) const override; + virtual bool findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, + const glm::vec3& acceleration, OctreeElementPointer& element, float& parabolicDistance, + BoxFace& face, glm::vec3& surfaceNormal, + QVariantMap& extraInfo, bool precisionPicking) const override; virtual void setShapeType(ShapeType type) override; virtual void setCompoundShapeURL(const QString& url) override; @@ -78,8 +82,6 @@ public: virtual bool isReadyToComputeShape() const override; virtual void computeShapeInfo(ShapeInfo& shapeInfo) override; - void setCollisionShape(const btCollisionShape* shape) override; - virtual bool contains(const glm::vec3& point) const override; void stopModelOverrideIfNoParent(); @@ -112,10 +114,6 @@ public: virtual QStringList getJointNames() const override; bool getMeshes(MeshProxyList& result) override; // deprecated - const void* getCollisionMeshKey() const { return _collisionMeshKey; } - -signals: - void requestCollisionGeometryUpdate(); private: bool needsUpdateModelBounds() const; @@ -130,7 +128,6 @@ private: QVariantMap _originalTextures; bool _dimensionsInitialized { true }; bool _needsJointSimulation { false }; - const void* _collisionMeshKey { nullptr }; }; namespace render { namespace entities { @@ -149,6 +146,9 @@ public: void addMaterial(graphics::MaterialLayer material, const std::string& parentMaterialName) override; void removeMaterial(graphics::MaterialPointer material, const std::string& parentMaterialName) override; + // FIXME: model mesh parts should fade individually + bool isFading() const override { return false; } + protected: virtual void removeFromScene(const ScenePointer& scene, Transaction& transaction) override; virtual void onRemoveFromSceneTyped(const TypedEntityPointer& entity) override; @@ -161,7 +161,6 @@ protected: virtual bool needsRenderUpdate() const override; virtual void doRender(RenderArgs* args) override; virtual void doRenderUpdateSynchronousTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) override; - void setCollisionMeshKey(const void* key); render::hifi::Tag getTagMask() const override; diff --git a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp index 309671f49e..18c4921836 100644 --- a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp @@ -13,9 +13,8 @@ #include #include +#include -#include "textured_particle_vert.h" -#include "textured_particle_frag.h" using namespace render; using namespace render::entities; @@ -23,8 +22,6 @@ using namespace render::entities; static uint8_t CUSTOM_PIPELINE_NUMBER = 0; static gpu::Stream::FormatPointer _vertexFormat; static std::weak_ptr _texturedPipeline; -// FIXME: This is interfering with the uniform buffers in DeferredLightingEffect.cpp, so use 12 to avoid collisions -static int32_t PARTICLE_UNIFORM_SLOT { 12 }; static ShapePipelinePointer shapePipelineFactory(const ShapePlumber& plumber, const ShapeKey& key, gpu::Batch& batch) { auto texturedPipeline = _texturedPipeline.lock(); @@ -36,17 +33,8 @@ static ShapePipelinePointer shapePipelineFactory(const ShapePlumber& plumber, co gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); PrepareStencil::testMask(*state); - auto vertShader = textured_particle_vert::getShader(); - auto fragShader = textured_particle_frag::getShader(); - - auto program = gpu::Shader::createProgram(vertShader, fragShader); + auto program = gpu::Shader::createProgram(shader::entities_renderer::program::textured_particle); _texturedPipeline = texturedPipeline = gpu::Pipeline::create(program, state); - - batch.runLambda([program] { - gpu::Shader::BindingSet slotBindings; - slotBindings.insert(gpu::Shader::Binding(std::string("particleBuffer"), PARTICLE_UNIFORM_SLOT)); - gpu::Shader::makeProgram(*program, slotBindings); - }); } return std::make_shared(texturedPipeline, nullptr, nullptr, nullptr); @@ -101,6 +89,10 @@ void ParticleEffectEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePoi _timeUntilNextEmit = 0; withWriteLock([&]{ _particleProperties = newParticleProperties; + if (!_prevEmitterShouldTrailInitialized) { + _prevEmitterShouldTrailInitialized = true; + _prevEmitterShouldTrail = _particleProperties.emission.shouldTrail; + } }); } _emitting = entity->getIsEmitting(); @@ -144,7 +136,12 @@ void ParticleEffectEntityRenderer::doRenderUpdateAsynchronousTyped(const TypedEn particleUniforms.color.middle = _particleProperties.getColorMiddle(); particleUniforms.color.finish = _particleProperties.getColorFinish(); particleUniforms.color.spread = _particleProperties.getColorSpread(); + particleUniforms.spin.start = _particleProperties.spin.range.start; + particleUniforms.spin.middle = _particleProperties.spin.gradient.target; + particleUniforms.spin.finish = _particleProperties.spin.range.finish; + particleUniforms.spin.spread = _particleProperties.spin.gradient.spread; particleUniforms.lifespan = _particleProperties.lifespan; + particleUniforms.rotateWithEntity = _particleProperties.rotateWithEntity ? 1 : 0; }); // Update particle uniforms memcpy(&_uniformBuffer.edit(), &particleUniforms, sizeof(ParticleUniforms)); @@ -176,7 +173,7 @@ ParticleEffectEntityRenderer::CpuParticle ParticleEffectEntityRenderer::createPa const auto& azimuthFinish = particleProperties.azimuth.finish; const auto& emitDimensions = particleProperties.emission.dimensions; const auto& emitAcceleration = particleProperties.emission.acceleration.target; - auto emitOrientation = particleProperties.emission.orientation; + auto emitOrientation = baseTransform.getRotation() * particleProperties.emission.orientation; const auto& emitRadiusStart = glm::max(particleProperties.radiusStart, EPSILON); // Avoid math complications at center const auto& emitSpeed = particleProperties.emission.speed.target; const auto& speedSpread = particleProperties.emission.speed.spread; @@ -185,10 +182,9 @@ ParticleEffectEntityRenderer::CpuParticle ParticleEffectEntityRenderer::createPa particle.seed = randFloatInRange(-1.0f, 1.0f); particle.expiration = now + (uint64_t)(particleProperties.lifespan * USECS_PER_SECOND); - if (particleProperties.emission.shouldTrail) { - particle.position = baseTransform.getTranslation(); - emitOrientation = baseTransform.getRotation() * emitOrientation; - } + + particle.relativePosition = glm::vec3(0.0f); + particle.basePosition = baseTransform.getTranslation(); // Position, velocity, and acceleration if (polarStart == 0.0f && polarFinish == 0.0f && emitDimensions.z == 0.0f) { @@ -237,7 +233,7 @@ ParticleEffectEntityRenderer::CpuParticle ParticleEffectEntityRenderer::createPa radii.y > 0.0f ? y / (radii.y * radii.y) : 0.0f, radii.z > 0.0f ? z / (radii.z * radii.z) : 0.0f )); - particle.position += emitOrientation * emitPosition; + particle.relativePosition += emitOrientation * emitPosition; } particle.velocity = (emitSpeed + randFloatInRange(-1.0f, 1.0f) * speedSpread) * (emitOrientation * emitDirection); @@ -262,8 +258,8 @@ void ParticleEffectEntityRenderer::stepSimulation() { particleProperties = _particleProperties; }); + const auto& modelTransform = getModelTransform(); if (_emitting && particleProperties.emitting()) { - const auto& modelTransform = getModelTransform(); uint64_t emitInterval = particleProperties.emitIntervalUsecs(); if (emitInterval > 0 && interval >= _timeUntilNextEmit) { auto timeRemaining = interval; @@ -288,15 +284,23 @@ void ParticleEffectEntityRenderer::stepSimulation() { const float deltaTime = (float)interval / (float)USECS_PER_SECOND; // update the particles for (auto& particle : _cpuParticles) { + if (_prevEmitterShouldTrail != particleProperties.emission.shouldTrail) { + if (_prevEmitterShouldTrail) { + particle.relativePosition = particle.relativePosition + particle.basePosition - modelTransform.getTranslation(); + } + particle.basePosition = modelTransform.getTranslation(); + } particle.integrate(deltaTime); } + _prevEmitterShouldTrail = particleProperties.emission.shouldTrail; // Build particle primitives static GpuParticles gpuParticles; gpuParticles.clear(); gpuParticles.reserve(_cpuParticles.size()); // Reserve space - std::transform(_cpuParticles.begin(), _cpuParticles.end(), std::back_inserter(gpuParticles), [](const CpuParticle& particle) { - return GpuParticle(particle.position, glm::vec2(particle.lifetime, particle.seed)); + std::transform(_cpuParticles.begin(), _cpuParticles.end(), std::back_inserter(gpuParticles), [&particleProperties, &modelTransform](const CpuParticle& particle) { + glm::vec3 position = particle.relativePosition + (particleProperties.emission.shouldTrail ? particle.basePosition : modelTransform.getTranslation()); + return GpuParticle(position, glm::vec2(particle.lifetime, particle.seed)); }); // Update particle buffer @@ -324,22 +328,16 @@ void ParticleEffectEntityRenderer::doRender(RenderArgs* args) { } Transform transform; - // In trail mode, the particles are created in world space. - // so we only set a transform if they're not in trail mode - if (!_particleProperties.emission.shouldTrail) { - - withReadLock([&] { - transform = _renderTransform; - }); - transform.setScale(vec3(1)); - } + // The particles are in world space, so the transform is unused, except for the rotation, which we use + // if the particles are marked rotateWithEntity + withReadLock([&] { + transform.setRotation(_renderTransform.getRotation()); + }); batch.setModelTransform(transform); - batch.setUniformBuffer(PARTICLE_UNIFORM_SLOT, _uniformBuffer); + batch.setUniformBuffer(0, _uniformBuffer); batch.setInputFormat(_vertexFormat); batch.setInputBuffer(0, _particleBuffer, 0, sizeof(GpuParticle)); auto numParticles = _particleBuffer->getSize() / sizeof(GpuParticle); batch.drawInstanced((gpu::uint32)numParticles, gpu::TRIANGLE_STRIP, (gpu::uint32)VERTEX_PER_PARTICLE); } - - diff --git a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.h b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.h index 8e9353894a..7655918c58 100644 --- a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.h +++ b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.h @@ -49,13 +49,14 @@ private: float seed { 0.0f }; uint64_t expiration { 0 }; float lifetime { 0.0f }; - glm::vec3 position; + glm::vec3 basePosition; + glm::vec3 relativePosition; glm::vec3 velocity; glm::vec3 acceleration; void integrate(float deltaTime) { glm::vec3 atSquared = (0.5f * deltaTime * deltaTime) * acceleration; - position += velocity * deltaTime + atSquared; + relativePosition += velocity * deltaTime + atSquared; velocity += acceleration * deltaTime; lifetime += deltaTime; } @@ -74,15 +75,18 @@ private: struct ParticleUniforms { InterpolationData radius; InterpolationData color; // rgba + InterpolationData spin; float lifespan; - glm::vec3 spare; + int rotateWithEntity; + glm::vec2 spare; }; - static CpuParticle createParticle(uint64_t now, const Transform& baseTransform, const particle::Properties& particleProperties); void stepSimulation(); particle::Properties _particleProperties; + bool _prevEmitterShouldTrail; + bool _prevEmitterShouldTrailInitialized { false }; CpuParticles _cpuParticles; bool _emitting { false }; uint64_t _timeUntilNextEmit { 0 }; diff --git a/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp index 7cab57123d..743df477ac 100644 --- a/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp @@ -17,60 +17,32 @@ #include #include #include +#include //#define POLYLINE_ENTITY_USE_FADE_EFFECT #ifdef POLYLINE_ENTITY_USE_FADE_EFFECT # include #endif -#include "paintStroke_vert.h" -#include "paintStroke_frag.h" - -#include "paintStroke_fade_vert.h" -#include "paintStroke_fade_frag.h" - using namespace render; using namespace render::entities; static uint8_t CUSTOM_PIPELINE_NUMBER { 0 }; static const int32_t PAINTSTROKE_TEXTURE_SLOT { 0 }; -// FIXME: This is interfering with the uniform buffers in DeferredLightingEffect.cpp, so use 12 to avoid collisions -static const int32_t PAINTSTROKE_UNIFORM_SLOT { 12 }; static gpu::Stream::FormatPointer polylineFormat; static gpu::PipelinePointer polylinePipeline; #ifdef POLYLINE_ENTITY_USE_FADE_EFFECT static gpu::PipelinePointer polylineFadePipeline; #endif -struct PolyLineUniforms { - glm::vec3 color; -}; - static render::ShapePipelinePointer shapePipelineFactory(const render::ShapePlumber& plumber, const render::ShapeKey& key, gpu::Batch& batch) { if (!polylinePipeline) { - auto VS = paintStroke_vert::getShader(); - auto PS = paintStroke_frag::getShader(); - gpu::ShaderPointer program = gpu::Shader::createProgram(VS, PS); + gpu::ShaderPointer program = gpu::Shader::createProgram(shader::entities_renderer::program::paintStroke); #ifdef POLYLINE_ENTITY_USE_FADE_EFFECT auto fadeVS = gpu::Shader::createVertex(std::string(paintStroke_fade_vert)); auto fadePS = gpu::Shader::createPixel(std::string(paintStroke_fade_frag)); gpu::ShaderPointer fadeProgram = gpu::Shader::createProgram(fadeVS, fadePS); #endif - batch.runLambda([program -#ifdef POLYLINE_ENTITY_USE_FADE_EFFECT - , fadeProgram -#endif - ] { - gpu::Shader::BindingSet slotBindings; - slotBindings.insert(gpu::Shader::Binding(std::string("originalTexture"), PAINTSTROKE_TEXTURE_SLOT)); - slotBindings.insert(gpu::Shader::Binding(std::string("polyLineBuffer"), PAINTSTROKE_UNIFORM_SLOT)); - gpu::Shader::makeProgram(*program, slotBindings); -#ifdef POLYLINE_ENTITY_USE_FADE_EFFECT - slotBindings.insert(gpu::Shader::Binding(std::string("fadeMaskMap"), PAINTSTROKE_TEXTURE_SLOT + 1)); - slotBindings.insert(gpu::Shader::Binding(std::string("fadeParametersBuffer"), PAINTSTROKE_UNIFORM_SLOT + 1)); - gpu::Shader::makeProgram(*fadeProgram, slotBindings); -#endif - }); gpu::StatePointer state = gpu::StatePointer(new gpu::State()); state->setDepthTest(true, true, gpu::LESS_EQUAL); PrepareStencil::testMask(*state); @@ -106,8 +78,6 @@ PolyLineEntityRenderer::PolyLineEntityRenderer(const EntityItemPointer& entity) polylineFormat->setAttribute(gpu::Stream::COLOR, 0, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::RGB), offsetof(Vertex, color)); }); - PolyLineUniforms uniforms; - _uniformBuffer = std::make_shared(sizeof(PolyLineUniforms), (const gpu::Byte*) &uniforms); _verticesBuffer = std::make_shared(); } @@ -148,9 +118,6 @@ void PolyLineEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& } void PolyLineEntityRenderer::doRenderUpdateAsynchronousTyped(const TypedEntityPointer& entity) { - PolyLineUniforms uniforms; - uniforms.color = toGlm(entity->getXColor()); - memcpy(&_uniformBuffer.edit(), &uniforms, sizeof(PolyLineUniforms)); auto pointsChanged = entity->pointsChanged(); auto strokeWidthsChanged = entity->strokeWidthsChanged(); auto normalsChanged = entity->normalsChanged(); @@ -296,7 +263,6 @@ void PolyLineEntityRenderer::doRender(RenderArgs* args) { gpu::Batch& batch = *args->_batch; batch.setModelTransform(_polylineTransform); - batch.setUniformBuffer(PAINTSTROKE_UNIFORM_SLOT, _uniformBuffer); if (_texture && _texture->isLoaded()) { batch.setResourceTexture(PAINTSTROKE_TEXTURE_SLOT, _texture->getGPUTexture()); diff --git a/libraries/entities-renderer/src/RenderablePolyLineEntityItem.h b/libraries/entities-renderer/src/RenderablePolyLineEntityItem.h index f460baac59..3ba26c74df 100644 --- a/libraries/entities-renderer/src/RenderablePolyLineEntityItem.h +++ b/libraries/entities-renderer/src/RenderablePolyLineEntityItem.h @@ -65,7 +65,6 @@ protected: QVector _lastStrokeColors; QVector _lastStrokeWidths; gpu::BufferPointer _verticesBuffer; - gpu::BufferView _uniformBuffer; uint32_t _numVertices { 0 }; bool _empty{ true }; diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp index 0211daff1e..2da5c30dc0 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp @@ -25,14 +25,15 @@ #include #include #include +#include +#include + +#include "entities-renderer/ShaderConstants.h" + +#include #include "EntityTreeRenderer.h" -#include "polyvox_vert.h" -#include "polyvox_frag.h" -#include "polyvox_fade_vert.h" -#include "polyvox_fade_frag.h" - #ifdef POLYVOX_ENTITY_USE_FADE_EFFECT # include #endif @@ -72,11 +73,6 @@ #include "EntityTreeRenderer.h" -#include "polyvox_vert.h" -#include "polyvox_frag.h" -#include "polyvox_fade_vert.h" -#include "polyvox_fade_frag.h" - #include "RenderablePolyVoxEntityItem.h" #include "EntityEditPacketSender.h" #include "PhysicalEntitySimulation.h" @@ -567,8 +563,7 @@ public: bool RenderablePolyVoxEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, - QVariantMap& extraInfo, bool precisionPicking) const -{ + QVariantMap& extraInfo, bool precisionPicking) const { // TODO -- correctly pick against marching-cube generated meshes if (!precisionPicking) { // just intersect with bounding box @@ -576,7 +571,6 @@ bool RenderablePolyVoxEntityItem::findDetailedRayIntersection(const glm::vec3& o } glm::mat4 wtvMatrix = worldToVoxelMatrix(); - glm::mat4 vtwMatrix = voxelToWorldMatrix(); glm::vec3 normDirection = glm::normalize(direction); // the PolyVox ray intersection code requires a near and far point. @@ -589,8 +583,6 @@ bool RenderablePolyVoxEntityItem::findDetailedRayIntersection(const glm::vec3& o glm::vec4 originInVoxel = wtvMatrix * glm::vec4(origin, 1.0f); glm::vec4 farInVoxel = wtvMatrix * glm::vec4(farPoint, 1.0f); - glm::vec4 directionInVoxel = glm::normalize(farInVoxel - originInVoxel); - glm::vec4 result = glm::vec4(0.0f, 0.0f, 0.0f, 0.0f); PolyVox::RaycastResult raycastResult = doRayCast(originInVoxel, farInVoxel, result); if (raycastResult == PolyVox::RaycastResults::Completed) { @@ -604,17 +596,92 @@ bool RenderablePolyVoxEntityItem::findDetailedRayIntersection(const glm::vec3& o voxelBox += result3 - Vectors::HALF; voxelBox += result3 + Vectors::HALF; - float voxelDistance; - - bool hit = voxelBox.findRayIntersection(glm::vec3(originInVoxel), glm::vec3(directionInVoxel), - voxelDistance, face, surfaceNormal); - - glm::vec4 voxelIntersectionPoint = glm::vec4(glm::vec3(originInVoxel) + glm::vec3(directionInVoxel) * voxelDistance, 1.0); - glm::vec4 intersectionPoint = vtwMatrix * voxelIntersectionPoint; - distance = glm::distance(origin, glm::vec3(intersectionPoint)); - return hit; + glm::vec3 directionInVoxel = vec3(wtvMatrix * glm::vec4(direction, 0.0f)); + return voxelBox.findRayIntersection(glm::vec3(originInVoxel), directionInVoxel, 1.0f / directionInVoxel, + distance, face, surfaceNormal); } +bool RenderablePolyVoxEntityItem::findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, + const glm::vec3& acceleration, OctreeElementPointer& element, + float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, + QVariantMap& extraInfo, bool precisionPicking) const { + // TODO -- correctly pick against marching-cube generated meshes + if (!precisionPicking) { + // just intersect with bounding box + return true; + } + + glm::mat4 wtvMatrix = worldToVoxelMatrix(); + glm::vec4 originInVoxel = wtvMatrix * glm::vec4(origin, 1.0f); + glm::vec4 velocityInVoxel = wtvMatrix * glm::vec4(velocity, 0.0f); + glm::vec4 accelerationInVoxel = wtvMatrix * glm::vec4(acceleration, 0.0f); + + // find the first intersection with the voxel bounding box (slightly enlarged so we can catch voxels that touch the sides) + bool success; + glm::vec3 center = getCenterPosition(success); + glm::vec3 dimensions = getScaledDimensions(); + const float FIRST_BOX_HALF_SCALE = 0.51f; + AABox voxelBox1(wtvMatrix * vec4(center - FIRST_BOX_HALF_SCALE * dimensions, 1.0f), + wtvMatrix * vec4(2.0f * FIRST_BOX_HALF_SCALE * dimensions, 0.0f)); + bool hit1; + float parabolicDistance1; + // If we're starting inside the box, our first point is originInVoxel + if (voxelBox1.contains(originInVoxel)) { + parabolicDistance1 = 0.0f; + hit1 = true; + } else { + BoxFace face1; + glm::vec3 surfaceNormal1; + hit1 = voxelBox1.findParabolaIntersection(glm::vec3(originInVoxel), glm::vec3(velocityInVoxel), glm::vec3(accelerationInVoxel), + parabolicDistance1, face1, surfaceNormal1); + } + + if (hit1) { + // find the second intersection, which should be with the inside of the box (use a slightly large box again) + const float SECOND_BOX_HALF_SCALE = 0.52f; + AABox voxelBox2(wtvMatrix * vec4(center - SECOND_BOX_HALF_SCALE * dimensions, 1.0f), + wtvMatrix * vec4(2.0f * SECOND_BOX_HALF_SCALE * dimensions, 0.0f)); + glm::vec4 originInVoxel2 = originInVoxel + velocityInVoxel * parabolicDistance1 + 0.5f * accelerationInVoxel * parabolicDistance1 * parabolicDistance1; + glm::vec4 velocityInVoxel2 = velocityInVoxel + accelerationInVoxel * parabolicDistance1; + glm::vec4 accelerationInVoxel2 = accelerationInVoxel; + float parabolicDistance2; + BoxFace face2; + glm::vec3 surfaceNormal2; + // this should always be true + if (voxelBox2.findParabolaIntersection(glm::vec3(originInVoxel2), glm::vec3(velocityInVoxel2), glm::vec3(accelerationInVoxel2), + parabolicDistance2, face2, surfaceNormal2)) { + const int MAX_SECTIONS = 15; + PolyVox::RaycastResult raycastResult = PolyVox::RaycastResults::Completed; + glm::vec4 result = glm::vec4(0.0f, 0.0f, 0.0f, 0.0f); + glm::vec4 segmentStartVoxel = originInVoxel2; + for (int i = 0; i < MAX_SECTIONS; i++) { + float t = parabolicDistance2 * ((float)(i + 1)) / ((float)MAX_SECTIONS); + glm::vec4 segmentEndVoxel = originInVoxel2 + velocityInVoxel2 * t + 0.5f * accelerationInVoxel2 * t * t; + raycastResult = doRayCast(segmentStartVoxel, segmentEndVoxel, result); + if (raycastResult != PolyVox::RaycastResults::Completed) { + // We hit something! + break; + } + segmentStartVoxel = segmentEndVoxel; + } + + if (raycastResult == PolyVox::RaycastResults::Completed) { + // the parabola completed its path -- nothing was hit. + return false; + } + + glm::vec3 result3 = glm::vec3(result); + + AABox voxelBox; + voxelBox += result3 - Vectors::HALF; + voxelBox += result3 + Vectors::HALF; + + return voxelBox.findParabolaIntersection(glm::vec3(originInVoxel), glm::vec3(velocityInVoxel), glm::vec3(accelerationInVoxel), + parabolicDistance, face, surfaceNormal); + } + } + return false; +} PolyVox::RaycastResult RenderablePolyVoxEntityItem::doRayCast(glm::vec4 originInVoxel, glm::vec4 farInVoxel, @@ -1485,7 +1552,6 @@ scriptable::ScriptableModelBase RenderablePolyVoxEntityItem::getScriptableModel( using namespace render; using namespace render::entities; -static const int MATERIAL_GPU_SLOT { 3 }; static uint8_t CUSTOM_PIPELINE_NUMBER; static gpu::PipelinePointer _pipelines[2]; static gpu::PipelinePointer _wireframePipelines[2]; @@ -1493,17 +1559,8 @@ static gpu::Stream::FormatPointer _vertexFormat; ShapePipelinePointer shapePipelineFactory(const ShapePlumber& plumber, const ShapeKey& key, gpu::Batch& batch) { if (!_pipelines[0]) { - gpu::ShaderPointer vertexShaders[2] = { polyvox_vert::getShader(), polyvox_fade_vert::getShader() }; - gpu::ShaderPointer pixelShaders[2] = { polyvox_frag::getShader(), polyvox_fade_frag::getShader() }; - - gpu::Shader::BindingSet slotBindings; - slotBindings.insert(gpu::Shader::Binding(std::string("materialBuffer"), MATERIAL_GPU_SLOT)); - slotBindings.insert(gpu::Shader::Binding(std::string("xMap"), 0)); - slotBindings.insert(gpu::Shader::Binding(std::string("yMap"), 1)); - slotBindings.insert(gpu::Shader::Binding(std::string("zMap"), 2)); -#ifdef POLYVOX_ENTITY_USE_FADE_EFFECT - slotBindings.insert(gpu::Shader::Binding(std::string("fadeMaskMap"), 3)); -#endif + using namespace shader::entities_renderer::program; + int programsIds[2] = { polyvox, polyvox_fade }; auto state = std::make_shared(); state->setCullMode(gpu::State::CULL_BACK); @@ -1518,12 +1575,7 @@ ShapePipelinePointer shapePipelineFactory(const ShapePlumber& plumber, const Sha // Two sets of pipelines: normal and fading for (auto i = 0; i < 2; i++) { - gpu::ShaderPointer program = gpu::Shader::createProgram(vertexShaders[i], pixelShaders[i]); - - batch.runLambda([program, slotBindings] { - gpu::Shader::makeProgram(*program, slotBindings); - }); - + gpu::ShaderPointer program = gpu::Shader::createProgram(programsIds[i]); _pipelines[i] = gpu::Pipeline::create(program, state); _wireframePipelines[i] = gpu::Pipeline::create(program, wireframeState); } @@ -1658,8 +1710,7 @@ void PolyVoxEntityRenderer::doRender(RenderArgs* args) { } } - int voxelVolumeSizeLocation = args->_shapePipeline->pipeline->getProgram()->getUniforms().findLocation("voxelVolumeSize"); - batch._glUniform3f(voxelVolumeSizeLocation, _lastVoxelVolumeSize.x, _lastVoxelVolumeSize.y, _lastVoxelVolumeSize.z); + batch._glUniform3f(entities_renderer::slot::uniform::PolyvoxVoxelSize, _lastVoxelVolumeSize.x, _lastVoxelVolumeSize.y, _lastVoxelVolumeSize.z); batch.drawIndexed(gpu::TRIANGLES, (gpu::uint32)_mesh->getNumIndices(), 0); } diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h index 7077ae799b..7afb9b41b4 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h @@ -51,11 +51,15 @@ public: int getOnCount() const override { return _onCount; } - virtual bool supportsDetailedRayIntersection() const override { return true; } + virtual bool supportsDetailedIntersection() const override { return true; } virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - OctreeElementPointer& element, float& distance, - BoxFace& face, glm::vec3& surfaceNormal, - QVariantMap& extraInfo, bool precisionPicking) const override; + OctreeElementPointer& element, float& distance, + BoxFace& face, glm::vec3& surfaceNormal, + QVariantMap& extraInfo, bool precisionPicking) const override; + virtual bool findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const vec3& accleration, + OctreeElementPointer& element, float& parabolicDistance, + BoxFace& face, glm::vec3& surfaceNormal, + QVariantMap& extraInfo, bool precisionPicking) const override; virtual void setVoxelData(const QByteArray& voxelData) override; virtual void setVoxelVolumeSize(const glm::vec3& voxelVolumeSize) override; diff --git a/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp b/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp index c50b3bd760..71e3a0ff27 100644 --- a/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp @@ -15,12 +15,7 @@ #include #include #include - -#include "render-utils/simple_vert.h" -#include "render-utils/simple_frag.h" -#include "render-utils/simple_transparent_frag.h" -#include "render-utils/forward_simple_frag.h" -#include "render-utils/forward_simple_transparent_frag.h" +#include #include "RenderPipelines.h" @@ -37,12 +32,10 @@ static const float SPHERE_ENTITY_SCALE = 0.5f; ShapeEntityRenderer::ShapeEntityRenderer(const EntityItemPointer& entity) : Parent(entity) { - _procedural._vertexSource = simple_vert::getSource(); + _procedural._vertexSource = gpu::Shader::getVertexShaderSource(shader::render_utils::vertex::simple); // FIXME: Setup proper uniform slots and use correct pipelines for forward rendering - _procedural._opaquefragmentSource = simple_frag::getSource(); - // FIXME: Transparent procedural entities only seem to work if they use the opaque pipelines - //_procedural._transparentfragmentSource = simple_transparent_frag::getSource(); - _procedural._transparentfragmentSource = simple_frag::getSource(); + _procedural._opaquefragmentSource = gpu::Shader::getFragmentShaderSource(shader::render_utils::fragment::simple); + _procedural._transparentfragmentSource = gpu::Shader::getFragmentShaderSource(shader::render_utils::fragment::simple_transparent); _procedural._opaqueState->setCullMode(gpu::State::CULL_NONE); _procedural._opaqueState->setDepthTest(true, true, gpu::LESS_EQUAL); PrepareStencil::testMaskDrawShape(*_procedural._opaqueState); @@ -216,7 +209,14 @@ ShapeKey ShapeEntityRenderer::getShapeKey() { return builder.build(); } else { - return Parent::getShapeKey(); + ShapeKey::Builder builder; + if (_procedural.isReady()) { + builder.withOwnPipeline(); + } + if (isTransparent()) { + builder.withTranslucent(); + } + return builder.build(); } } @@ -266,8 +266,10 @@ void ShapeEntityRenderer::doRender(RenderArgs* args) { geometryCache->renderSolidShapeInstance(args, batch, geometryShape, outColor, pipeline); } } else { - RenderPipelines::bindMaterial(mat, batch, args->_enableTexturing); - args->_details._materialSwitches++; + if (args->_renderMode != render::Args::RenderMode::SHADOW_RENDER_MODE) { + RenderPipelines::bindMaterial(mat, batch, args->_enableTexturing); + args->_details._materialSwitches++; + } geometryCache->renderShape(batch, geometryShape); } diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp index 793b4aa158..bc9ac84c91 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp @@ -53,7 +53,8 @@ WebEntityRenderer::ContentType WebEntityRenderer::getContentType(const QString& } const QUrl url(urlString); - if (url.scheme() == URL_SCHEME_HTTP || url.scheme() == URL_SCHEME_HTTPS || + auto scheme = url.scheme(); + if (scheme == URL_SCHEME_ABOUT || scheme == URL_SCHEME_HTTP || scheme == URL_SCHEME_HTTPS || urlString.toLower().endsWith(".htm") || urlString.toLower().endsWith(".html")) { return ContentType::HtmlContent; } diff --git a/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp b/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp index c5035431f6..9430e97e54 100644 --- a/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp @@ -64,6 +64,13 @@ void ZoneEntityRenderer::onRemoveFromSceneTyped(const TypedEntityPointer& entity _hazeIndex = INVALID_INDEX; } } + + if (_bloomStage) { + if (!BloomStage::isIndexInvalid(_bloomIndex)) { + _bloomStage->removeBloom(_bloomIndex); + _bloomIndex = INVALID_INDEX; + } + } } void ZoneEntityRenderer::doRender(RenderArgs* args) { @@ -112,6 +119,11 @@ void ZoneEntityRenderer::doRender(RenderArgs* args) { assert(_hazeStage); } + if (!_bloomStage) { + _bloomStage = args->_scene->getStage(); + assert(_bloomStage); + } + { // Sun // Need an update ? if (_needSunUpdate) { @@ -161,6 +173,15 @@ void ZoneEntityRenderer::doRender(RenderArgs* args) { } } + { + if (_needBloomUpdate) { + if (BloomStage::isIndexInvalid(_bloomIndex)) { + _bloomIndex = _bloomStage->addBloom(_bloom); + } + _needBloomUpdate = false; + } + } + if (_visible) { // Finally, push the lights visible in the frame // @@ -190,6 +211,12 @@ void ZoneEntityRenderer::doRender(RenderArgs* args) { if (_hazeMode != COMPONENT_MODE_INHERIT) { _hazeStage->_currentFrame.pushHaze(_hazeIndex); } + + if (_bloomMode == COMPONENT_MODE_DISABLED) { + _bloomStage->_currentFrame.pushBloom(INVALID_INDEX); + } else if (_bloomMode == COMPONENT_MODE_ENABLED) { + _bloomStage->_currentFrame.pushBloom(_bloomIndex); + } } } @@ -211,6 +238,7 @@ void ZoneEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scen bool ambientLightChanged = entity->ambientLightPropertiesChanged(); bool skyboxChanged = entity->skyboxPropertiesChanged(); bool hazeChanged = entity->hazePropertiesChanged(); + bool bloomChanged = entity->bloomPropertiesChanged(); entity->resetRenderingPropertiesChanged(); _lastPosition = entity->getWorldPosition(); @@ -221,6 +249,7 @@ void ZoneEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scen _ambientLightProperties = entity->getAmbientLightProperties(); _skyboxProperties = entity->getSkyboxProperties(); _hazeProperties = entity->getHazeProperties(); + _bloomProperties = entity->getBloomProperties(); #if 0 if (_lastShapeURL != _typedEntity->getCompoundShapeURL()) { @@ -258,6 +287,10 @@ void ZoneEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scen if (hazeChanged) { updateHazeFromEntity(entity); } + + if (bloomChanged) { + updateBloomFromEntity(entity); + } } void ZoneEntityRenderer::doRenderUpdateAsynchronousTyped(const TypedEntityPointer& entity) { @@ -276,6 +309,7 @@ bool ZoneEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPoint if (entity->keyLightPropertiesChanged() || entity->ambientLightPropertiesChanged() || entity->hazePropertiesChanged() || + entity->bloomPropertiesChanged() || entity->skyboxPropertiesChanged()) { return true; @@ -388,6 +422,16 @@ void ZoneEntityRenderer::updateHazeFromEntity(const TypedEntityPointer& entity) haze->setTransform(entity->getTransform().getMatrix()); } +void ZoneEntityRenderer::updateBloomFromEntity(const TypedEntityPointer& entity) { + setBloomMode((ComponentMode)entity->getBloomMode()); + + const auto& bloom = editBloom(); + + bloom->setBloomIntensity(_bloomProperties.getBloomIntensity()); + bloom->setBloomThreshold(_bloomProperties.getBloomThreshold()); + bloom->setBloomSize(_bloomProperties.getBloomSize()); +} + void ZoneEntityRenderer::updateKeyBackgroundFromEntity(const TypedEntityPointer& entity) { setSkyboxMode((ComponentMode)entity->getSkyboxMode()); @@ -510,6 +554,10 @@ void ZoneEntityRenderer::setSkyboxMode(ComponentMode mode) { _skyboxMode = mode; } +void ZoneEntityRenderer::setBloomMode(ComponentMode mode) { + _bloomMode = mode; +} + void ZoneEntityRenderer::setSkyboxColor(const glm::vec3& color) { editSkybox()->setColor(color); } diff --git a/libraries/entities-renderer/src/RenderableZoneEntityItem.h b/libraries/entities-renderer/src/RenderableZoneEntityItem.h index c48679e5d4..3e2690e1bd 100644 --- a/libraries/entities-renderer/src/RenderableZoneEntityItem.h +++ b/libraries/entities-renderer/src/RenderableZoneEntityItem.h @@ -1,7 +1,6 @@ // // RenderableZoneEntityItem.h // -// // Created by Clement on 4/22/15. // Copyright 2015 High Fidelity, Inc. // @@ -15,10 +14,12 @@ #include #include #include +#include #include #include #include #include +#include #include #include "RenderableEntityItem.h" #include @@ -50,6 +51,7 @@ private: void updateAmbientLightFromEntity(const TypedEntityPointer& entity); void updateHazeFromEntity(const TypedEntityPointer& entity); void updateKeyBackgroundFromEntity(const TypedEntityPointer& entity); + void updateBloomFromEntity(const TypedEntityPointer& entity); void updateAmbientMap(); void updateSkyboxMap(); void setAmbientURL(const QString& ambientUrl); @@ -59,6 +61,7 @@ private: void setKeyLightMode(ComponentMode mode); void setAmbientLightMode(ComponentMode mode); void setSkyboxMode(ComponentMode mode); + void setBloomMode(ComponentMode mode); void setSkyboxColor(const glm::vec3& color); void setProceduralUserData(const QString& userData); @@ -68,6 +71,7 @@ private: graphics::SunSkyStagePointer editBackground() { _needBackgroundUpdate = true; return _background; } graphics::SkyboxPointer editSkybox() { return editBackground()->getSkybox(); } graphics::HazePointer editHaze() { _needHazeUpdate = true; return _haze; } + graphics::BloomPointer editBloom() { _needBloomUpdate = true; return _bloom; } glm::vec3 _lastPosition; glm::vec3 _lastDimensions; @@ -82,36 +86,43 @@ private: #endif LightStagePointer _stage; - const graphics::LightPointer _sunLight{ std::make_shared() }; - const graphics::LightPointer _ambientLight{ std::make_shared() }; - const graphics::SunSkyStagePointer _background{ std::make_shared() }; - const graphics::HazePointer _haze{ std::make_shared() }; + const graphics::LightPointer _sunLight { std::make_shared() }; + const graphics::LightPointer _ambientLight { std::make_shared() }; + const graphics::SunSkyStagePointer _background { std::make_shared() }; + const graphics::HazePointer _haze { std::make_shared() }; + const graphics::BloomPointer _bloom { std::make_shared() }; ComponentMode _keyLightMode { COMPONENT_MODE_INHERIT }; ComponentMode _ambientLightMode { COMPONENT_MODE_INHERIT }; ComponentMode _skyboxMode { COMPONENT_MODE_INHERIT }; ComponentMode _hazeMode { COMPONENT_MODE_INHERIT }; + ComponentMode _bloomMode { COMPONENT_MODE_INHERIT }; - indexed_container::Index _sunIndex{ LightStage::INVALID_INDEX }; - indexed_container::Index _shadowIndex{ LightStage::INVALID_INDEX }; - indexed_container::Index _ambientIndex{ LightStage::INVALID_INDEX }; + indexed_container::Index _sunIndex { LightStage::INVALID_INDEX }; + indexed_container::Index _shadowIndex { LightStage::INVALID_INDEX }; + indexed_container::Index _ambientIndex { LightStage::INVALID_INDEX }; BackgroundStagePointer _backgroundStage; - BackgroundStage::Index _backgroundIndex{ BackgroundStage::INVALID_INDEX }; + BackgroundStage::Index _backgroundIndex { BackgroundStage::INVALID_INDEX }; HazeStagePointer _hazeStage; - HazeStage::Index _hazeIndex{ HazeStage::INVALID_INDEX }; + HazeStage::Index _hazeIndex { HazeStage::INVALID_INDEX }; + + BloomStagePointer _bloomStage; + BloomStage::Index _bloomIndex { BloomStage::INVALID_INDEX }; bool _needUpdate{ true }; bool _needSunUpdate{ true }; bool _needAmbientUpdate{ true }; bool _needBackgroundUpdate{ true }; bool _needHazeUpdate{ true }; + bool _needBloomUpdate { true }; KeyLightPropertyGroup _keyLightProperties; AmbientLightPropertyGroup _ambientLightProperties; HazePropertyGroup _hazeProperties; SkyboxPropertyGroup _skyboxProperties; + BloomPropertyGroup _bloomProperties; // More attributes used for rendering: QString _ambientTextureURL; diff --git a/libraries/entities-renderer/src/entities-renderer/ShaderConstants.h b/libraries/entities-renderer/src/entities-renderer/ShaderConstants.h new file mode 100644 index 0000000000..58b0bf5688 --- /dev/null +++ b/libraries/entities-renderer/src/entities-renderer/ShaderConstants.h @@ -0,0 +1,61 @@ +// + +// <@if not ENTITIES_SHADER_CONSTANTS_H@> +// <@def ENTITIES_SHADER_CONSTANTS_H@> + +// Hack comment to absorb the extra '//' scribe prepends + +#ifndef ENTITIES_SHADER_CONSTANTS_H +#define ENTITIES_SHADER_CONSTANTS_H + +// Polyvox +#define ENTITIES_UNIFORM_POLYVOX_VOXEL_SIZE 0 +#define ENTITIES_TEXTURE_POLYVOX_XMAP 0 +#define ENTITIES_TEXTURE_POLYVOX_YMAP 1 +#define ENTITIES_TEXTURE_POLYVOX_ZMAP 2 + + + +// +// Hack Comment + +#endif // ENTITIES_SHADER_CONSTANTS_H + +// <@if 1@> +// Trigger Scribe include +// <@endif@> + +// <@endif@> + +// Hack Comment diff --git a/libraries/entities-renderer/src/entities-renderer/paintStroke.slp b/libraries/entities-renderer/src/entities-renderer/paintStroke.slp new file mode 100644 index 0000000000..e69de29bb2 diff --git a/libraries/entities-renderer/src/entities-renderer/polyvox.slp b/libraries/entities-renderer/src/entities-renderer/polyvox.slp new file mode 100644 index 0000000000..e69de29bb2 diff --git a/libraries/entities-renderer/src/entities-renderer/polyvox_fade.slp b/libraries/entities-renderer/src/entities-renderer/polyvox_fade.slp new file mode 100644 index 0000000000..e69de29bb2 diff --git a/libraries/entities-renderer/src/entities-renderer/textured_particle.slp b/libraries/entities-renderer/src/entities-renderer/textured_particle.slp new file mode 100644 index 0000000000..e69de29bb2 diff --git a/libraries/entities-renderer/src/paintStroke.slf b/libraries/entities-renderer/src/paintStroke.slf index 738561eccc..211685a9ba 100644 --- a/libraries/entities-renderer/src/paintStroke.slf +++ b/libraries/entities-renderer/src/paintStroke.slf @@ -14,26 +14,15 @@ <@include DeferredBufferWrite.slh@> - // the albedo texture -uniform sampler2D originalTexture; +layout(binding=0) uniform sampler2D originalTexture; // the interpolated normal -in vec3 interpolatedNormal; -in vec2 varTexcoord; -in vec4 varColor; - -struct PolyLineUniforms { - vec3 color; -}; - -uniform polyLineBuffer { - PolyLineUniforms polyline; -}; +layout(location=0) in vec3 interpolatedNormal; +layout(location=1) in vec2 varTexcoord; +layout(location=2) in vec4 varColor; void main(void) { - - vec4 texel = texture(originalTexture, varTexcoord); int frontCondition = 1 -int(gl_FrontFacing) * 2; vec3 color = varColor.rgb; diff --git a/libraries/entities-renderer/src/paintStroke.slv b/libraries/entities-renderer/src/paintStroke.slv index 0cf9596cce..ecf52d61cf 100644 --- a/libraries/entities-renderer/src/paintStroke.slv +++ b/libraries/entities-renderer/src/paintStroke.slv @@ -18,21 +18,19 @@ <$declareStandardTransform()$> // the interpolated normal -out vec3 interpolatedNormal; +layout(location=0) out vec3 interpolatedNormal; //the diffuse texture -out vec2 varTexcoord; +layout(location=1) out vec2 varTexcoord; -out vec4 varColor; +layout(location=2) out vec4 varColor; void main(void) { - varTexcoord = inTexCoord0.st; - + // pass along the diffuse color varColor = color_sRGBAToLinear(inColor); - // standard transform TransformCamera cam = getTransformCamera(); TransformObject obj = getTransformObject(); diff --git a/libraries/entities-renderer/src/paintStroke_fade.slf b/libraries/entities-renderer/src/paintStroke_fade.slf index cc037aeac4..8739c9bb9b 100644 --- a/libraries/entities-renderer/src/paintStroke_fade.slf +++ b/libraries/entities-renderer/src/paintStroke_fade.slf @@ -2,7 +2,7 @@ <$VERSION_HEADER$> // Generated on <$_SCRIBE_DATE$> // -// paintStroke_fade.slf +// paintStroke_fade.frag // fragment shader // // Created by Olivier Prat on 19/07/17. @@ -18,19 +18,19 @@ <$declareFadeFragment()$> // the albedo texture -uniform sampler2D originalTexture; +layout(binding=0) uniform sampler2D originalTexture; // the interpolated normal -in vec3 interpolatedNormal; -in vec2 varTexcoord; -in vec4 varColor; -in vec4 _worldPosition; +layout(location=0) in vec3 interpolatedNormal; +layout(location=1) in vec2 varTexcoord; +layout(location=2) in vec4 varColor; +layout(location=3) in vec4 _worldPosition; struct PolyLineUniforms { vec3 color; }; -uniform polyLineBuffer { +layout(binding=0) uniform polyLineBuffer { PolyLineUniforms polyline; }; diff --git a/libraries/entities-renderer/src/paintStroke_fade.slv b/libraries/entities-renderer/src/paintStroke_fade.slv index b6075caaf8..f6fcb18c98 100644 --- a/libraries/entities-renderer/src/paintStroke_fade.slv +++ b/libraries/entities-renderer/src/paintStroke_fade.slv @@ -2,7 +2,7 @@ <$VERSION_HEADER$> // Generated on <$_SCRIBE_DATE$> // -// paintStroke_fade.slv +// paintStroke_fade.vert // vertex shader // // Created by Olivier Prat on 19/07/17. @@ -18,13 +18,13 @@ <$declareStandardTransform()$> // the interpolated normal -out vec3 interpolatedNormal; +layout(location=0) out vec3 interpolatedNormal; //the diffuse texture -out vec2 varTexcoord; +layout(location=1) out vec2 varTexcoord; -out vec4 varColor; -out vec4 _worldPosition; +layout(location=2) out vec4 varColor; +layout(location=3) out vec4 _worldPosition; void main(void) { diff --git a/libraries/entities-renderer/src/polyvox.slf b/libraries/entities-renderer/src/polyvox.slf index 50034db48c..ba2fd7031b 100644 --- a/libraries/entities-renderer/src/polyvox.slf +++ b/libraries/entities-renderer/src/polyvox.slf @@ -13,15 +13,17 @@ <@include graphics/Material.slh@> <@include DeferredBufferWrite.slh@> +<@include render-utils/ShaderConstants.h@> +<@include entities-renderer/ShaderConstants.h@> -in vec3 _normal; -in vec4 _position; -in vec4 _worldPosition; +layout(location=RENDER_UTILS_ATTR_NORMAL_MS) in vec3 _normal; +layout(location=RENDER_UTILS_ATTR_POSITION_MS) in vec4 _position; +layout(location=RENDER_UTILS_ATTR_POSITION_WS) in vec4 _worldPosition; -uniform sampler2D xMap; -uniform sampler2D yMap; -uniform sampler2D zMap; -uniform vec3 voxelVolumeSize; +layout(binding=ENTITIES_TEXTURE_POLYVOX_XMAP) uniform sampler2D xMap; +layout(binding=ENTITIES_TEXTURE_POLYVOX_YMAP) uniform sampler2D yMap; +layout(binding=ENTITIES_TEXTURE_POLYVOX_ZMAP) uniform sampler2D zMap; +layout(location=ENTITIES_UNIFORM_POLYVOX_VOXEL_SIZE) uniform vec3 voxelVolumeSize; void main(void) { vec3 worldNormal = cross(dFdy(_worldPosition.xyz), dFdx(_worldPosition.xyz)); diff --git a/libraries/entities-renderer/src/polyvox.slv b/libraries/entities-renderer/src/polyvox.slv index eb8d264a1b..d17974c994 100644 --- a/libraries/entities-renderer/src/polyvox.slv +++ b/libraries/entities-renderer/src/polyvox.slv @@ -11,14 +11,14 @@ // <@include gpu/Inputs.slh@> - <@include gpu/Transform.slh@> +<@include render-utils/ShaderConstants.h@> <$declareStandardTransform()$> -out vec4 _position; -out vec4 _worldPosition; -out vec3 _normal; +layout(location=RENDER_UTILS_ATTR_POSITION_MS) out vec4 _position; +layout(location=RENDER_UTILS_ATTR_POSITION_WS) out vec4 _worldPosition; +layout(location=RENDER_UTILS_ATTR_NORMAL_MS) out vec3 _normal; void main(void) { // standard transform diff --git a/libraries/entities-renderer/src/polyvox_fade.slf b/libraries/entities-renderer/src/polyvox_fade.slf index 4c179a15b6..2247e472ea 100644 --- a/libraries/entities-renderer/src/polyvox_fade.slf +++ b/libraries/entities-renderer/src/polyvox_fade.slf @@ -13,18 +13,21 @@ <@include graphics/Material.slh@> <@include DeferredBufferWrite.slh@> +<@include render-utils/ShaderConstants.h@> +<@include entities-renderer/ShaderConstants.h@> <@include Fade.slh@> -in vec3 _normal; -in vec4 _position; -in vec4 _worldPosition; -in vec4 _worldFadePosition; +layout(location=RENDER_UTILS_ATTR_NORMAL_MS) in vec3 _normal; +layout(location=RENDER_UTILS_ATTR_POSITION_MS) in vec4 _position; +layout(location=RENDER_UTILS_ATTR_POSITION_WS) in vec4 _worldPosition; +layout(location=RENDER_UTILS_ATTR_POSITION_ES) in vec4 _worldFadePosition; -uniform sampler2D xMap; -uniform sampler2D yMap; -uniform sampler2D zMap; -uniform vec3 voxelVolumeSize; +layout(binding=ENTITIES_TEXTURE_POLYVOX_XMAP) uniform sampler2D xMap; +layout(binding=ENTITIES_TEXTURE_POLYVOX_YMAP) uniform sampler2D yMap; +layout(binding=ENTITIES_TEXTURE_POLYVOX_ZMAP) uniform sampler2D zMap; + +layout(location=ENTITIES_UNIFORM_POLYVOX_VOXEL_SIZE) uniform vec3 voxelVolumeSize; // Declare after all samplers to prevent sampler location mix up with voxel shading (sampler locations are hardcoded in RenderablePolyVoxEntityItem) <$declareFadeFragment()$> diff --git a/libraries/entities-renderer/src/polyvox_fade.slv b/libraries/entities-renderer/src/polyvox_fade.slv index 506b5d16e7..97b98f5840 100644 --- a/libraries/entities-renderer/src/polyvox_fade.slv +++ b/libraries/entities-renderer/src/polyvox_fade.slv @@ -12,15 +12,15 @@ // <@include gpu/Inputs.slh@> - <@include gpu/Transform.slh@> +<@include render-utils/ShaderConstants.h@> <$declareStandardTransform()$> -out vec4 _position; -out vec4 _worldPosition; -out vec4 _worldFadePosition; -out vec3 _normal; +layout(location=RENDER_UTILS_ATTR_POSITION_MS) out vec4 _position; +layout(location=RENDER_UTILS_ATTR_POSITION_WS) out vec4 _worldPosition; +layout(location=RENDER_UTILS_ATTR_NORMAL_MS) out vec3 _normal; +layout(location=RENDER_UTILS_ATTR_POSITION_ES) out vec4 _worldFadePosition; void main(void) { // standard transform diff --git a/libraries/entities-renderer/src/textured_particle.slf b/libraries/entities-renderer/src/textured_particle.slf index e139c7cc01..7a0cedf011 100644 --- a/libraries/entities-renderer/src/textured_particle.slf +++ b/libraries/entities-renderer/src/textured_particle.slf @@ -1,7 +1,8 @@ <@include gpu/Config.slh@> <$VERSION_HEADER$> // Generated on <$_SCRIBE_DATE$> -// fragment shader +// +// textured_particle.frag // // Copyright 2015 High Fidelity, Inc. // @@ -9,12 +10,12 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -uniform sampler2D colorMap; +layout(binding=0) uniform sampler2D colorMap; -in vec4 varColor; -in vec2 varTexcoord; +layout(location=0) in vec4 varColor; +layout(location=1) in vec2 varTexcoord; -out vec4 outFragColor; +layout(location=0) out vec4 outFragColor; void main(void) { outFragColor = texture(colorMap, varTexcoord.xy) * varColor; diff --git a/libraries/entities-renderer/src/textured_particle.slv b/libraries/entities-renderer/src/textured_particle.slv index 7653bc0a42..3eacaec3b5 100644 --- a/libraries/entities-renderer/src/textured_particle.slv +++ b/libraries/entities-renderer/src/textured_particle.slv @@ -2,7 +2,7 @@ <$VERSION_HEADER$> // Generated on <$_SCRIBE_DATE$> // -// particle vertex shader +// texture_particle.vert // // Copyright 2015 High Fidelity, Inc. // @@ -27,31 +27,31 @@ struct Colors { vec4 finish; vec4 spread; }; +struct Spin { + float start; + float middle; + float finish; + float spread; +}; struct ParticleUniforms { Radii radius; Colors color; - vec4 lifespan; // x is lifespan, 3 spare floats + Spin spin; + float lifespan; + int rotateWithEntity; + vec2 spare; }; -layout(std140) uniform particleBuffer { +layout(std140, binding=0) uniform particleBuffer { ParticleUniforms particle; }; layout(location=0) in vec3 inPosition; layout(location=2) in vec2 inColor; // This is actual Lifetime + Seed -out vec4 varColor; -out vec2 varTexcoord; - -const int NUM_VERTICES_PER_PARTICLE = 4; -// This ordering ensures that un-rotated particles render upright in the viewer. -const vec4 UNIT_QUAD[NUM_VERTICES_PER_PARTICLE] = vec4[NUM_VERTICES_PER_PARTICLE]( - vec4(-1.0, 1.0, 0.0, 0.0), - vec4(-1.0, -1.0, 0.0, 0.0), - vec4(1.0, 1.0, 0.0, 0.0), - vec4(1.0, -1.0, 0.0, 0.0) -); +layout(location=0) out vec4 varColor; +layout(location=1) out vec2 varTexcoord; float bezierInterpolate(float y1, float y2, float y3, float u) { // https://en.wikipedia.org/wiki/Bezier_curve @@ -103,6 +103,15 @@ vec4 interpolate3Vec4(vec4 y1, vec4 y2, vec4 y3, float u) { interpolate3Points(y1.w, y2.w, y3.w, u)); } +const int NUM_VERTICES_PER_PARTICLE = 4; +const vec2 TEX_COORDS[NUM_VERTICES_PER_PARTICLE] = vec2[NUM_VERTICES_PER_PARTICLE]( + vec2(-1.0, 0.0), + vec2(-1.0, 1.0), + vec2(0.0, 0.0), + vec2(0.0, 1.0) +); + + void main(void) { TransformCamera cam = getTransformCamera(); TransformObject obj = getTransformObject(); @@ -113,28 +122,54 @@ void main(void) { int twoTriID = gl_VertexID - particleID * NUM_VERTICES_PER_PARTICLE; // Particle properties - float age = inColor.x / particle.lifespan.x; + float age = inColor.x / particle.lifespan; float seed = inColor.y; - // Pass the texcoord and the z texcoord is representing the texture icon - // Offset for corrected vertex ordering. - varTexcoord = vec2((UNIT_QUAD[twoTriID].xy -1.0) * vec2(0.5, -0.5)); + // Pass the texcoord + varTexcoord = TEX_COORDS[twoTriID].xy; varColor = interpolate3Vec4(particle.color.start, particle.color.middle, particle.color.finish, age); vec3 colorSpread = 2.0 * vec3(hifi_hash(seed), hifi_hash(seed * 2.0), hifi_hash(seed * 3.0)) - 1.0; varColor.rgb = clamp(varColor.rgb + colorSpread * particle.color.spread.rgb, vec3(0), vec3(1)); float alphaSpread = 2.0 * hifi_hash(seed * 4.0) - 1.0; varColor.a = clamp(varColor.a + alphaSpread * particle.color.spread.a, 0.0, 1.0); + float spin = interpolate3Points(particle.spin.start, particle.spin.middle, particle.spin.finish, age); + float spinSpread = 2.0 * hifi_hash(seed * 5.0) - 1.0; + spin = spin + spinSpread * particle.spin.spread; + // anchor point in eye space float radius = interpolate3Points(particle.radius.start, particle.radius.middle, particle.radius.finish, age); - float radiusSpread = 2.0 * hifi_hash(seed * 5.0) - 1.0; + float radiusSpread = 2.0 * hifi_hash(seed * 6.0) - 1.0; radius = max(radius + radiusSpread * particle.radius.spread, 0.0); - vec4 quadPos = radius * UNIT_QUAD[twoTriID]; - vec4 anchorPoint; - vec4 _inPosition = vec4(inPosition, 1.0); - <$transformModelToEyePos(cam, obj, _inPosition, anchorPoint)$> + // inPosition is in world space + vec4 anchorPoint = cam._view * vec4(inPosition, 1.0); - vec4 eyePos = anchorPoint + quadPos; + mat3 view3 = mat3(cam._view); + vec3 UP = vec3(0, 1, 0); + vec3 modelUpWorld; + <$transformModelToWorldDir(cam, obj, UP, modelUpWorld)$> + vec3 upWorld = mix(UP, normalize(modelUpWorld), float(particle.rotateWithEntity)); + vec3 upEye = normalize(view3 * upWorld); + vec3 FORWARD = vec3(0, 0, -1); + vec3 particleRight = normalize(cross(FORWARD, upEye)); + vec3 particleUp = cross(particleRight, FORWARD); // don't need to normalize + // This ordering ensures that un-rotated particles render upright in the viewer. + vec3 UNIT_QUAD[NUM_VERTICES_PER_PARTICLE] = vec3[NUM_VERTICES_PER_PARTICLE]( + normalize(-particleRight + particleUp), + normalize(-particleRight - particleUp), + normalize(particleRight + particleUp), + normalize(particleRight - particleUp) + ); + float c = cos(spin); + float s = sin(spin); + mat4 rotation = mat4( + c, -s, 0, 0, + s, c, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1 + ); + vec4 quadPos = radius * vec4(UNIT_QUAD[twoTriID], 0.0); + vec4 eyePos = anchorPoint + rotation * quadPos; <$transformEyeToClipPos(cam, eyePos, gl_Position)$> } diff --git a/libraries/entities/src/BloomPropertyGroup.cpp b/libraries/entities/src/BloomPropertyGroup.cpp new file mode 100644 index 0000000000..2c4d46ab35 --- /dev/null +++ b/libraries/entities/src/BloomPropertyGroup.cpp @@ -0,0 +1,159 @@ +// +// BloomPropertyGroup.cpp +// libraries/entities/src +// +// Created by Sam Gondelman on 8/7/2018 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "BloomPropertyGroup.h" + +#include + +#include "EntityItemProperties.h" +#include "EntityItemPropertiesMacros.h" + +void BloomPropertyGroup::copyToScriptValue(const EntityPropertyFlags& desiredProperties, QScriptValue& properties, QScriptEngine* engine, bool skipDefaults, EntityItemProperties& defaultEntityProperties) const { + COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_BLOOM_INTENSITY, Bloom, bloom, BloomIntensity, bloomIntensity); + COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_BLOOM_THRESHOLD, Bloom, bloom, BloomThreshold, bloomThreshold); + COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_BLOOM_SIZE, Bloom, bloom, BloomSize, bloomSize); +} + +void BloomPropertyGroup::copyFromScriptValue(const QScriptValue& object, bool& _defaultSettings) { + COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(bloom, bloomIntensity, float, setBloomIntensity); + COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(bloom, bloomThreshold, float, setBloomThreshold); + COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(bloom, bloomSize, float, setBloomSize); +} + +void BloomPropertyGroup::merge(const BloomPropertyGroup& other) { + COPY_PROPERTY_IF_CHANGED(bloomIntensity); + COPY_PROPERTY_IF_CHANGED(bloomThreshold); + COPY_PROPERTY_IF_CHANGED(bloomSize); +} + +void BloomPropertyGroup::debugDump() const { + qCDebug(entities) << " BloomPropertyGroup: ---------------------------------------------"; + qCDebug(entities) << " _bloomIntensity:" << _bloomIntensity; + qCDebug(entities) << " _bloomThreshold:" << _bloomThreshold; + qCDebug(entities) << " _bloomSize:" << _bloomSize; +} + +void BloomPropertyGroup::listChangedProperties(QList& out) { + if (bloomIntensityChanged()) { + out << "bloom-bloomIntensity"; + } + if (bloomThresholdChanged()) { + out << "bloom-bloomThreshold"; + } + if (bloomSizeChanged()) { + out << "bloom-bloomSize"; + } +} + +bool BloomPropertyGroup::appendToEditPacket(OctreePacketData* packetData, + EntityPropertyFlags& requestedProperties, + EntityPropertyFlags& propertyFlags, + EntityPropertyFlags& propertiesDidntFit, + int& propertyCount, + OctreeElement::AppendState& appendState) const { + bool successPropertyFits = true; + + APPEND_ENTITY_PROPERTY(PROP_BLOOM_INTENSITY, getBloomIntensity()); + APPEND_ENTITY_PROPERTY(PROP_BLOOM_THRESHOLD, getBloomThreshold()); + APPEND_ENTITY_PROPERTY(PROP_BLOOM_SIZE, getBloomSize()); + + return true; +} + +bool BloomPropertyGroup::decodeFromEditPacket(EntityPropertyFlags& propertyFlags, const unsigned char*& dataAt , int& processedBytes) { + int bytesRead = 0; + bool overwriteLocalData = true; + bool somethingChanged = false; + + READ_ENTITY_PROPERTY(PROP_BLOOM_INTENSITY, float, setBloomIntensity); + READ_ENTITY_PROPERTY(PROP_BLOOM_THRESHOLD, float, setBloomThreshold); + READ_ENTITY_PROPERTY(PROP_BLOOM_SIZE, float, setBloomSize); + + DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_BLOOM_INTENSITY, BloomIntensity); + DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_BLOOM_THRESHOLD, BloomThreshold); + DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_BLOOM_SIZE, BloomSize); + + processedBytes += bytesRead; + + Q_UNUSED(somethingChanged); + + return true; +} + +void BloomPropertyGroup::markAllChanged() { + _bloomIntensityChanged = true; + _bloomThresholdChanged = true; + _bloomSizeChanged = true; +} + +EntityPropertyFlags BloomPropertyGroup::getChangedProperties() const { + EntityPropertyFlags changedProperties; + + CHECK_PROPERTY_CHANGE(PROP_BLOOM_INTENSITY, bloomIntensity); + CHECK_PROPERTY_CHANGE(PROP_BLOOM_THRESHOLD, bloomThreshold); + CHECK_PROPERTY_CHANGE(PROP_BLOOM_SIZE, bloomSize); + + return changedProperties; +} + +void BloomPropertyGroup::getProperties(EntityItemProperties& properties) const { + COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Bloom, BloomIntensity, getBloomIntensity); + COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Bloom, BloomThreshold, getBloomThreshold); + COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Bloom, BloomSize, getBloomSize); +} + +bool BloomPropertyGroup::setProperties(const EntityItemProperties& properties) { + bool somethingChanged = false; + + SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Bloom, BloomIntensity, bloomIntensity, setBloomIntensity); + SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Bloom, BloomThreshold, bloomThreshold, setBloomThreshold); + SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Bloom, BloomSize, bloomSize, setBloomSize); + + return somethingChanged; +} + +EntityPropertyFlags BloomPropertyGroup::getEntityProperties(EncodeBitstreamParams& params) const { + EntityPropertyFlags requestedProperties; + + requestedProperties += PROP_BLOOM_INTENSITY; + requestedProperties += PROP_BLOOM_THRESHOLD; + requestedProperties += PROP_BLOOM_SIZE; + + return requestedProperties; +} + +void BloomPropertyGroup::appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, + EntityTreeElementExtraEncodeDataPointer entityTreeElementExtraEncodeData, + EntityPropertyFlags& requestedProperties, + EntityPropertyFlags& propertyFlags, + EntityPropertyFlags& propertiesDidntFit, + int& propertyCount, + OctreeElement::AppendState& appendState) const { + bool successPropertyFits = true; + + APPEND_ENTITY_PROPERTY(PROP_BLOOM_INTENSITY, getBloomIntensity()); + APPEND_ENTITY_PROPERTY(PROP_BLOOM_THRESHOLD, getBloomThreshold()); + APPEND_ENTITY_PROPERTY(PROP_BLOOM_SIZE, getBloomSize()); +} + +int BloomPropertyGroup::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, + ReadBitstreamToTreeParams& args, + EntityPropertyFlags& propertyFlags, bool overwriteLocalData, + bool& somethingChanged) { + int bytesRead = 0; + const unsigned char* dataAt = data; + + READ_ENTITY_PROPERTY(PROP_BLOOM_INTENSITY, float, setBloomIntensity); + READ_ENTITY_PROPERTY(PROP_BLOOM_THRESHOLD, float, setBloomThreshold); + READ_ENTITY_PROPERTY(PROP_BLOOM_SIZE, float, setBloomSize); + + return bytesRead; +} diff --git a/libraries/entities/src/BloomPropertyGroup.h b/libraries/entities/src/BloomPropertyGroup.h new file mode 100644 index 0000000000..a1f9b6d748 --- /dev/null +++ b/libraries/entities/src/BloomPropertyGroup.h @@ -0,0 +1,94 @@ +// +// BloomPropertyGroup.h +// libraries/entities/src +// +// Created by Sam Gondelman on 8/7/2018 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_BloomPropertyGroup_h +#define hifi_BloomPropertyGroup_h + +#include +#include + +#include + +#include "PropertyGroup.h" +#include "EntityItemPropertiesMacros.h" + +class EntityItemProperties; +class EncodeBitstreamParams; +class OctreePacketData; +class EntityTreeElementExtraEncodeData; +class ReadBitstreamToTreeParams; + +static const float INITIAL_BLOOM_INTENSITY { 0.25f }; +static const float INITIAL_BLOOM_THRESHOLD { 0.7f }; +static const float INITIAL_BLOOM_SIZE { 0.9f }; + +/**jsdoc + * Bloom is defined by the following properties. + * @typedef {object} Entities.Bloom + * + * @property {number} bloomIntensity=0.25 - The intensity of the bloom effect. + * @property {number} bloomThreshold=0.7 - The threshold for the bloom effect. + * @property {number} bloomSize=0.9 - The size of the bloom effect. + */ +class BloomPropertyGroup : public PropertyGroup { +public: + // EntityItemProperty related helpers + virtual void copyToScriptValue(const EntityPropertyFlags& desiredProperties, QScriptValue& properties, + QScriptEngine* engine, bool skipDefaults, + EntityItemProperties& defaultEntityProperties) const override; + virtual void copyFromScriptValue(const QScriptValue& object, bool& _defaultSettings) override; + + void merge(const BloomPropertyGroup& other); + + virtual void debugDump() const override; + virtual void listChangedProperties(QList& out) override; + + virtual bool appendToEditPacket(OctreePacketData* packetData, + EntityPropertyFlags& requestedProperties, + EntityPropertyFlags& propertyFlags, + EntityPropertyFlags& propertiesDidntFit, + int& propertyCount, + OctreeElement::AppendState& appendState) const override; + + virtual bool decodeFromEditPacket(EntityPropertyFlags& propertyFlags, + const unsigned char*& dataAt, int& processedBytes) override; + virtual void markAllChanged() override; + virtual EntityPropertyFlags getChangedProperties() const override; + + // EntityItem related helpers + // methods for getting/setting all properties of an entity + virtual void getProperties(EntityItemProperties& propertiesOut) const override; + + /// returns true if something changed + virtual bool setProperties(const EntityItemProperties& properties) override; + + virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const override; + + virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, + EntityTreeElementExtraEncodeDataPointer entityTreeElementExtraEncodeData, + EntityPropertyFlags& requestedProperties, + EntityPropertyFlags& propertyFlags, + EntityPropertyFlags& propertiesDidntFit, + int& propertyCount, + OctreeElement::AppendState& appendState) const override; + + virtual int readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, + ReadBitstreamToTreeParams& args, + EntityPropertyFlags& propertyFlags, bool overwriteLocalData, + bool& somethingChanged) override; + + DEFINE_PROPERTY(PROP_BLOOM_INTENSITY, BloomIntensity, bloomIntensity, float, INITIAL_BLOOM_INTENSITY); + DEFINE_PROPERTY(PROP_BLOOM_THRESHOLD, BloomThreshold, bloomThreshold, float, INITIAL_BLOOM_THRESHOLD); + DEFINE_PROPERTY(PROP_BLOOM_SIZE, BloomSize, bloomSize, float, INITIAL_BLOOM_SIZE); + +}; + +#endif // hifi_BloomPropertyGroup_h diff --git a/libraries/entities/src/EntityEditPacketSender.cpp b/libraries/entities/src/EntityEditPacketSender.cpp index 9ca102d016..4aa66db227 100644 --- a/libraries/entities/src/EntityEditPacketSender.cpp +++ b/libraries/entities/src/EntityEditPacketSender.cpp @@ -84,9 +84,15 @@ void EntityEditPacketSender::queueEditEntityMessage(PacketType type, EntityTreePointer entityTree, EntityItemID entityItemID, const EntityItemProperties& properties) { - if (properties.getClientOnly() && properties.getOwningAvatarID() == _myAvatar->getID()) { - // this is an avatar-based entity --> update our avatar-data rather than sending to the entity-server - queueEditAvatarEntityMessage(type, entityTree, entityItemID, properties); + if (properties.getClientOnly()) { + if (!_myAvatar) { + qCWarning(entities) << "Suppressing entity edit message: cannot send clientOnly edit with no myAvatar"; + } else if (properties.getOwningAvatarID() == _myAvatar->getID()) { + // this is an avatar-based entity --> update our avatar-data rather than sending to the entity-server + queueEditAvatarEntityMessage(type, entityTree, entityItemID, properties); + } else { + qCWarning(entities) << "Suppressing entity edit message: cannot send clientOnly edit for another avatar"; + } return; } @@ -143,11 +149,6 @@ void EntityEditPacketSender::queueEditEntityMessage(PacketType type, void EntityEditPacketSender::queueEraseEntityMessage(const EntityItemID& entityItemID) { - // in case this was a clientOnly entity: - if(_myAvatar) { - _myAvatar->clearAvatarEntity(entityItemID); - } - QByteArray bufferOut(NLPacket::maxPayloadSize(PacketType::EntityErase), 0); if (EntityItemProperties::encodeEraseEntityMessage(entityItemID, bufferOut)) { diff --git a/libraries/entities/src/EntityEditPacketSender.h b/libraries/entities/src/EntityEditPacketSender.h index 31f91707b8..9bf9095f7f 100644 --- a/libraries/entities/src/EntityEditPacketSender.h +++ b/libraries/entities/src/EntityEditPacketSender.h @@ -27,7 +27,6 @@ public: void setMyAvatar(AvatarData* myAvatar) { _myAvatar = myAvatar; } AvatarData* getMyAvatar() { return _myAvatar; } - void clearAvatarEntity(QUuid entityID) { assert(_myAvatar); _myAvatar->clearAvatarEntity(entityID); } /// Queues an array of several voxel edit messages. Will potentially send a pending multi-command packet. Determines /// which voxel-server node or nodes the packet should be sent to. Can be called even before voxel servers are known, in diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index 3a11fd821a..47ae8de9ad 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -159,11 +159,15 @@ public: virtual void debugDump() const; - virtual bool supportsDetailedRayIntersection() const { return false; } + virtual bool supportsDetailedIntersection() const { return false; } virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) const { return true; } + virtual bool findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, + const glm::vec3& acceleration, OctreeElementPointer& element, float& parabolicDistance, + BoxFace& face, glm::vec3& surfaceNormal, + QVariantMap& extraInfo, bool precisionPicking) const { return true; } // attributes applicable to all entity types EntityTypes::EntityType getType() const { return _type; } @@ -378,8 +382,6 @@ public: /// return preferred shape type (actual physical shape may differ) virtual ShapeType getShapeType() const { return SHAPE_TYPE_NONE; } - virtual void setCollisionShape(const btCollisionShape* shape) {} - void setPosition(const glm::vec3& value); virtual void setParentID(const QUuid& parentID) override; virtual void setShapeType(ShapeType type) { /* do nothing */ } diff --git a/libraries/entities/src/EntityItemID.cpp b/libraries/entities/src/EntityItemID.cpp index 3b4ca1cea0..28b8e109ca 100644 --- a/libraries/entities/src/EntityItemID.cpp +++ b/libraries/entities/src/EntityItemID.cpp @@ -69,3 +69,4 @@ QVector qVectorEntityItemIDFromScriptValue(const QScriptValue& arr return newVector; } +size_t std::hash::operator()(const EntityItemID& id) const { return qHash(id); } diff --git a/libraries/entities/src/EntityItemID.h b/libraries/entities/src/EntityItemID.h index 41a11147f8..c9ffa13941 100644 --- a/libraries/entities/src/EntityItemID.h +++ b/libraries/entities/src/EntityItemID.h @@ -45,4 +45,7 @@ QScriptValue EntityItemIDtoScriptValue(QScriptEngine* engine, const EntityItemID void EntityItemIDfromScriptValue(const QScriptValue &object, EntityItemID& properties); QVector qVectorEntityItemIDFromScriptValue(const QScriptValue& array); +// Allow the use of std::unordered_map with QUuid keys +namespace std { template<> struct hash { size_t operator()(const EntityItemID& id) const; }; } + #endif // hifi_EntityItemID_h diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index efd2376677..dc29d4bb2a 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -37,6 +37,7 @@ AnimationPropertyGroup EntityItemProperties::_staticAnimation; SkyboxPropertyGroup EntityItemProperties::_staticSkybox; HazePropertyGroup EntityItemProperties::_staticHaze; +BloomPropertyGroup EntityItemProperties::_staticBloom; KeyLightPropertyGroup EntityItemProperties::_staticKeyLight; AmbientLightPropertyGroup EntityItemProperties::_staticAmbientLight; @@ -84,6 +85,7 @@ void EntityItemProperties::debugDump() const { getHaze().debugDump(); getKeyLight().debugDump(); getAmbientLight().debugDump(); + getBloom().debugDump(); qCDebug(entities) << " changed properties..."; EntityPropertyFlags props = getChangedProperties(); @@ -211,6 +213,10 @@ QString EntityItemProperties::getHazeModeAsString() const { return getComponentModeAsString(_hazeMode); } +QString EntityItemProperties::getBloomModeAsString() const { + return getComponentModeAsString(_bloomMode); +} + QString EntityItemProperties::getComponentModeString(uint32_t mode) { // return "inherit" if mode is not valid if (mode < COMPONENT_MODE_ITEM_COUNT) { @@ -235,6 +241,15 @@ void EntityItemProperties::setHazeModeFromString(const QString& hazeMode) { } } +void EntityItemProperties::setBloomModeFromString(const QString& bloomMode) { + auto result = findComponent(bloomMode); + + if (result != COMPONENT_MODES.end()) { + _bloomMode = result->first; + _bloomModeChanged = true; + } +} + QString EntityItemProperties::getKeyLightModeAsString() const { return getComponentModeAsString(_keyLightMode); } @@ -369,6 +384,11 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { CHECK_PROPERTY_CHANGE(PROP_MATERIAL_MAPPING_ROT, materialMappingRot); CHECK_PROPERTY_CHANGE(PROP_MATERIAL_DATA, materialData); CHECK_PROPERTY_CHANGE(PROP_VISIBLE_IN_SECONDARY_CAMERA, isVisibleInSecondaryCamera); + CHECK_PROPERTY_CHANGE(PROP_PARTICLE_SPIN, particleSpin); + CHECK_PROPERTY_CHANGE(PROP_SPIN_SPREAD, spinSpread); + CHECK_PROPERTY_CHANGE(PROP_SPIN_START, spinStart); + CHECK_PROPERTY_CHANGE(PROP_SPIN_FINISH, spinFinish); + CHECK_PROPERTY_CHANGE(PROP_PARTICLE_ROTATE_WITH_ENTITY, rotateWithEntity); // Certifiable Properties CHECK_PROPERTY_CHANGE(PROP_ITEM_NAME, itemName); @@ -389,6 +409,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { CHECK_PROPERTY_CHANGE(PROP_KEY_LIGHT_MODE, keyLightMode); CHECK_PROPERTY_CHANGE(PROP_AMBIENT_LIGHT_MODE, ambientLightMode); CHECK_PROPERTY_CHANGE(PROP_SKYBOX_MODE, skyboxMode); + CHECK_PROPERTY_CHANGE(PROP_BLOOM_MODE, bloomMode); CHECK_PROPERTY_CHANGE(PROP_SOURCE_URL, sourceUrl); CHECK_PROPERTY_CHANGE(PROP_VOXEL_VOLUME_SIZE, voxelVolumeSize); @@ -449,6 +470,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { changedProperties += _ambientLight.getChangedProperties(); changedProperties += _skybox.getChangedProperties(); changedProperties += _haze.getChangedProperties(); + changedProperties += _bloom.getChangedProperties(); return changedProperties; } @@ -886,28 +908,39 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { * @property {string} textures="" - The URL of a JPG or PNG image file to display for each particle. If you want transparency, * use PNG format. * @property {number} particleRadius=0.025 - The radius of each particle at the middle of its life. - * @property {number} radiusStart=NAN - The radius of each particle at the start of its life. If NAN, the + * @property {number} radiusStart=NaN - The radius of each particle at the start of its life. If NaN, the * particleRadius value is used. - * @property {number} radiusFinish=NAN - The radius of each particle at the end of its life. If NAN, the + * @property {number} radiusFinish=NaN - The radius of each particle at the end of its life. If NaN, the * particleRadius value is used. * @property {number} radiusSpread=0 - The spread in radius that each particle is given. If particleRadius == 0.5 - * and radiusSpread == 0.25, each particle will have a radius in the range 0.250.75. + * and radiusSpread == 0.25, each particle will have a radius in the range 0.25 – + * 0.75. * @property {Color} color=255,255,255 - The color of each particle at the middle of its life. - * @property {Color} colorStart=NAN,NAN,NAN - The color of each particle at the start of its life. If any of the values are NAN, the - * color value is used. - * @property {Color} colorFinish=NAN,NAN,NAN - The color of each particle at the end of its life. If any of the values are NAN, the - * color value is used. + * @property {Color} colorStart={} - The color of each particle at the start of its life. If any of the component values are + * undefined, the color value is used. + * @property {Color} colorFinish={} - The color of each particle at the end of its life. If any of the component values are + * undefined, the color value is used. * @property {Color} colorSpread=0,0,0 - The spread in color that each particle is given. If * color == {red: 100, green: 100, blue: 100} and colorSpread == - * {red: 10, green: 25, blue: 50}, each particle will have an acceleration in the range {red: 90, green: 75, blue: 50} - * – {red: 110, green: 125, blue: 150}. + * {red: 10, green: 25, blue: 50}, each particle will have a color in the range + * {red: 90, green: 75, blue: 50}{red: 110, green: 125, blue: 150}. * @property {number} alpha=1 - The alpha of each particle at the middle of its life. - * @property {number} alphaStart=NAN - The alpha of each particle at the start of its life. If NAN, the + * @property {number} alphaStart=NaN - The alpha of each particle at the start of its life. If NaN, the * alpha value is used. - * @property {number} alphaFinish=NAN - The alpha of each particle at the end of its life. If NAN, the + * @property {number} alphaFinish=NaN - The alpha of each particle at the end of its life. If NaN, the * alpha value is used. * @property {number} alphaSpread=0 - The spread in alpha that each particle is given. If alpha == 0.5 - * and alphaSpread == 0.25, each particle will have an alpha in the range 0.250.75. + * and alphaSpread == 0.25, each particle will have an alpha in the range 0.25 – + * 0.75. + * @property {number} particleSpin=0 - The spin of each particle at the middle of its life. In the range -2*PI2*PI. + * @property {number} spinStart=NaN - The spin of each particle at the start of its life. In the range -2*PI2*PI. + * If NaN, the particleSpin value is used. + * @property {number} spinFinish=NaN - The spin of each particle at the end of its life. In the range -2*PI2*PI. + * If NaN, the particleSpin value is used. + * @property {number} spinSpread=0 - The spread in spin that each particle is given. In the range 02*PI. If particleSpin == PI + * and spinSpread == PI/2, each particle will have a spin in the range PI/23*PI/2. + * @property {boolean} rotateWithEntity=false - Whether or not the particles' spin will rotate with the entity. If false, when particleSpin == 0, the particles will point + * up in the world. If true, they will point towards the entity's up vector, based on its orientation. * * @property {ShapeType} shapeType="none" - Currently not used. Read-only. * @@ -1148,6 +1181,12 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { * "enabled": The haze properties of this zone are enabled, overriding the haze from any enclosing zone. * @property {Entities.Haze} haze - The haze properties of the zone. * + * @property {string} bloomMode="inherit" - Configures the bloom in the zone. Possible values:
+ * "inherit": The bloom from any enclosing zone continues into this zone.
+ * "disabled": The bloom from any enclosing zone and the bloom of this zone are disabled in this zone.
+ * "enabled": The bloom properties of this zone are enabled, overriding the bloom from any enclosing zone. + * @property {Entities.Bloom} bloom - The bloom properties of the zone. + * * @property {boolean} flyingAllowed=true - If true then visitors can fly in the zone; otherwise they cannot. * @property {boolean} ghostingAllowed=true - If true then visitors with avatar collisions turned off will not * collide with content in the zone; otherwise visitors will always collide with content in the zone. @@ -1291,6 +1330,11 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ALPHA_START, alphaStart); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ALPHA_FINISH, alphaFinish); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_EMITTER_SHOULD_TRAIL, emitterShouldTrail); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_PARTICLE_SPIN, particleSpin); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_SPIN_SPREAD, spinSpread); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_SPIN_START, spinStart); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_SPIN_FINISH, spinFinish); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_PARTICLE_ROTATE_WITH_ENTITY, rotateWithEntity); } // Models only @@ -1361,6 +1405,9 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_HAZE_MODE, hazeMode, getHazeModeAsString()); _haze.copyToScriptValue(_desiredProperties, properties, engine, skipDefaults, defaultEntityProperties); + COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_BLOOM_MODE, bloomMode, getBloomModeAsString()); + _bloom.copyToScriptValue(_desiredProperties, properties, engine, skipDefaults, defaultEntityProperties); + COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_KEY_LIGHT_MODE, keyLightMode, getKeyLightModeAsString()); COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_AMBIENT_LIGHT_MODE, ambientLightMode, getAmbientLightModeAsString()); COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_SKYBOX_MODE, skyboxMode, getSkyboxModeAsString()); @@ -1583,6 +1630,11 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool COPY_PROPERTY_FROM_QSCRIPTVALUE(materialMappingRot, float, setMaterialMappingRot); COPY_PROPERTY_FROM_QSCRIPTVALUE(materialData, QString, setMaterialData); COPY_PROPERTY_FROM_QSCRIPTVALUE(isVisibleInSecondaryCamera, bool, setIsVisibleInSecondaryCamera); + COPY_PROPERTY_FROM_QSCRIPTVALUE(particleSpin, float, setParticleSpin); + COPY_PROPERTY_FROM_QSCRIPTVALUE(spinSpread, float, setSpinSpread); + COPY_PROPERTY_FROM_QSCRIPTVALUE(spinStart, float, setSpinStart); + COPY_PROPERTY_FROM_QSCRIPTVALUE(spinFinish, float, setSpinFinish); + COPY_PROPERTY_FROM_QSCRIPTVALUE(rotateWithEntity, bool, setRotateWithEntity); // Certifiable Properties COPY_PROPERTY_FROM_QSCRIPTVALUE(itemName, QString, setItemName); @@ -1604,6 +1656,7 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(keyLightMode, KeyLightMode); COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(ambientLightMode, AmbientLightMode); COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(skyboxMode, SkyboxMode); + COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(bloomMode, BloomMode); COPY_PROPERTY_FROM_QSCRIPTVALUE(sourceUrl, QString, setSourceUrl); COPY_PROPERTY_FROM_QSCRIPTVALUE(voxelVolumeSize, vec3, setVoxelVolumeSize); @@ -1636,6 +1689,7 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool _ambientLight.copyFromScriptValue(object, _defaultSettings); _skybox.copyFromScriptValue(object, _defaultSettings); _haze.copyFromScriptValue(object, _defaultSettings); + _bloom.copyFromScriptValue(object, _defaultSettings); COPY_PROPERTY_FROM_QSCRIPTVALUE(xTextureURL, QString, setXTextureURL); COPY_PROPERTY_FROM_QSCRIPTVALUE(yTextureURL, QString, setYTextureURL); @@ -1751,6 +1805,11 @@ void EntityItemProperties::merge(const EntityItemProperties& other) { COPY_PROPERTY_IF_CHANGED(radiusSpread); COPY_PROPERTY_IF_CHANGED(radiusStart); COPY_PROPERTY_IF_CHANGED(radiusFinish); + COPY_PROPERTY_IF_CHANGED(particleSpin); + COPY_PROPERTY_IF_CHANGED(spinSpread); + COPY_PROPERTY_IF_CHANGED(spinStart); + COPY_PROPERTY_IF_CHANGED(spinFinish); + COPY_PROPERTY_IF_CHANGED(rotateWithEntity); // Certifiable Properties COPY_PROPERTY_IF_CHANGED(itemName); @@ -1772,6 +1831,7 @@ void EntityItemProperties::merge(const EntityItemProperties& other) { COPY_PROPERTY_IF_CHANGED(keyLightMode); COPY_PROPERTY_IF_CHANGED(ambientLightMode); COPY_PROPERTY_IF_CHANGED(skyboxMode); + COPY_PROPERTY_IF_CHANGED(bloomMode); COPY_PROPERTY_IF_CHANGED(sourceUrl); COPY_PROPERTY_IF_CHANGED(voxelVolumeSize); @@ -1794,6 +1854,7 @@ void EntityItemProperties::merge(const EntityItemProperties& other) { _ambientLight.merge(other._ambientLight); _skybox.merge(other._skybox); _haze.merge(other._haze); + _bloom.merge(other._bloom); COPY_PROPERTY_IF_CHANGED(xTextureURL); COPY_PROPERTY_IF_CHANGED(yTextureURL); @@ -1964,6 +2025,12 @@ void EntityItemProperties::entityPropertyFlagsFromScriptValue(const QScriptValue ADD_PROPERTY_TO_MAP(PROP_VISIBLE_IN_SECONDARY_CAMERA, IsVisibleInSecondaryCamera, isVisibleInSecondaryCamera, bool); + ADD_PROPERTY_TO_MAP(PROP_PARTICLE_SPIN, ParticleSpin, particleSpin, float); + ADD_PROPERTY_TO_MAP(PROP_SPIN_SPREAD, SpinSpread, spinSpread, float); + ADD_PROPERTY_TO_MAP(PROP_SPIN_START, SpinStart, spinStart, float); + ADD_PROPERTY_TO_MAP(PROP_SPIN_FINISH, SpinFinish, spinFinish, float); + ADD_PROPERTY_TO_MAP(PROP_PARTICLE_ROTATE_WITH_ENTITY, RotateWithEntity, rotateWithEntity, float); + // Certifiable Properties ADD_PROPERTY_TO_MAP(PROP_ITEM_NAME, ItemName, itemName, QString); ADD_PROPERTY_TO_MAP(PROP_ITEM_DESCRIPTION, ItemDescription, itemDescription, QString); @@ -2059,6 +2126,11 @@ void EntityItemProperties::entityPropertyFlagsFromScriptValue(const QScriptValue ADD_GROUP_PROPERTY_TO_MAP(PROP_HAZE_KEYLIGHT_RANGE, Haze, haze, HazeKeyLightRange, hazeKeyLightRange); ADD_GROUP_PROPERTY_TO_MAP(PROP_HAZE_KEYLIGHT_ALTITUDE, Haze, haze, HazeKeyLightAltitude, hazeKeyLightAltitude); + ADD_PROPERTY_TO_MAP(PROP_BLOOM_MODE, BloomMode, bloomMode, uint32_t); + ADD_GROUP_PROPERTY_TO_MAP(PROP_BLOOM_INTENSITY, Bloom, bloom, BloomIntensity, bloomIntensity); + ADD_GROUP_PROPERTY_TO_MAP(PROP_BLOOM_THRESHOLD, Bloom, bloom, BloomThreshold, bloomThreshold); + ADD_GROUP_PROPERTY_TO_MAP(PROP_BLOOM_SIZE, Bloom, bloom, BloomSize, bloomSize); + ADD_PROPERTY_TO_MAP(PROP_KEY_LIGHT_MODE, KeyLightMode, keyLightMode, uint32_t); ADD_PROPERTY_TO_MAP(PROP_AMBIENT_LIGHT_MODE, AmbientLightMode, ambientLightMode, uint32_t); ADD_PROPERTY_TO_MAP(PROP_SKYBOX_MODE, SkyboxMode, skyboxMode, uint32_t); @@ -2292,6 +2364,11 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy APPEND_ENTITY_PROPERTY(PROP_ALPHA_START, properties.getAlphaStart()); APPEND_ENTITY_PROPERTY(PROP_ALPHA_FINISH, properties.getAlphaFinish()); APPEND_ENTITY_PROPERTY(PROP_EMITTER_SHOULD_TRAIL, properties.getEmitterShouldTrail()); + APPEND_ENTITY_PROPERTY(PROP_PARTICLE_SPIN, properties.getParticleSpin()); + APPEND_ENTITY_PROPERTY(PROP_SPIN_SPREAD, properties.getSpinSpread()); + APPEND_ENTITY_PROPERTY(PROP_SPIN_START, properties.getSpinStart()); + APPEND_ENTITY_PROPERTY(PROP_SPIN_FINISH, properties.getSpinFinish()); + APPEND_ENTITY_PROPERTY(PROP_PARTICLE_ROTATE_WITH_ENTITY, properties.getRotateWithEntity()) } if (properties.getType() == EntityTypes::Zone) { @@ -2315,6 +2392,10 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy _staticHaze.setProperties(properties); _staticHaze.appendToEditPacket(packetData, requestedProperties, propertyFlags, propertiesDidntFit, propertyCount, appendState); + APPEND_ENTITY_PROPERTY(PROP_BLOOM_MODE, (uint32_t)properties.getBloomMode()); + _staticBloom.setProperties(properties); + _staticBloom.appendToEditPacket(packetData, requestedProperties, propertyFlags, propertiesDidntFit, propertyCount, appendState); + APPEND_ENTITY_PROPERTY(PROP_KEY_LIGHT_MODE, (uint32_t)properties.getKeyLightMode()); APPEND_ENTITY_PROPERTY(PROP_AMBIENT_LIGHT_MODE, (uint32_t)properties.getAmbientLightMode()); APPEND_ENTITY_PROPERTY(PROP_SKYBOX_MODE, (uint32_t)properties.getSkyboxMode()); @@ -2667,6 +2748,11 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ALPHA_START, float, setAlphaStart); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ALPHA_FINISH, float, setAlphaFinish); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_EMITTER_SHOULD_TRAIL, bool, setEmitterShouldTrail); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_PARTICLE_SPIN, float, setParticleSpin); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SPIN_SPREAD, float, setSpinSpread); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SPIN_START, float, setSpinStart); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SPIN_FINISH, float, setSpinFinish); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_PARTICLE_ROTATE_WITH_ENTITY, bool, setRotateWithEntity); } if (properties.getType() == EntityTypes::Zone) { @@ -2684,6 +2770,9 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_HAZE_MODE, uint32_t, setHazeMode); properties.getHaze().decodeFromEditPacket(propertyFlags, dataAt, processedBytes); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_BLOOM_MODE, uint32_t, setBloomMode); + properties.getBloom().decodeFromEditPacket(propertyFlags, dataAt, processedBytes); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_KEY_LIGHT_MODE, uint32_t, setKeyLightMode); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_AMBIENT_LIGHT_MODE, uint32_t, setAmbientLightMode); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SKYBOX_MODE, uint32_t, setSkyboxMode); @@ -2936,7 +3025,7 @@ void EntityItemProperties::markAllChanged() { _shapeTypeChanged = true; _isEmittingChanged = true; - _emitterShouldTrail = true; + _emitterShouldTrailChanged = true; _maxParticlesChanged = true; _lifespanChanged = true; _emitRateChanged = true; @@ -2961,6 +3050,11 @@ void EntityItemProperties::markAllChanged() { _colorFinishChanged = true; _alphaStartChanged = true; _alphaFinishChanged = true; + _particleSpinChanged = true; + _spinStartChanged = true; + _spinFinishChanged = true; + _spinSpreadChanged = true; + _rotateWithEntityChanged = true; _materialURLChanged = true; _materialMappingModeChanged = true; @@ -2992,10 +3086,12 @@ void EntityItemProperties::markAllChanged() { _skyboxModeChanged = true; _ambientLightModeChanged = true; _hazeModeChanged = true; + _bloomModeChanged = true; _animation.markAllChanged(); _skybox.markAllChanged(); _haze.markAllChanged(); + _bloom.markAllChanged(); _sourceUrlChanged = true; _voxelVolumeSizeChanged = true; @@ -3309,6 +3405,21 @@ QList EntityItemProperties::listChangedProperties() { if (radiusFinishChanged()) { out += "radiusFinish"; } + if (particleSpinChanged()) { + out += "particleSpin"; + } + if (spinSpreadChanged()) { + out += "spinSpread"; + } + if (spinStartChanged()) { + out += "spinStart"; + } + if (spinFinishChanged()) { + out += "spinFinish"; + } + if (rotateWithEntityChanged()) { + out += "rotateWithEntity"; + } if (materialURLChanged()) { out += "materialURL"; } @@ -3375,15 +3486,15 @@ QList EntityItemProperties::listChangedProperties() { if (hazeModeChanged()) { out += "hazeMode"; } - + if (bloomModeChanged()) { + out += "bloomMode"; + } if (keyLightModeChanged()) { out += "keyLightMode"; } - if (ambientLightModeChanged()) { out += "ambientLightMode"; } - if (skyboxModeChanged()) { out += "skyboxMode"; } @@ -3514,6 +3625,7 @@ QList EntityItemProperties::listChangedProperties() { getAmbientLight().listChangedProperties(out); getSkybox().listChangedProperties(out); getHaze().listChangedProperties(out); + getBloom().listChangedProperties(out); return out; } @@ -3676,6 +3788,8 @@ bool EntityItemProperties::verifyStaticCertificateProperties() { void EntityItemProperties::convertToCloneProperties(const EntityItemID& entityIDToClone) { setName(getName() + "-clone-" + entityIDToClone.toString()); setLocked(false); + setParentID(QUuid()); + setParentJointIndex(-1); setLifetime(getCloneLifetime()); setDynamic(getCloneDynamic()); setClientOnly(getCloneAvatarEntity()); diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index 93b8c991d5..50305345de 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -42,6 +42,7 @@ #include "SimulationOwner.h" #include "SkyboxPropertyGroup.h" #include "HazePropertyGroup.h" +#include "BloomPropertyGroup.h" #include "TextEntityItem.h" #include "ZoneEntityItem.h" @@ -195,9 +196,11 @@ public: DEFINE_PROPERTY_REF_ENUM(PROP_SKYBOX_MODE, SkyboxMode, skyboxMode, uint32_t, (uint32_t)COMPONENT_MODE_INHERIT); DEFINE_PROPERTY_REF_ENUM(PROP_AMBIENT_LIGHT_MODE, AmbientLightMode, ambientLightMode, uint32_t, (uint32_t)COMPONENT_MODE_INHERIT); DEFINE_PROPERTY_REF_ENUM(PROP_HAZE_MODE, HazeMode, hazeMode, uint32_t, (uint32_t)COMPONENT_MODE_INHERIT); + DEFINE_PROPERTY_REF_ENUM(PROP_BLOOM_MODE, BloomMode, bloomMode, uint32_t, (uint32_t)COMPONENT_MODE_INHERIT); DEFINE_PROPERTY_GROUP(Skybox, skybox, SkyboxPropertyGroup); DEFINE_PROPERTY_GROUP(Haze, haze, HazePropertyGroup); + DEFINE_PROPERTY_GROUP(Bloom, bloom, BloomPropertyGroup); DEFINE_PROPERTY_GROUP(Animation, animation, AnimationPropertyGroup); DEFINE_PROPERTY_REF(PROP_SOURCE_URL, SourceUrl, sourceUrl, QString, ""); DEFINE_PROPERTY(PROP_LINE_WIDTH, LineWidth, lineWidth, float, LineEntityItem::DEFAULT_LINE_WIDTH); @@ -235,6 +238,12 @@ public: DEFINE_PROPERTY(PROP_VISIBLE_IN_SECONDARY_CAMERA, IsVisibleInSecondaryCamera, isVisibleInSecondaryCamera, bool, ENTITY_ITEM_DEFAULT_VISIBLE_IN_SECONDARY_CAMERA); + DEFINE_PROPERTY(PROP_PARTICLE_SPIN, ParticleSpin, particleSpin, float, particle::DEFAULT_PARTICLE_SPIN); + DEFINE_PROPERTY(PROP_SPIN_SPREAD, SpinSpread, spinSpread, float, particle::DEFAULT_SPIN_SPREAD); + DEFINE_PROPERTY(PROP_SPIN_START, SpinStart, spinStart, float, particle::DEFAULT_SPIN_START); + DEFINE_PROPERTY(PROP_SPIN_FINISH, SpinFinish, spinFinish, float, particle::DEFAULT_SPIN_FINISH); + DEFINE_PROPERTY(PROP_PARTICLE_ROTATE_WITH_ENTITY, RotateWithEntity, rotateWithEntity, bool, particle::DEFAULT_ROTATE_WITH_ENTITY); + // Certifiable Properties - related to Proof of Purchase certificates DEFINE_PROPERTY_REF(PROP_ITEM_NAME, ItemName, itemName, QString, ENTITY_ITEM_DEFAULT_ITEM_NAME); DEFINE_PROPERTY_REF(PROP_ITEM_DESCRIPTION, ItemDescription, itemDescription, QString, ENTITY_ITEM_DEFAULT_ITEM_DESCRIPTION); @@ -527,6 +536,7 @@ inline QDebug operator<<(QDebug debug, const EntityItemProperties& properties) { DEBUG_PROPERTY_IF_CHANGED(debug, properties, KeyLightMode, keyLightMode, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, AmbientLightMode, ambientLightMode, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, SkyboxMode, skyboxMode, ""); + DEBUG_PROPERTY_IF_CHANGED(debug, properties, BloomMode, bloomMode, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, Cloneable, cloneable, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, CloneLifetime, cloneLifetime, ""); diff --git a/libraries/entities/src/EntityPropertyFlags.h b/libraries/entities/src/EntityPropertyFlags.h index d43a991f22..3932730661 100644 --- a/libraries/entities/src/EntityPropertyFlags.h +++ b/libraries/entities/src/EntityPropertyFlags.h @@ -251,6 +251,17 @@ enum EntityPropertyList { PROP_VISIBLE_IN_SECONDARY_CAMERA, // not sent over the wire, only used locally + PROP_PARTICLE_SPIN, + PROP_SPIN_START, + PROP_SPIN_FINISH, + PROP_SPIN_SPREAD, + PROP_PARTICLE_ROTATE_WITH_ENTITY, + + PROP_BLOOM_MODE, + PROP_BLOOM_INTENSITY, + PROP_BLOOM_THRESHOLD, + PROP_BLOOM_SIZE, + //////////////////////////////////////////////////////////////////////////////////////////////////// // ATTENTION: add new properties to end of list just ABOVE this line PROP_AFTER_LAST_ITEM, diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index 09d9823728..d9924cb9fd 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -38,6 +38,8 @@ #include #include +const QString GRABBABLE_USER_DATA = "{\"grabbableKey\":{\"grabbable\":true}}"; +const QString NOT_GRABBABLE_USER_DATA = "{\"grabbableKey\":{\"grabbable\":false}}"; EntityScriptingInterface::EntityScriptingInterface(bool bidOnSimulationOwnership) : _entityTree(NULL), @@ -303,7 +305,7 @@ bool EntityScriptingInterface::addLocalEntityCopy(EntityItemProperties& properti } QUuid EntityScriptingInterface::addModelEntity(const QString& name, const QString& modelUrl, const QString& textures, - const QString& shapeType, bool dynamic, bool collisionless, + const QString& shapeType, bool dynamic, bool collisionless, bool grabbable, const glm::vec3& position, const glm::vec3& gravity) { _activityTracking.addedEntityCount++; @@ -314,6 +316,7 @@ QUuid EntityScriptingInterface::addModelEntity(const QString& name, const QStrin properties.setShapeTypeFromString(shapeType); properties.setDynamic(dynamic); properties.setCollisionless(collisionless); + properties.setUserData(grabbable ? GRABBABLE_USER_DATA : NOT_GRABBABLE_USER_DATA); properties.setPosition(position); properties.setGravity(gravity); if (!textures.isEmpty()) { @@ -571,7 +574,7 @@ void EntityScriptingInterface::deleteEntity(QUuid id) { _activityTracking.deletedEntityCount++; EntityItemID entityID(id); - bool shouldDelete = true; + bool shouldSendDeleteToServer = true; // If we have a local entity tree set, then also update it. if (_entityTree) { @@ -588,16 +591,21 @@ void EntityScriptingInterface::deleteEntity(QUuid id) { auto avatarHashMap = DependencyManager::get(); AvatarSharedPointer myAvatar = avatarHashMap->getAvatarBySessionID(myNodeID); myAvatar->insertDetachedEntityID(id); - shouldDelete = false; + shouldSendDeleteToServer = false; return; } if (entity->getLocked()) { - shouldDelete = false; + shouldSendDeleteToServer = false; } else { // only delete local entities, server entities will round trip through the server filters if (entity->getClientOnly() || _entityTree->isServerlessMode()) { + shouldSendDeleteToServer = false; _entityTree->deleteEntity(entityID); + + if (entity->getClientOnly() && getEntityPacketSender()->getMyAvatar()) { + getEntityPacketSender()->getMyAvatar()->clearAvatarEntity(entityID, false); + } } } } @@ -605,7 +613,7 @@ void EntityScriptingInterface::deleteEntity(QUuid id) { } // if at this point, we know the id, and we should still delete the entity, send the update to the entity server - if (shouldDelete) { + if (shouldSendDeleteToServer) { getEntityPacketSender()->queueEraseEntityMessage(entityID); } } @@ -871,6 +879,30 @@ RayToEntityIntersectionResult EntityScriptingInterface::findRayIntersectionWorke return result; } +ParabolaToEntityIntersectionResult EntityScriptingInterface::findParabolaIntersectionVector(const PickParabola& parabola, bool precisionPicking, + const QVector& entityIdsToInclude, const QVector& entityIdsToDiscard, bool visibleOnly, bool collidableOnly) { + PROFILE_RANGE(script_entities, __FUNCTION__); + + return findParabolaIntersectionWorker(parabola, Octree::Lock, precisionPicking, entityIdsToInclude, entityIdsToDiscard, visibleOnly, collidableOnly); +} + +ParabolaToEntityIntersectionResult EntityScriptingInterface::findParabolaIntersectionWorker(const PickParabola& parabola, + Octree::lockType lockType, bool precisionPicking, const QVector& entityIdsToInclude, + const QVector& entityIdsToDiscard, bool visibleOnly, bool collidableOnly) { + + + ParabolaToEntityIntersectionResult result; + if (_entityTree) { + OctreeElementPointer element; + result.entityID = _entityTree->findParabolaIntersection(parabola, + entityIdsToInclude, entityIdsToDiscard, visibleOnly, collidableOnly, precisionPicking, + element, result.intersection, result.distance, result.parabolicDistance, result.face, result.surfaceNormal, + result.extraInfo, lockType, &result.accurate); + result.intersects = !result.entityID.isNull(); + } + return result; +} + bool EntityScriptingInterface::reloadServerScripts(QUuid entityID) { auto client = DependencyManager::get(); return client->reloadServerScript(entityID); @@ -1025,75 +1057,17 @@ bool EntityScriptingInterface::getDrawZoneBoundaries() const { return ZoneEntityItem::getDrawZoneBoundaries(); } -RayToEntityIntersectionResult::RayToEntityIntersectionResult() : - intersects(false), - accurate(true), // assume it's accurate - entityID(), - distance(0), - face() -{ -} - QScriptValue RayToEntityIntersectionResultToScriptValue(QScriptEngine* engine, const RayToEntityIntersectionResult& value) { - PROFILE_RANGE(script_entities, __FUNCTION__); - QScriptValue obj = engine->newObject(); obj.setProperty("intersects", value.intersects); obj.setProperty("accurate", value.accurate); QScriptValue entityItemValue = EntityItemIDtoScriptValue(engine, value.entityID); obj.setProperty("entityID", entityItemValue); - obj.setProperty("distance", value.distance); - - QString faceName = ""; - // handle BoxFace - /**jsdoc - *

A BoxFace specifies the face of an axis-aligned (AA) box. - * - * - * - * - * - * - * - * - * - * - * - * - * - *
ValueDescription
"MIN_X_FACE"The minimum x-axis face.
"MAX_X_FACE"The maximum x-axis face.
"MIN_Y_FACE"The minimum y-axis face.
"MAX_Y_FACE"The maximum y-axis face.
"MIN_Z_FACE"The minimum z-axis face.
"MAX_Z_FACE"The maximum z-axis face.
"UNKNOWN_FACE"Unknown value.
- * @typedef {string} BoxFace - */ - // FIXME: Move enum to string function to BoxBase.cpp. - switch (value.face) { - case MIN_X_FACE: - faceName = "MIN_X_FACE"; - break; - case MAX_X_FACE: - faceName = "MAX_X_FACE"; - break; - case MIN_Y_FACE: - faceName = "MIN_Y_FACE"; - break; - case MAX_Y_FACE: - faceName = "MAX_Y_FACE"; - break; - case MIN_Z_FACE: - faceName = "MIN_Z_FACE"; - break; - case MAX_Z_FACE: - faceName = "MAX_Z_FACE"; - break; - case UNKNOWN_FACE: - faceName = "UNKNOWN_FACE"; - break; - } - obj.setProperty("face", faceName); + obj.setProperty("face", boxFaceToString(value.face)); QScriptValue intersection = vec3toScriptValue(engine, value.intersection); obj.setProperty("intersection", intersection); - QScriptValue surfaceNormal = vec3toScriptValue(engine, value.surfaceNormal); obj.setProperty("surfaceNormal", surfaceNormal); obj.setProperty("extraInfo", engine->toScriptValue(value.extraInfo)); @@ -1101,29 +1075,13 @@ QScriptValue RayToEntityIntersectionResultToScriptValue(QScriptEngine* engine, c } void RayToEntityIntersectionResultFromScriptValue(const QScriptValue& object, RayToEntityIntersectionResult& value) { - PROFILE_RANGE(script_entities, __FUNCTION__); - value.intersects = object.property("intersects").toVariant().toBool(); value.accurate = object.property("accurate").toVariant().toBool(); QScriptValue entityIDValue = object.property("entityID"); - // EntityItemIDfromScriptValue(entityIDValue, value.entityID); quuidFromScriptValue(entityIDValue, value.entityID); value.distance = object.property("distance").toVariant().toFloat(); + value.face = boxFaceFromString(object.property("face").toVariant().toString()); - QString faceName = object.property("face").toVariant().toString(); - if (faceName == "MIN_X_FACE") { - value.face = MIN_X_FACE; - } else if (faceName == "MAX_X_FACE") { - value.face = MAX_X_FACE; - } else if (faceName == "MIN_Y_FACE") { - value.face = MIN_Y_FACE; - } else if (faceName == "MAX_Y_FACE") { - value.face = MAX_Y_FACE; - } else if (faceName == "MIN_Z_FACE") { - value.face = MIN_Z_FACE; - } else { - value.face = MAX_Z_FACE; - }; QScriptValue intersection = object.property("intersection"); if (intersection.isValid()) { vec3FromScriptValue(intersection, value.intersection); @@ -1299,7 +1257,7 @@ bool EntityScriptingInterface::actionWorker(const QUuid& entityID, EntityItemPointer entity; bool doTransmit = false; - _entityTree->withWriteLock([&] { + _entityTree->withWriteLock([this, &entity, entityID, myNodeID, &doTransmit, actor, &properties] { EntitySimulationPointer simulation = _entityTree->getSimulation(); entity = _entityTree->findEntityByEntityItemID(entityID); if (!entity) { diff --git a/libraries/entities/src/EntityScriptingInterface.h b/libraries/entities/src/EntityScriptingInterface.h index 50df825e5f..3e0f040fd6 100644 --- a/libraries/entities/src/EntityScriptingInterface.h +++ b/libraries/entities/src/EntityScriptingInterface.h @@ -39,6 +39,9 @@ class EntityTree; class MeshProxy; +extern const QString GRABBABLE_USER_DATA; +extern const QString NOT_GRABBABLE_USER_DATA; + // helper factory to compose standardized, async metadata queries for "magic" Entity properties // like .script and .serverScripts. This is used for automated testing of core scripting features // as well as to provide early adopters a self-discoverable, consistent way to diagnose common @@ -71,22 +74,31 @@ private: // "accurate" is currently always true because the ray intersection is always performed with an Octree::Lock. class RayToEntityIntersectionResult { public: - RayToEntityIntersectionResult(); - bool intersects; - bool accurate; + bool intersects { false }; + bool accurate { true }; QUuid entityID; - float distance; + float distance { 0.0f }; BoxFace face; glm::vec3 intersection; glm::vec3 surfaceNormal; QVariantMap extraInfo; }; - Q_DECLARE_METATYPE(RayToEntityIntersectionResult) - QScriptValue RayToEntityIntersectionResultToScriptValue(QScriptEngine* engine, const RayToEntityIntersectionResult& results); void RayToEntityIntersectionResultFromScriptValue(const QScriptValue& object, RayToEntityIntersectionResult& results); +class ParabolaToEntityIntersectionResult { +public: + bool intersects { false }; + bool accurate { true }; + QUuid entityID; + float distance { 0.0f }; + float parabolicDistance { 0.0f }; + BoxFace face; + glm::vec3 intersection; + glm::vec3 surfaceNormal; + QVariantMap extraInfo; +}; /**jsdoc * The Entities API provides facilities to create and interact with entities. Entities are 2D and 3D objects that are visible @@ -131,6 +143,12 @@ public: void resetActivityTracking(); ActivityTracking getActivityTracking() const { return _activityTracking; } + + // TODO: expose to script? + ParabolaToEntityIntersectionResult findParabolaIntersectionVector(const PickParabola& parabola, bool precisionPicking, + const QVector& entityIdsToInclude, const QVector& entityIdsToDiscard, + bool visibleOnly, bool collidableOnly); + public slots: /**jsdoc @@ -222,7 +240,7 @@ public slots: /// temporary method until addEntity can be used from QJSEngine /// Deliberately not adding jsdoc, only used internally. Q_INVOKABLE QUuid addModelEntity(const QString& name, const QString& modelUrl, const QString& textures, const QString& shapeType, bool dynamic, - bool collisionless, const glm::vec3& position, const glm::vec3& gravity); + bool collisionless, bool grabbable, const glm::vec3& position, const glm::vec3& gravity); /**jsdoc * Create a clone of an entity. A clone can be created by a client that doesn't have rez permissions in the current domain. @@ -1895,6 +1913,11 @@ private: bool precisionPicking, const QVector& entityIdsToInclude, const QVector& entityIdsToDiscard, bool visibleOnly = false, bool collidableOnly = false); + /// actually does the work of finding the parabola intersection, can be called in locking mode or tryLock mode + ParabolaToEntityIntersectionResult findParabolaIntersectionWorker(const PickParabola& parabola, Octree::lockType lockType, + bool precisionPicking, const QVector& entityIdsToInclude, const QVector& entityIdsToDiscard, + bool visibleOnly = false, bool collidableOnly = false); + EntityTreePointer _entityTree; std::recursive_mutex _entitiesScriptEngineLock; diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 0315ba7186..a7c88ddc7d 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -48,6 +48,7 @@ public: // Inputs glm::vec3 origin; glm::vec3 direction; + glm::vec3 invDirection; const QVector& entityIdsToInclude; const QVector& entityIdsToDiscard; bool visibleOnly; @@ -63,6 +64,27 @@ public: EntityItemID entityID; }; +class ParabolaArgs { +public: + // Inputs + glm::vec3 origin; + glm::vec3 velocity; + glm::vec3 acceleration; + const QVector& entityIdsToInclude; + const QVector& entityIdsToDiscard; + bool visibleOnly; + bool collidableOnly; + bool precisionPicking; + + // Outputs + OctreeElementPointer& element; + float& parabolicDistance; + BoxFace& face; + glm::vec3& surfaceNormal; + QVariantMap& extraInfo; + EntityItemID entityID; +}; + EntityTree::EntityTree(bool shouldReaverage) : Octree(shouldReaverage) @@ -804,29 +826,51 @@ bool findRayIntersectionOp(const OctreeElementPointer& element, void* extraData) RayArgs* args = static_cast(extraData); bool keepSearching = true; EntityTreeElementPointer entityTreeElementPointer = std::static_pointer_cast(element); - EntityItemID entityID = entityTreeElementPointer->findRayIntersection(args->origin, args->direction, keepSearching, + EntityItemID entityID = entityTreeElementPointer->findRayIntersection(args->origin, args->direction, args->element, args->distance, args->face, args->surfaceNormal, args->entityIdsToInclude, args->entityIdsToDiscard, args->visibleOnly, args->collidableOnly, args->extraInfo, args->precisionPicking); if (!entityID.isNull()) { args->entityID = entityID; + // We recurse OctreeElements in order, so if we hit something, we can stop immediately + keepSearching = false; } return keepSearching; } +float findRayIntersectionSortingOp(const OctreeElementPointer& element, void* extraData) { + RayArgs* args = static_cast(extraData); + EntityTreeElementPointer entityTreeElementPointer = std::static_pointer_cast(element); + float distance = FLT_MAX; + // If origin is inside the cube, always check this element first + if (entityTreeElementPointer->getAACube().contains(args->origin)) { + distance = 0.0f; + } else { + float boundDistance = FLT_MAX; + BoxFace face; + glm::vec3 surfaceNormal; + if (entityTreeElementPointer->getAACube().findRayIntersection(args->origin, args->direction, args->invDirection, boundDistance, face, surfaceNormal)) { + // Don't add this cell if it's already farther than our best distance so far + if (boundDistance < args->distance) { + distance = boundDistance; + } + } + } + return distance; +} + EntityItemID EntityTree::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, QVector entityIdsToInclude, QVector entityIdsToDiscard, bool visibleOnly, bool collidableOnly, bool precisionPicking, OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, Octree::lockType lockType, bool* accurateResult) { - RayArgs args = { origin, direction, entityIdsToInclude, entityIdsToDiscard, - visibleOnly, collidableOnly, precisionPicking, - element, distance, face, surfaceNormal, extraInfo, EntityItemID() }; + RayArgs args = { origin, direction, 1.0f / direction, entityIdsToInclude, entityIdsToDiscard, + visibleOnly, collidableOnly, precisionPicking, element, distance, face, surfaceNormal, extraInfo, EntityItemID() }; distance = FLT_MAX; bool requireLock = lockType == Octree::Lock; bool lockResult = withReadLock([&]{ - recurseTreeWithOperation(findRayIntersectionOp, &args); + recurseTreeWithOperationSorted(findRayIntersectionOp, findRayIntersectionSortingOp, &args); }, requireLock); if (accurateResult) { @@ -836,6 +880,70 @@ EntityItemID EntityTree::findRayIntersection(const glm::vec3& origin, const glm: return args.entityID; } +bool findParabolaIntersectionOp(const OctreeElementPointer& element, void* extraData) { + ParabolaArgs* args = static_cast(extraData); + bool keepSearching = true; + EntityTreeElementPointer entityTreeElementPointer = std::static_pointer_cast(element); + EntityItemID entityID = entityTreeElementPointer->findParabolaIntersection(args->origin, args->velocity, args->acceleration, + args->element, args->parabolicDistance, args->face, args->surfaceNormal, args->entityIdsToInclude, + args->entityIdsToDiscard, args->visibleOnly, args->collidableOnly, args->extraInfo, args->precisionPicking); + if (!entityID.isNull()) { + args->entityID = entityID; + // We recurse OctreeElements in order, so if we hit something, we can stop immediately + keepSearching = false; + } + return keepSearching; +} + +float findParabolaIntersectionSortingOp(const OctreeElementPointer& element, void* extraData) { + ParabolaArgs* args = static_cast(extraData); + EntityTreeElementPointer entityTreeElementPointer = std::static_pointer_cast(element); + float distance = FLT_MAX; + // If origin is inside the cube, always check this element first + if (entityTreeElementPointer->getAACube().contains(args->origin)) { + distance = 0.0f; + } else { + float boundDistance = FLT_MAX; + BoxFace face; + glm::vec3 surfaceNormal; + if (entityTreeElementPointer->getAACube().findParabolaIntersection(args->origin, args->velocity, args->acceleration, boundDistance, face, surfaceNormal)) { + // Don't add this cell if it's already farther than our best distance so far + if (boundDistance < args->parabolicDistance) { + distance = boundDistance; + } + } + } + return distance; +} + +EntityItemID EntityTree::findParabolaIntersection(const PickParabola& parabola, + QVector entityIdsToInclude, QVector entityIdsToDiscard, + bool visibleOnly, bool collidableOnly, bool precisionPicking, + OctreeElementPointer& element, glm::vec3& intersection, float& distance, float& parabolicDistance, + BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, + Octree::lockType lockType, bool* accurateResult) { + ParabolaArgs args = { parabola.origin, parabola.velocity, parabola.acceleration, entityIdsToInclude, entityIdsToDiscard, + visibleOnly, collidableOnly, precisionPicking, element, parabolicDistance, face, surfaceNormal, extraInfo, EntityItemID() }; + parabolicDistance = FLT_MAX; + distance = FLT_MAX; + + bool requireLock = lockType == Octree::Lock; + bool lockResult = withReadLock([&] { + recurseTreeWithOperationSorted(findParabolaIntersectionOp, findParabolaIntersectionSortingOp, &args); + }, requireLock); + + if (accurateResult) { + *accurateResult = lockResult; // if user asked to accuracy or result, let them know this is accurate + } + + if (!args.entityID.isNull()) { + intersection = parabola.origin + parabola.velocity * parabolicDistance + 0.5f * parabola.acceleration * parabolicDistance * parabolicDistance; + distance = glm::distance(intersection, parabola.origin); + } + + return args.entityID; +} + EntityItemPointer EntityTree::findClosestEntity(const glm::vec3& position, float targetRadius) { FindNearPointArgs args = { position, targetRadius, false, NULL, FLT_MAX }; @@ -1374,7 +1482,7 @@ void EntityTree::validatePop(const QString& certID, const EntityItemID& entityIt QNetworkReply* networkReply = networkAccessManager.put(networkRequest, QJsonDocument(request).toJson()); - connect(networkReply, &QNetworkReply::finished, [=]() { + connect(networkReply, &QNetworkReply::finished, [this, networkReply, entityItemID, certID, senderNode]() { QJsonObject jsonObject = QJsonDocument::fromJson(networkReply->readAll()).object(); jsonObject = jsonObject["data"].toObject(); diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index 22b468cf4e..2f971b8566 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -95,7 +95,14 @@ public: virtual EntityItemID findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, QVector entityIdsToInclude, QVector entityIdsToDiscard, bool visibleOnly, bool collidableOnly, bool precisionPicking, - OctreeElementPointer& node, float& distance, + OctreeElementPointer& element, float& distance, + BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, + Octree::lockType lockType = Octree::TryLock, bool* accurateResult = NULL); + + virtual EntityItemID findParabolaIntersection(const PickParabola& parabola, + QVector entityIdsToInclude, QVector entityIdsToDiscard, + bool visibleOnly, bool collidableOnly, bool precisionPicking, + OctreeElementPointer& element, glm::vec3& intersection, float& distance, float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, Octree::lockType lockType = Octree::TryLock, bool* accurateResult = NULL); diff --git a/libraries/entities/src/EntityTreeElement.cpp b/libraries/entities/src/EntityTreeElement.cpp index bc5bb1e81d..efe5dafccf 100644 --- a/libraries/entities/src/EntityTreeElement.cpp +++ b/libraries/entities/src/EntityTreeElement.cpp @@ -140,35 +140,22 @@ bool EntityTreeElement::bestFitBounds(const glm::vec3& minPoint, const glm::vec3 } EntityItemID EntityTreeElement::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - bool& keepSearching, OctreeElementPointer& element, float& distance, - BoxFace& face, glm::vec3& surfaceNormal, const QVector& entityIdsToInclude, - const QVector& entityIdsToDiscard, bool visibleOnly, bool collidableOnly, - QVariantMap& extraInfo, bool precisionPicking) { + OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, + const QVector& entityIdsToInclude, const QVector& entityIdsToDiscard, + bool visibleOnly, bool collidableOnly, QVariantMap& extraInfo, bool precisionPicking) { EntityItemID result; - float distanceToElementCube = std::numeric_limits::max(); BoxFace localFace; glm::vec3 localSurfaceNormal; - // if the ray doesn't intersect with our cube OR the distance to element is less than current best distance - // we can stop searching! - bool hit = _cube.findRayIntersection(origin, direction, distanceToElementCube, localFace, localSurfaceNormal); - if (!hit || (!_cube.contains(origin) && distanceToElementCube > distance)) { - keepSearching = false; // no point in continuing to search - return result; // we did not intersect + if (!canPickIntersect()) { + return result; } - // by default, we only allow intersections with leaves with content - if (!canRayIntersect()) { - return result; // we don't intersect with non-leaves, and we keep searching - } - - // if the distance to the element cube is not less than the current best distance, then it's not possible - // for any details inside the cube to be closer so we don't need to consider them. QVariantMap localExtraInfo; float distanceToElementDetails = distance; EntityItemID entityID = findDetailedRayIntersection(origin, direction, element, distanceToElementDetails, - face, localSurfaceNormal, entityIdsToInclude, entityIdsToDiscard, visibleOnly, collidableOnly, + localFace, localSurfaceNormal, entityIdsToInclude, entityIdsToDiscard, visibleOnly, collidableOnly, localExtraInfo, precisionPicking); if (!entityID.isNull() && distanceToElementDetails < distance) { distance = distanceToElementDetails; @@ -228,11 +215,11 @@ EntityItemID EntityTreeElement::findDetailedRayIntersection(const glm::vec3& ori float localDistance; BoxFace localFace; glm::vec3 localSurfaceNormal; - if (entityFrameBox.findRayIntersection(entityFrameOrigin, entityFrameDirection, localDistance, + if (entityFrameBox.findRayIntersection(entityFrameOrigin, entityFrameDirection, 1.0f / entityFrameDirection, localDistance, localFace, localSurfaceNormal)) { if (entityFrameBox.contains(entityFrameOrigin) || localDistance < distance) { // now ask the entity if we actually intersect - if (entity->supportsDetailedRayIntersection()) { + if (entity->supportsDetailedIntersection()) { QVariantMap localExtraInfo; if (entity->findDetailedRayIntersection(origin, direction, element, localDistance, localFace, localSurfaceNormal, localExtraInfo, precisionPicking)) { @@ -250,7 +237,8 @@ EntityItemID EntityTreeElement::findDetailedRayIntersection(const glm::vec3& ori if (localDistance < distance && entity->getType() != EntityTypes::ParticleEffect) { distance = localDistance; face = localFace; - surfaceNormal = glm::vec3(rotation * glm::vec4(localSurfaceNormal, 1.0f)); + surfaceNormal = glm::vec3(rotation * glm::vec4(localSurfaceNormal, 0.0f)); + extraInfo = QVariantMap(); entityID = entity->getEntityItemID(); } } @@ -287,6 +275,132 @@ bool EntityTreeElement::findSpherePenetration(const glm::vec3& center, float rad return result; } +EntityItemID EntityTreeElement::findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, + const glm::vec3& acceleration, OctreeElementPointer& element, float& parabolicDistance, + BoxFace& face, glm::vec3& surfaceNormal, const QVector& entityIdsToInclude, + const QVector& entityIdsToDiscard, bool visibleOnly, bool collidableOnly, + QVariantMap& extraInfo, bool precisionPicking) { + + EntityItemID result; + BoxFace localFace; + glm::vec3 localSurfaceNormal; + + if (!canPickIntersect()) { + return result; + } + + QVariantMap localExtraInfo; + float distanceToElementDetails = parabolicDistance; + // We can precompute the world-space parabola normal and reuse it for the parabola plane intersects AABox sphere check + glm::vec3 vectorOnPlane = velocity; + if (glm::dot(glm::normalize(velocity), glm::normalize(acceleration)) > 1.0f - EPSILON) { + // Handle the degenerate case where velocity is parallel to acceleration + // We pick t = 1 and calculate a second point on the plane + vectorOnPlane = velocity + 0.5f * acceleration; + } + // Get the normal of the plane, the cross product of two vectors on the plane + glm::vec3 normal = glm::normalize(glm::cross(vectorOnPlane, acceleration)); + EntityItemID entityID = findDetailedParabolaIntersection(origin, velocity, acceleration, normal, element, distanceToElementDetails, + localFace, localSurfaceNormal, entityIdsToInclude, entityIdsToDiscard, visibleOnly, collidableOnly, + localExtraInfo, precisionPicking); + if (!entityID.isNull() && distanceToElementDetails < parabolicDistance) { + parabolicDistance = distanceToElementDetails; + face = localFace; + surfaceNormal = localSurfaceNormal; + extraInfo = localExtraInfo; + result = entityID; + } + return result; +} + +EntityItemID EntityTreeElement::findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + const glm::vec3& normal, OctreeElementPointer& element, float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, + const QVector& entityIdsToInclude, const QVector& entityIDsToDiscard, + bool visibleOnly, bool collidableOnly, QVariantMap& extraInfo, bool precisionPicking) { + + // only called if we do intersect our bounding cube, but find if we actually intersect with entities... + int entityNumber = 0; + EntityItemID entityID; + forEachEntity([&](EntityItemPointer entity) { + // use simple line-sphere for broadphase check + // (this is faster and more likely to cull results than the filter check below so we do it first) + bool success; + AABox entityBox = entity->getAABox(success); + if (!success) { + return; + } + + // Instead of checking parabolaInstersectsBoundingSphere here, we are just going to check if the plane + // defined by the parabola slices the sphere. The solution to parabolaIntersectsBoundingSphere is cubic, + // the solution to which is more computationally expensive than the quadratic AABox::findParabolaIntersection + // below + if (!entityBox.parabolaPlaneIntersectsBoundingSphere(origin, velocity, acceleration, normal)) { + return; + } + + // check RayPick filter settings + if ((visibleOnly && !entity->isVisible()) + || (collidableOnly && (entity->getCollisionless() || entity->getShapeType() == SHAPE_TYPE_NONE)) + || (entityIdsToInclude.size() > 0 && !entityIdsToInclude.contains(entity->getID())) + || (entityIDsToDiscard.size() > 0 && entityIDsToDiscard.contains(entity->getID())) ) { + return; + } + + // extents is the entity relative, scaled, centered extents of the entity + glm::mat4 rotation = glm::mat4_cast(entity->getWorldOrientation()); + glm::mat4 translation = glm::translate(entity->getWorldPosition()); + glm::mat4 entityToWorldMatrix = translation * rotation; + glm::mat4 worldToEntityMatrix = glm::inverse(entityToWorldMatrix); + + glm::vec3 dimensions = entity->getRaycastDimensions(); + glm::vec3 registrationPoint = entity->getRegistrationPoint(); + glm::vec3 corner = -(dimensions * registrationPoint); + + AABox entityFrameBox(corner, dimensions); + + glm::vec3 entityFrameOrigin = glm::vec3(worldToEntityMatrix * glm::vec4(origin, 1.0f)); + glm::vec3 entityFrameVelocity = glm::vec3(worldToEntityMatrix * glm::vec4(velocity, 0.0f)); + glm::vec3 entityFrameAcceleration = glm::vec3(worldToEntityMatrix * glm::vec4(acceleration, 0.0f)); + + // we can use the AABox's ray intersection by mapping our origin and direction into the entity frame + // and testing intersection there. + float localDistance; + BoxFace localFace; + glm::vec3 localSurfaceNormal; + if (entityFrameBox.findParabolaIntersection(entityFrameOrigin, entityFrameVelocity, entityFrameAcceleration, localDistance, + localFace, localSurfaceNormal)) { + if (entityFrameBox.contains(entityFrameOrigin) || localDistance < parabolicDistance) { + // now ask the entity if we actually intersect + if (entity->supportsDetailedIntersection()) { + QVariantMap localExtraInfo; + if (entity->findDetailedParabolaIntersection(origin, velocity, acceleration, element, localDistance, + localFace, localSurfaceNormal, localExtraInfo, precisionPicking)) { + if (localDistance < parabolicDistance) { + parabolicDistance = localDistance; + face = localFace; + surfaceNormal = localSurfaceNormal; + extraInfo = localExtraInfo; + entityID = entity->getEntityItemID(); + } + } + } else { + // if the entity type doesn't support a detailed intersection, then just return the non-AABox results + // Never intersect with particle entities + if (localDistance < parabolicDistance && entity->getType() != EntityTypes::ParticleEffect) { + parabolicDistance = localDistance; + face = localFace; + surfaceNormal = glm::vec3(rotation * glm::vec4(localSurfaceNormal, 0.0f)); + extraInfo = QVariantMap(); + entityID = entity->getEntityItemID(); + } + } + } + } + entityNumber++; + }); + return entityID; +} + EntityItemPointer EntityTreeElement::getClosestEntity(glm::vec3 position) const { EntityItemPointer closestEntity = NULL; float closestEntityDistance = FLT_MAX; diff --git a/libraries/entities/src/EntityTreeElement.h b/libraries/entities/src/EntityTreeElement.h index 76e1e40812..793340c9a4 100644 --- a/libraries/entities/src/EntityTreeElement.h +++ b/libraries/entities/src/EntityTreeElement.h @@ -134,12 +134,11 @@ public: virtual bool isRendered() const override { return getShouldRender(); } virtual bool deleteApproved() const override { return !hasEntities(); } - virtual bool canRayIntersect() const override { return hasEntities(); } + virtual bool canPickIntersect() const override { return hasEntities(); } virtual EntityItemID findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - bool& keepSearching, OctreeElementPointer& node, float& distance, - BoxFace& face, glm::vec3& surfaceNormal, const QVector& entityIdsToInclude, - const QVector& entityIdsToDiscard, bool visibleOnly, bool collidableOnly, - QVariantMap& extraInfo, bool precisionPicking = false); + OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, + const QVector& entityIdsToInclude, const QVector& entityIdsToDiscard, + bool visibleOnly, bool collidableOnly, QVariantMap& extraInfo, bool precisionPicking = false); virtual EntityItemID findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, const QVector& entityIdsToInclude, @@ -148,6 +147,16 @@ public: virtual bool findSpherePenetration(const glm::vec3& center, float radius, glm::vec3& penetration, void** penetratedObject) const override; + virtual EntityItemID findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, + const glm::vec3& acceleration, OctreeElementPointer& element, float& parabolicDistance, + BoxFace& face, glm::vec3& surfaceNormal, const QVector& entityIdsToInclude, + const QVector& entityIdsToDiscard, bool visibleOnly, bool collidableOnly, + QVariantMap& extraInfo, bool precisionPicking = false); + virtual EntityItemID findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, + const glm::vec3& normal, const glm::vec3& acceleration, OctreeElementPointer& element, float& parabolicDistance, + BoxFace& face, glm::vec3& surfaceNormal, const QVector& entityIdsToInclude, + const QVector& entityIdsToDiscard, bool visibleOnly, bool collidableOnly, + QVariantMap& extraInfo, bool precisionPicking); template void forEachEntity(F f) const { diff --git a/libraries/entities/src/LightEntityItem.cpp b/libraries/entities/src/LightEntityItem.cpp index e95af7ebf9..1db67fc0b6 100644 --- a/libraries/entities/src/LightEntityItem.cpp +++ b/libraries/entities/src/LightEntityItem.cpp @@ -309,3 +309,14 @@ bool LightEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const return _lightsArePickable; } +bool LightEntityItem::findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, + const glm::vec3& acceleration, OctreeElementPointer& element, float& parabolicDistance, + BoxFace& face, glm::vec3& surfaceNormal, + QVariantMap& extraInfo, bool precisionPicking) const { + // TODO: consider if this is really what we want to do. We've made it so that "lights are pickable" is a global state + // this is probably reasonable since there's typically only one tree you'd be picking on at a time. Technically we could + // be on the clipboard and someone might be trying to use the parabola intersection API there. Anyway... if you ever try to + // do parabola intersection testing off of trees other than the main tree of the main entity renderer, then we'll need to + // fix this mechanism. + return _lightsArePickable; +} diff --git a/libraries/entities/src/LightEntityItem.h b/libraries/entities/src/LightEntityItem.h index 4d0bde3718..518cb18de2 100644 --- a/libraries/entities/src/LightEntityItem.h +++ b/libraries/entities/src/LightEntityItem.h @@ -84,11 +84,15 @@ public: bool lightPropertiesChanged() const { return _lightPropertiesChanged; } void resetLightPropertiesChanged(); - virtual bool supportsDetailedRayIntersection() const override { return true; } + virtual bool supportsDetailedIntersection() const override { return true; } virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) const override; + virtual bool findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, + const glm::vec3& acceleration, OctreeElementPointer& element, float& parabolicDistance, + BoxFace& face, glm::vec3& surfaceNormal, + QVariantMap& extraInfo, bool precisionPicking) const override; private: // properties of a light diff --git a/libraries/entities/src/LineEntityItem.h b/libraries/entities/src/LineEntityItem.h index 84f9acf5f5..7c21b5c9d2 100644 --- a/libraries/entities/src/LineEntityItem.h +++ b/libraries/entities/src/LineEntityItem.h @@ -58,12 +58,17 @@ class LineEntityItem : public EntityItem { virtual ShapeType getShapeType() const override { return SHAPE_TYPE_NONE; } // never have a ray intersection pick a LineEntityItem. - virtual bool supportsDetailedRayIntersection() const override { return true; } + virtual bool supportsDetailedIntersection() const override { return true; } virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) const override { return false; } + virtual bool findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, + const glm::vec3& acceleration, OctreeElementPointer& element, float& parabolicDistance, + BoxFace& face, glm::vec3& surfaceNormal, + QVariantMap& extraInfo, + bool precisionPicking) const override { return false; } bool pointsChanged() const { return _pointsChanged; } void resetPointsChanged(); virtual void debugDump() const override; diff --git a/libraries/entities/src/ModelEntityItem.cpp b/libraries/entities/src/ModelEntityItem.cpp index cf89a73214..5d5344c9c8 100644 --- a/libraries/entities/src/ModelEntityItem.cpp +++ b/libraries/entities/src/ModelEntityItem.cpp @@ -371,7 +371,7 @@ void ModelEntityItem::setAnimationFPS(float value) { // virtual bool ModelEntityItem::shouldBePhysical() const { - return !isDead() && getShapeType() != SHAPE_TYPE_NONE; + return !isDead() && getShapeType() != SHAPE_TYPE_NONE && QUrl(_modelURL).isValid(); } void ModelEntityItem::resizeJointArrays(int newSize) { diff --git a/libraries/entities/src/ParticleEffectEntityItem.cpp b/libraries/entities/src/ParticleEffectEntityItem.cpp index 18cf7c9252..238f41b05f 100644 --- a/libraries/entities/src/ParticleEffectEntityItem.cpp +++ b/libraries/entities/src/ParticleEffectEntityItem.cpp @@ -91,6 +91,8 @@ bool operator==(const Properties& a, const Properties& b) { (a.color == b.color) && (a.alpha == b.alpha) && (a.radius == b.radius) && + (a.spin == b.spin) && + (a.rotateWithEntity == b.rotateWithEntity) && (a.radiusStart == b.radiusStart) && (a.lifespan == b.lifespan) && (a.maxParticles == b.maxParticles) && @@ -130,7 +132,11 @@ bool Properties::valid() const { (radius.gradient.target == glm::clamp(radius.gradient.target, MINIMUM_PARTICLE_RADIUS, MAXIMUM_PARTICLE_RADIUS)) && (radius.range.start == glm::clamp(radius.range.start, MINIMUM_PARTICLE_RADIUS, MAXIMUM_PARTICLE_RADIUS)) && (radius.range.finish == glm::clamp(radius.range.finish, MINIMUM_PARTICLE_RADIUS, MAXIMUM_PARTICLE_RADIUS)) && - (radius.gradient.spread == glm::clamp(radius.gradient.spread, MINIMUM_PARTICLE_RADIUS, MAXIMUM_PARTICLE_RADIUS)); + (radius.gradient.spread == glm::clamp(radius.gradient.spread, MINIMUM_PARTICLE_RADIUS, MAXIMUM_PARTICLE_RADIUS)) && + (spin.gradient.target == glm::clamp(spin.gradient.target, MINIMUM_PARTICLE_SPIN, MAXIMUM_PARTICLE_SPIN)) && + (spin.range.start == glm::clamp(spin.range.start, MINIMUM_PARTICLE_SPIN, MAXIMUM_PARTICLE_SPIN)) && + (spin.range.finish == glm::clamp(spin.range.finish, MINIMUM_PARTICLE_SPIN, MAXIMUM_PARTICLE_SPIN)) && + (spin.gradient.spread == glm::clamp(spin.gradient.spread, MINIMUM_PARTICLE_SPIN, MAXIMUM_PARTICLE_SPIN)); } bool Properties::emitting() const { @@ -332,6 +338,43 @@ void ParticleEffectEntityItem::setRadiusSpread(float radiusSpread) { } } +void ParticleEffectEntityItem::setParticleSpin(float particleSpin) { + particleSpin = glm::clamp(particleSpin, MINIMUM_PARTICLE_SPIN, MAXIMUM_PARTICLE_SPIN); + if (particleSpin != _particleProperties.spin.gradient.target) { + withWriteLock([&] { + _particleProperties.spin.gradient.target = particleSpin; + }); + } +} + +void ParticleEffectEntityItem::setSpinStart(float spinStart) { + spinStart = + glm::isnan(spinStart) ? spinStart : glm::clamp(spinStart, MINIMUM_PARTICLE_SPIN, MAXIMUM_PARTICLE_SPIN); + if (spinStart != _particleProperties.spin.range.start) { + withWriteLock([&] { + _particleProperties.spin.range.start = spinStart; + }); + } +} + +void ParticleEffectEntityItem::setSpinFinish(float spinFinish) { + spinFinish = + glm::isnan(spinFinish) ? spinFinish : glm::clamp(spinFinish, MINIMUM_PARTICLE_SPIN, MAXIMUM_PARTICLE_SPIN); + if (spinFinish != _particleProperties.spin.range.finish) { + withWriteLock([&] { + _particleProperties.spin.range.finish = spinFinish; + }); + } +} + +void ParticleEffectEntityItem::setSpinSpread(float spinSpread) { + spinSpread = glm::clamp(spinSpread, MINIMUM_PARTICLE_SPIN, MAXIMUM_PARTICLE_SPIN); + if (spinSpread != _particleProperties.spin.gradient.spread) { + withWriteLock([&] { + _particleProperties.spin.gradient.spread = spinSpread; + }); + } +} void ParticleEffectEntityItem::computeAndUpdateDimensions() { particle::Properties particleProperties; @@ -398,6 +441,11 @@ EntityItemProperties ParticleEffectEntityItem::getProperties(EntityPropertyFlags COPY_ENTITY_PROPERTY_TO_PROPERTIES(alphaFinish, getAlphaFinish); COPY_ENTITY_PROPERTY_TO_PROPERTIES(textures, getTextures); COPY_ENTITY_PROPERTY_TO_PROPERTIES(emitterShouldTrail, getEmitterShouldTrail); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(particleSpin, getParticleSpin); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(spinSpread, getSpinSpread); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(spinStart, getSpinStart); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(spinFinish, getSpinFinish); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(rotateWithEntity, getRotateWithEntity); return properties; } @@ -435,6 +483,11 @@ bool ParticleEffectEntityItem::setProperties(const EntityItemProperties& propert SET_ENTITY_PROPERTY_FROM_PROPERTIES(alphaFinish, setAlphaFinish); SET_ENTITY_PROPERTY_FROM_PROPERTIES(textures, setTextures); SET_ENTITY_PROPERTY_FROM_PROPERTIES(emitterShouldTrail, setEmitterShouldTrail); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(particleSpin, setParticleSpin); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(spinSpread, setSpinSpread); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(spinStart, setSpinStart); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(spinFinish, setSpinFinish); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(rotateWithEntity, setRotateWithEntity); if (somethingChanged) { bool wantDebug = false; @@ -515,6 +568,12 @@ int ParticleEffectEntityItem::readEntitySubclassDataFromBuffer(const unsigned ch READ_ENTITY_PROPERTY(PROP_EMITTER_SHOULD_TRAIL, bool, setEmitterShouldTrail); + READ_ENTITY_PROPERTY(PROP_PARTICLE_SPIN, float, setParticleSpin); + READ_ENTITY_PROPERTY(PROP_SPIN_SPREAD, float, setSpinSpread); + READ_ENTITY_PROPERTY(PROP_SPIN_START, float, setSpinStart); + READ_ENTITY_PROPERTY(PROP_SPIN_FINISH, float, setSpinFinish); + READ_ENTITY_PROPERTY(PROP_PARTICLE_ROTATE_WITH_ENTITY, bool, setRotateWithEntity); + return bytesRead; } @@ -551,6 +610,11 @@ EntityPropertyFlags ParticleEffectEntityItem::getEntityProperties(EncodeBitstrea requestedProperties += PROP_AZIMUTH_START; requestedProperties += PROP_AZIMUTH_FINISH; requestedProperties += PROP_EMITTER_SHOULD_TRAIL; + requestedProperties += PROP_PARTICLE_SPIN; + requestedProperties += PROP_SPIN_SPREAD; + requestedProperties += PROP_SPIN_START; + requestedProperties += PROP_SPIN_FINISH; + requestedProperties += PROP_PARTICLE_ROTATE_WITH_ENTITY; return requestedProperties; } @@ -594,6 +658,11 @@ void ParticleEffectEntityItem::appendSubclassData(OctreePacketData* packetData, APPEND_ENTITY_PROPERTY(PROP_AZIMUTH_START, getAzimuthStart()); APPEND_ENTITY_PROPERTY(PROP_AZIMUTH_FINISH, getAzimuthFinish()); APPEND_ENTITY_PROPERTY(PROP_EMITTER_SHOULD_TRAIL, getEmitterShouldTrail()); + APPEND_ENTITY_PROPERTY(PROP_PARTICLE_SPIN, getParticleSpin()); + APPEND_ENTITY_PROPERTY(PROP_SPIN_SPREAD, getSpinSpread()); + APPEND_ENTITY_PROPERTY(PROP_SPIN_START, getSpinStart()); + APPEND_ENTITY_PROPERTY(PROP_SPIN_FINISH, getSpinFinish()); + APPEND_ENTITY_PROPERTY(PROP_PARTICLE_ROTATE_WITH_ENTITY, getRotateWithEntity()); } @@ -665,6 +734,12 @@ void ParticleEffectEntityItem::setEmitterShouldTrail(bool emitterShouldTrail) { }); } +void ParticleEffectEntityItem::setRotateWithEntity(bool rotateWithEntity) { + withWriteLock([&] { + _particleProperties.rotateWithEntity = rotateWithEntity; + }); +} + particle::Properties ParticleEffectEntityItem::getParticleProperties() const { particle::Properties result; withReadLock([&] { @@ -689,6 +764,12 @@ particle::Properties ParticleEffectEntityItem::getParticleProperties() const { if (glm::isnan(result.radius.range.finish)) { result.radius.range.finish = getParticleRadius(); } + if (glm::isnan(result.spin.range.start)) { + result.spin.range.start = getParticleSpin(); + } + if (glm::isnan(result.spin.range.finish)) { + result.spin.range.finish = getParticleSpin(); + } }); if (!result.valid()) { diff --git a/libraries/entities/src/ParticleEffectEntityItem.h b/libraries/entities/src/ParticleEffectEntityItem.h index 7e507ab46a..02284768ce 100644 --- a/libraries/entities/src/ParticleEffectEntityItem.h +++ b/libraries/entities/src/ParticleEffectEntityItem.h @@ -39,7 +39,7 @@ namespace particle { static const float MINIMUM_EMIT_RATE = 0.0f; static const float MAXIMUM_EMIT_RATE = 100000.0f; static const float DEFAULT_EMIT_SPEED = 5.0f; - static const float MINIMUM_EMIT_SPEED = 0.0f; + static const float MINIMUM_EMIT_SPEED = -1000.0f; static const float MAXIMUM_EMIT_SPEED = 1000.0f; // Approx mach 3 static const float DEFAULT_SPEED_SPREAD = 1.0f; static const glm::quat DEFAULT_EMIT_ORIENTATION = glm::angleAxis(-PI_OVER_TWO, Vectors::UNIT_X); // Vertical @@ -69,8 +69,15 @@ namespace particle { static const float DEFAULT_RADIUS_SPREAD = 0.0f; static const float DEFAULT_RADIUS_START = UNINITIALIZED; static const float DEFAULT_RADIUS_FINISH = UNINITIALIZED; + static const float DEFAULT_PARTICLE_SPIN = 0.0f; + static const float DEFAULT_SPIN_START = UNINITIALIZED; + static const float DEFAULT_SPIN_FINISH = UNINITIALIZED; + static const float DEFAULT_SPIN_SPREAD = 0.0f; + static const float MINIMUM_PARTICLE_SPIN = -2.0f * SCRIPT_MAXIMUM_PI; + static const float MAXIMUM_PARTICLE_SPIN = 2.0f * SCRIPT_MAXIMUM_PI; static const QString DEFAULT_TEXTURES = ""; static const bool DEFAULT_EMITTER_SHOULD_TRAIL = false; + static const bool DEFAULT_ROTATE_WITH_ENTITY = false; template struct Range { @@ -151,6 +158,8 @@ namespace particle { RangeGradient alpha { DEFAULT_ALPHA, DEFAULT_ALPHA_START, DEFAULT_ALPHA_FINISH, DEFAULT_ALPHA_SPREAD }; float radiusStart { DEFAULT_EMIT_RADIUS_START }; RangeGradient radius { DEFAULT_PARTICLE_RADIUS, DEFAULT_RADIUS_START, DEFAULT_RADIUS_FINISH, DEFAULT_RADIUS_SPREAD }; + RangeGradient spin { DEFAULT_PARTICLE_SPIN, DEFAULT_SPIN_START, DEFAULT_SPIN_FINISH, DEFAULT_SPIN_SPREAD }; + bool rotateWithEntity { DEFAULT_ROTATE_WITH_ENTITY }; float lifespan { DEFAULT_LIFESPAN }; uint32_t maxParticles { DEFAULT_MAX_PARTICLES }; EmitProperties emission; @@ -168,6 +177,8 @@ namespace particle { Properties& operator =(const Properties& other) { color = other.color; alpha = other.alpha; + spin = other.spin; + rotateWithEntity = other.rotateWithEntity; radius = other.radius; lifespan = other.lifespan; maxParticles = other.maxParticles; @@ -306,6 +317,21 @@ public: void setRadiusSpread(float radiusSpread); float getRadiusSpread() const { return _particleProperties.radius.gradient.spread; } + void setParticleSpin(float particleSpin); + float getParticleSpin() const { return _particleProperties.spin.gradient.target; } + + void setSpinStart(float spinStart); + float getSpinStart() const { return _particleProperties.spin.range.start; } + + void setSpinFinish(float spinFinish); + float getSpinFinish() const { return _particleProperties.spin.range.finish; } + + void setSpinSpread(float spinSpread); + float getSpinSpread() const { return _particleProperties.spin.gradient.spread; } + + void setRotateWithEntity(bool rotateWithEntity); + bool getRotateWithEntity() const { return _particleProperties.rotateWithEntity; } + void computeAndUpdateDimensions(); void setTextures(const QString& textures); @@ -314,7 +340,7 @@ public: bool getEmitterShouldTrail() const { return _particleProperties.emission.shouldTrail; } void setEmitterShouldTrail(bool emitterShouldTrail); - virtual bool supportsDetailedRayIntersection() const override { return false; } + virtual bool supportsDetailedIntersection() const override { return false; } particle::Properties getParticleProperties() const; diff --git a/libraries/entities/src/PolyLineEntityItem.h b/libraries/entities/src/PolyLineEntityItem.h index c76419af02..52ec8e8c2d 100644 --- a/libraries/entities/src/PolyLineEntityItem.h +++ b/libraries/entities/src/PolyLineEntityItem.h @@ -89,11 +89,15 @@ class PolyLineEntityItem : public EntityItem { // never have a ray intersection pick a PolyLineEntityItem. - virtual bool supportsDetailedRayIntersection() const override { return true; } + virtual bool supportsDetailedIntersection() const override { return true; } virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) const override { return false; } + virtual bool findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, + const glm::vec3& acceleration, OctreeElementPointer& element, float& parabolicDistance, + BoxFace& face, glm::vec3& surfaceNormal, + QVariantMap& extraInfo, bool precisionPicking) const override { return false; } // disable these external interfaces as PolyLineEntities caculate their own dimensions based on the points they contain virtual void setRegistrationPoint(const glm::vec3& value) override {}; // FIXME: this is suspicious! diff --git a/libraries/entities/src/PolyVoxEntityItem.h b/libraries/entities/src/PolyVoxEntityItem.h index 4dfe7b9535..d2ca4db124 100644 --- a/libraries/entities/src/PolyVoxEntityItem.h +++ b/libraries/entities/src/PolyVoxEntityItem.h @@ -42,11 +42,15 @@ class PolyVoxEntityItem : public EntityItem { bool& somethingChanged) override; // never have a ray intersection pick a PolyVoxEntityItem. - virtual bool supportsDetailedRayIntersection() const override { return true; } + virtual bool supportsDetailedIntersection() const override { return true; } virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) const override { return false; } + virtual bool findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, + const glm::vec3& acceleration, OctreeElementPointer& element, float& parabolicDistance, + BoxFace& face, glm::vec3& surfaceNormal, + QVariantMap& extraInfo, bool precisionPicking) const override { return false; } virtual void debugDump() const override; diff --git a/libraries/entities/src/ShapeEntityItem.cpp b/libraries/entities/src/ShapeEntityItem.cpp index 943ae2e462..773a7059dc 100644 --- a/libraries/entities/src/ShapeEntityItem.cpp +++ b/libraries/entities/src/ShapeEntityItem.cpp @@ -250,7 +250,7 @@ void ShapeEntityItem::setUnscaledDimensions(const glm::vec3& value) { } } -bool ShapeEntityItem::supportsDetailedRayIntersection() const { +bool ShapeEntityItem::supportsDetailedIntersection() const { return _shape == entity::Sphere; } @@ -262,19 +262,44 @@ bool ShapeEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const glm::mat4 entityToWorldMatrix = getEntityToWorldMatrix(); glm::mat4 worldToEntityMatrix = glm::inverse(entityToWorldMatrix); glm::vec3 entityFrameOrigin = glm::vec3(worldToEntityMatrix * glm::vec4(origin, 1.0f)); - glm::vec3 entityFrameDirection = glm::normalize(glm::vec3(worldToEntityMatrix * glm::vec4(direction, 0.0f))); + glm::vec3 entityFrameDirection = glm::vec3(worldToEntityMatrix * glm::vec4(direction, 0.0f)); - float localDistance; // NOTE: unit sphere has center of 0,0,0 and radius of 0.5 - if (findRaySphereIntersection(entityFrameOrigin, entityFrameDirection, glm::vec3(0.0f), 0.5f, localDistance)) { - // determine where on the unit sphere the hit point occured - glm::vec3 entityFrameHitAt = entityFrameOrigin + (entityFrameDirection * localDistance); - // then translate back to work coordinates - glm::vec3 hitAt = glm::vec3(entityToWorldMatrix * glm::vec4(entityFrameHitAt, 1.0f)); - distance = glm::distance(origin, hitAt); + if (findRaySphereIntersection(entityFrameOrigin, entityFrameDirection, glm::vec3(0.0f), 0.5f, distance)) { bool success; - surfaceNormal = glm::normalize(hitAt - getCenterPosition(success)); - if (!success) { + glm::vec3 center = getCenterPosition(success); + if (success) { + // FIXME: this is only correct for uniformly scaled spheres + // determine where on the unit sphere the hit point occured + glm::vec3 hitAt = origin + (direction * distance); + surfaceNormal = glm::normalize(hitAt - center); + } else { + return false; + } + return true; + } + return false; +} + +bool ShapeEntityItem::findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + OctreeElementPointer& element, float& parabolicDistance, + BoxFace& face, glm::vec3& surfaceNormal, + QVariantMap& extraInfo, bool precisionPicking) const { + // determine the parabola in the frame of the entity transformed from a unit sphere + glm::mat4 entityToWorldMatrix = getEntityToWorldMatrix(); + glm::mat4 worldToEntityMatrix = glm::inverse(entityToWorldMatrix); + glm::vec3 entityFrameOrigin = glm::vec3(worldToEntityMatrix * glm::vec4(origin, 1.0f)); + glm::vec3 entityFrameVelocity = glm::vec3(worldToEntityMatrix * glm::vec4(velocity, 0.0f)); + glm::vec3 entityFrameAcceleration = glm::vec3(worldToEntityMatrix * glm::vec4(acceleration, 0.0f)); + + // NOTE: unit sphere has center of 0,0,0 and radius of 0.5 + if (findParabolaSphereIntersection(entityFrameOrigin, entityFrameVelocity, entityFrameAcceleration, glm::vec3(0.0f), 0.5f, parabolicDistance)) { + bool success; + glm::vec3 center = getCenterPosition(success); + if (success) { + // FIXME: this is only correct for uniformly scaled spheres + surfaceNormal = glm::normalize((origin + velocity * parabolicDistance + 0.5f * acceleration * parabolicDistance * parabolicDistance) - center); + } else { return false; } return true; diff --git a/libraries/entities/src/ShapeEntityItem.h b/libraries/entities/src/ShapeEntityItem.h index adc33b764b..ded5df15fe 100644 --- a/libraries/entities/src/ShapeEntityItem.h +++ b/libraries/entities/src/ShapeEntityItem.h @@ -90,11 +90,15 @@ public: bool shouldBePhysical() const override { return !isDead(); } - bool supportsDetailedRayIntersection() const override; + bool supportsDetailedIntersection() const override; bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) const override; + bool findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, + const glm::vec3& acceleration, OctreeElementPointer& element, float& parabolicDistance, + BoxFace& face, glm::vec3& surfaceNormal, + QVariantMap& extraInfo, bool precisionPicking) const override; void debugDump() const override; diff --git a/libraries/entities/src/TextEntityItem.cpp b/libraries/entities/src/TextEntityItem.cpp index 56e12e66d9..f130995bb5 100644 --- a/libraries/entities/src/TextEntityItem.cpp +++ b/libraries/entities/src/TextEntityItem.cpp @@ -127,17 +127,55 @@ void TextEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBits } bool TextEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - OctreeElementPointer& element, float& distance, - BoxFace& face, glm::vec3& surfaceNormal, - QVariantMap& extraInfo, bool precisionPicking) const { + OctreeElementPointer& element, float& distance, + BoxFace& face, glm::vec3& surfaceNormal, + QVariantMap& extraInfo, bool precisionPicking) const { glm::vec3 dimensions = getScaledDimensions(); glm::vec2 xyDimensions(dimensions.x, dimensions.y); glm::quat rotation = getWorldOrientation(); - glm::vec3 position = getWorldPosition() + rotation * - (dimensions * (ENTITY_ITEM_DEFAULT_REGISTRATION_POINT - getRegistrationPoint())); + glm::vec3 position = getWorldPosition() + rotation * (dimensions * (ENTITY_ITEM_DEFAULT_REGISTRATION_POINT - getRegistrationPoint())); - // FIXME - should set face and surfaceNormal - return findRayRectangleIntersection(origin, direction, rotation, position, xyDimensions, distance); + if (findRayRectangleIntersection(origin, direction, rotation, position, xyDimensions, distance)) { + glm::vec3 forward = rotation * Vectors::FRONT; + if (glm::dot(forward, direction) > 0.0f) { + face = MAX_Z_FACE; + surfaceNormal = -forward; + } else { + face = MIN_Z_FACE; + surfaceNormal = forward; + } + return true; + } + return false; +} + +bool TextEntityItem::findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + OctreeElementPointer& element, float& parabolicDistance, + BoxFace& face, glm::vec3& surfaceNormal, + QVariantMap& extraInfo, bool precisionPicking) const { + glm::vec3 dimensions = getScaledDimensions(); + glm::vec2 xyDimensions(dimensions.x, dimensions.y); + glm::quat rotation = getWorldOrientation(); + glm::vec3 position = getWorldPosition() + rotation * (dimensions * (ENTITY_ITEM_DEFAULT_REGISTRATION_POINT - getRegistrationPoint())); + + glm::quat inverseRot = glm::inverse(rotation); + glm::vec3 localOrigin = inverseRot * (origin - position); + glm::vec3 localVelocity = inverseRot * velocity; + glm::vec3 localAcceleration = inverseRot * acceleration; + + if (findParabolaRectangleIntersection(localOrigin, localVelocity, localAcceleration, xyDimensions, parabolicDistance)) { + float localIntersectionVelocityZ = localVelocity.z + localAcceleration.z * parabolicDistance; + glm::vec3 forward = rotation * Vectors::FRONT; + if (localIntersectionVelocityZ > 0.0f) { + face = MIN_Z_FACE; + surfaceNormal = forward; + } else { + face = MAX_Z_FACE; + surfaceNormal = -forward; + } + return true; + } + return false; } void TextEntityItem::setText(const QString& value) { diff --git a/libraries/entities/src/TextEntityItem.h b/libraries/entities/src/TextEntityItem.h index efdc84bcd8..4ce5ef3297 100644 --- a/libraries/entities/src/TextEntityItem.h +++ b/libraries/entities/src/TextEntityItem.h @@ -45,11 +45,15 @@ public: EntityPropertyFlags& propertyFlags, bool overwriteLocalData, bool& somethingChanged) override; - virtual bool supportsDetailedRayIntersection() const override { return true; } + virtual bool supportsDetailedIntersection() const override { return true; } virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) const override; + virtual bool findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, + const glm::vec3& acceleration, OctreeElementPointer& element, float& parabolicDistance, + BoxFace& face, glm::vec3& surfaceNormal, + QVariantMap& extraInfo, bool precisionPicking) const override; static const QString DEFAULT_TEXT; void setText(const QString& value); diff --git a/libraries/entities/src/WebEntityItem.cpp b/libraries/entities/src/WebEntityItem.cpp index f3159ba3f8..0070eb538c 100644 --- a/libraries/entities/src/WebEntityItem.cpp +++ b/libraries/entities/src/WebEntityItem.cpp @@ -113,8 +113,44 @@ bool WebEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const g glm::vec3 position = getWorldPosition() + rotation * (dimensions * (ENTITY_ITEM_DEFAULT_REGISTRATION_POINT - getRegistrationPoint())); if (findRayRectangleIntersection(origin, direction, rotation, position, xyDimensions, distance)) { - surfaceNormal = rotation * Vectors::UNIT_Z; - face = glm::dot(surfaceNormal, direction) > 0 ? MIN_Z_FACE : MAX_Z_FACE; + glm::vec3 forward = rotation * Vectors::FRONT; + if (glm::dot(forward, direction) > 0.0f) { + face = MAX_Z_FACE; + surfaceNormal = -forward; + } else { + face = MIN_Z_FACE; + surfaceNormal = forward; + } + return true; + } else { + return false; + } +} + +bool WebEntityItem::findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + OctreeElementPointer& element, float& parabolicDistance, + BoxFace& face, glm::vec3& surfaceNormal, + QVariantMap& extraInfo, bool precisionPicking) const { + glm::vec3 dimensions = getScaledDimensions(); + glm::vec2 xyDimensions(dimensions.x, dimensions.y); + glm::quat rotation = getWorldOrientation(); + glm::vec3 position = getWorldPosition() + rotation * (dimensions * (ENTITY_ITEM_DEFAULT_REGISTRATION_POINT - getRegistrationPoint())); + + glm::quat inverseRot = glm::inverse(rotation); + glm::vec3 localOrigin = inverseRot * (origin - position); + glm::vec3 localVelocity = inverseRot * velocity; + glm::vec3 localAcceleration = inverseRot * acceleration; + + if (findParabolaRectangleIntersection(localOrigin, localVelocity, localAcceleration, xyDimensions, parabolicDistance)) { + float localIntersectionVelocityZ = localVelocity.z + localAcceleration.z * parabolicDistance; + glm::vec3 forward = rotation * Vectors::FRONT; + if (localIntersectionVelocityZ > 0.0f) { + face = MIN_Z_FACE; + surfaceNormal = forward; + } else { + face = MAX_Z_FACE; + surfaceNormal = -forward; + } return true; } else { return false; diff --git a/libraries/entities/src/WebEntityItem.h b/libraries/entities/src/WebEntityItem.h index 1179f22ded..2fa2033445 100644 --- a/libraries/entities/src/WebEntityItem.h +++ b/libraries/entities/src/WebEntityItem.h @@ -44,11 +44,15 @@ public: EntityPropertyFlags& propertyFlags, bool overwriteLocalData, bool& somethingChanged) override; - virtual bool supportsDetailedRayIntersection() const override { return true; } + virtual bool supportsDetailedIntersection() const override { return true; } virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) const override; + virtual bool findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, + const glm::vec3& acceleration, OctreeElementPointer& element, float& parabolicDistance, + BoxFace& face, glm::vec3& surfaceNormal, + QVariantMap& extraInfo, bool precisionPicking) const override; virtual void setSourceUrl(const QString& value); QString getSourceUrl() const; diff --git a/libraries/entities/src/ZoneEntityItem.cpp b/libraries/entities/src/ZoneEntityItem.cpp index 3a6095b89f..a7dfa1a41e 100644 --- a/libraries/entities/src/ZoneEntityItem.cpp +++ b/libraries/entities/src/ZoneEntityItem.cpp @@ -47,33 +47,27 @@ ZoneEntityItem::ZoneEntityItem(const EntityItemID& entityItemID) : EntityItem(en EntityItemProperties ZoneEntityItem::getProperties(EntityPropertyFlags desiredProperties) const { EntityItemProperties properties = EntityItem::getProperties(desiredProperties); // get the properties from our base class - // Contains a QString property, must be synchronized + // Contain QString properties, must be synchronized withReadLock([&] { _keyLightProperties.getProperties(properties); - }); - - withReadLock([&] { _ambientLightProperties.getProperties(properties); + _skyboxProperties.getProperties(properties); }); + _hazeProperties.getProperties(properties); + _bloomProperties.getProperties(properties); COPY_ENTITY_PROPERTY_TO_PROPERTIES(shapeType, getShapeType); COPY_ENTITY_PROPERTY_TO_PROPERTIES(compoundShapeURL, getCompoundShapeURL); - // Contains a QString property, must be synchronized - withReadLock([&] { - _skyboxProperties.getProperties(properties); - }); - COPY_ENTITY_PROPERTY_TO_PROPERTIES(flyingAllowed, getFlyingAllowed); COPY_ENTITY_PROPERTY_TO_PROPERTIES(ghostingAllowed, getGhostingAllowed); COPY_ENTITY_PROPERTY_TO_PROPERTIES(filterURL, getFilterURL); COPY_ENTITY_PROPERTY_TO_PROPERTIES(hazeMode, getHazeMode); - _hazeProperties.getProperties(properties); - COPY_ENTITY_PROPERTY_TO_PROPERTIES(keyLightMode, getKeyLightMode); COPY_ENTITY_PROPERTY_TO_PROPERTIES(ambientLightMode, getAmbientLightMode); COPY_ENTITY_PROPERTY_TO_PROPERTIES(skyboxMode, getSkyboxMode); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(bloomMode, getBloomMode); return properties; } @@ -102,32 +96,27 @@ bool ZoneEntityItem::setSubClassProperties(const EntityItemProperties& propertie // Contains a QString property, must be synchronized withWriteLock([&] { _keyLightPropertiesChanged = _keyLightProperties.setProperties(properties); - }); - withWriteLock([&] { _ambientLightPropertiesChanged = _ambientLightProperties.setProperties(properties); + _skyboxPropertiesChanged = _skyboxProperties.setProperties(properties); }); + _hazePropertiesChanged = _hazeProperties.setProperties(properties); + _bloomPropertiesChanged = _bloomProperties.setProperties(properties); SET_ENTITY_PROPERTY_FROM_PROPERTIES(shapeType, setShapeType); SET_ENTITY_PROPERTY_FROM_PROPERTIES(compoundShapeURL, setCompoundShapeURL); - // Contains a QString property, must be synchronized - withWriteLock([&] { - _skyboxPropertiesChanged = _skyboxProperties.setProperties(properties); - }); - SET_ENTITY_PROPERTY_FROM_PROPERTIES(flyingAllowed, setFlyingAllowed); SET_ENTITY_PROPERTY_FROM_PROPERTIES(ghostingAllowed, setGhostingAllowed); SET_ENTITY_PROPERTY_FROM_PROPERTIES(filterURL, setFilterURL); SET_ENTITY_PROPERTY_FROM_PROPERTIES(hazeMode, setHazeMode); - _hazePropertiesChanged = _hazeProperties.setProperties(properties); - SET_ENTITY_PROPERTY_FROM_PROPERTIES(keyLightMode, setKeyLightMode); SET_ENTITY_PROPERTY_FROM_PROPERTIES(ambientLightMode, setAmbientLightMode); SET_ENTITY_PROPERTY_FROM_PROPERTIES(skyboxMode, setSkyboxMode); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(bloomMode, setBloomMode); somethingChanged = somethingChanged || _keyLightPropertiesChanged || _ambientLightPropertiesChanged || - _stagePropertiesChanged || _skyboxPropertiesChanged || _hazePropertiesChanged; + _stagePropertiesChanged || _skyboxPropertiesChanged || _hazePropertiesChanged || _bloomPropertiesChanged; return somethingChanged; } @@ -139,54 +128,67 @@ int ZoneEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesRead = 0; const unsigned char* dataAt = data; - int bytesFromKeylight; - withWriteLock([&] { - bytesFromKeylight = _keyLightProperties.readEntitySubclassDataFromBuffer(dataAt, (bytesLeftToRead - bytesRead), args, - propertyFlags, overwriteLocalData, _keyLightPropertiesChanged); - }); + { + int bytesFromKeylight; + withWriteLock([&] { + bytesFromKeylight = _keyLightProperties.readEntitySubclassDataFromBuffer(dataAt, (bytesLeftToRead - bytesRead), args, + propertyFlags, overwriteLocalData, _keyLightPropertiesChanged); + }); + somethingChanged = somethingChanged || _keyLightPropertiesChanged; + bytesRead += bytesFromKeylight; + dataAt += bytesFromKeylight; + } - somethingChanged = somethingChanged || _keyLightPropertiesChanged; - bytesRead += bytesFromKeylight; - dataAt += bytesFromKeylight; + { + int bytesFromAmbientlight; + withWriteLock([&] { + bytesFromAmbientlight = _ambientLightProperties.readEntitySubclassDataFromBuffer(dataAt, (bytesLeftToRead - bytesRead), args, + propertyFlags, overwriteLocalData, _ambientLightPropertiesChanged); + }); + somethingChanged = somethingChanged || _ambientLightPropertiesChanged; + bytesRead += bytesFromAmbientlight; + dataAt += bytesFromAmbientlight; + } - int bytesFromAmbientlight; - withWriteLock([&] { - bytesFromAmbientlight = _ambientLightProperties.readEntitySubclassDataFromBuffer(dataAt, (bytesLeftToRead - bytesRead), args, - propertyFlags, overwriteLocalData, _ambientLightPropertiesChanged); - }); + { + int bytesFromSkybox; + withWriteLock([&] { + bytesFromSkybox = _skyboxProperties.readEntitySubclassDataFromBuffer(dataAt, (bytesLeftToRead - bytesRead), args, + propertyFlags, overwriteLocalData, _skyboxPropertiesChanged); + }); + somethingChanged = somethingChanged || _skyboxPropertiesChanged; + bytesRead += bytesFromSkybox; + dataAt += bytesFromSkybox; + } - somethingChanged = somethingChanged || _ambientLightPropertiesChanged; - bytesRead += bytesFromAmbientlight; - dataAt += bytesFromAmbientlight; + { + int bytesFromHaze = _hazeProperties.readEntitySubclassDataFromBuffer(dataAt, (bytesLeftToRead - bytesRead), args, + propertyFlags, overwriteLocalData, _hazePropertiesChanged); + somethingChanged = somethingChanged || _hazePropertiesChanged; + bytesRead += bytesFromHaze; + dataAt += bytesFromHaze; + } + + { + int bytesFromBloom = _bloomProperties.readEntitySubclassDataFromBuffer(dataAt, (bytesLeftToRead - bytesRead), args, + propertyFlags, overwriteLocalData, _bloomPropertiesChanged); + somethingChanged = somethingChanged || _bloomPropertiesChanged; + bytesRead += bytesFromBloom; + dataAt += bytesFromBloom; + } READ_ENTITY_PROPERTY(PROP_SHAPE_TYPE, ShapeType, setShapeType); READ_ENTITY_PROPERTY(PROP_COMPOUND_SHAPE_URL, QString, setCompoundShapeURL); - int bytesFromSkybox; - withWriteLock([&] { - bytesFromSkybox = _skyboxProperties.readEntitySubclassDataFromBuffer(dataAt, (bytesLeftToRead - bytesRead), args, - propertyFlags, overwriteLocalData, _skyboxPropertiesChanged); - }); - somethingChanged = somethingChanged || _skyboxPropertiesChanged; - bytesRead += bytesFromSkybox; - dataAt += bytesFromSkybox; - READ_ENTITY_PROPERTY(PROP_FLYING_ALLOWED, bool, setFlyingAllowed); READ_ENTITY_PROPERTY(PROP_GHOSTING_ALLOWED, bool, setGhostingAllowed); READ_ENTITY_PROPERTY(PROP_FILTER_URL, QString, setFilterURL); READ_ENTITY_PROPERTY(PROP_HAZE_MODE, uint32_t, setHazeMode); - - int bytesFromHaze = _hazeProperties.readEntitySubclassDataFromBuffer(dataAt, (bytesLeftToRead - bytesRead), args, - propertyFlags, overwriteLocalData, _hazePropertiesChanged); - - somethingChanged = somethingChanged || _hazePropertiesChanged; - bytesRead += bytesFromHaze; - dataAt += bytesFromHaze; - READ_ENTITY_PROPERTY(PROP_KEY_LIGHT_MODE, uint32_t, setKeyLightMode); READ_ENTITY_PROPERTY(PROP_AMBIENT_LIGHT_MODE, uint32_t, setAmbientLightMode); READ_ENTITY_PROPERTY(PROP_SKYBOX_MODE, uint32_t, setSkyboxMode); + READ_ENTITY_PROPERTY(PROP_BLOOM_MODE, uint32_t, setBloomMode); return bytesRead; } @@ -196,29 +198,24 @@ EntityPropertyFlags ZoneEntityItem::getEntityProperties(EncodeBitstreamParams& p withReadLock([&] { requestedProperties += _keyLightProperties.getEntityProperties(params); - }); - - withReadLock([&] { requestedProperties += _ambientLightProperties.getEntityProperties(params); + requestedProperties += _skyboxProperties.getEntityProperties(params); }); + requestedProperties += _hazeProperties.getEntityProperties(params); + requestedProperties += _bloomProperties.getEntityProperties(params); requestedProperties += PROP_SHAPE_TYPE; requestedProperties += PROP_COMPOUND_SHAPE_URL; - withReadLock([&] { - requestedProperties += _skyboxProperties.getEntityProperties(params); - }); - requestedProperties += PROP_FLYING_ALLOWED; requestedProperties += PROP_GHOSTING_ALLOWED; requestedProperties += PROP_FILTER_URL; requestedProperties += PROP_HAZE_MODE; - requestedProperties += _hazeProperties.getEntityProperties(params); - requestedProperties += PROP_KEY_LIGHT_MODE; requestedProperties += PROP_AMBIENT_LIGHT_MODE; requestedProperties += PROP_SKYBOX_MODE; + requestedProperties += PROP_BLOOM_MODE; return requestedProperties; } @@ -235,44 +232,46 @@ void ZoneEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBits _keyLightProperties.appendSubclassData(packetData, params, modelTreeElementExtraEncodeData, requestedProperties, propertyFlags, propertiesDidntFit, propertyCount, appendState); - _ambientLightProperties.appendSubclassData(packetData, params, modelTreeElementExtraEncodeData, requestedProperties, propertyFlags, propertiesDidntFit, propertyCount, appendState); + _skyboxProperties.appendSubclassData(packetData, params, modelTreeElementExtraEncodeData, requestedProperties, + propertyFlags, propertiesDidntFit, propertyCount, appendState); + _hazeProperties.appendSubclassData(packetData, params, modelTreeElementExtraEncodeData, requestedProperties, + propertyFlags, propertiesDidntFit, propertyCount, appendState); + _bloomProperties.appendSubclassData(packetData, params, modelTreeElementExtraEncodeData, requestedProperties, + propertyFlags, propertiesDidntFit, propertyCount, appendState); APPEND_ENTITY_PROPERTY(PROP_SHAPE_TYPE, (uint32_t)getShapeType()); APPEND_ENTITY_PROPERTY(PROP_COMPOUND_SHAPE_URL, getCompoundShapeURL()); - _skyboxProperties.appendSubclassData(packetData, params, modelTreeElementExtraEncodeData, requestedProperties, - propertyFlags, propertiesDidntFit, propertyCount, appendState); - APPEND_ENTITY_PROPERTY(PROP_FLYING_ALLOWED, getFlyingAllowed()); APPEND_ENTITY_PROPERTY(PROP_GHOSTING_ALLOWED, getGhostingAllowed()); APPEND_ENTITY_PROPERTY(PROP_FILTER_URL, getFilterURL()); APPEND_ENTITY_PROPERTY(PROP_HAZE_MODE, (uint32_t)getHazeMode()); - _hazeProperties.appendSubclassData(packetData, params, modelTreeElementExtraEncodeData, requestedProperties, - propertyFlags, propertiesDidntFit, propertyCount, appendState); - APPEND_ENTITY_PROPERTY(PROP_KEY_LIGHT_MODE, (uint32_t)getKeyLightMode()); APPEND_ENTITY_PROPERTY(PROP_AMBIENT_LIGHT_MODE, (uint32_t)getAmbientLightMode()); APPEND_ENTITY_PROPERTY(PROP_SKYBOX_MODE, (uint32_t)getSkyboxMode()); + APPEND_ENTITY_PROPERTY(PROP_BLOOM_MODE, (uint32_t)getBloomMode()); } void ZoneEntityItem::debugDump() const { quint64 now = usecTimestampNow(); qCDebug(entities) << " ZoneEntityItem id:" << getEntityItemID() << "---------------------------------------------"; - qCDebug(entities) << " position:" << debugTreeVector(getWorldPosition()); - qCDebug(entities) << " dimensions:" << debugTreeVector(getScaledDimensions()); - qCDebug(entities) << " getLastEdited:" << debugTime(getLastEdited(), now); - qCDebug(entities) << " _hazeMode:" << EntityItemProperties::getComponentModeString(_hazeMode); - qCDebug(entities) << " _keyLightMode:" << EntityItemProperties::getComponentModeString(_keyLightMode); - qCDebug(entities) << " _ambientLightMode:" << EntityItemProperties::getComponentModeString(_ambientLightMode); - qCDebug(entities) << " _skyboxMode:" << EntityItemProperties::getComponentModeString(_skyboxMode); + qCDebug(entities) << " position:" << debugTreeVector(getWorldPosition()); + qCDebug(entities) << " dimensions:" << debugTreeVector(getScaledDimensions()); + qCDebug(entities) << " getLastEdited:" << debugTime(getLastEdited(), now); + qCDebug(entities) << " _hazeMode:" << EntityItemProperties::getComponentModeString(_hazeMode); + qCDebug(entities) << " _keyLightMode:" << EntityItemProperties::getComponentModeString(_keyLightMode); + qCDebug(entities) << " _ambientLightMode:" << EntityItemProperties::getComponentModeString(_ambientLightMode); + qCDebug(entities) << " _skyboxMode:" << EntityItemProperties::getComponentModeString(_skyboxMode); + qCDebug(entities) << " _bloomMode:" << EntityItemProperties::getComponentModeString(_bloomMode); _keyLightProperties.debugDump(); _ambientLightProperties.debugDump(); _skyboxProperties.debugDump(); _hazeProperties.debugDump(); + _bloomProperties.debugDump(); } ShapeType ZoneEntityItem::getShapeType() const { @@ -294,10 +293,16 @@ void ZoneEntityItem::setCompoundShapeURL(const QString& url) { } bool ZoneEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - OctreeElementPointer& element, float& distance, + OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) const { + return _zonesArePickable; +} +bool ZoneEntityItem::findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, + const glm::vec3& acceleration, OctreeElementPointer& element, float& parabolicDistance, + BoxFace& face, glm::vec3& surfaceNormal, + QVariantMap& extraInfo, bool precisionPicking) const { return _zonesArePickable; } @@ -338,6 +343,7 @@ void ZoneEntityItem::resetRenderingPropertiesChanged() { _ambientLightPropertiesChanged = false; _skyboxPropertiesChanged = false; _hazePropertiesChanged = false; + _bloomPropertiesChanged = false; _stagePropertiesChanged = false; }); } @@ -353,6 +359,17 @@ uint32_t ZoneEntityItem::getHazeMode() const { return _hazeMode; } +void ZoneEntityItem::setBloomMode(const uint32_t value) { + if (value < COMPONENT_MODE_ITEM_COUNT && value != _bloomMode) { + _bloomMode = value; + _bloomPropertiesChanged = true; + } +} + +uint32_t ZoneEntityItem::getBloomMode() const { + return _bloomMode; +} + void ZoneEntityItem::setKeyLightMode(const uint32_t value) { if (value < COMPONENT_MODE_ITEM_COUNT && value != _keyLightMode) { _keyLightMode = value; diff --git a/libraries/entities/src/ZoneEntityItem.h b/libraries/entities/src/ZoneEntityItem.h index 3a9c7cb1e6..e2ebf16f11 100644 --- a/libraries/entities/src/ZoneEntityItem.h +++ b/libraries/entities/src/ZoneEntityItem.h @@ -18,6 +18,7 @@ #include "EntityTree.h" #include "SkyboxPropertyGroup.h" #include "HazePropertyGroup.h" +#include "BloomPropertyGroup.h" #include class ZoneEntityItem : public EntityItem { @@ -79,9 +80,13 @@ public: void setSkyboxMode(uint32_t value); uint32_t getSkyboxMode() const; + void setBloomMode(const uint32_t value); + uint32_t getBloomMode() const; + SkyboxPropertyGroup getSkyboxProperties() const { return resultWithReadLock([&] { return _skyboxProperties; }); } const HazePropertyGroup& getHazeProperties() const { return _hazeProperties; } + const BloomPropertyGroup& getBloomProperties() const { return _bloomProperties; } bool getFlyingAllowed() const { return _flyingAllowed; } void setFlyingAllowed(bool value) { _flyingAllowed = value; } @@ -93,20 +98,22 @@ public: bool keyLightPropertiesChanged() const { return _keyLightPropertiesChanged; } bool ambientLightPropertiesChanged() const { return _ambientLightPropertiesChanged; } bool skyboxPropertiesChanged() const { return _skyboxPropertiesChanged; } - - bool hazePropertiesChanged() const { - return _hazePropertiesChanged; - } + bool hazePropertiesChanged() const { return _hazePropertiesChanged; } + bool bloomPropertiesChanged() const { return _bloomPropertiesChanged; } bool stagePropertiesChanged() const { return _stagePropertiesChanged; } void resetRenderingPropertiesChanged(); - virtual bool supportsDetailedRayIntersection() const override { return true; } + virtual bool supportsDetailedIntersection() const override { return true; } virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) const override; + virtual bool findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, + const glm::vec3& acceleration, OctreeElementPointer& element, float& parabolicDistance, + BoxFace& face, glm::vec3& surfaceNormal, + QVariantMap& extraInfo, bool precisionPicking) const override; virtual void debugDump() const override; @@ -129,9 +136,11 @@ protected: uint32_t _ambientLightMode { COMPONENT_MODE_INHERIT }; uint32_t _hazeMode { COMPONENT_MODE_INHERIT }; + uint32_t _bloomMode { COMPONENT_MODE_INHERIT }; SkyboxPropertyGroup _skyboxProperties; HazePropertyGroup _hazeProperties; + BloomPropertyGroup _bloomProperties; bool _flyingAllowed { DEFAULT_FLYING_ALLOWED }; bool _ghostingAllowed { DEFAULT_GHOSTING_ALLOWED }; @@ -142,6 +151,7 @@ protected: bool _ambientLightPropertiesChanged { false }; bool _skyboxPropertiesChanged { false }; bool _hazePropertiesChanged{ false }; + bool _bloomPropertiesChanged { false }; bool _stagePropertiesChanged { false }; static bool _drawZoneBoundaries; diff --git a/libraries/fbx/src/FBX.h b/libraries/fbx/src/FBX.h index fc94236c96..7bd87a4554 100644 --- a/libraries/fbx/src/FBX.h +++ b/libraries/fbx/src/FBX.h @@ -28,6 +28,20 @@ #include #include +#if defined(Q_OS_ANDROID) +#define FBX_PACK_NORMALS 0 +#else +#define FBX_PACK_NORMALS 1 +#endif + +#if FBX_PACK_NORMALS +using NormalType = glm::uint32; +#define FBX_NORMAL_ELEMENT gpu::Element::VEC4F_NORMALIZED_XYZ10W2 +#else +using NormalType = glm::vec3; +#define FBX_NORMAL_ELEMENT gpu::Element::VEC3F_XYZ +#endif + // See comment in FBXReader::parseFBX(). static const int FBX_HEADER_BYTES_BEFORE_VERSION = 23; static const QByteArray FBX_BINARY_PROLOG("Kaydara FBX Binary "); @@ -226,6 +240,7 @@ public: QVector vertices; QVector normals; QVector tangents; + mutable QVector normalsAndTangents; // Populated later if needed for blendshapes QVector colors; QVector texCoords; QVector texCoords1; diff --git a/libraries/fbx/src/FBXReader.h b/libraries/fbx/src/FBXReader.h index ad002b237c..c391ea6647 100644 --- a/libraries/fbx/src/FBXReader.h +++ b/libraries/fbx/src/FBXReader.h @@ -34,20 +34,6 @@ class QIODevice; class FBXNode; -#if defined(Q_OS_ANDROID) -#define FBX_PACK_NORMALS 0 -#else -#define FBX_PACK_NORMALS 1 -#endif - -#if FBX_PACK_NORMALS -using NormalType = glm::uint32; -#define FBX_NORMAL_ELEMENT gpu::Element::VEC4F_NORMALIZED_XYZ10W2 -#else -using NormalType = glm::vec3; -#define FBX_NORMAL_ELEMENT gpu::Element::VEC3F_XYZ -#endif - /// Reads FBX geometry from the supplied model and mapping data. /// \exception QString if an error occurs in parsing FBXGeometry* readFBX(const QByteArray& model, const QVariantHash& mapping, const QString& url = "", bool loadLightmaps = true, float lightmapLevel = 1.0f); diff --git a/libraries/fbx/src/GLTFReader.cpp b/libraries/fbx/src/GLTFReader.cpp index 300e6f4846..317342b886 100644 --- a/libraries/fbx/src/GLTFReader.cpp +++ b/libraries/fbx/src/GLTFReader.cpp @@ -935,7 +935,7 @@ FBXGeometry* GLTFReader::readGLTF(QByteArray& model, const QVariantHash& mapping } bool GLTFReader::readBinary(const QString& url, QByteArray& outdata) { - QUrl binaryUrl = _url.resolved(QUrl(url).fileName()); + QUrl binaryUrl = _url.resolved(url); qCDebug(modelformat) << "binaryUrl: " << binaryUrl << " OriginalUrl: " << _url; bool success; @@ -948,7 +948,7 @@ bool GLTFReader::doesResourceExist(const QString& url) { if (_url.isEmpty()) { return false; } - QUrl candidateUrl = _url.resolved(QUrl(url).fileName()); + QUrl candidateUrl = _url.resolved(url); return DependencyManager::get()->resourceExists(candidateUrl); } @@ -1001,8 +1001,9 @@ FBXTexture GLTFReader::getFBXTexture(const GLTFTexture& texture) { fbxtex.texcoordSet = 0; if (texture.defined["source"]) { - QString fname = QUrl(_file.images[texture.source].uri).fileName(); - QUrl textureUrl = _url.resolved(fname); + QString url = _file.images[texture.source].uri; + QString fname = QUrl(url).fileName(); + QUrl textureUrl = _url.resolved(url); qCDebug(modelformat) << "fname: " << fname; qCDebug(modelformat) << "textureUrl: " << textureUrl; qCDebug(modelformat) << "Url: " << _url; diff --git a/libraries/fbx/src/OBJReader.cpp b/libraries/fbx/src/OBJReader.cpp index caac08f777..c46a1e234c 100644 --- a/libraries/fbx/src/OBJReader.cpp +++ b/libraries/fbx/src/OBJReader.cpp @@ -263,15 +263,19 @@ void OBJReader::parseMaterialLibrary(QIODevice* device) { default: materials[matName] = currentMaterial; #ifdef WANT_DEBUG - qCDebug(modelformat) << "OBJ Reader Last material illumination model:" << currentMaterial.illuminationModel << - " shininess:" << currentMaterial.shininess << " opacity:" << currentMaterial.opacity << - " diffuse color:" << currentMaterial.diffuseColor << " specular color:" << - currentMaterial.specularColor << " emissive color:" << currentMaterial.emissiveColor << - " diffuse texture:" << currentMaterial.diffuseTextureFilename << " specular texture:" << - currentMaterial.specularTextureFilename << " emissive texture:" << - currentMaterial.emissiveTextureFilename << " bump texture:" << - currentMaterial.bumpTextureFilename; - #endif + qCDebug(modelformat) << + "OBJ Reader Last material illumination model:" << currentMaterial.illuminationModel << + " shininess:" << currentMaterial.shininess << + " opacity:" << currentMaterial.opacity << + " diffuse color:" << currentMaterial.diffuseColor << + " specular color:" << currentMaterial.specularColor << + " emissive color:" << currentMaterial.emissiveColor << + " diffuse texture:" << currentMaterial.diffuseTextureFilename << + " specular texture:" << currentMaterial.specularTextureFilename << + " emissive texture:" << currentMaterial.emissiveTextureFilename << + " bump texture:" << currentMaterial.bumpTextureFilename << + " opacity texture:" << currentMaterial.opacityTextureFilename; +#endif return; } QByteArray token = tokenizer.getDatum(); @@ -289,6 +293,8 @@ void OBJReader::parseMaterialLibrary(QIODevice* device) { currentMaterial.emissiveTextureFilename = ""; currentMaterial.specularTextureFilename = ""; currentMaterial.bumpTextureFilename = ""; + currentMaterial.opacityTextureFilename = ""; + } else if (token == "Ns") { currentMaterial.shininess = tokenizer.getFloat(); } else if (token == "Ni") { @@ -321,7 +327,7 @@ void OBJReader::parseMaterialLibrary(QIODevice* device) { currentMaterial.emissiveColor = tokenizer.getVec3(); } else if (token == "Ks") { currentMaterial.specularColor = tokenizer.getVec3(); - } else if ((token == "map_Kd") || (token == "map_Ke") || (token == "map_Ks") || (token == "map_bump") || (token == "bump")) { + } else if ((token == "map_Kd") || (token == "map_Ke") || (token == "map_Ks") || (token == "map_bump") || (token == "bump") || (token == "map_d")) { const QByteArray textureLine = tokenizer.getLineAsDatum(); QByteArray filename; OBJMaterialTextureOptions textureOptions; @@ -341,6 +347,8 @@ void OBJReader::parseMaterialLibrary(QIODevice* device) { } else if ((token == "map_bump") || (token == "bump")) { currentMaterial.bumpTextureFilename = filename; currentMaterial.bumpTextureOptions = textureOptions; + } else if (token == "map_d") { + currentMaterial.opacityTextureFilename = filename; } } } @@ -900,6 +908,9 @@ FBXGeometry::Pointer OBJReader::readOBJ(QByteArray& model, const QVariantHash& m fbxMaterial.normalTexture.isBumpmap = true; fbxMaterial.bumpMultiplier = objMaterial.bumpTextureOptions.bumpMultiplier; } + if (!objMaterial.opacityTextureFilename.isEmpty()) { + fbxMaterial.opacityTexture.filename = objMaterial.opacityTextureFilename; + } modelMaterial->setEmissive(fbxMaterial.emissiveColor); modelMaterial->setAlbedo(fbxMaterial.diffuseColor); diff --git a/libraries/fbx/src/OBJReader.h b/libraries/fbx/src/OBJReader.h index 13ddc6e21c..e432a3ea51 100644 --- a/libraries/fbx/src/OBJReader.h +++ b/libraries/fbx/src/OBJReader.h @@ -66,6 +66,8 @@ public: QByteArray specularTextureFilename; QByteArray emissiveTextureFilename; QByteArray bumpTextureFilename; + QByteArray opacityTextureFilename; + OBJMaterialTextureOptions bumpTextureOptions; int illuminationModel; bool used { false }; diff --git a/libraries/gl/src/gl/Context.cpp b/libraries/gl/src/gl/Context.cpp index 309839808e..ad7e51fbd3 100644 --- a/libraries/gl/src/gl/Context.cpp +++ b/libraries/gl/src/gl/Context.cpp @@ -269,7 +269,9 @@ void Context::create() { #if defined(USE_GLES) _version = 0x0200; #else - if (GLAD_GL_VERSION_4_5) { + if (gl::disableGl45()) { + _version = 0x0401; + } else if (GLAD_GL_VERSION_4_5) { _version = 0x0405; } else if (GLAD_GL_VERSION_4_3) { _version = 0x0403; diff --git a/libraries/gl/src/gl/GLHelpers.cpp b/libraries/gl/src/gl/GLHelpers.cpp index e3f85afa78..7ebba4f8d8 100644 --- a/libraries/gl/src/gl/GLHelpers.cpp +++ b/libraries/gl/src/gl/GLHelpers.cpp @@ -24,6 +24,26 @@ size_t evalGLFormatSwapchainPixelSize(const QSurfaceFormat& format) { return pixelSize; } +bool gl::disableGl45() { +#if defined(USE_GLES) + return false; +#else + static const QString DEBUG_FLAG("HIFI_DISABLE_OPENGL_45"); + static bool disableOpenGL45 = QProcessEnvironment::systemEnvironment().contains(DEBUG_FLAG); + return disableOpenGL45; +#endif +} + +void gl::getTargetVersion(int& major, int& minor) { +#if defined(USE_GLES) + major = 3; + minor = 2; +#else + major = 4; + minor = disableGl45() ? 1 : 5; +#endif +} + const QSurfaceFormat& getDefaultOpenGLSurfaceFormat() { static QSurfaceFormat format; static std::once_flag once; @@ -40,7 +60,10 @@ const QSurfaceFormat& getDefaultOpenGLSurfaceFormat() { // Qt Quick may need a depth and stencil buffer. Always make sure these are available. format.setDepthBufferSize(DEFAULT_GL_DEPTH_BUFFER_BITS); format.setStencilBufferSize(DEFAULT_GL_STENCIL_BUFFER_BITS); - setGLFormatVersion(format); + int major, minor; + ::gl::getTargetVersion(major, minor); + format.setMajorVersion(major); + format.setMinorVersion(minor); QSurfaceFormat::setDefaultFormat(format); }); return format; diff --git a/libraries/gl/src/gl/GLHelpers.h b/libraries/gl/src/gl/GLHelpers.h index dcbf5a9e77..6252eba2f0 100644 --- a/libraries/gl/src/gl/GLHelpers.h +++ b/libraries/gl/src/gl/GLHelpers.h @@ -26,15 +26,6 @@ class QOpenGLDebugMessage; class QSurfaceFormat; class QGLFormat; -template -// https://bugreports.qt.io/browse/QTBUG-64703 prevents us from using "defined(QT_OPENGL_ES_3_1)" -#if defined(USE_GLES) -void setGLFormatVersion(F& format, int major = 3, int minor = 2) -#else -void setGLFormatVersion(F& format, int major = 4, int minor = 5) -#endif - { format.setVersion(major, minor); } - size_t evalGLFormatSwapchainPixelSize(const QSurfaceFormat& format); const QSurfaceFormat& getDefaultOpenGLSurfaceFormat(); @@ -50,6 +41,9 @@ namespace gl { bool checkGLErrorDebug(const char* name); + bool disableGl45(); + + void getTargetVersion(int& major, int& minor); } // namespace gl #define CHECK_GL_ERROR() ::gl::checkGLErrorDebug(__FUNCTION__) diff --git a/libraries/gl/src/gl/GLShaders.cpp b/libraries/gl/src/gl/GLShaders.cpp index 9bfe214fcf..a0d976d727 100644 --- a/libraries/gl/src/gl/GLShaders.cpp +++ b/libraries/gl/src/gl/GLShaders.cpp @@ -13,32 +13,194 @@ using namespace gl; void Uniform::load(GLuint glprogram, int index) { + this->index = index; const GLint NAME_LENGTH = 256; GLchar glname[NAME_LENGTH]; GLint length = 0; glGetActiveUniform(glprogram, index, NAME_LENGTH, &length, &size, &type, glname); + // Length does NOT include the null terminator + // https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glGetActiveUniform.xhtml name = std::string(glname, length); - location = glGetUniformLocation(glprogram, glname); + binding = glGetUniformLocation(glprogram, glname); } -Uniforms gl::loadUniforms(GLuint glprogram) { +bool isTextureType(GLenum type) { + switch (type) { +#ifndef USE_GLES + case GL_SAMPLER_1D: + case GL_SAMPLER_1D_ARRAY: + case GL_SAMPLER_1D_SHADOW: + case GL_SAMPLER_1D_ARRAY_SHADOW: +#endif + case GL_SAMPLER_2D: + case GL_SAMPLER_3D: + case GL_SAMPLER_CUBE: + case GL_SAMPLER_2D_ARRAY: + case GL_SAMPLER_CUBE_MAP_ARRAY: + case GL_SAMPLER_2D_SHADOW: + case GL_SAMPLER_2D_ARRAY_SHADOW: + case GL_SAMPLER_BUFFER: + return true; + default: + break; + } + return false; +} + +Uniforms Uniform::load(GLuint glprogram, const std::function& filter) { + Uniforms result; GLint uniformsCount = 0; glGetProgramiv(glprogram, GL_ACTIVE_UNIFORMS, &uniformsCount); - - Uniforms result; - result.resize(uniformsCount); + result.reserve(uniformsCount); for (int i = 0; i < uniformsCount; i++) { - result[i].load(glprogram, i); + result.emplace_back(glprogram, i); + } + result.erase(std::remove_if(result.begin(), result.end(), filter), result.end()); + return result; +} + + +Uniforms Uniform::loadTextures(GLuint glprogram) { + return load(glprogram, [](const Uniform& uniform) -> bool { + if (std::string::npos != uniform.name.find('.')) { + return true; + } + if (std::string::npos != uniform.name.find('[')) { + return true; + } + if (!isTextureType(uniform.type)) { + return true; + } + return false; + }); +} + +Uniforms Uniform::load(GLuint glprogram) { + return load(glprogram, [](const Uniform& uniform) -> bool { + if (std::string::npos != uniform.name.find('.')) { + return true; + } + if (std::string::npos != uniform.name.find('[')) { + return true; + } + if (isTextureType(uniform.type)) { + return true; + } + return false; + }); +} + +Uniforms Uniform::load(GLuint glprogram, const std::vector& indices) { + Uniforms result; + result.reserve(indices.size()); + for (const auto& i : indices) { + if (i == GL_INVALID_INDEX) { + continue; + } + result.emplace_back(glprogram, i); + } + return result; +} + +Uniform Uniform::loadByName(GLuint glprogram, const std::string& name) { + GLuint index; + const char* nameCStr = name.c_str(); + glGetUniformIndices(glprogram, 1, &nameCStr, &index); + Uniform result; + if (index != GL_INVALID_INDEX) { + result.load(glprogram, index); + } + return result; +} + + +Uniforms Uniform::load(GLuint glprogram, const std::vector& cnames) { + GLsizei count = static_cast(cnames.size()); + if (0 == count) { + return {}; + } + std::vector indices; + indices.resize(count); + glGetUniformIndices(glprogram, count, cnames.data(), indices.data()); + return load(glprogram, indices); +} + + +template +std::vector toCNames(const C& container, F lambda) { + std::vector result; + result.reserve(container.size()); + std::transform(container.begin(), container.end(), std::back_inserter(result), lambda); + return result; +} + +Uniforms Uniform::load(GLuint glprogram, const std::vector& names) { + auto cnames = toCNames(names, [](const std::string& name) { return name.c_str(); }); + return load(glprogram, cnames); +} + +void UniformBlock::load(GLuint glprogram, int index) { + this->index = index; + GLint length = 0; + + // Length DOES include the null terminator + // https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glGetActiveUniformBlock.xhtml + glGetActiveUniformBlockiv(glprogram, index, GL_UNIFORM_BLOCK_NAME_LENGTH, &length); + if (length > 1) { + std::vector nameBuffer; + nameBuffer.resize(length); + glGetActiveUniformBlockName(glprogram, index, length, nullptr, nameBuffer.data()); + name = std::string(nameBuffer.data(), length - 1); + } + glGetActiveUniformBlockiv(glprogram, index, GL_UNIFORM_BLOCK_BINDING, &binding); + glGetActiveUniformBlockiv(glprogram, index, GL_UNIFORM_BLOCK_DATA_SIZE, &size); +} + +UniformBlocks UniformBlock::load(GLuint glprogram) { + GLint buffersCount = -1; + glGetProgramiv(glprogram, GL_ACTIVE_UNIFORM_BLOCKS, &buffersCount); + + // fast exit + if (buffersCount <= 0) { + return {}; + } + + UniformBlocks uniformBlocks; + for (int i = 0; i < buffersCount; ++i) { + uniformBlocks.emplace_back(glprogram, i); + } + return uniformBlocks; +} + +void Input::load(GLuint glprogram, int index) { + const GLint NAME_LENGTH = 256; + GLchar name[NAME_LENGTH]; + GLint length = 0; + // Length does NOT include the null terminator + // https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glGetActiveAttrib.xhtml + glGetActiveAttrib(glprogram, index, NAME_LENGTH, &length, &size, &type, name); + if (length > 0) { + this->name = std::string(name, length); + } + binding = glGetAttribLocation(glprogram, name); +} + +Inputs Input::load(GLuint glprogram) { + Inputs result; + GLint count; + glGetProgramiv(glprogram, GL_ACTIVE_ATTRIBUTES, &count); + for (int i = 0; i < count; ++i) { + result.emplace_back(glprogram, i); } return result; } #ifdef SEPARATE_PROGRAM bool gl::compileShader(GLenum shaderDomain, - const std::string& shaderSource, - GLuint& shaderObject, - GLuint& programObject, - std::string& message) { + const std::string& shaderSource, + GLuint& shaderObject, + GLuint& programObject, + std::string& message) { return compileShader(shaderDomain, std::vector{ shaderSource }, shaderObject, programObject, message); } #else @@ -49,15 +211,15 @@ bool gl::compileShader(GLenum shaderDomain, const std::string& shaderSource, GLu #ifdef SEPARATE_PROGRAM bool gl::compileShader(GLenum shaderDomain, - const std::string& shaderSource, - GLuint& shaderObject, - GLuint& programObject, - std::string& message) { + const std::string& shaderSource, + GLuint& shaderObject, + GLuint& programObject, + std::string& message) { #else bool gl::compileShader(GLenum shaderDomain, - const std::vector& shaderSources, - GLuint& shaderObject, - std::string& message) { + const std::vector& shaderSources, + GLuint& shaderObject, + std::string& message) { #endif if (shaderSources.empty()) { qCDebug(glLogging) << "GLShader::compileShader - no GLSL shader source code ? so failed to create"; @@ -85,57 +247,28 @@ bool gl::compileShader(GLenum shaderDomain, GLint compiled = 0; glGetShaderiv(glshader, GL_COMPILE_STATUS, &compiled); - GLint infoLength = 0; - glGetShaderiv(glshader, GL_INFO_LOG_LENGTH, &infoLength); - - if ((infoLength > 0) || !compiled) { - char* temp = new char[infoLength]; - glGetShaderInfoLog(glshader, infoLength, NULL, temp); - - message = std::string(temp); - - // if compilation fails - if (!compiled) { - // save the source code to a temp file so we can debug easily - /* - std::ofstream filestream; - filestream.open("debugshader.glsl"); - if (filestream.is_open()) { - filestream << srcstr[0]; - filestream << srcstr[1]; - filestream.close(); + getShaderInfoLog(glshader, message); + // if compilation fails + if (!compiled) { + qCCritical(glLogging) << "GLShader::compileShader - failed to compile the gl shader object:"; + int lineNumber = 0; + for (const auto& s : cstrs) { + QString str(s); + QStringList lines = str.split("\n"); + for (auto& line : lines) { + qCCritical(glLogging).noquote() << QString("%1: %2").arg(lineNumber++, 5, 10, QChar('0')).arg(line); } - */ - - /* - filestream.open("debugshader.glsl.info.txt"); - if (filestream.is_open()) { - filestream << std::string(temp); - filestream.close(); - } - */ - - qCCritical(glLogging) << "GLShader::compileShader - failed to compile the gl shader object:"; - int lineNumber = 0; - for (const auto& s : cstrs) { - QString str(s); - QStringList lines = str.split("\n"); - for (auto& line : lines) { - qCCritical(glLogging).noquote() << QString("%1: %2").arg(lineNumber++, 5, 10, QChar('0')).arg(line); - } - } - qCCritical(glLogging) << "GLShader::compileShader - errors:"; - qCCritical(glLogging) << temp; - - delete[] temp; - glDeleteShader(glshader); - return false; } + qCCritical(glLogging) << "GLShader::compileShader - errors:"; + qCCritical(glLogging) << message.c_str(); + glDeleteShader(glshader); + return false; + } + if (!message.empty()) { // Compilation success qCWarning(glLogging) << "GLShader::compileShader - Success:"; - qCWarning(glLogging) << temp; - delete[] temp; + qCWarning(glLogging) << message.c_str(); } #ifdef SEPARATE_PROGRAM @@ -193,7 +326,47 @@ bool gl::compileShader(GLenum shaderDomain, return true; } -GLuint gl::compileProgram(const std::vector& glshaders, std::string& message, CachedShader& cachedShader) { +void gl::getShaderInfoLog(GLuint glshader, std::string& message) { + std::string result; + GLint infoLength = 0; + glGetShaderiv(glshader, GL_INFO_LOG_LENGTH, &infoLength); + if (infoLength > 0) { + char* temp = new char[infoLength]; + glGetShaderInfoLog(glshader, infoLength, NULL, temp); + message = std::string(temp); + delete[] temp; + } else { + message.clear(); + } +} + +void gl::getProgramInfoLog(GLuint glprogram, std::string& message) { + std::string result; + GLint infoLength = 0; + glGetProgramiv(glprogram, GL_INFO_LOG_LENGTH, &infoLength); + if (infoLength > 0) { + char* temp = new char[infoLength]; + glGetProgramInfoLog(glprogram, infoLength, NULL, temp); + message = std::string(temp); + delete[] temp; + } else { + message.clear(); + } +} + +void gl::getProgramBinary(GLuint glprogram, CachedShader& cachedShader) { + GLint binaryLength = 0; + glGetProgramiv(glprogram, GL_PROGRAM_BINARY_LENGTH, &binaryLength); + if (binaryLength > 0) { + cachedShader.binary.resize(binaryLength); + glGetProgramBinary(glprogram, binaryLength, NULL, &cachedShader.format, cachedShader.binary.data()); + } else { + cachedShader.binary.clear(); + cachedShader.format = 0; + } +} + +GLuint gl::buildProgram(const std::vector& glshaders) { // A brand new program: GLuint glprogram = glCreateProgram(); if (!glprogram) { @@ -201,80 +374,55 @@ GLuint gl::compileProgram(const std::vector& glshaders, std::string& mes return 0; } - bool binaryLoaded = false; - - if (glshaders.empty() && cachedShader) { - glProgramBinary(glprogram, cachedShader.format, cachedShader.binary.data(), (GLsizei)cachedShader.binary.size()); - binaryLoaded = true; - } else { - // glProgramParameteri(glprogram, GL_PROGRAM_, GL_TRUE); - // Create the program from the sub shaders - for (auto so : glshaders) { - glAttachShader(glprogram, so); - } - - // Link! - glLinkProgram(glprogram); - } - - GLint linked = 0; - glGetProgramiv(glprogram, GL_LINK_STATUS, &linked); - - GLint infoLength = 0; - glGetProgramiv(glprogram, GL_INFO_LOG_LENGTH, &infoLength); - - if ((infoLength > 0) || !linked) { - char* temp = new char[infoLength]; - glGetProgramInfoLog(glprogram, infoLength, NULL, temp); - - message = std::string(temp); - - if (!linked) { - /* - // save the source code to a temp file so we can debug easily - std::ofstream filestream; - filestream.open("debugshader.glsl"); - if (filestream.is_open()) { - filestream << shaderSource->source; - filestream.close(); - } - */ - - qCDebug(glLogging) << "GLShader::compileProgram - failed to LINK the gl program object :"; - qCDebug(glLogging) << temp; - - delete[] temp; - - /* - filestream.open("debugshader.glsl.info.txt"); - if (filestream.is_open()) { - filestream << std::string(temp); - filestream.close(); - } - */ - - glDeleteProgram(glprogram); - return 0; - } else { - qCDebug(glLogging) << "GLShader::compileProgram - success:"; - qCDebug(glLogging) << temp; - delete[] temp; - } - } - - // If linked get the binaries - if (linked && !binaryLoaded) { - GLint binaryLength = 0; - glGetProgramiv(glprogram, GL_PROGRAM_BINARY_LENGTH, &binaryLength); - if (binaryLength > 0) { - cachedShader.binary.resize(binaryLength); - glGetProgramBinary(glprogram, binaryLength, NULL, &cachedShader.format, cachedShader.binary.data()); - } + // glProgramParameteri(glprogram, GL_PROGRAM_, GL_TRUE); + // Create the program from the sub shaders + for (auto so : glshaders) { + glAttachShader(glprogram, so); } return glprogram; } + +GLuint gl::buildProgram(const CachedShader& cachedShader) { + // A brand new program: + GLuint glprogram = glCreateProgram(); + if (!glprogram) { + qCDebug(glLogging) << "GLShader::compileProgram - failed to create the gl program object"; + return 0; + } + glProgramBinary(glprogram, cachedShader.format, cachedShader.binary.data(), (GLsizei)cachedShader.binary.size()); + GLint linked = 0; + glGetProgramiv(glprogram, GL_LINK_STATUS, &linked); + if (!linked) { + glDeleteProgram(glprogram); + return 0; + } + + return glprogram; +} + + +bool gl::linkProgram(GLuint glprogram, std::string& message) { + glLinkProgram(glprogram); + + GLint linked = 0; + glGetProgramiv(glprogram, GL_LINK_STATUS, &linked); + ::gl::getProgramInfoLog(glprogram, message); + if (!linked) { + qCDebug(glLogging) << "GLShader::compileProgram - failed to LINK the gl program object :"; + qCDebug(glLogging) << message.c_str(); + return false; + } + + if (!message.empty()) { + qCDebug(glLogging) << "GLShader::compileProgram - success:"; + qCDebug(glLogging) << message.c_str(); + } + + return true; +} + const QString& getShaderCacheFile() { static const QString SHADER_CACHE_FOLDER{ "shaders" }; static const QString SHADER_CACHE_FILE_NAME{ "cache.json" }; @@ -287,6 +435,7 @@ static const char* SHADER_JSON_SOURCE_KEY = "source"; static const char* SHADER_JSON_DATA_KEY = "data"; void gl::loadShaderCache(ShaderCache& cache) { +#if !defined(DISABLE_QML) QString shaderCacheFile = getShaderCacheFile(); if (QFileInfo(shaderCacheFile).exists()) { QString json = FileUtils::readFile(shaderCacheFile); @@ -302,6 +451,7 @@ void gl::loadShaderCache(ShaderCache& cache) { cachedShader.source = programObject[SHADER_JSON_SOURCE_KEY].toString().toStdString(); } } +#endif } void gl::saveShaderCache(const ShaderCache& cache) { diff --git a/libraries/gl/src/gl/GLShaders.h b/libraries/gl/src/gl/GLShaders.h index e6c11b4eb3..c12e9d2305 100644 --- a/libraries/gl/src/gl/GLShaders.h +++ b/libraries/gl/src/gl/GLShaders.h @@ -12,50 +12,114 @@ #include "Config.h" -#include +#include #include #include +#include +#include namespace gl { - struct Uniform { - std::string name; - GLint size{ -1 }; - GLenum type{ GL_FLOAT }; - GLint location{ -1 }; - void load(GLuint glprogram, int index); - }; +struct ShaderBinding { + int index; + std::string name; + GLint size{ -1 }; + GLint binding{ -1 }; +}; - using Uniforms = std::vector; +struct Uniform : public ShaderBinding { + Uniform(){}; + Uniform(GLint program, int index) { load(program, index); }; + using Vector = std::vector; + GLenum type{ GL_FLOAT }; - Uniforms loadUniforms(GLuint glprogram); + void load(GLuint glprogram, int index); + // Incredibly slow on mac, DO NOT USE + static Vector load(GLuint glprogram, const std::function& filter); + static Vector loadTextures(GLuint glprogram); + static Vector load(GLuint glprogram); + static Vector load(GLuint glprogram, const std::vector& indices); + static Vector load(GLuint glprogram, const std::vector& names); + static Vector load(GLuint glprogram, const std::vector& names); + static Uniform loadByName(GLuint glprogram, const std::string& names); - struct CachedShader { - GLenum format{ 0 }; - std::string source; - std::vector binary; - inline operator bool() const { - return format != 0 && !binary.empty(); + template + static Vector loadByName(GLuint glprogram, const C& names) { + if (names.empty()) { + return {}; } - }; + std::vector cnames; + cnames.reserve(names.size()); + for (const auto& name : names) { + cnames.push_back(name.c_str()); + } + return load(glprogram, cnames); + } +}; - using ShaderCache = std::unordered_map; +using Uniforms = Uniform::Vector; - std::string getShaderHash(const std::string& shaderSource); - void loadShaderCache(ShaderCache& cache); - void saveShaderCache(const ShaderCache& cache); +struct UniformBlock : public ShaderBinding { + UniformBlock(){}; + UniformBlock(GLint program, int index) { load(program, index); }; + using Vector = std::vector; + void load(GLuint glprogram, int index); + static Vector load(GLuint glprogram); +}; + +using UniformBlocks = UniformBlock::Vector; + +struct Input : public ShaderBinding { + Input(){}; + Input(GLint program, int index) { load(program, index); }; + using Vector = std::vector; + GLenum type{ GL_FLOAT }; + + void load(GLuint glprogram, int index); + static Vector load(GLuint glprogram); +}; + +using Inputs = Input::Vector; + +struct CachedShader { + GLenum format{ 0 }; + std::string source; + std::vector binary; + inline operator bool() const { return format != 0 && !binary.empty(); } +}; + +using ShaderCache = std::unordered_map; + +std::string getShaderHash(const std::string& shaderSource); +void loadShaderCache(ShaderCache& cache); +void saveShaderCache(const ShaderCache& cache); #ifdef SEPARATE_PROGRAM - bool compileShader(GLenum shaderDomain, const std::string& shaderSource, GLuint &shaderObject, GLuint &programObject, std::string& message); - bool compileShader(GLenum shaderDomain, const std::vector& shaderSources, GLuint &shaderObject, GLuint &programObject, std::string& message); +bool compileShader(GLenum shaderDomain, + const std::string& shaderSource, + GLuint& shaderObject, + GLuint& programObject, + std::string& message); +bool compileShader(GLenum shaderDomain, + const std::vector& shaderSources, + GLuint& shaderObject, + GLuint& programObject, + std::string& message); #else - bool compileShader(GLenum shaderDomain, const std::string& shaderSource, GLuint &shaderObject, std::string& message); - bool compileShader(GLenum shaderDomain, const std::vector& shaderSources, GLuint &shaderObject, std::string& message); +bool compileShader(GLenum shaderDomain, const std::string& shaderSource, GLuint& shaderObject, std::string& message); +bool compileShader(GLenum shaderDomain, + const std::vector& shaderSources, + GLuint& shaderObject, + std::string& message); #endif - GLuint compileProgram(const std::vector& glshaders, std::string& message, CachedShader& binary); - -} +GLuint buildProgram(const std::vector& glshaders); +GLuint buildProgram(const CachedShader& binary); +bool linkProgram(GLuint glprogram, std::string& message); +void getShaderInfoLog(GLuint glshader, std::string& message); +void getProgramInfoLog(GLuint glprogram, std::string& message); +void getProgramBinary(GLuint glprogram, CachedShader& cachedShader); +} // namespace gl #endif diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLBackend.cpp b/libraries/gpu-gl-common/src/gpu/gl/GLBackend.cpp index 2321342eb4..c1848d99b1 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLBackend.cpp +++ b/libraries/gpu-gl-common/src/gpu/gl/GLBackend.cpp @@ -100,11 +100,33 @@ GLBackend::CommandCall GLBackend::_commandCalls[Batch::NUM_COMMANDS] = (&::gpu::gl::GLBackend::do_popProfileRange), }; +#define GL_GET_INTEGER(NAME) glGetIntegerv(GL_##NAME, &const_cast(NAME)); + +GLint GLBackend::MAX_TEXTURE_IMAGE_UNITS{ 0 }; +GLint GLBackend::MAX_UNIFORM_BUFFER_BINDINGS{ 0 }; +GLint GLBackend::MAX_COMBINED_UNIFORM_BLOCKS{ 0 }; +GLint GLBackend::MAX_COMBINED_TEXTURE_IMAGE_UNITS{ 0 }; +GLint GLBackend::MAX_UNIFORM_BLOCK_SIZE{ 0 }; +GLint GLBackend::UNIFORM_BUFFER_OFFSET_ALIGNMENT{ 1 }; + void GLBackend::init() { static std::once_flag once; std::call_once(once, [] { + + QString vendor{ (const char*)glGetString(GL_VENDOR) }; QString renderer{ (const char*)glGetString(GL_RENDERER) }; + + // Textures + GL_GET_INTEGER(MAX_TEXTURE_IMAGE_UNITS); + GL_GET_INTEGER(MAX_COMBINED_TEXTURE_IMAGE_UNITS); + + // Uniform blocks + GL_GET_INTEGER(MAX_UNIFORM_BUFFER_BINDINGS); + GL_GET_INTEGER(MAX_COMBINED_UNIFORM_BLOCKS); + GL_GET_INTEGER(MAX_UNIFORM_BLOCK_SIZE); + GL_GET_INTEGER(UNIFORM_BUFFER_OFFSET_ALIGNMENT); + qCDebug(gpugllogging) << "GL Version: " << QString((const char*) glGetString(GL_VERSION)); qCDebug(gpugllogging) << "GL Shader Language Version: " << QString((const char*) glGetString(GL_SHADING_LANGUAGE_VERSION)); qCDebug(gpugllogging) << "GL Vendor: " << vendor; @@ -115,15 +137,26 @@ void GLBackend::init() { qCDebug(gpugllogging) << "\tcard:" << gpu->getName(); qCDebug(gpugllogging) << "\tdriver:" << gpu->getDriver(); qCDebug(gpugllogging) << "\tdedicated memory:" << gpu->getMemory() << "MB"; + qCDebug(gpugllogging) << "Limits:"; + qCDebug(gpugllogging) << "\tmax textures:" << MAX_TEXTURE_IMAGE_UNITS; + qCDebug(gpugllogging) << "\tmax texture binding:" << MAX_COMBINED_TEXTURE_IMAGE_UNITS; + qCDebug(gpugllogging) << "\tmax uniforms:" << MAX_UNIFORM_BUFFER_BINDINGS; + qCDebug(gpugllogging) << "\tmax uniform binding:" << MAX_COMBINED_UNIFORM_BLOCKS; + qCDebug(gpugllogging) << "\tmax uniform size:" << MAX_UNIFORM_BLOCK_SIZE; + qCDebug(gpugllogging) << "\tuniform alignment:" << UNIFORM_BUFFER_OFFSET_ALIGNMENT; #if !defined(USE_GLES) qCDebug(gpugllogging, "V-Sync is %s\n", (::gl::getSwapInterval() > 0 ? "ON" : "OFF")); #endif }); } +GLBackend::GLBackend(bool syncCache) { + _pipeline._cameraCorrectionBuffer._buffer->flush(); + initShaderBinaryCache(); +} + GLBackend::GLBackend() { _pipeline._cameraCorrectionBuffer._buffer->flush(); - glGetIntegerv(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT, &_uboAlignment); initShaderBinaryCache(); } @@ -386,17 +419,11 @@ void GLBackend::do_popProfileRange(const Batch& batch, size_t paramOffset) { } } + // TODO: As long as we have gl calls explicitely issued from interface // code, we need to be able to record and batch these calls. THe long // term strategy is to get rid of any GL calls in favor of the HIFI GPU API -// As long as we don;t use several versions of shaders we can avoid this more complex code path -#ifdef GPU_STEREO_CAMERA_BUFFER -#define GET_UNIFORM_LOCATION(shaderUniformLoc) ((_pipeline._programShader) ? _pipeline._programShader->getUniformLocation(shaderUniformLoc, (GLShader::Version) isStereo()) : -1) -#else -#define GET_UNIFORM_LOCATION(shaderUniformLoc) shaderUniformLoc -#endif - void GLBackend::do_glUniform1i(const Batch& batch, size_t paramOffset) { if (_pipeline._program == 0) { // We should call updatePipeline() to bind the program but we are not doing that @@ -405,8 +432,9 @@ void GLBackend::do_glUniform1i(const Batch& batch, size_t paramOffset) { } updatePipeline(); + GLint location = getRealUniformLocation(batch._params[paramOffset + 1]._int); glUniform1i( - GET_UNIFORM_LOCATION(batch._params[paramOffset + 1]._int), + location, batch._params[paramOffset + 0]._int); (void)CHECK_GL_ERROR(); } @@ -419,8 +447,9 @@ void GLBackend::do_glUniform1f(const Batch& batch, size_t paramOffset) { } updatePipeline(); + GLint location = getRealUniformLocation(batch._params[paramOffset + 1]._int); glUniform1f( - GET_UNIFORM_LOCATION(batch._params[paramOffset + 1]._int), + location, batch._params[paramOffset + 0]._float); (void)CHECK_GL_ERROR(); } @@ -432,8 +461,9 @@ void GLBackend::do_glUniform2f(const Batch& batch, size_t paramOffset) { return; } updatePipeline(); + GLint location = getRealUniformLocation(batch._params[paramOffset + 2]._int); glUniform2f( - GET_UNIFORM_LOCATION(batch._params[paramOffset + 2]._int), + location, batch._params[paramOffset + 1]._float, batch._params[paramOffset + 0]._float); (void)CHECK_GL_ERROR(); @@ -446,8 +476,9 @@ void GLBackend::do_glUniform3f(const Batch& batch, size_t paramOffset) { return; } updatePipeline(); + GLint location = getRealUniformLocation(batch._params[paramOffset + 3]._int); glUniform3f( - GET_UNIFORM_LOCATION(batch._params[paramOffset + 3]._int), + location, batch._params[paramOffset + 2]._float, batch._params[paramOffset + 1]._float, batch._params[paramOffset + 0]._float); @@ -461,8 +492,9 @@ void GLBackend::do_glUniform4f(const Batch& batch, size_t paramOffset) { return; } updatePipeline(); + GLint location = getRealUniformLocation(batch._params[paramOffset + 4]._int); glUniform4f( - GET_UNIFORM_LOCATION(batch._params[paramOffset + 4]._int), + location, batch._params[paramOffset + 3]._float, batch._params[paramOffset + 2]._float, batch._params[paramOffset + 1]._float, @@ -477,8 +509,9 @@ void GLBackend::do_glUniform3fv(const Batch& batch, size_t paramOffset) { return; } updatePipeline(); + GLint location = getRealUniformLocation(batch._params[paramOffset + 2]._int); glUniform3fv( - GET_UNIFORM_LOCATION(batch._params[paramOffset + 2]._int), + location, batch._params[paramOffset + 1]._uint, (const GLfloat*)batch.readData(batch._params[paramOffset + 0]._uint)); @@ -493,7 +526,7 @@ void GLBackend::do_glUniform4fv(const Batch& batch, size_t paramOffset) { } updatePipeline(); - GLint location = GET_UNIFORM_LOCATION(batch._params[paramOffset + 2]._int); + GLint location = getRealUniformLocation(batch._params[paramOffset + 2]._int); GLsizei count = batch._params[paramOffset + 1]._uint; const GLfloat* value = (const GLfloat*)batch.readData(batch._params[paramOffset + 0]._uint); glUniform4fv(location, count, value); @@ -508,8 +541,9 @@ void GLBackend::do_glUniform4iv(const Batch& batch, size_t paramOffset) { return; } updatePipeline(); + GLint location = getRealUniformLocation(batch._params[paramOffset + 2]._int); glUniform4iv( - GET_UNIFORM_LOCATION(batch._params[paramOffset + 2]._int), + location, batch._params[paramOffset + 1]._uint, (const GLint*)batch.readData(batch._params[paramOffset + 0]._uint)); @@ -524,8 +558,9 @@ void GLBackend::do_glUniformMatrix3fv(const Batch& batch, size_t paramOffset) { } updatePipeline(); + GLint location = getRealUniformLocation(batch._params[paramOffset + 3]._int); glUniformMatrix3fv( - GET_UNIFORM_LOCATION(batch._params[paramOffset + 3]._int), + location, batch._params[paramOffset + 2]._uint, batch._params[paramOffset + 1]._uint, (const GLfloat*)batch.readData(batch._params[paramOffset + 0]._uint)); @@ -540,8 +575,9 @@ void GLBackend::do_glUniformMatrix4fv(const Batch& batch, size_t paramOffset) { } updatePipeline(); + GLint location = getRealUniformLocation(batch._params[paramOffset + 3]._int); glUniformMatrix4fv( - GET_UNIFORM_LOCATION(batch._params[paramOffset + 3]._int), + location, batch._params[paramOffset + 2]._uint, batch._params[paramOffset + 1]._uint, (const GLfloat*)batch.readData(batch._params[paramOffset + 0]._uint)); @@ -729,7 +765,6 @@ void GLBackend::recycle() const { } _textureManagement._transferEngine->manageMemory(); - Texture::KtxStorage::releaseOpenKtxFiles(); } void GLBackend::setCameraCorrection(const Mat4& correction, const Mat4& prevRenderView, bool reset) { diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLBackend.h b/libraries/gpu-gl-common/src/gpu/gl/GLBackend.h index 6faccb1527..cadcec7a56 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLBackend.h +++ b/libraries/gpu-gl-common/src/gpu/gl/GLBackend.h @@ -68,7 +68,29 @@ protected: explicit GLBackend(bool syncCache); GLBackend(); public: - static bool makeProgram(Shader& shader, const Shader::BindingSet& slotBindings, const Shader::CompilationHandler& handler); + +#if defined(USE_GLES) + // https://www.khronos.org/registry/OpenGL-Refpages/es3/html/glGet.xhtml + static const GLint MIN_REQUIRED_TEXTURE_IMAGE_UNITS = 16; + static const GLint MIN_REQUIRED_COMBINED_UNIFORM_BLOCKS = 60; + static const GLint MIN_REQUIRED_COMBINED_TEXTURE_IMAGE_UNITS = 48; + static const GLint MIN_REQUIRED_UNIFORM_BUFFER_BINDINGS = 72; + static const GLint MIN_REQUIRED_UNIFORM_LOCATIONS = 1024; +#else + // https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glGet.xhtml + static const GLint MIN_REQUIRED_TEXTURE_IMAGE_UNITS = 16; + static const GLint MIN_REQUIRED_COMBINED_UNIFORM_BLOCKS = 70; + static const GLint MIN_REQUIRED_COMBINED_TEXTURE_IMAGE_UNITS = 48; + static const GLint MIN_REQUIRED_UNIFORM_BUFFER_BINDINGS = 36; + static const GLint MIN_REQUIRED_UNIFORM_LOCATIONS = 1024; +#endif + + static GLint MAX_TEXTURE_IMAGE_UNITS; + static GLint MAX_UNIFORM_BUFFER_BINDINGS; + static GLint MAX_COMBINED_UNIFORM_BLOCKS; + static GLint MAX_COMBINED_TEXTURE_IMAGE_UNITS; + static GLint MAX_UNIFORM_BLOCK_SIZE; + static GLint UNIFORM_BUFFER_OFFSET_ALIGNMENT; virtual ~GLBackend(); @@ -107,7 +129,6 @@ public: // Texture Tables offers 2 dedicated slot (taken from the ubo slots) static const int MAX_NUM_RESOURCE_TABLE_TEXTURES = 2; - static const int RESOURCE_TABLE_TEXTURE_SLOT_OFFSET = TRANSFORM_CAMERA_SLOT + 1; size_t getMaxNumResourceTextureTables() const { return MAX_NUM_RESOURCE_TABLE_TEXTURES; } @@ -238,6 +259,7 @@ public: bool isTextureManagementSparseEnabled() const override { return (_textureManagement._sparseCapable && Texture::getEnableSparseTextures()); } protected: + virtual GLint getRealUniformLocation(GLint location) const; void recycle() const override; @@ -247,7 +269,6 @@ protected: static const size_t INVALID_OFFSET = (size_t)-1; bool _inRenderTransferPass { false }; - int32_t _uboAlignment { 0 }; int _currentDraw { -1 }; std::list profileRanges; @@ -394,10 +415,24 @@ protected: virtual void transferTransformState(const Batch& batch) const = 0; struct UniformStageState { - std::array _buffers; - //Buffers _buffers { }; + struct BufferState { + BufferPointer buffer; + GLintptr offset{ 0 }; + GLsizeiptr size{ 0 }; + BufferState(const BufferPointer& buffer = nullptr, GLintptr offset = 0, GLsizeiptr size = 0); + bool operator ==(BufferState& other) const { + return offset == other.offset && size == other.size && buffer == other.buffer; + } + }; + + // MAX_NUM_UNIFORM_BUFFERS-1 is the max uniform index BATCHES are allowed to set, but + // MIN_REQUIRED_UNIFORM_BUFFER_BINDINGS is used here because the backend sets some + // internal UBOs for things like camera correction + std::array _buffers; } _uniform; + // Helper function that provides common code + void bindUniformBuffer(uint32_t slot, const BufferPointer& buffer, GLintptr offset = 0, GLsizeiptr size = 0); void releaseUniformBuffer(uint32_t slot); void resetUniformStage(); @@ -410,6 +445,7 @@ protected: // do_setResourceTextureTable (in non-bindless mode) void bindResourceTexture(uint32_t slot, const TexturePointer& texture); + // update resource cache and do the gl unbind call with the current gpu::Texture cached at slot s void releaseResourceTexture(uint32_t slot); @@ -436,7 +472,7 @@ protected: PipelinePointer _pipeline; GLuint _program { 0 }; - GLint _cameraCorrectionLocation { -1 }; + bool _cameraCorrection { false }; GLShader* _programShader { nullptr }; bool _invalidProgram { false }; @@ -457,6 +493,7 @@ protected: } _pipeline; // Backend dependant compilation of the shader + virtual void postLinkProgram(ShaderObject& programObject, const Shader& program) const; virtual GLShader* compileBackendProgram(const Shader& program, const Shader::CompilationHandler& handler); virtual GLShader* compileBackendShader(const Shader& shader, const Shader::CompilationHandler& handler); virtual std::string getBackendShaderHeader() const = 0; @@ -467,7 +504,6 @@ protected: // The program string returned can be used as a key for a cache of shader binaries // The shader strings can be reliably sent to the low level `compileShader` functions virtual std::string getShaderSource(const Shader& shader, int version) final; - virtual void makeProgramBindings(ShaderObject& shaderObject); class ElementResource { public: gpu::Element _element; @@ -475,15 +511,6 @@ protected: ElementResource(Element&& elem, uint16 resource) : _element(elem), _resource(resource) {} }; ElementResource getFormatFromGLUniform(GLenum gltype); - static const GLint UNUSED_SLOT {-1}; - static bool isUnusedSlot(GLint binding) { return (binding == UNUSED_SLOT); } - virtual int makeUniformSlots(const ShaderObject& program, const Shader::BindingSet& slotBindings, - Shader::SlotSet& uniforms, Shader::SlotSet& textures, Shader::SlotSet& samplers); - virtual int makeUniformBlockSlots(const ShaderObject& program, const Shader::BindingSet& slotBindings, Shader::SlotSet& buffers); - virtual int makeResourceBufferSlots(const ShaderObject& program, const Shader::BindingSet& slotBindings, Shader::SlotSet& resourceBuffers) = 0; - virtual int makeInputSlots(const ShaderObject& program, const Shader::BindingSet& slotBindings, Shader::SlotSet& inputs); - virtual int makeOutputSlots(const ShaderObject& program, const Shader::BindingSet& slotBindings, Shader::SlotSet& outputs); - // Synchronize the state cache of this Backend with the actual real state of the GL Context void syncOutputStateCache(); diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLBackendPipeline.cpp b/libraries/gpu-gl-common/src/gpu/gl/GLBackendPipeline.cpp index adea3292e1..d3d2bc0938 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLBackendPipeline.cpp +++ b/libraries/gpu-gl-common/src/gpu/gl/GLBackendPipeline.cpp @@ -10,6 +10,7 @@ // #include "GLBackend.h" #include +#include #include "GLShared.h" #include "GLPipeline.h" @@ -36,7 +37,7 @@ void GLBackend::do_setPipeline(const Batch& batch, size_t paramOffset) { _pipeline._pipeline.reset(); _pipeline._program = 0; - _pipeline._cameraCorrectionLocation = -1; + _pipeline._cameraCorrection = false; _pipeline._programShader = nullptr; _pipeline._invalidProgram = true; @@ -62,7 +63,7 @@ void GLBackend::do_setPipeline(const Batch& batch, size_t paramOffset) { _pipeline._program = glprogram; _pipeline._programShader = pipelineObject->_program; _pipeline._invalidProgram = true; - _pipeline._cameraCorrectionLocation = pipelineObject->_cameraCorrection; + _pipeline._cameraCorrection = pipelineObject->_cameraCorrection; } // Now for the state @@ -78,16 +79,13 @@ void GLBackend::do_setPipeline(const Batch& batch, size_t paramOffset) { // THis should be done on Pipeline::update... if (_pipeline._invalidProgram) { glUseProgram(_pipeline._program); - if (_pipeline._cameraCorrectionLocation != -1) { - gl::GLBuffer* cameraCorrectionBuffer = nullptr; - if (_transform._viewCorrectionEnabled) { - cameraCorrectionBuffer = syncGPUObject(*_pipeline._cameraCorrectionBuffer._buffer); - } else { - cameraCorrectionBuffer = syncGPUObject(*_pipeline._cameraCorrectionBufferIdentity._buffer); - } + if (_pipeline._cameraCorrection) { // Invalidate uniform buffer cache slot - _uniform._buffers[_pipeline._cameraCorrectionLocation].reset(); - glBindBufferRange(GL_UNIFORM_BUFFER, _pipeline._cameraCorrectionLocation, cameraCorrectionBuffer->_id, 0, sizeof(CameraCorrection)); + _uniform._buffers[gpu::slot::buffer::CameraCorrection] = {}; + auto& cameraCorrectionBuffer = _transform._viewCorrectionEnabled ? + _pipeline._cameraCorrectionBuffer._buffer : + _pipeline._cameraCorrectionBufferIdentity._buffer; + bindUniformBuffer(gpu::slot::buffer::CameraCorrection, cameraCorrectionBuffer, 0, sizeof(CameraCorrection)); } (void)CHECK_GL_ERROR(); _pipeline._invalidProgram = false; @@ -138,15 +136,18 @@ void GLBackend::resetPipelineStage() { glUseProgram(0); } +GLBackend::UniformStageState::BufferState::BufferState(const BufferPointer& buffer, GLintptr offset, GLsizeiptr size) + : buffer(buffer), offset(offset), size(size) {} + void GLBackend::releaseUniformBuffer(uint32_t slot) { auto& buf = _uniform._buffers[slot]; - if (buf) { - auto* object = Backend::getGPUObject(*buf); + if (buf.buffer) { + auto* object = Backend::getGPUObject(*buf.buffer); if (object) { glBindBufferBase(GL_UNIFORM_BUFFER, slot, 0); // RELEASE (void)CHECK_GL_ERROR(); } - buf.reset(); + buf = UniformStageState::BufferState(); } } @@ -156,6 +157,33 @@ void GLBackend::resetUniformStage() { } } +void GLBackend::bindUniformBuffer(uint32_t slot, const BufferPointer& buffer, GLintptr offset, GLsizeiptr size) { + if (!buffer) { + releaseUniformBuffer(slot); + return; + } + + UniformStageState::BufferState bufferState{ buffer, offset, size }; + + // check cache before thinking + if (_uniform._buffers[slot] == bufferState) { + return; + } + + // Sync BufferObject + auto* object = syncGPUObject(*bufferState.buffer); + if (object) { + glBindBufferRange(GL_UNIFORM_BUFFER, slot, object->_buffer, bufferState.offset, bufferState.size); + + _uniform._buffers[slot] = bufferState; + (void)CHECK_GL_ERROR(); + } else { + releaseUniformBuffer(slot); + return; + } + +} + void GLBackend::do_setUniformBuffer(const Batch& batch, size_t paramOffset) { GLuint slot = batch._params[paramOffset + 3]._uint; if (slot > (GLuint)MAX_NUM_UNIFORM_BUFFERS) { @@ -163,31 +191,12 @@ void GLBackend::do_setUniformBuffer(const Batch& batch, size_t paramOffset) { << " which doesn't exist. MaxNumUniformBuffers = " << getMaxNumUniformBuffers(); return; } + BufferPointer uniformBuffer = batch._buffers.get(batch._params[paramOffset + 2]._uint); GLintptr rangeStart = batch._params[paramOffset + 1]._uint; GLsizeiptr rangeSize = batch._params[paramOffset + 0]._uint; - if (!uniformBuffer) { - releaseUniformBuffer(slot); - return; - } - - // check cache before thinking - if (_uniform._buffers[slot] == uniformBuffer) { - return; - } - - // Sync BufferObject - auto* object = syncGPUObject(*uniformBuffer); - if (object) { - glBindBufferRange(GL_UNIFORM_BUFFER, slot, object->_buffer, rangeStart, rangeSize); - - _uniform._buffers[slot] = uniformBuffer; - (void)CHECK_GL_ERROR(); - } else { - releaseUniformBuffer(slot); - return; - } + bindUniformBuffer(slot, uniformBuffer, rangeStart, rangeSize); } void GLBackend::releaseResourceTexture(uint32_t slot) { diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLBackendShader.cpp b/libraries/gpu-gl-common/src/gpu/gl/GLBackendShader.cpp index af6a0df297..7267e29be2 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLBackendShader.cpp +++ b/libraries/gpu-gl-common/src/gpu/gl/GLBackendShader.cpp @@ -13,7 +13,6 @@ using namespace gpu; using namespace gpu::gl; using CachedShader = ::gl::CachedShader; - // Shader domain static const size_t NUM_SHADER_DOMAINS = 3; static_assert(Shader::Type::NUM_DOMAINS == NUM_SHADER_DOMAINS, "GL shader domains must equal defined GPU shader domains"); @@ -178,24 +177,22 @@ GLShader* GLBackend::compileBackendProgram(const Shader& program, const Shader:: } } - GLuint glprogram = 0; - // If we have a cached binary program, try to load it instead of compiling the individual shaders if (cachedBinary) { - glprogram = ::gl::compileProgram({}, compilationLogs[version].message, cachedBinary); + glprogram = ::gl::buildProgram(cachedBinary); if (0 != glprogram) { ++gpuBinaryShadersLoaded; - } - } - - // If we have no program, then either no cached binary, or the binary failed to load (perhaps a GPU driver update invalidated the cache) - if (0 == glprogram) { - cachedBinary = CachedShader(); - { + } else { + cachedBinary = CachedShader(); std::unique_lock shaderCacheLock{ _shaderBinaryCache._mutex }; _shaderBinaryCache._binaries.erase(hash); } + } + + // If we have no program, then either no cached binary, or the binary failed to load + // (perhaps a GPU driver update invalidated the cache) + if (0 == glprogram) { // Let's go through every shaders and make sure they are ready to go std::vector shaderGLObjects; shaderGLObjects.reserve(program.getShaders().size()); @@ -212,8 +209,18 @@ GLShader* GLBackend::compileBackendProgram(const Shader& program, const Shader:: } } - glprogram = ::gl::compileProgram(shaderGLObjects, compilationLogs[version].message, cachedBinary); - if (cachedBinary) { + glprogram = ::gl::buildProgram(shaderGLObjects); + + if (!::gl::linkProgram(glprogram, compilationLogs[version].message)) { + qCWarning(gpugllogging) << "GLBackend::compileBackendProgram - Program didn't link:\n" << compilationLogs[version].message.c_str(); + compilationLogs[version].compiled = false; + glDeleteProgram(glprogram); + glprogram = 0; + return nullptr; + } + + if (!cachedBinary) { + ::gl::getProgramBinary(glprogram, cachedBinary); cachedBinary.source = programSource; std::unique_lock shaderCacheLock{ _shaderBinaryCache._mutex }; _shaderBinaryCache._binaries[hash] = cachedBinary; @@ -228,7 +235,7 @@ GLShader* GLBackend::compileBackendProgram(const Shader& program, const Shader:: compilationLogs[version].compiled = true; programObject.glprogram = glprogram; - makeProgramBindings(programObject); + postLinkProgram(programObject, program); } // Compilation feedback program.setCompilationLogs(compilationLogs); @@ -236,10 +243,45 @@ GLShader* GLBackend::compileBackendProgram(const Shader& program, const Shader:: // So far so good, the program versions have all been created successfully GLShader* object = new GLShader(this->shared_from_this()); object->_shaderObjects = programObjects; - return object; } +static const GLint INVALID_UNIFORM_INDEX = -1; + +GLint GLBackend::getRealUniformLocation(GLint location) const { + auto& shader = _pipeline._programShader->_shaderObjects[(GLShader::Version)isStereo()]; + auto itr = shader.uniformRemap.find(location); + if (itr == shader.uniformRemap.end()) { + // This shouldn't happen, because we use reflection to determine all the possible + // uniforms. If someone is requesting a uniform that isn't in the remapping structure + // that's a bug from the calling code, because it means that location wasn't in the + // reflection + qWarning() << "Unexpected location requested for shader: #" << location; + return INVALID_UNIFORM_INDEX; + } + return itr->second; +} + +void GLBackend::postLinkProgram(ShaderObject& shaderObject, const Shader& program) const { + const auto& glprogram = shaderObject.glprogram; + const auto& expectedUniforms = program.getUniforms(); + const auto expectedLocationsByName = expectedUniforms.getLocationsByName(); + const auto uniforms = ::gl::Uniform::load(glprogram, expectedUniforms.getNames()); + auto& uniformRemap = shaderObject.uniformRemap; + + // Pre-initialize all the uniforms with an invalid location + for (const auto& entry : expectedLocationsByName) { + uniformRemap[entry.second] = INVALID_UNIFORM_INDEX; + } + + // Now load up all the actual found uniform location + for (const auto& uniform : uniforms) { + const auto& name = uniform.name; + const auto& expectedLocation = expectedLocationsByName.at(name); + const auto& location = uniform.binding; + uniformRemap[expectedLocation] = location; + } +} GLBackend::ElementResource GLBackend::getFormatFromGLUniform(GLenum gltype) { switch (gltype) { @@ -405,197 +447,8 @@ GLBackend::ElementResource GLBackend::getFormatFromGLUniform(GLenum gltype) { //{GL_UNSIGNED_INT_IMAGE_2D_MULTISAMPLE uimage2DMS}, //{GL_UNSIGNED_INT_IMAGE_2D_MULTISAMPLE_ARRAY uimage2DMSArray}, //{GL_UNSIGNED_INT_ATOMIC_COUNTER atomic_uint} - - }; -int GLBackend::makeUniformSlots(const ShaderObject& shaderProgram, const Shader::BindingSet& slotBindings, - Shader::SlotSet& uniforms, Shader::SlotSet& textures, Shader::SlotSet& samplers) { - auto& glprogram = shaderProgram.glprogram; - - for (const auto& uniform : shaderProgram.uniforms) { - const auto& type = uniform.type; - const auto& location = uniform.location; - const auto& size = uniform.size; - const auto& name = uniform.name; - const GLint INVALID_UNIFORM_LOCATION = -1; - - // Try to make sense of the gltype - auto elementResource = getFormatFromGLUniform(type); - - // The uniform as a standard var type - if (location != INVALID_UNIFORM_LOCATION) { - auto sname = uniform.name; - // Let's make sure the name doesn't contains an array element - auto foundBracket = sname.find_first_of('['); - if (foundBracket != std::string::npos) { - // std::string arrayname = sname.substr(0, foundBracket); - - if (sname[foundBracket + 1] == '0') { - sname = sname.substr(0, foundBracket); - } else { - // skip this uniform since it's not the first element of an array - continue; - } - } - - if (elementResource._resource == Resource::BUFFER) { - uniforms.insert(Shader::Slot(sname, location, elementResource._element, elementResource._resource)); - } else { - // For texture/Sampler, the location is the actual binding value - GLint binding = -1; - glGetUniformiv(glprogram, location, &binding); - - auto requestedBinding = slotBindings.find(std::string(sname)); - if (requestedBinding != slotBindings.end()) { - if (binding != (*requestedBinding)._location) { - binding = (*requestedBinding)._location; - for (auto i = 0; i < size; i++) { - // If we are working with an array of textures, reserve for each elemet - glProgramUniform1i(glprogram, location+i, binding+i); - } - } - } - - textures.insert(Shader::Slot(name, binding, elementResource._element, elementResource._resource)); - samplers.insert(Shader::Slot(name, binding, elementResource._element, elementResource._resource)); - } - } - } - - return static_cast(shaderProgram.uniforms.size()); -} - -int GLBackend::makeUniformBlockSlots(const ShaderObject& shaderProgram, const Shader::BindingSet& slotBindings, Shader::SlotSet& buffers) { - const auto& glprogram = shaderProgram.glprogram; - GLint buffersCount = 0; - - glGetProgramiv(glprogram, GL_ACTIVE_UNIFORM_BLOCKS, &buffersCount); - - // fast exit - if (buffersCount == 0) { - return 0; - } - - GLint maxNumUniformBufferSlots = 0; - glGetIntegerv(GL_MAX_UNIFORM_BUFFER_BINDINGS, &maxNumUniformBufferSlots); - std::vector uniformBufferSlotMap(maxNumUniformBufferSlots, -1); - - struct UniformBlockInfo { - using Vector = std::vector; - const GLuint index{ 0 }; - const std::string name; - GLint binding{ -1 }; - GLint size{ 0 }; - - static std::string getName(GLuint glprogram, GLuint i) { - static const GLint NAME_LENGTH = 256; - GLint length = 0; - GLchar nameBuffer[NAME_LENGTH]; - glGetActiveUniformBlockiv(glprogram, i, GL_UNIFORM_BLOCK_NAME_LENGTH, &length); - glGetActiveUniformBlockName(glprogram, i, NAME_LENGTH, &length, nameBuffer); - return std::string(nameBuffer); - } - - UniformBlockInfo(GLuint glprogram, GLuint i) : index(i), name(getName(glprogram, i)) { - glGetActiveUniformBlockiv(glprogram, index, GL_UNIFORM_BLOCK_BINDING, &binding); - glGetActiveUniformBlockiv(glprogram, index, GL_UNIFORM_BLOCK_DATA_SIZE, &size); - } - }; - - UniformBlockInfo::Vector uniformBlocks; - uniformBlocks.reserve(buffersCount); - for (int i = 0; i < buffersCount; i++) { - uniformBlocks.push_back(UniformBlockInfo(glprogram, i)); - } - - for (auto& info : uniformBlocks) { - auto requestedBinding = slotBindings.find(info.name); - if (requestedBinding != slotBindings.end()) { - info.binding = (*requestedBinding)._location; - glUniformBlockBinding(glprogram, info.index, info.binding); - uniformBufferSlotMap[info.binding] = info.index; - } - } - - for (auto& info : uniformBlocks) { - if (slotBindings.count(info.name)) { - continue; - } - - // If the binding is 0, or the binding maps to an already used binding - if (info.binding == 0 || !isUnusedSlot(uniformBufferSlotMap[info.binding])) { - // If no binding was assigned then just do it finding a free slot - auto slotIt = std::find_if(uniformBufferSlotMap.begin(), uniformBufferSlotMap.end(), GLBackend::isUnusedSlot); - if (slotIt != uniformBufferSlotMap.end()) { - info.binding = slotIt - uniformBufferSlotMap.begin(); - glUniformBlockBinding(glprogram, info.index, info.binding); - } else { - // This should neve happen, an active ubo cannot find an available slot among the max available?! - info.binding = -1; - } - } - - uniformBufferSlotMap[info.binding] = info.index; - } - - for (auto& info : uniformBlocks) { - static const Element element(SCALAR, gpu::UINT32, gpu::UNIFORM_BUFFER); - buffers.insert(Shader::Slot(info.name, info.binding, element, Resource::BUFFER, info.size)); - } - return buffersCount; -} - -int GLBackend::makeInputSlots(const ShaderObject& shaderProgram, const Shader::BindingSet& slotBindings, Shader::SlotSet& inputs) { - const auto& glprogram = shaderProgram.glprogram; - GLint inputsCount = 0; - - glGetProgramiv(glprogram, GL_ACTIVE_ATTRIBUTES, &inputsCount); - - for (int i = 0; i < inputsCount; i++) { - const GLint NAME_LENGTH = 256; - GLchar name[NAME_LENGTH]; - GLint length = 0; - GLint size = 0; - GLenum type = 0; - glGetActiveAttrib(glprogram, i, NAME_LENGTH, &length, &size, &type, name); - - GLint binding = glGetAttribLocation(glprogram, name); - - auto elementResource = getFormatFromGLUniform(type); - inputs.insert(Shader::Slot(name, binding, elementResource._element, -1)); - } - - return inputsCount; -} - -int GLBackend::makeOutputSlots(const ShaderObject& shaderProgram, const Shader::BindingSet& slotBindings, Shader::SlotSet& outputs) { - /* GLint outputsCount = 0; - - glGetProgramiv(glprogram, GL_ACTIVE_, &outputsCount); - - for (int i = 0; i < inputsCount; i++) { - const GLint NAME_LENGTH = 256; - GLchar name[NAME_LENGTH]; - GLint length = 0; - GLint size = 0; - GLenum type = 0; - glGetActiveAttrib(glprogram, i, NAME_LENGTH, &length, &size, &type, name); - - auto element = getFormatFromGLUniform(type); - outputs.insert(Shader::Slot(name, i, element)); - } - */ - return 0; //inputsCount; -} - -void GLBackend::makeProgramBindings(ShaderObject& shaderObject) { - if (!shaderObject.glprogram) { - return; - } -} - - void GLBackend::initShaderBinaryCache() { GLint numBinFormats = 0; glGetIntegerv(GL_NUM_PROGRAM_BINARY_FORMATS, &numBinFormats); diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLBackendTransform.cpp b/libraries/gpu-gl-common/src/gpu/gl/GLBackendTransform.cpp index ed356acf68..2c2a4e254c 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLBackendTransform.cpp +++ b/libraries/gpu-gl-common/src/gpu/gl/GLBackendTransform.cpp @@ -168,9 +168,8 @@ void GLBackend::TransformStageState::update(size_t commandIndex, const StereoSta void GLBackend::TransformStageState::bindCurrentCamera(int eye) const { if (_currentCameraOffset != INVALID_OFFSET) { - static_assert(TRANSFORM_CAMERA_SLOT >= MAX_NUM_UNIFORM_BUFFERS, "TransformCamera may overlap pipeline uniform buffer slots. Invalidate uniform buffer slot cache for safety (call _uniform._buffers[TRANSFORM_CAMERA_SLOT].reset())."); - glBindBufferRange(GL_UNIFORM_BUFFER, TRANSFORM_CAMERA_SLOT, _cameraBuffer, _currentCameraOffset + eye * _cameraUboSize, - sizeof(CameraBufferElement)); + static_assert(slot::buffer::Buffer::CameraTransform >= MAX_NUM_UNIFORM_BUFFERS, "TransformCamera may overlap pipeline uniform buffer slots. Invalidate uniform buffer slot cache for safety (call _uniform._buffers[TRANSFORM_CAMERA_SLOT].reset())."); + glBindBufferRange(GL_UNIFORM_BUFFER, slot::buffer::Buffer::CameraTransform, _cameraBuffer, _currentCameraOffset + eye * _cameraUboSize, sizeof(CameraBufferElement)); } } diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLPipeline.cpp b/libraries/gpu-gl-common/src/gpu/gl/GLPipeline.cpp index ebf1a55232..1b479dceb8 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLPipeline.cpp +++ b/libraries/gpu-gl-common/src/gpu/gl/GLPipeline.cpp @@ -10,6 +10,7 @@ #include "GLShader.h" #include "GLState.h" +#include "GLBackend.h" using namespace gpu; using namespace gpu::gl; @@ -51,7 +52,7 @@ GLPipeline* GLPipeline::sync(GLBackend& backend, const Pipeline& pipeline) { // Special case for view correction matrices, any pipeline that declares the correction buffer // uniform will automatically have it provided without any client code necessary. // Required for stable lighting in the HMD. - object->_cameraCorrection = shader->getUniformBuffers().findLocation("cameraCorrectionBuffer"); + object->_cameraCorrection = shader->getUniformBuffers().isValid(gpu::slot::buffer::CameraCorrection); object->_program = programObject; object->_state = stateObject; diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLPipeline.h b/libraries/gpu-gl-common/src/gpu/gl/GLPipeline.h index a298f149d9..a102e33b14 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLPipeline.h +++ b/libraries/gpu-gl-common/src/gpu/gl/GLPipeline.h @@ -20,7 +20,7 @@ public: GLState* _state { nullptr }; // Bit of a hack, any pipeline can need the camera correction buffer at execution time, so // we store whether a given pipeline has declared the uniform buffer for it. - int32 _cameraCorrection { -1 }; + bool _cameraCorrection{ false }; }; } } diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLShader.cpp b/libraries/gpu-gl-common/src/gpu/gl/GLShader.cpp index 0a527185ef..44d2bd6ca0 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLShader.cpp +++ b/libraries/gpu-gl-common/src/gpu/gl/GLShader.cpp @@ -56,52 +56,5 @@ GLShader* GLShader::sync(GLBackend& backend, const Shader& shader, const Shader: return object; } -bool GLShader::makeProgram(GLBackend& backend, Shader& shader, const Shader::BindingSet& slotBindings, const Shader::CompilationHandler& handler) { - - // First make sure the Shader has been compiled - GLShader* object = sync(backend, shader, handler); - if (!object) { - return false; - } - - // Apply bindings to all program versions and generate list of slots from default version - for (int version = 0; version < GLShader::NumVersions; version++) { - auto& shaderObject = object->_shaderObjects[version]; - if (shaderObject.glprogram) { - shaderObject.uniforms = ::gl::loadUniforms(shaderObject.glprogram); - Shader::SlotSet buffers; - backend.makeUniformBlockSlots(shaderObject, slotBindings, buffers); - - Shader::SlotSet uniforms; - Shader::SlotSet textures; - Shader::SlotSet samplers; - backend.makeUniformSlots(shaderObject, slotBindings, uniforms, textures, samplers); - - Shader::SlotSet resourceBuffers; - backend.makeResourceBufferSlots(shaderObject, slotBindings, resourceBuffers); - - Shader::SlotSet inputs; - backend.makeInputSlots(shaderObject, slotBindings, inputs); - - Shader::SlotSet outputs; - backend.makeOutputSlots(shaderObject, slotBindings, outputs); - - // Define the public slots only from the default version - if (version == 0) { - shader.defineSlots(uniforms, buffers, resourceBuffers, textures, samplers, inputs, outputs); - } // else - { - GLShader::UniformMapping mapping; - for (auto srcUniform : shader.getUniforms()) { - mapping[srcUniform._location] = uniforms.findLocation(srcUniform._name); - } - object->_uniformMappings.push_back(mapping); - } - } - } - - return true; -} - diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLShader.h b/libraries/gpu-gl-common/src/gpu/gl/GLShader.h index 0ba77e50c6..5d5d8a4a3c 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLShader.h +++ b/libraries/gpu-gl-common/src/gpu/gl/GLShader.h @@ -14,18 +14,16 @@ namespace gpu { namespace gl { struct ShaderObject { - using Uniforms = ::gl::Uniforms; GLuint glshader { 0 }; GLuint glprogram { 0 }; - GLint transformCameraSlot { -1 }; - GLint transformObjectSlot { -1 }; - Uniforms uniforms; + + using LocationMap = std::unordered_map ; + LocationMap uniformRemap; }; class GLShader : public GPUObject { public: static GLShader* sync(GLBackend& backend, const Shader& shader, const Shader::CompilationHandler& handler = nullptr); - static bool makeProgram(GLBackend& backend, Shader& shader, const Shader::BindingSet& slotBindings, const Shader::CompilationHandler& handler); enum Version { Mono = 0, @@ -44,22 +42,11 @@ public: ~GLShader(); ShaderObjects _shaderObjects; - UniformMappingVersions _uniformMappings; GLuint getProgram(Version version = Mono) const { return _shaderObjects[version].glprogram; } - GLint getUniformLocation(GLint srcLoc, Version version = Mono) const { - // This check protect against potential invalid src location for this shader, if unknown then return -1. - const auto& mapping = _uniformMappings[version]; - auto found = mapping.find(srcLoc); - if (found == mapping.end()) { - return -1; - } - return found->second; - } - const std::weak_ptr _backend; }; diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLTextureTransfer.cpp b/libraries/gpu-gl-common/src/gpu/gl/GLTextureTransfer.cpp index 07cb5fa15f..f4e81448cc 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLTextureTransfer.cpp +++ b/libraries/gpu-gl-common/src/gpu/gl/GLTextureTransfer.cpp @@ -129,9 +129,10 @@ void GLBackend::killTextureManagementStage() { } std::vector GLTextureTransferEngine::getAllTextures() { - std::remove_if(_registeredTextures.begin(), _registeredTextures.end(), [&](const std::weak_ptr& weak) -> bool { - return weak.expired(); + auto expiredBegin = std::remove_if(_registeredTextures.begin(), _registeredTextures.end(), [&](const std::weak_ptr& weak) -> bool { + return weak.expired(); }); + _registeredTextures.erase(expiredBegin, _registeredTextures.end()); std::vector result; result.reserve(_registeredTextures.size()); @@ -404,6 +405,7 @@ bool GLTextureTransferEngineDefault::processActiveBufferQueue() { _activeTransferQueue.splice(_activeTransferQueue.end(), activeBufferQueue); } + Texture::KtxStorage::releaseOpenKtxFiles(); return true; } diff --git a/libraries/gpu-gl/src/gpu/gl/GLDesktopBackend.cpp b/libraries/gpu-gl/src/gpu/gl/GLDesktopBackend.cpp index 72a76f8f90..bc7cbdff4a 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLDesktopBackend.cpp +++ b/libraries/gpu-gl/src/gpu/gl/GLDesktopBackend.cpp @@ -16,6 +16,7 @@ #include #include +#include #include #include "../gl41/GL41Backend.h" @@ -24,9 +25,6 @@ using namespace gpu; using namespace gpu::gl; -static const QString DEBUG_FLAG("HIFI_DISABLE_OPENGL_45"); -static bool disableOpenGL45 = QProcessEnvironment::systemEnvironment().contains(DEBUG_FLAG); - static GLBackend* INSTANCE{ nullptr }; BackendPointer GLBackend::createBackend() { @@ -34,7 +32,7 @@ BackendPointer GLBackend::createBackend() { // Where the gpuContext is initialized and where the TRUE Backend is created and assigned auto version = QOpenGLContextWrapper::currentContextVersion(); std::shared_ptr result; - if (!disableOpenGL45 && version >= 0x0405) { + if (!::gl::disableGl45() && version >= 0x0405) { qCDebug(gpugllogging) << "Using OpenGL 4.5 backend"; result = std::make_shared(); } else { @@ -57,7 +55,3 @@ GLBackend& getBackend() { } return *INSTANCE; } - -bool GLBackend::makeProgram(Shader& shader, const Shader::BindingSet& slotBindings, const Shader::CompilationHandler& handler) { - return GLShader::makeProgram(getBackend(), shader, slotBindings, handler); -} diff --git a/libraries/gpu-gl/src/gpu/gl41/GL41Backend.h b/libraries/gpu-gl/src/gpu/gl41/GL41Backend.h index e840b9fe78..c6fbc43ae5 100644 --- a/libraries/gpu-gl/src/gpu/gl41/GL41Backend.h +++ b/libraries/gpu-gl/src/gpu/gl41/GL41Backend.h @@ -19,11 +19,7 @@ #define GPU_CORE_41 410 #define GPU_CORE_43 430 -#ifdef Q_OS_MAC #define GPU_INPUT_PROFILE GPU_CORE_41 -#else -#define GPU_INPUT_PROFILE GPU_CORE_43 -#endif namespace gpu { namespace gl41 { @@ -35,7 +31,6 @@ class GL41Backend : public GLBackend { friend class Context; public: - static const GLint TRANSFORM_OBJECT_SLOT { 31 }; static const GLint RESOURCE_TRANSFER_TEX_UNIT { 32 }; static const GLint RESOURCE_TRANSFER_EXTRA_TEX_UNIT { 33 }; static const GLint RESOURCE_BUFFER_TEXBUF_TEX_UNIT { 34 }; @@ -172,9 +167,8 @@ protected: void do_blit(const Batch& batch, size_t paramOffset) override; std::string getBackendShaderHeader() const override; - void makeProgramBindings(ShaderObject& shaderObject) override; - int makeResourceBufferSlots(const ShaderObject& shaderProgram, const Shader::BindingSet& slotBindings,Shader::SlotSet& resourceBuffers) override; + void postLinkProgram(ShaderObject& programObject, const Shader& program) const override; }; } } diff --git a/libraries/gpu-gl/src/gpu/gl41/GL41BackendShader.cpp b/libraries/gpu-gl/src/gpu/gl41/GL41BackendShader.cpp index 0fa1b1bf42..8a67ff9619 100644 --- a/libraries/gpu-gl/src/gpu/gl41/GL41BackendShader.cpp +++ b/libraries/gpu-gl/src/gpu/gl41/GL41BackendShader.cpp @@ -22,90 +22,54 @@ std::string GL41Backend::getBackendShaderHeader() const { return header; } -int GL41Backend::makeResourceBufferSlots(const ShaderObject& shaderProgram, const Shader::BindingSet& slotBindings,Shader::SlotSet& resourceBuffers) { - GLint ssboCount = 0; - const auto& glprogram = shaderProgram.glprogram; - for (const auto& uniform : shaderProgram.uniforms) { - const auto& name = uniform.name; - const auto& type = uniform.type; - const auto& location = uniform.location; - const GLint INVALID_UNIFORM_LOCATION = -1; - - // Try to make sense of the gltype - auto elementResource = getFormatFromGLUniform(type); - - // The uniform as a standard var type - if (location != INVALID_UNIFORM_LOCATION) { - - if (elementResource._resource == Resource::BUFFER) { - if (elementResource._element.getSemantic() == gpu::RESOURCE_BUFFER) { - // Let's make sure the name doesn't contains an array element - std::string sname(name); - auto foundBracket = sname.find_first_of('['); - if (foundBracket != std::string::npos) { - // std::string arrayname = sname.substr(0, foundBracket); - - if (sname[foundBracket + 1] == '0') { - sname = sname.substr(0, foundBracket); - } else { - // skip this uniform since it's not the first element of an array - continue; - } - } - - // For texture/Sampler, the location is the actual binding value - GLint binding = -1; - glGetUniformiv(glprogram, location, &binding); - - if (binding == GL41Backend::TRANSFORM_OBJECT_SLOT) { - continue; - } - - auto requestedBinding = slotBindings.find(std::string(sname)); - if (requestedBinding != slotBindings.end()) { - GLint requestedLoc = (*requestedBinding)._location + GL41Backend::RESOURCE_BUFFER_SLOT0_TEX_UNIT; - if (binding != requestedLoc) { - binding = requestedLoc; - } - } else { - binding += GL41Backend::RESOURCE_BUFFER_SLOT0_TEX_UNIT; - } - glProgramUniform1i(glprogram, location, binding); - - ssboCount++; - resourceBuffers.insert(Shader::Slot(name, binding, elementResource._element, elementResource._resource)); - } +void GL41Backend::postLinkProgram(ShaderObject& programObject, const Shader& program) const { + Parent::postLinkProgram(programObject, program); + const auto& glprogram = programObject.glprogram; + // For the UBOs, use glUniformBlockBinding to fixup the locations based on the reflection + { + const auto expectedUbos = program.getUniformBuffers().getLocationsByName(); + auto ubos = ::gl::UniformBlock::load(glprogram); + for (const auto& ubo : ubos) { + const auto& name = ubo.name; + if (0 == expectedUbos.count(name)) { + continue; } + const auto& targetLocation = expectedUbos.at(name); + glUniformBlockBinding(glprogram, ubo.index, targetLocation); } } - return ssboCount; + // For the Textures, use glUniform1i to fixup the active texture slots based on the reflection + { + const auto expectedTextures = program.getTextures().getLocationsByName(); + for (const auto& expectedTexture : expectedTextures) { + auto location = glGetUniformLocation(glprogram, expectedTexture.first.c_str()); + if (location < 0) { + continue; + } + glProgramUniform1i(glprogram, location, expectedTexture.second); + } + } + + // For the resource buffers, do the same as for the textures, since in GL 4.1 that's how they're implemented + { + const auto expectedResourceBuffers = program.getResourceBuffers().getLocationsByName(); + const auto resourceBufferUniforms = ::gl::Uniform::loadByName(glprogram, program.getResourceBuffers().getNames()); + for (const auto& resourceBuffer : resourceBufferUniforms) { + const auto& targetBinding = expectedResourceBuffers.at(resourceBuffer.name); + glProgramUniform1i(glprogram, resourceBuffer.binding, targetBinding); + } + } + + // Special case for the transformObjectBuffer, which is filtered out of the reflection data at shader load time + // + { + static const std::string TRANSFORM_OBJECT_BUFFER = "transformObjectBuffer"; + const auto uniform = ::gl::Uniform::loadByName(glprogram, TRANSFORM_OBJECT_BUFFER); + if (-1 != uniform.binding) { + glProgramUniform1i(glprogram, uniform.binding, ::gpu::slot::texture::ObjectTransforms); + } + } } -void GL41Backend::makeProgramBindings(ShaderObject& shaderObject) { - if (!shaderObject.glprogram) { - return; - } - GLuint glprogram = shaderObject.glprogram; - GLint loc = -1; - - GLBackend::makeProgramBindings(shaderObject); - - // now assign the ubo binding, then DON't relink! - - //Check for gpu specific uniform slotBindings - loc = glGetUniformLocation(glprogram, "transformObjectBuffer"); - if (loc >= 0) { - glProgramUniform1i(glprogram, loc, GL41Backend::TRANSFORM_OBJECT_SLOT); - shaderObject.transformObjectSlot = GL41Backend::TRANSFORM_OBJECT_SLOT; - } - - loc = glGetUniformBlockIndex(glprogram, "transformCameraBuffer"); - if (loc >= 0) { - glUniformBlockBinding(glprogram, loc, gpu::TRANSFORM_CAMERA_SLOT); - shaderObject.transformCameraSlot = gpu::TRANSFORM_CAMERA_SLOT; - } - - (void)CHECK_GL_ERROR(); -} diff --git a/libraries/gpu-gl/src/gpu/gl41/GL41BackendTransform.cpp b/libraries/gpu-gl/src/gpu/gl41/GL41BackendTransform.cpp index 41a3c5cf25..b11707eba2 100644 --- a/libraries/gpu-gl/src/gpu/gl41/GL41BackendTransform.cpp +++ b/libraries/gpu-gl/src/gpu/gl41/GL41BackendTransform.cpp @@ -20,7 +20,7 @@ void GL41Backend::initTransform() { glGenTextures(1, &_transform._objectBufferTexture); size_t cameraSize = sizeof(TransformStageState::CameraBufferElement); while (_transform._cameraUboSize < cameraSize) { - _transform._cameraUboSize += _uboAlignment; + _transform._cameraUboSize += UNIFORM_BUFFER_OFFSET_ALIGNMENT; } } @@ -58,7 +58,7 @@ void GL41Backend::transferTransformState(const Batch& batch) const { glBindBuffer(GL_ARRAY_BUFFER, 0); } - glActiveTexture(GL_TEXTURE0 + GL41Backend::TRANSFORM_OBJECT_SLOT); + glActiveTexture(GL_TEXTURE0 + slot::texture::ObjectTransforms); glBindTexture(GL_TEXTURE_BUFFER, _transform._objectBufferTexture); if (!batch._objects.empty()) { glTexBuffer(GL_TEXTURE_BUFFER, GL_RGBA32F, _transform._objectBuffer); diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45Backend.cpp b/libraries/gpu-gl/src/gpu/gl45/GL45Backend.cpp index c119e27d5b..bbe011d237 100644 --- a/libraries/gpu-gl/src/gpu/gl45/GL45Backend.cpp +++ b/libraries/gpu-gl/src/gpu/gl45/GL45Backend.cpp @@ -18,8 +18,26 @@ Q_LOGGING_CATEGORY(gpugl45logging, "hifi.gpu.gl45") using namespace gpu; using namespace gpu::gl45; +GLint GL45Backend::MAX_COMBINED_SHADER_STORAGE_BLOCKS{ 0 }; +GLint GL45Backend::MAX_UNIFORM_LOCATIONS{ 0 }; + +static void staticInit() { + static std::once_flag once; + std::call_once(once, [&] { + glGetIntegerv(GL_MAX_COMBINED_SHADER_STORAGE_BLOCKS, &GL45Backend::MAX_COMBINED_SHADER_STORAGE_BLOCKS); + glGetIntegerv(GL_MAX_UNIFORM_LOCATIONS, &GL45Backend::MAX_UNIFORM_LOCATIONS); + }); +} const std::string GL45Backend::GL45_VERSION { "GL45" }; +GL45Backend::GL45Backend(bool syncCache) : Parent(syncCache) { + staticInit(); +} + +GL45Backend::GL45Backend() : Parent() { + staticInit(); +} + void GL45Backend::recycle() const { Parent::recycle(); } diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h b/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h index cb7ddce930..658bea2a3e 100644 --- a/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h +++ b/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h @@ -33,18 +33,14 @@ class GL45Backend : public GLBackend { friend class Context; public: + static GLint MAX_COMBINED_SHADER_STORAGE_BLOCKS; + static GLint MAX_UNIFORM_LOCATIONS; #if GPU_BINDLESS_TEXTURES virtual bool supportsBindless() const override { return true; } #endif -#ifdef GPU_SSBO_TRANSFORM_OBJECT - static const GLint TRANSFORM_OBJECT_SLOT { 14 }; // SSBO binding slot -#else - static const GLint TRANSFORM_OBJECT_SLOT { 31 }; // TBO binding slot -#endif - - explicit GL45Backend(bool syncCache) : Parent(syncCache) {} - GL45Backend() : Parent() {} + explicit GL45Backend(bool syncCache); + GL45Backend(); virtual ~GL45Backend() { // call resetStages here rather than in ~GLBackend dtor because it will call releaseResourceBuffer // which is pure virtual from GLBackend's dtor. @@ -273,8 +269,6 @@ protected: // Shader Stage std::string getBackendShaderHeader() const override; - void makeProgramBindings(ShaderObject& shaderObject) override; - int makeResourceBufferSlots(const ShaderObject& shaderProgram, const Shader::BindingSet& slotBindings,Shader::SlotSet& resourceBuffers) override; // Texture Management Stage void initTextureManagementStage() override; diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45BackendShader.cpp b/libraries/gpu-gl/src/gpu/gl45/GL45BackendShader.cpp index f1f388d501..6cc0d226d6 100644 --- a/libraries/gpu-gl/src/gpu/gl45/GL45BackendShader.cpp +++ b/libraries/gpu-gl/src/gpu/gl45/GL45BackendShader.cpp @@ -26,152 +26,3 @@ std::string GL45Backend::getBackendShaderHeader() const { ); return header; } - -int GL45Backend::makeResourceBufferSlots(const ShaderObject& shaderProgram, const Shader::BindingSet& slotBindings,Shader::SlotSet& resourceBuffers) { - const auto& glprogram = shaderProgram.glprogram; - GLint buffersCount = 0; - glGetProgramInterfaceiv(glprogram, GL_SHADER_STORAGE_BLOCK, GL_ACTIVE_RESOURCES, &buffersCount); - - // fast exit - if (buffersCount == 0) { - return 0; - } - - GLint maxNumResourceBufferSlots = 0; - glGetIntegerv(GL_MAX_SHADER_STORAGE_BUFFER_BINDINGS, &maxNumResourceBufferSlots); - std::vector resourceBufferSlotMap(maxNumResourceBufferSlots, -1); - - struct ResourceBlockInfo { - using Vector = std::vector; - const GLuint index{ 0 }; - const std::string name; - GLint binding{ -1 }; - GLint size{ 0 }; - - static std::string getName(GLuint glprogram, GLuint i) { - static const GLint NAME_LENGTH = 256; - GLint length = 0; - GLchar nameBuffer[NAME_LENGTH]; - glGetProgramResourceName(glprogram, GL_SHADER_STORAGE_BLOCK, i, NAME_LENGTH, &length, nameBuffer); - return std::string(nameBuffer); - } - - ResourceBlockInfo(GLuint glprogram, GLuint i) : index(i), name(getName(glprogram, i)) { - GLenum props[2] = { GL_BUFFER_BINDING, GL_BUFFER_DATA_SIZE}; - glGetProgramResourceiv(glprogram, GL_SHADER_STORAGE_BLOCK, i, 2, props, 2, nullptr, &binding); - } - }; - - ResourceBlockInfo::Vector resourceBlocks; - resourceBlocks.reserve(buffersCount); - for (int i = 0; i < buffersCount; i++) { - resourceBlocks.push_back(ResourceBlockInfo(glprogram, i)); - } - - for (auto& info : resourceBlocks) { - auto requestedBinding = slotBindings.find(info.name); - if (requestedBinding != slotBindings.end()) { - info.binding = (*requestedBinding)._location; - glShaderStorageBlockBinding(glprogram, info.index, info.binding); - resourceBufferSlotMap[info.binding] = info.index; - } - } - - for (auto& info : resourceBlocks) { - if (slotBindings.count(info.name)) { - continue; - } - - // If the binding is -1, or the binding maps to an already used binding - if (info.binding == -1 || !isUnusedSlot(resourceBufferSlotMap[info.binding])) { - // If no binding was assigned then just do it finding a free slot - auto slotIt = std::find_if(resourceBufferSlotMap.begin(), resourceBufferSlotMap.end(), GLBackend::isUnusedSlot); - if (slotIt != resourceBufferSlotMap.end()) { - info.binding = slotIt - resourceBufferSlotMap.begin(); - glShaderStorageBlockBinding(glprogram, info.index, info.binding); - } else { - // This should never happen, an active ssbo cannot find an available slot among the max available?! - info.binding = -1; - } - } - - resourceBufferSlotMap[info.binding] = info.index; - } - - for (auto& info : resourceBlocks) { - static const Element element(SCALAR, gpu::UINT32, gpu::RESOURCE_BUFFER); - resourceBuffers.insert(Shader::Slot(info.name, info.binding, element, Resource::BUFFER, info.size)); - } - return buffersCount; -/* - GLint ssboCount = 0; - glGetProgramInterfaceiv(glprogram, GL_SHADER_STORAGE_BLOCK, GL_ACTIVE_RESOURCES, &ssboCount); - if (ssboCount > 0) { - GLint maxNameLength = 0; - glGetProgramInterfaceiv(glprogram, GL_SHADER_STORAGE_BLOCK, GL_MAX_NAME_LENGTH, &maxNameLength); - std::vector nameBytes(maxNameLength); - - for (GLint b = 0; b < ssboCount; b++) { - GLint length; - glGetProgramResourceName(glprogram, GL_SHADER_STORAGE_BLOCK, b, maxNameLength, &length, nameBytes.data()); - std::string bufferName(nameBytes.data()); - - GLenum props = GL_BUFFER_BINDING; - GLint binding = -1; - glGetProgramResourceiv(glprogram, GL_SHADER_STORAGE_BLOCK, b, 1, &props, 1, nullptr, &binding); - - auto requestedBinding = slotBindings.find(std::string(bufferName)); - if (requestedBinding != slotBindings.end()) { - if (binding != (*requestedBinding)._location) { - binding = (*requestedBinding)._location; - glShaderStorageBlockBinding(glprogram, b, binding); - } - } - - static const Element element(SCALAR, gpu::UINT32, gpu::RESOURCE_BUFFER); - resourceBuffers.insert(Shader::Slot(bufferName, binding, element, -1)); - } - } - return ssboCount;*/ -} - -void GL45Backend::makeProgramBindings(ShaderObject& shaderObject) { - if (!shaderObject.glprogram) { - return; - } - GLuint glprogram = shaderObject.glprogram; - GLint loc = -1; - - GLBackend::makeProgramBindings(shaderObject); - - // now assign the ubo binding, then DON't relink! - - //Check for gpu specific uniform slotBindings -#ifdef GPU_SSBO_TRANSFORM_OBJECT - loc = glGetProgramResourceIndex(glprogram, GL_SHADER_STORAGE_BLOCK, "transformObjectBuffer"); - if (loc >= 0) { - glShaderStorageBlockBinding(glprogram, loc, GL45Backend::TRANSFORM_OBJECT_SLOT); - shaderObject.transformObjectSlot = GL45Backend::TRANSFORM_OBJECT_SLOT; - } -#else - loc = glGetUniformLocation(glprogram, "transformObjectBuffer"); - if (loc >= 0) { - glProgramUniform1i(glprogram, loc, GL45Backend::TRANSFORM_OBJECT_SLOT); - shaderObject.transformObjectSlot = GL45Backend::TRANSFORM_OBJECT_SLOT; - } -#endif - - loc = glGetUniformBlockIndex(glprogram, "transformCameraBuffer"); - if (loc >= 0) { - glUniformBlockBinding(glprogram, loc, gpu::TRANSFORM_CAMERA_SLOT); - shaderObject.transformCameraSlot = gpu::TRANSFORM_CAMERA_SLOT; - } - - loc = glGetUniformBlockIndex(glprogram, "gpu_resourceTextureTable0"); - if (loc >= 0) { - glUniformBlockBinding(glprogram, loc, RESOURCE_TABLE_TEXTURE_SLOT_OFFSET); - } - - (void)CHECK_GL_ERROR(); -} - diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45BackendTransform.cpp b/libraries/gpu-gl/src/gpu/gl45/GL45BackendTransform.cpp index ae323612c4..f389c5f62c 100644 --- a/libraries/gpu-gl/src/gpu/gl45/GL45BackendTransform.cpp +++ b/libraries/gpu-gl/src/gpu/gl45/GL45BackendTransform.cpp @@ -25,7 +25,7 @@ void GL45Backend::initTransform() { #endif size_t cameraSize = sizeof(TransformStageState::CameraBufferElement); while (_transform._cameraUboSize < cameraSize) { - _transform._cameraUboSize += _uboAlignment; + _transform._cameraUboSize += UNIFORM_BUFFER_OFFSET_ALIGNMENT; } } @@ -57,9 +57,9 @@ void GL45Backend::transferTransformState(const Batch& batch) const { } #ifdef GPU_SSBO_TRANSFORM_OBJECT - glBindBufferBase(GL_SHADER_STORAGE_BUFFER, GL45Backend::TRANSFORM_OBJECT_SLOT, _transform._objectBuffer); + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, slot::storage::ObjectTransforms, _transform._objectBuffer); #else - glActiveTexture(GL_TEXTURE0 + GL45Backend::TRANSFORM_OBJECT_SLOT); + glActiveTexture(GL_TEXTURE0 + slot::texture::ObjectTransforms); glBindTexture(GL_TEXTURE_BUFFER, _transform._objectBufferTexture); glTextureBuffer(_transform._objectBufferTexture, GL_RGBA32F, _transform._objectBuffer); #endif diff --git a/libraries/gpu-gles/src/gpu/gl/GLESBackend.cpp b/libraries/gpu-gles/src/gpu/gl/GLESBackend.cpp index 2e2c988e77..df963ee40c 100644 --- a/libraries/gpu-gles/src/gpu/gl/GLESBackend.cpp +++ b/libraries/gpu-gles/src/gpu/gl/GLESBackend.cpp @@ -50,7 +50,3 @@ GLBackend& getBackend() { } return *INSTANCE; } - -bool GLBackend::makeProgram(Shader& shader, const Shader::BindingSet& slotBindings, const Shader::CompilationHandler& handler) { - return GLShader::makeProgram(getBackend(), shader, slotBindings, handler); -} diff --git a/libraries/gpu-gles/src/gpu/gles/GLESBackend.h b/libraries/gpu-gles/src/gpu/gles/GLESBackend.h index 9656d29ac5..785f4c3ef9 100644 --- a/libraries/gpu-gles/src/gpu/gles/GLESBackend.h +++ b/libraries/gpu-gles/src/gpu/gles/GLESBackend.h @@ -163,9 +163,6 @@ protected: void do_blit(const Batch& batch, size_t paramOffset) override; std::string getBackendShaderHeader() const override; - void makeProgramBindings(ShaderObject& shaderObject) override; - int makeResourceBufferSlots(const ShaderObject& shaderObject, const Shader::BindingSet& slotBindings,Shader::SlotSet& resourceBuffers) override; - }; } } @@ -173,4 +170,4 @@ protected: Q_DECLARE_LOGGING_CATEGORY(gpugleslogging) -#endif \ No newline at end of file +#endif diff --git a/libraries/gpu-gles/src/gpu/gles/GLESBackendShader.cpp b/libraries/gpu-gles/src/gpu/gles/GLESBackendShader.cpp index 34caa97696..ee8408c533 100644 --- a/libraries/gpu-gles/src/gpu/gles/GLESBackendShader.cpp +++ b/libraries/gpu-gles/src/gpu/gles/GLESBackendShader.cpp @@ -24,93 +24,3 @@ std::string GLESBackend::getBackendShaderHeader() const { )SHADER"); return header; } - -int GLESBackend::makeResourceBufferSlots(const ShaderObject& shaderObject, const Shader::BindingSet& slotBindings,Shader::SlotSet& resourceBuffers) { - GLint ssboCount = 0; - GLint uniformsCount = 0; - const auto& glprogram = shaderObject.glprogram; - - for (const auto& uniform : shaderObject.uniforms) { - const auto& type = uniform.type; - const auto& location = uniform.location; - const auto& name = uniform.name; - const GLint INVALID_UNIFORM_LOCATION = -1; - - // Try to make sense of the gltype - auto elementResource = getFormatFromGLUniform(type); - - // The uniform as a standard var type - if (location != INVALID_UNIFORM_LOCATION) { - - if (elementResource._resource == Resource::BUFFER) { - if (elementResource._element.getSemantic() == gpu::RESOURCE_BUFFER) { - // Let's make sure the name doesn't contains an array element - std::string sname(name); - auto foundBracket = sname.find_first_of('['); - if (foundBracket != std::string::npos) { - // std::string arrayname = sname.substr(0, foundBracket); - - if (sname[foundBracket + 1] == '0') { - sname = sname.substr(0, foundBracket); - } else { - // skip this uniform since it's not the first element of an array - continue; - } - } - - // For texture/Sampler, the location is the actual binding value - GLint binding = -1; - glGetUniformiv(glprogram, location, &binding); - - if (binding == GLESBackend::TRANSFORM_OBJECT_SLOT) { - continue; - } - - auto requestedBinding = slotBindings.find(std::string(sname)); - if (requestedBinding != slotBindings.end()) { - GLint requestedLoc = (*requestedBinding)._location + GLESBackend::RESOURCE_BUFFER_SLOT0_TEX_UNIT; - if (binding != requestedLoc) { - binding = requestedLoc; - } - } else { - binding += GLESBackend::RESOURCE_BUFFER_SLOT0_TEX_UNIT; - } - glProgramUniform1i(glprogram, location, binding); - - ssboCount++; - resourceBuffers.insert(Shader::Slot(name, binding, elementResource._element, elementResource._resource)); - } - } - } - } - - return ssboCount; -} - -void GLESBackend::makeProgramBindings(ShaderObject& shaderObject) { - if (!shaderObject.glprogram) { - return; - } - GLuint glprogram = shaderObject.glprogram; - GLint loc = -1; - - GLBackend::makeProgramBindings(shaderObject); - - // now assign the ubo binding, then DON't relink! - - //Check for gpu specific uniform slotBindings - loc = glGetUniformLocation(glprogram, "transformObjectBuffer"); - if (loc >= 0) { - glProgramUniform1i(glprogram, loc, GLESBackend::TRANSFORM_OBJECT_SLOT); - shaderObject.transformObjectSlot = GLESBackend::TRANSFORM_OBJECT_SLOT; - } - - loc = glGetUniformBlockIndex(glprogram, "transformCameraBuffer"); - if (loc >= 0) { - glUniformBlockBinding(glprogram, loc, gpu::TRANSFORM_CAMERA_SLOT); - shaderObject.transformCameraSlot = gpu::TRANSFORM_CAMERA_SLOT; - } - - (void)CHECK_GL_ERROR(); -} - diff --git a/libraries/gpu-gles/src/gpu/gles/GLESBackendTexture.cpp b/libraries/gpu-gles/src/gpu/gles/GLESBackendTexture.cpp index 911dfb8bb8..23dc271af9 100644 --- a/libraries/gpu-gles/src/gpu/gles/GLESBackendTexture.cpp +++ b/libraries/gpu-gles/src/gpu/gles/GLESBackendTexture.cpp @@ -65,20 +65,24 @@ GLTexture* GLESBackend::syncGPUObject(const TexturePointer& texturePointer) { object = new GLESAttachmentTexture(shared_from_this(), texture); break; - case TextureUsageType::RESOURCE: -// FIXME disabling variable allocation textures for now, while debugging android rendering -// and crashes -#if 0 - qCDebug(gpugllogging) << "variable / Strict texture " << texture.source().c_str(); - object = new GLESResourceTexture(shared_from_this(), texture); - GLVariableAllocationSupport::addMemoryManagedTexture(texturePointer); - break; -#endif case TextureUsageType::STRICT_RESOURCE: qCDebug(gpugllogging) << "Strict texture " << texture.source().c_str(); object = new GLESStrictResourceTexture(shared_from_this(), texture); break; + case TextureUsageType::RESOURCE: { + auto &transferEngine = _textureManagement._transferEngine; + if (transferEngine->allowCreate()) { + object = new GLESResourceTexture(shared_from_this(), texture); + transferEngine->addMemoryManagedTexture(texturePointer); + } else { + auto fallback = texturePointer->getFallbackTexture(); + if (fallback) { + object = static_cast(syncGPUObject(fallback)); + } + } + break; + } default: Q_UNREACHABLE(); } @@ -195,7 +199,6 @@ Size GLESTexture::copyMipFaceLinesFromTexture(uint16_t mip, uint8_t face, const glTexSubImage2D(target, mip, 0, yOffset, size.x, size.y, format, type, sourcePointer); } } else { - // TODO: implement for android assert(false); amountCopied = 0; } @@ -385,7 +388,6 @@ void GLESVariableAllocationTexture::allocateStorage(uint16 allocatedMip) { const auto totalMips = _gpuObject.getNumMips(); const auto mips = totalMips - _allocatedMip; withPreservedTexture([&] { - // FIXME technically GL 4.2, but OSX includes the ARB_texture_storage extension glTexStorage2D(_target, mips, texelFormat.internalFormat, dimensions.x, dimensions.y); CHECK_GL_ERROR(); }); auto mipLevels = _gpuObject.getNumMips(); @@ -426,139 +428,26 @@ void GLESVariableAllocationTexture::syncSampler() const { }); } - -void copyUncompressedTexGPUMem(const gpu::Texture& texture, GLenum texTarget, GLuint srcId, GLuint destId, uint16_t numMips, uint16_t srcMipOffset, uint16_t destMipOffset, uint16_t populatedMips) { - // DestID must be bound to the GLESBackend::RESOURCE_TRANSFER_TEX_UNIT - - GLuint fbo { 0 }; - glGenFramebuffers(1, &fbo); - glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo); - - uint16_t mips = numMips; - // copy pre-existing mips - for (uint16_t mip = populatedMips; mip < mips; ++mip) { +void copyTexGPUMem(const gpu::Texture& texture, GLenum texTarget, GLuint srcId, GLuint destId, uint16_t numMips, uint16_t srcMipOffset, uint16_t destMipOffset, uint16_t populatedMips) { + for (uint16_t mip = populatedMips; mip < numMips; ++mip) { auto mipDimensions = texture.evalMipDimensions(mip); uint16_t targetMip = mip - destMipOffset; uint16_t sourceMip = mip - srcMipOffset; - for (GLenum target : GLTexture::getFaceTargets(texTarget)) { - glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, target, srcId, sourceMip); - (void)CHECK_GL_ERROR(); - glCopyTexSubImage2D(target, targetMip, 0, 0, 0, 0, mipDimensions.x, mipDimensions.y); + auto faces = GLTexture::getFaceCount(texTarget); + for (uint8_t face = 0; face < faces; ++face) { + glCopyImageSubData( + srcId, texTarget, sourceMip, 0, 0, face, + destId, texTarget, targetMip, 0, 0, face, + mipDimensions.x, mipDimensions.y, 1 + ); (void)CHECK_GL_ERROR(); } } - - // destroy the transfer framebuffer - glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); - glDeleteFramebuffers(1, &fbo); -} - -void copyCompressedTexGPUMem(const gpu::Texture& texture, GLenum texTarget, GLuint srcId, GLuint destId, uint16_t numMips, uint16_t srcMipOffset, uint16_t destMipOffset, uint16_t populatedMips) { - // DestID must be bound to the GLESBackend::RESOURCE_TRANSFER_TEX_UNIT - - struct MipDesc { - GLint _faceSize; - GLint _size; - GLint _offset; - GLint _width; - GLint _height; - }; - std::vector sourceMips(numMips); - - std::vector bytes; - - glActiveTexture(GL_TEXTURE0 + GLESBackend::RESOURCE_TRANSFER_EXTRA_TEX_UNIT); - glBindTexture(texTarget, srcId); - const auto& faceTargets = GLTexture::getFaceTargets(texTarget); - GLint internalFormat { 0 }; - - // Collect the mip description from the source texture - GLint bufferOffset { 0 }; - for (uint16_t mip = populatedMips; mip < numMips; ++mip) { - auto& sourceMip = sourceMips[mip]; - - uint16_t sourceLevel = mip - srcMipOffset; - - // Grab internal format once - if (internalFormat == 0) { - glGetTexLevelParameteriv(faceTargets[0], sourceLevel, GL_TEXTURE_INTERNAL_FORMAT, &internalFormat); - } - - // Collect the size of the first face, and then compute the total size offset needed for this mip level - auto mipDimensions = texture.evalMipDimensions(mip); - sourceMip._width = mipDimensions.x; - sourceMip._height = mipDimensions.y; -#ifdef DEBUG_COPY - glGetTexLevelParameteriv(faceTargets.front(), sourceLevel, GL_TEXTURE_WIDTH, &sourceMip._width); - glGetTexLevelParameteriv(faceTargets.front(), sourceLevel, GL_TEXTURE_HEIGHT, &sourceMip._height); -#endif - // TODO: retrieve the size of a compressed image - assert(false); - //glGetTexLevelParameteriv(faceTargets.front(), sourceLevel, GL_TEXTURE_COMPRESSED_IMAGE_SIZE, &sourceMip._faceSize); - sourceMip._size = (GLint)faceTargets.size() * sourceMip._faceSize; - sourceMip._offset = bufferOffset; - bufferOffset += sourceMip._size; - } - (void)CHECK_GL_ERROR(); - - // Allocate the PBO to accomodate for all the mips to copy - GLuint pbo { 0 }; - glGenBuffers(1, &pbo); - glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo); - glBufferData(GL_PIXEL_PACK_BUFFER, bufferOffset, nullptr, GL_STATIC_COPY); - (void)CHECK_GL_ERROR(); - - // Transfer from source texture to pbo - for (uint16_t mip = populatedMips; mip < numMips; ++mip) { - auto& sourceMip = sourceMips[mip]; - - uint16_t sourceLevel = mip - srcMipOffset; - - for (GLint f = 0; f < (GLint)faceTargets.size(); f++) { - // TODO: implement for android - //glGetCompressedTexImage(faceTargets[f], sourceLevel, BUFFER_OFFSET(sourceMip._offset + f * sourceMip._faceSize)); - } - (void)CHECK_GL_ERROR(); - } - - // Now populate the new texture from the pbo - glBindTexture(texTarget, 0); - glBindBuffer(GL_PIXEL_PACK_BUFFER, 0); - glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo); - - glActiveTexture(GL_TEXTURE0 + GLESBackend::RESOURCE_TRANSFER_TEX_UNIT); - - // Transfer from pbo to new texture - for (uint16_t mip = populatedMips; mip < numMips; ++mip) { - auto& sourceMip = sourceMips[mip]; - - uint16_t destLevel = mip - destMipOffset; - - for (GLint f = 0; f < (GLint)faceTargets.size(); f++) { -#ifdef DEBUG_COPY - GLint destWidth, destHeight, destSize; - glGetTexLevelParameteriv(faceTargets.front(), destLevel, GL_TEXTURE_WIDTH, &destWidth); - glGetTexLevelParameteriv(faceTargets.front(), destLevel, GL_TEXTURE_HEIGHT, &destHeight); - glGetTexLevelParameteriv(faceTargets.front(), destLevel, GL_TEXTURE_COMPRESSED_IMAGE_SIZE, &destSize); -#endif - glCompressedTexSubImage2D(faceTargets[f], destLevel, 0, 0, sourceMip._width, sourceMip._height, internalFormat, - sourceMip._faceSize, BUFFER_OFFSET(sourceMip._offset + f * sourceMip._faceSize)); - } - } - - glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); - glDeleteBuffers(1, &pbo); } void GLESVariableAllocationTexture::copyTextureMipsInGPUMem(GLuint srcId, GLuint destId, uint16_t srcMipOffset, uint16_t destMipOffset, uint16_t populatedMips) { uint16_t numMips = _gpuObject.getNumMips(); - withPreservedTexture([&] { - if (_texelFormat.isCompressed()) { - copyCompressedTexGPUMem(_gpuObject, _target, srcId, destId, numMips, srcMipOffset, destMipOffset, populatedMips); - } else { - copyUncompressedTexGPUMem(_gpuObject, _target, srcId, destId, numMips, srcMipOffset, destMipOffset, populatedMips); - } - }); + copyTexGPUMem(_gpuObject, _target, srcId, destId, numMips, srcMipOffset, destMipOffset, populatedMips); } size_t GLESVariableAllocationTexture::promote() { diff --git a/libraries/gpu-gles/src/gpu/gles/GLESBackendTransform.cpp b/libraries/gpu-gles/src/gpu/gles/GLESBackendTransform.cpp index 7d33ca822d..661eb0de99 100644 --- a/libraries/gpu-gles/src/gpu/gles/GLESBackendTransform.cpp +++ b/libraries/gpu-gles/src/gpu/gles/GLESBackendTransform.cpp @@ -19,8 +19,10 @@ void GLESBackend::initTransform() { glGenBuffers(1, &_transform._drawCallInfoBuffer); glGenTextures(1, &_transform._objectBufferTexture); size_t cameraSize = sizeof(TransformStageState::CameraBufferElement); - while (_transform._cameraUboSize < cameraSize) { - _transform._cameraUboSize += _uboAlignment; + if (UNIFORM_BUFFER_OFFSET_ALIGNMENT > 0) { + while (_transform._cameraUboSize < cameraSize) { + _transform._cameraUboSize += UNIFORM_BUFFER_OFFSET_ALIGNMENT; + } } } diff --git a/libraries/gpu/CMakeLists.txt b/libraries/gpu/CMakeLists.txt index 207431d8c7..7573cc5473 100644 --- a/libraries/gpu/CMakeLists.txt +++ b/libraries/gpu/CMakeLists.txt @@ -1,6 +1,6 @@ set(TARGET_NAME gpu) -autoscribe_shader_lib(gpu) + setup_hifi_library() -link_hifi_libraries(shared ktx) +link_hifi_libraries(shared ktx shaders) target_nsight() diff --git a/libraries/gpu/src/gpu/Batch.h b/libraries/gpu/src/gpu/Batch.h index 836d7ca5ff..bcbfe0616d 100644 --- a/libraries/gpu/src/gpu/Batch.h +++ b/libraries/gpu/src/gpu/Batch.h @@ -24,15 +24,12 @@ #include "Stream.h" #include "Texture.h" #include "Transform.h" +#include "ShaderConstants.h" class QDebug; #define BATCH_PREALLOCATE_MIN 128 namespace gpu { -enum ReservedSlot { - TRANSFORM_CAMERA_SLOT = 15, -}; - // The named batch data provides a mechanism for accumulating data into buffers over the course // of many independent calls. For instance, two objects in the scene might both want to render // a simple box, but are otherwise unaware of each other. The common code that they call to render @@ -170,10 +167,10 @@ public: void resetViewTransform() { setViewTransform(Transform(), false); } void setViewTransform(const Transform& view, bool camera = true); void setProjectionTransform(const Mat4& proj); - void setProjectionJitter(float jx = 0.0f, float jy = 0.0f); - // Very simple 1 level stack management of jitter. - void pushProjectionJitter(float jx = 0.0f, float jy = 0.0f); - void popProjectionJitter(); + void setProjectionJitter(float jx = 0.0f, float jy = 0.0f); + // Very simple 1 level stack management of jitter. + void pushProjectionJitter(float jx = 0.0f, float jy = 0.0f); + void popProjectionJitter(); // Viewport is xy = low left corner in framebuffer, zw = width height of the viewport, expressed in pixels void setViewportTransform(const Vec4i& viewport); void setDepthRangeTransform(float nearDepth, float farDepth); @@ -299,9 +296,9 @@ public: COMMAND_setModelTransform, COMMAND_setViewTransform, - COMMAND_setProjectionTransform, - COMMAND_setProjectionJitter, - COMMAND_setViewportTransform, + COMMAND_setProjectionTransform, + COMMAND_setProjectionJitter, + COMMAND_setViewportTransform, COMMAND_setDepthRangeTransform, COMMAND_setPipeline, @@ -504,7 +501,7 @@ public: NamedBatchDataMap _namedData; - glm::vec2 _projectionJitter{ 0.0f, 0.0f }; + glm::vec2 _projectionJitter{ 0.0f, 0.0f }; bool _enableStereo{ true }; bool _enableSkybox { false }; diff --git a/libraries/gpu/src/gpu/Context.cpp b/libraries/gpu/src/gpu/Context.cpp index bb6b27626a..5783b7f59e 100644 --- a/libraries/gpu/src/gpu/Context.cpp +++ b/libraries/gpu/src/gpu/Context.cpp @@ -34,7 +34,6 @@ void ContextStats::evalDelta(const ContextStats& begin, const ContextStats& end) Context::CreateBackend Context::_createBackendCallback = nullptr; -Context::MakeProgram Context::_makeProgramCallback = nullptr; std::once_flag Context::_initialized; Context::Context() { @@ -139,20 +138,6 @@ void Context::executeFrame(const FramePointer& frame) const { _frameStats.evalDelta(beginStats, endStats); } -bool Context::makeProgram(Shader& shader, const Shader::BindingSet& bindings, const Shader::CompilationHandler& handler) { - PROFILE_RANGE_EX(app, "makeProgram", 0xff4040c0, shader.getID()); - // If we're running in another DLL context, we need to fetch the program callback out of the application - // FIXME find a way to do this without reliance on Qt app properties - if (!_makeProgramCallback) { - void* rawCallback = qApp->property(hifi::properties::gl::MAKE_PROGRAM_CALLBACK).value(); - _makeProgramCallback = reinterpret_cast(rawCallback); - } - if (shader.isProgram() && _makeProgramCallback) { - return _makeProgramCallback(shader, bindings, handler); - } - return false; -} - void Context::enableStereo(bool enable) { _stereo._enable = enable; } diff --git a/libraries/gpu/src/gpu/Context.h b/libraries/gpu/src/gpu/Context.h index 4560ea5526..011f980957 100644 --- a/libraries/gpu/src/gpu/Context.h +++ b/libraries/gpu/src/gpu/Context.h @@ -115,7 +115,7 @@ public: static ContextMetricSize textureResourceIdealGPUMemSize; protected: - virtual bool isStereo() { + virtual bool isStereo() const { return _stereo.isStereo(); } @@ -140,14 +140,12 @@ class Context { public: using Size = Resource::Size; typedef BackendPointer (*CreateBackend)(); - typedef bool (*MakeProgram)(Shader& shader, const Shader::BindingSet& bindings, const Shader::CompilationHandler& handler); // This one call must happen before any context is created or used (Shader::MakeProgram) in order to setup the Backend and any singleton data needed template static void init() { std::call_once(_initialized, [] { _createBackendCallback = T::createBackend; - _makeProgramCallback = T::makeProgram; T::init(); }); } @@ -261,14 +259,7 @@ protected: // Sampled at the end of every frame, the stats of all the counters mutable ContextStats _frameStats; - // This function can only be called by "static Shader::makeProgram()" - // makeProgramShader(...) make a program shader ready to be used in a Batch. - // It compiles the sub shaders, link them and defines the Slots and their bindings. - // If the shader passed is not a program, nothing happens. - static bool makeProgram(Shader& shader, const Shader::BindingSet& bindings, const Shader::CompilationHandler& handler); - static CreateBackend _createBackendCallback; - static MakeProgram _makeProgramCallback; static std::once_flag _initialized; friend class Shader; diff --git a/libraries/gpu/src/gpu/DrawColor.slf b/libraries/gpu/src/gpu/DrawColor.slf index c24d69d29f..2540d56d69 100644 --- a/libraries/gpu/src/gpu/DrawColor.slf +++ b/libraries/gpu/src/gpu/DrawColor.slf @@ -2,6 +2,8 @@ <$VERSION_HEADER$> // Generated on <$_SCRIBE_DATE$> // +// DrawColor.frag +// // Draw with color uniform // // Created by Olivier Prat on 25/10/2017 @@ -10,9 +12,12 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -uniform vec4 color; -out vec4 outFragColor; +<@include gpu/ShaderConstants.h@> + +layout(location=GPU_UNIFORM_COLOR) uniform vec4 color; + +layout(location=0) out vec4 outFragColor; void main(void) { outFragColor = color; diff --git a/libraries/gpu/src/gpu/DrawColoredTexture.slf b/libraries/gpu/src/gpu/DrawColoredTexture.slf index 2a7f6aae36..632bf18391 100755 --- a/libraries/gpu/src/gpu/DrawColoredTexture.slf +++ b/libraries/gpu/src/gpu/DrawColoredTexture.slf @@ -2,6 +2,8 @@ <$VERSION_HEADER$> // Generated on <$_SCRIBE_DATE$> // +// DrawColoredTexture.frag +// // Draw texture 0 fetched at texcoord.xy, Blend with color uniform // // Created by Sam Gateau on 7/12/2015 @@ -11,12 +13,13 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +<@include gpu/ShaderConstants.h@> -uniform sampler2D colorMap; -uniform vec4 color; +layout(binding=0) uniform sampler2D colorMap; +layout(location=GPU_UNIFORM_COLOR) uniform vec4 color; -in vec2 varTexCoord0; -out vec4 outFragColor; +layout(location=0) in vec2 varTexCoord0; +layout(location=0) out vec4 outFragColor; void main(void) { outFragColor = texture(colorMap, varTexCoord0) * color; diff --git a/libraries/gpu/src/gpu/DrawNada.slf b/libraries/gpu/src/gpu/DrawNada.slf new file mode 100644 index 0000000000..6f286a5164 --- /dev/null +++ b/libraries/gpu/src/gpu/DrawNada.slf @@ -0,0 +1,5 @@ +<@include gpu/Config.slh@> + +<$VERSION_HEADER$> + +void main(void) { } diff --git a/libraries/gpu/src/gpu/DrawTexcoordRectTransformUnitQuad.slv b/libraries/gpu/src/gpu/DrawTexcoordRectTransformUnitQuad.slv index 1f788051bc..d401fc40c2 100755 --- a/libraries/gpu/src/gpu/DrawTexcoordRectTransformUnitQuad.slv +++ b/libraries/gpu/src/gpu/DrawTexcoordRectTransformUnitQuad.slv @@ -1,6 +1,9 @@ <@include gpu/Config.slh@> <$VERSION_HEADER$> // Generated on <$_SCRIBE_DATE$> +// +// DrawTexcoordRectTransformUnitQuad.vert + // // Draw and transform the unit quad [-1,-1 -> 1,1] // Transform the normalized texcoords [0, 1] to be in the range [texcoordRect.xy, texcoordRect.xy + texcoordRect.zw] @@ -14,12 +17,13 @@ // <@include gpu/Transform.slh@> +<@include gpu/ShaderConstants.h@> <$declareStandardTransform()$> -uniform vec4 texcoordRect; +layout(location=GPU_UNIFORM_TEXCOORD_RECT) uniform vec4 texcoordRect; -out vec2 varTexCoord0; +layout(location=0) out vec2 varTexCoord0; void main(void) { const vec4 UNIT_QUAD[4] = vec4[4]( diff --git a/libraries/gpu/src/gpu/DrawTexture.slf b/libraries/gpu/src/gpu/DrawTexture.slf index 64389f02b3..4298729b8b 100755 --- a/libraries/gpu/src/gpu/DrawTexture.slf +++ b/libraries/gpu/src/gpu/DrawTexture.slf @@ -2,6 +2,8 @@ <$VERSION_HEADER$> // Generated on <$_SCRIBE_DATE$> // +// DrawTexture.frag +// // Draw texture 0 fetched at texcoord.xy // // Created by Sam Gateau on 6/22/2015 @@ -12,10 +14,10 @@ // -uniform sampler2D colorMap; +layout(binding=0) uniform sampler2D colorMap; -in vec2 varTexCoord0; -out vec4 outFragColor; +layout(location=0) in vec2 varTexCoord0; +layout(location=0) out vec4 outFragColor; void main(void) { outFragColor = texture(colorMap, varTexCoord0); diff --git a/libraries/gpu/src/gpu/DrawTextureMirroredX.slf b/libraries/gpu/src/gpu/DrawTextureMirroredX.slf index aef4033496..ab6333f08d 100644 --- a/libraries/gpu/src/gpu/DrawTextureMirroredX.slf +++ b/libraries/gpu/src/gpu/DrawTextureMirroredX.slf @@ -2,6 +2,8 @@ <$VERSION_HEADER$> // Generated on <$_SCRIBE_DATE$> // +// DrawTextureMirroredX.frag +// // Draw texture 0 fetched at (1.0 - texcoord.x, texcoord.y) // // Created by Sam Gondelman on 10/24/2017 @@ -12,10 +14,10 @@ // -uniform sampler2D colorMap; +layout(binding=0) uniform sampler2D colorMap; -in vec2 varTexCoord0; -out vec4 outFragColor; +layout(location=0) in vec2 varTexCoord0; +layout(location=0) out vec4 outFragColor; void main(void) { outFragColor = texture(colorMap, vec2(1.0 - varTexCoord0.x, varTexCoord0.y)); diff --git a/libraries/gpu/src/gpu/DrawTextureOpaque.slf b/libraries/gpu/src/gpu/DrawTextureOpaque.slf index 14d2072ff3..b3227325bf 100755 --- a/libraries/gpu/src/gpu/DrawTextureOpaque.slf +++ b/libraries/gpu/src/gpu/DrawTextureOpaque.slf @@ -2,6 +2,8 @@ <$VERSION_HEADER$> // Generated on <$_SCRIBE_DATE$> // +// DrawTextureOpaque.frag +// // Draw texture 0 fetched at texcoord.xy // Alpha is 1 // @@ -12,11 +14,13 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +<@include gpu/ShaderConstants.h@> -uniform sampler2D colorMap; +layout(binding=0) uniform sampler2D colorMap; -in vec2 varTexCoord0; -out vec4 outFragColor; +layout(location=0) in vec2 varTexCoord0; + +layout(location=0) out vec4 outFragColor; void main(void) { outFragColor = vec4(texture(colorMap, varTexCoord0).xyz, 1.0); diff --git a/libraries/gpu/src/gpu/DrawTransformUnitQuad.slv b/libraries/gpu/src/gpu/DrawTransformUnitQuad.slv index 845cf0326d..eed3c92245 100755 --- a/libraries/gpu/src/gpu/DrawTransformUnitQuad.slv +++ b/libraries/gpu/src/gpu/DrawTransformUnitQuad.slv @@ -2,6 +2,8 @@ <$VERSION_HEADER$> // Generated on <$_SCRIBE_DATE$> // +// DrawTransformUnitQuad.vert +// // Draw and transform the unit quad [-1,-1 -> 1,1] // Simply draw a Triangle_strip of 2 triangles, no input buffers or index buffer needed // @@ -16,7 +18,7 @@ <$declareStandardTransform()$> -out vec2 varTexCoord0; +layout(location=0) out vec2 varTexCoord0; void main(void) { const vec4 UNIT_QUAD[4] = vec4[4]( diff --git a/libraries/gpu/src/gpu/DrawTransformVertexPosition.slv b/libraries/gpu/src/gpu/DrawTransformVertexPosition.slv index cf66a615f5..39771403d1 100644 --- a/libraries/gpu/src/gpu/DrawTransformVertexPosition.slv +++ b/libraries/gpu/src/gpu/DrawTransformVertexPosition.slv @@ -2,6 +2,8 @@ <$VERSION_HEADER$> // Generated on <$_SCRIBE_DATE$> // +// DrawTransformVertexPosition.vert +// // Draw and transform the fed vertex position with the standard MVP stack // Output the clip position // @@ -16,9 +18,9 @@ <$declareStandardTransform()$> -layout(location = 0) in vec4 inPosition; +layout(location=0) in vec4 inPosition; -out vec3 varWorldPos; +layout(location=0) out vec3 varWorldPos; void main(void) { // standard transform diff --git a/libraries/gpu/src/gpu/DrawUnitQuadTexcoord.slv b/libraries/gpu/src/gpu/DrawUnitQuadTexcoord.slv index 289d8f96b1..0378cd30e3 100644 --- a/libraries/gpu/src/gpu/DrawUnitQuadTexcoord.slv +++ b/libraries/gpu/src/gpu/DrawUnitQuadTexcoord.slv @@ -2,6 +2,8 @@ <$VERSION_HEADER$> // Generated on <$_SCRIBE_DATE$> // +// DrawUnitQuadTexcoord.vert +// // Draw the unit quad [-1,-1 -> 1,1] amd pass along the unit texcoords [0, 0 -> 1, 1]. Not transform used. // Simply draw a Triangle_strip of 2 triangles, no input buffers or index buffer needed // @@ -11,7 +13,7 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -out vec2 varTexCoord0; +layout(location=0) out vec2 varTexCoord0; void main(void) { const float depth = 1.0; diff --git a/libraries/gpu/src/gpu/DrawVertexPosition.slv b/libraries/gpu/src/gpu/DrawVertexPosition.slv index b12280d577..e9961750ba 100644 --- a/libraries/gpu/src/gpu/DrawVertexPosition.slv +++ b/libraries/gpu/src/gpu/DrawVertexPosition.slv @@ -2,6 +2,8 @@ <$VERSION_HEADER$> // Generated on <$_SCRIBE_DATE$> // +// DrawVertexPosition.vert +// // Draw the fed vertex position, pass straight as clip pos // Output the clip position // @@ -12,7 +14,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -layout(location = 0) in vec4 inPosition; +layout(location=0) in vec4 inPosition; void main(void) { gl_Position = inPosition; diff --git a/libraries/gpu/src/gpu/DrawViewportQuadTransformTexcoord.slv b/libraries/gpu/src/gpu/DrawViewportQuadTransformTexcoord.slv index 554728417b..ae252f73d2 100755 --- a/libraries/gpu/src/gpu/DrawViewportQuadTransformTexcoord.slv +++ b/libraries/gpu/src/gpu/DrawViewportQuadTransformTexcoord.slv @@ -2,6 +2,8 @@ <$VERSION_HEADER$> // Generated on <$_SCRIBE_DATE$> // +// DrawViewportQuatTransformTexcoord.vert +// // Draw the unit quad [-1,-1 -> 1,1] filling in // Simply draw a Triangle_strip of 2 triangles, no input buffers or index buffer needed // @@ -16,7 +18,7 @@ <$declareStandardTransform()$> -out vec2 varTexCoord0; +layout(location=0) out vec2 varTexCoord0; void main(void) { const vec4 UNIT_QUAD[4] = vec4[4]( diff --git a/libraries/gpu/src/gpu/DrawWhite.slf b/libraries/gpu/src/gpu/DrawWhite.slf index bdecc0c5c5..1cdc047655 100644 --- a/libraries/gpu/src/gpu/DrawWhite.slf +++ b/libraries/gpu/src/gpu/DrawWhite.slf @@ -2,7 +2,7 @@ <$VERSION_HEADER$> // Generated on <$_SCRIBE_DATE$> // -// Draw white +// DrawWhite.frag // // Created by Sam Gateau on 5/30/2017 // Copyright 2017 High Fidelity, Inc. @@ -11,7 +11,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -out vec4 outFragColor; +layout(location=0) out vec4 outFragColor; void main(void) { outFragColor = vec4(1.0); diff --git a/libraries/gpu/src/gpu/Inputs.slh b/libraries/gpu/src/gpu/Inputs.slh index 109762136e..a121b46ed8 100644 --- a/libraries/gpu/src/gpu/Inputs.slh +++ b/libraries/gpu/src/gpu/Inputs.slh @@ -10,15 +10,18 @@ !> <@if not GPU_INPUTS_SLH@> <@def GPU_INPUTS_SLH@> -layout(location = 0) in vec4 inPosition; -layout(location = 1) in vec4 inNormal; -layout(location = 2) in vec4 inColor; -layout(location = 3) in vec4 inTexCoord0; -layout(location = 4) in vec4 inTangent; -layout(location = 5) in ivec4 inSkinClusterIndex; -layout(location = 6) in vec4 inSkinClusterWeight; -layout(location = 7) in vec4 inTexCoord1; -layout(location = 8) in vec4 inTexCoord2; -layout(location = 9) in vec4 inTexCoord3; -layout(location = 10) in vec4 inTexCoord4; + +<@include gpu/ShaderConstants.h@> + +layout(location=GPU_ATTR_POSITION) in vec4 inPosition; +layout(location=GPU_ATTR_NORMAL) in vec4 inNormal; +layout(location=GPU_ATTR_COLOR) in vec4 inColor; +layout(location=GPU_ATTR_TEXCOORD0) in vec4 inTexCoord0; +layout(location=GPU_ATTR_TANGENT) in vec4 inTangent; +layout(location=GPU_ATTR_SKIN_CLUSTER_INDEX) in ivec4 inSkinClusterIndex; +layout(location=GPU_ATTR_SKIN_CLUSTER_WEIGHT) in vec4 inSkinClusterWeight; +layout(location=GPU_ATTR_TEXCOORD1) in vec4 inTexCoord1; +layout(location=GPU_ATTR_TEXCOORD2) in vec4 inTexCoord2; +layout(location=GPU_ATTR_TEXCOORD3) in vec4 inTexCoord3; +layout(location=GPU_ATTR_TEXCOORD4) in vec4 inTexCoord4; <@endif@> diff --git a/libraries/gpu/src/gpu/Shader.cpp b/libraries/gpu/src/gpu/Shader.cpp index b539a84925..0191d9d4f1 100755 --- a/libraries/gpu/src/gpu/Shader.cpp +++ b/libraries/gpu/src/gpu/Shader.cpp @@ -12,6 +12,12 @@ #include "Shader.h" #include #include +#include + +#include +#include + +#include #include "Context.h" @@ -49,6 +55,14 @@ Shader::~Shader() { } +void populateSlotSet(Shader::SlotSet& slotSet, const Shader::LocationMap& map) { + for (const auto& entry : map) { + const auto& name = entry.first; + const auto& location = entry.second; + slotSet.insert({ name, location, Element() }); + } +} + Shader::Pointer Shader::createOrReuseDomainShader(Type type, const Source& source) { auto found = _domainShaderMaps[type].find(source); if (found != _domainShaderMaps[type].end()) { @@ -58,21 +72,32 @@ Shader::Pointer Shader::createOrReuseDomainShader(Type type, const Source& sourc } } auto shader = Pointer(new Shader(type, source)); + const auto& reflection = source.getReflection(); + if (0 != reflection.count(BindingType::INPUT)) { + populateSlotSet(shader->_inputs, reflection.find(BindingType::INPUT)->second); + } + if (0 != reflection.count(BindingType::OUTPUT)) { + populateSlotSet(shader->_outputs, reflection.find(BindingType::OUTPUT)->second); + } + if (0 != reflection.count(BindingType::UNIFORM_BUFFER)) { + populateSlotSet(shader->_uniformBuffers, reflection.find(BindingType::UNIFORM_BUFFER)->second); + } + if (0 != reflection.count(BindingType::RESOURCE_BUFFER)) { + populateSlotSet(shader->_resourceBuffers, reflection.find(BindingType::RESOURCE_BUFFER)->second); + } + if (0 != reflection.count(BindingType::TEXTURE)) { + populateSlotSet(shader->_textures, reflection.find(BindingType::TEXTURE)->second); + } + if (0 != reflection.count(BindingType::SAMPLER)) { + populateSlotSet(shader->_samplers, reflection.find(BindingType::SAMPLER)->second); + } + if (0 != reflection.count(BindingType::UNIFORM)) { + populateSlotSet(shader->_uniforms, reflection.find(BindingType::UNIFORM)->second); + } _domainShaderMaps[type].emplace(source, std::weak_ptr(shader)); return shader; } -Shader::Pointer Shader::createVertex(const Source& source) { - return createOrReuseDomainShader(VERTEX, source); -} - -Shader::Pointer Shader::createPixel(const Source& source) { - return createOrReuseDomainShader(PIXEL, source); -} - -Shader::Pointer Shader::createGeometry(const Source& source) { - return createOrReuseDomainShader(GEOMETRY, source); -} ShaderPointer Shader::createOrReuseProgramShader(Type type, const Pointer& vertexShader, const Pointer& geometryShader, const Pointer& pixelShader) { PROFILE_RANGE(app, "createOrReuseProgramShader"); @@ -112,36 +137,32 @@ ShaderPointer Shader::createOrReuseProgramShader(Type type, const Pointer& verte // Program is a new one, let's create it auto program = Pointer(new Shader(type, vertexShader, geometryShader, pixelShader)); + + // Combine the slots from the sub-shaders + for (const auto& shader : program->_shaders) { + const auto& reflection = shader->_source.getReflection(); + if (0 != reflection.count(BindingType::UNIFORM_BUFFER)) { + populateSlotSet(program->_uniformBuffers, reflection.find(BindingType::UNIFORM_BUFFER)->second); + } + if (0 != reflection.count(BindingType::RESOURCE_BUFFER)) { + populateSlotSet(program->_resourceBuffers, reflection.find(BindingType::RESOURCE_BUFFER)->second); + } + if (0 != reflection.count(BindingType::TEXTURE)) { + populateSlotSet(program->_textures, reflection.find(BindingType::TEXTURE)->second); + } + if (0 != reflection.count(BindingType::SAMPLER)) { + populateSlotSet(program->_samplers, reflection.find(BindingType::SAMPLER)->second); + } + if (0 != reflection.count(BindingType::UNIFORM)) { + populateSlotSet(program->_uniforms, reflection.find(BindingType::UNIFORM)->second); + } + + } + _programMap.emplace(key, std::weak_ptr(program)); return program; } - -Shader::Pointer Shader::createProgram(const Pointer& vertexShader, const Pointer& pixelShader) { - return createOrReuseProgramShader(PROGRAM, vertexShader, nullptr, pixelShader); -} - -Shader::Pointer Shader::createProgram(const Pointer& vertexShader, const Pointer& geometryShader, const Pointer& pixelShader) { - return createOrReuseProgramShader(PROGRAM, vertexShader, geometryShader, pixelShader); -} - -void Shader::defineSlots(const SlotSet& uniforms, const SlotSet& uniformBuffers, const SlotSet& resourceBuffers, const SlotSet& textures, const SlotSet& samplers, const SlotSet& inputs, const SlotSet& outputs) { - _uniforms = uniforms; - _uniformBuffers = uniformBuffers; - _resourceBuffers = resourceBuffers; - _textures = textures; - _samplers = samplers; - _inputs = inputs; - _outputs = outputs; -} - -bool Shader::makeProgram(Shader& shader, const Shader::BindingSet& bindings, const CompilationHandler& handler) { - if (shader.isProgram()) { - return Context::makeProgram(shader, bindings, handler); - } - return false; -} - void Shader::setCompilationLogs(const CompilationLogs& logs) const { _compilationLogs.clear(); for (const auto& log : logs) { @@ -153,3 +174,124 @@ void Shader::incrementCompilationAttempt() const { _numCompilationAttempts++; } +Shader::Pointer Shader::createVertex(const Source& source) { + return createOrReuseDomainShader(VERTEX, source); +} + +Shader::Pointer Shader::createPixel(const Source& source) { + return createOrReuseDomainShader(FRAGMENT, source); +} + +Shader::Pointer Shader::createVertex(uint32_t id) { + return createVertex(getShaderSource(id)); +} + +Shader::Pointer Shader::createPixel(uint32_t id) { + return createPixel(getShaderSource(id)); +} + +Shader::Pointer Shader::createProgram(const Pointer& vertexShader, const Pointer& pixelShader) { + return createOrReuseProgramShader(PROGRAM, vertexShader, nullptr, pixelShader); +} + +Shader::Pointer Shader::createProgram(uint32_t programId) { + auto vertexShader = createVertex(shader::getVertexId(programId)); + auto fragmentShader = createPixel(shader::getFragmentId(programId)); + return createOrReuseProgramShader(PROGRAM, vertexShader, nullptr, fragmentShader); +} + +Shader::Pointer Shader::createProgram(const Pointer& vertexShader, const Pointer& geometryShader, const Pointer& pixelShader) { + return createOrReuseProgramShader(PROGRAM, vertexShader, geometryShader, pixelShader); +} + +static const std::string IGNORED_BINDING = "transformObjectBuffer"; + +void updateBindingsFromJsonObject(Shader::LocationMap& inOutSet, const QJsonObject& json) { + for (const auto& key : json.keys()) { + auto keyStr = key.toStdString(); + if (IGNORED_BINDING == keyStr) { + continue; + } + inOutSet[keyStr] = json[key].toInt(); + } +} + +void updateTextureAndResourceBuffersFromJsonObjects(Shader::LocationMap& inOutTextures, Shader::LocationMap& inOutResourceBuffers, + const QJsonObject& json, const QJsonObject& types) { + static const std::string RESOURCE_BUFFER_TEXTURE_TYPE = "samplerBuffer"; + for (const auto& key : json.keys()) { + auto keyStr = key.toStdString(); + if (keyStr == IGNORED_BINDING) { + continue; + } + auto location = json[key].toInt(); + auto type = types[key].toString().toStdString(); + if (type == RESOURCE_BUFFER_TEXTURE_TYPE) { + inOutResourceBuffers[keyStr] = location; + } else { + inOutTextures[key.toStdString()] = location; + } + } +} + +Shader::ReflectionMap getShaderReflection(const std::string& reflectionJson) { + if (reflectionJson.empty() && reflectionJson != std::string("null")) { + return {}; + } + +#define REFLECT_KEY_INPUTS "inputs" +#define REFLECT_KEY_OUTPUTS "outputs" +#define REFLECT_KEY_UBOS "uniformBuffers" +#define REFLECT_KEY_SSBOS "storageBuffers" +#define REFLECT_KEY_UNIFORMS "uniforms" +#define REFLECT_KEY_TEXTURES "textures" +#define REFLECT_KEY_TEXTURE_TYPES "textureTypes" + + auto doc = QJsonDocument::fromJson(reflectionJson.c_str()); + if (doc.isNull()) { + qWarning() << "Invalid shader reflection JSON" << reflectionJson.c_str(); + return {}; + } + + Shader::ReflectionMap result; + auto json = doc.object(); + if (json.contains(REFLECT_KEY_INPUTS)) { + updateBindingsFromJsonObject(result[Shader::BindingType::INPUT], json[REFLECT_KEY_INPUTS].toObject()); + } + if (json.contains(REFLECT_KEY_OUTPUTS)) { + updateBindingsFromJsonObject(result[Shader::BindingType::OUTPUT], json[REFLECT_KEY_OUTPUTS].toObject()); + } + // FIXME eliminate the last of the uniforms + if (json.contains(REFLECT_KEY_UNIFORMS)) { + updateBindingsFromJsonObject(result[Shader::BindingType::UNIFORM], json[REFLECT_KEY_UNIFORMS].toObject()); + } + if (json.contains(REFLECT_KEY_UBOS)) { + updateBindingsFromJsonObject(result[Shader::BindingType::UNIFORM_BUFFER], json[REFLECT_KEY_UBOS].toObject()); + } + + // SSBOs need to come BEFORE the textures. In GL 4.5 the reflection slots aren't really used, but in 4.1 the slots + // are used to explicitly setup bindings after shader linkage, so we want the resource buffer slots to contain the + // texture locations, not the SSBO locations + if (json.contains(REFLECT_KEY_SSBOS)) { + updateBindingsFromJsonObject(result[Shader::BindingType::RESOURCE_BUFFER], json[REFLECT_KEY_SSBOS].toObject()); + } + + // samplerBuffer textures map to gpu ResourceBuffer, while all other textures map to regular gpu Texture + if (json.contains(REFLECT_KEY_TEXTURES)) { + updateTextureAndResourceBuffersFromJsonObjects( + result[Shader::BindingType::TEXTURE], + result[Shader::BindingType::RESOURCE_BUFFER], + json[REFLECT_KEY_TEXTURES].toObject(), + json[REFLECT_KEY_TEXTURE_TYPES].toObject()); + } + + + return result; +} + +Shader::Source Shader::getShaderSource(uint32_t id) { + auto source = shader::loadShaderSource(id); + auto reflectionJson = shader::loadShaderReflection(id); + auto reflection = getShaderReflection(reflectionJson); + return { source, reflection }; +} diff --git a/libraries/gpu/src/gpu/Shader.h b/libraries/gpu/src/gpu/Shader.h index fe92da1469..ad828a0cff 100755 --- a/libraries/gpu/src/gpu/Shader.h +++ b/libraries/gpu/src/gpu/Shader.h @@ -15,10 +15,12 @@ #include #include #include +#include +#include #include #include #include - + namespace gpu { class Shader { @@ -26,30 +28,71 @@ public: // unique identifier of a shader using ID = uint32_t; - typedef std::shared_ptr< Shader > Pointer; - typedef std::vector< Pointer > Shaders; + enum Type + { + VERTEX = 0, + PIXEL, + FRAGMENT = PIXEL, + GEOMETRY, + NUM_DOMAINS, + + PROGRAM, + }; + + typedef std::shared_ptr Pointer; + typedef std::vector Shaders; + + // Needs to match values in shaders/Shaders.h + enum class BindingType + { + INVALID = -1, + INPUT = 0, + OUTPUT, + TEXTURE, + SAMPLER, + UNIFORM_BUFFER, + RESOURCE_BUFFER, + UNIFORM, + }; + + using LocationMap = std::unordered_map; + using ReflectionMap = std::map; class Source { public: - enum Language { + enum Language + { + INVALID = -1, GLSL = 0, + SPIRV = 1, + MSL = 2, + HLSL = 3, }; Source() {} - Source(const std::string& code, Language lang = GLSL) : _code(code), _lang(lang) {} - Source(const Source& source) : _code(source._code), _lang(source._lang) {} + Source(const std::string& code, const ReflectionMap& reflection, Language lang = GLSL) : + _code(code), _reflection(reflection), _lang(lang) {} + Source(const Source& source) : _code(source._code), _reflection(source._reflection), _lang(source._lang) {} virtual ~Source() {} - + virtual const std::string& getCode() const { return _code; } + virtual const ReflectionMap& getReflection() const { return _reflection; } class Less { public: - bool operator() (const Source& x, const Source& y) const { if (x._lang == y._lang) { return x._code < y._code; } else { return (x._lang < y._lang); } } + bool operator()(const Source& x, const Source& y) const { + if (x._lang == y._lang) { + return x._code < y._code; + } else { + return (x._lang < y._lang); + } + } }; protected: std::string _code; - Language _lang = GLSL; + ReflectionMap _reflection; + Language _lang; }; struct CompilationLog { @@ -57,30 +100,40 @@ public: bool compiled{ false }; CompilationLog() {} - CompilationLog(const CompilationLog& src) : - message(src.message), - compiled(src.compiled) {} + CompilationLog(const CompilationLog& src) : message(src.message), compiled(src.compiled) {} }; using CompilationLogs = std::vector; static const int32 INVALID_LOCATION = -1; + template + class Less { + public: + bool operator()(const T& x, const T& y) const { return x._name < y._name; } + }; + class Slot { public: - std::string _name; - int32 _location{INVALID_LOCATION}; + int32 _location{ INVALID_LOCATION }; Element _element; - uint16 _resourceType{Resource::BUFFER}; - uint32 _size { 0 }; - - Slot(const Slot& s) : _name(s._name), _location(s._location), _element(s._element), _resourceType(s._resourceType), _size(s._size) {} - Slot(Slot&& s) : _name(s._name), _location(s._location), _element(s._element), _resourceType(s._resourceType), _size(s._size) {} - Slot(const std::string& name, int32 location, const Element& element, uint16 resourceType = Resource::BUFFER, uint32 size = 0) : - _name(name), _location(location), _element(element), _resourceType(resourceType), _size(size) {} + uint16 _resourceType{ Resource::BUFFER }; + uint32 _size{ 0 }; + + Slot(const Slot& s) : + _name(s._name), _location(s._location), _element(s._element), _resourceType(s._resourceType), _size(s._size) {} + Slot(Slot&& s) : + _name(s._name), _location(s._location), _element(s._element), _resourceType(s._resourceType), _size(s._size) {} + Slot(const std::string& name, + int32 location, + const Element& element, + uint16 resourceType = Resource::BUFFER, + uint32 size = 0) : + _name(name), + _location(location), _element(element), _resourceType(resourceType), _size(size) {} Slot(const std::string& name) : _name(name) {} - Slot& operator= (const Slot& s) { + Slot& operator=(const Slot& s) { _name = s._name; _location = s._location; _element = s._element; @@ -90,54 +143,62 @@ public: } }; - class Binding { - public: - std::string _name; - int32 _location; - Binding(const std::string& name, int32 loc = INVALID_LOCATION) : _name(name), _location(loc) {} - }; + class SlotSet : protected std::set> { + using Parent = std::set>; - template class Less { public: - bool operator() (const T& x, const T& y) const { return x._name < y._name; } - }; - - class SlotSet : public std::set> { - public: - Slot findSlot(const std::string& name) const { - auto key = Slot(name); - auto found = static_cast>*>(this)->find(key); - if (found != end()) { - return (*found); + void insert(const Parent::value_type& value) { + Parent::insert(value); + if (value._location != INVALID_LOCATION) { + _validSlots.insert(value._location); } - return key; } - int32 findLocation(const std::string& name) const { - return findSlot(name)._location; + + using Parent::begin; + using Parent::empty; + using Parent::end; + using Parent::size; + + using LocationMap = std::unordered_map; + using NameVector = std::vector; + + NameVector getNames() const { + NameVector result; + for (const auto& entry : *this) { + result.push_back(entry._name); + } + return result; } + + LocationMap getLocationsByName() const { + LocationMap result; + for (const auto& entry : *this) { + result.insert({ entry._name, entry._location }); + } + return result; + } + + bool isValid(int32 slot) const { return 0 != _validSlots.count(slot); } + protected: + std::unordered_set _validSlots; }; - - typedef std::set> BindingSet; - - enum Type { - VERTEX = 0, - PIXEL, - GEOMETRY, - NUM_DOMAINS, - - PROGRAM, - }; + static Source getShaderSource(uint32_t id); + static Source getVertexShaderSource(uint32_t id) { return getShaderSource(id); } + static Source getFragmentShaderSource(uint32_t id) { return getShaderSource(id); } static Pointer createVertex(const Source& source); static Pointer createPixel(const Source& source); static Pointer createGeometry(const Source& source); + static Pointer createVertex(uint32_t shaderId); + static Pointer createPixel(uint32_t shaderId); + static Pointer createGeometry(uint32_t shaderId); + static Pointer createProgram(uint32_t programId); static Pointer createProgram(const Pointer& vertexShader, const Pointer& pixelShader); static Pointer createProgram(const Pointer& vertexShader, const Pointer& geometryShader, const Pointer& pixelShader); - ~Shader(); ID getID() const { return _ID; } @@ -156,22 +217,6 @@ public: const SlotSet& getResourceBuffers() const { return _resourceBuffers; } const SlotSet& getTextures() const { return _textures; } const SlotSet& getSamplers() const { return _samplers; } - - const SlotSet& getInputs() const { return _inputs; } - const SlotSet& getOutputs() const { return _outputs; } - - // Define the list of uniforms, inputs and outputs for the shader - // This call is intendend to build the list of exposed slots in order - // to correctly bind resource to the shader. - // These can be build "manually" from knowledge of the atual shader code - // or automatically by calling "makeShader()", this is the preferred way - void defineSlots(const SlotSet& uniforms, - const SlotSet& uniformBuffers, - const SlotSet& resourceBuffers, - const SlotSet& textures, - const SlotSet& samplers, - const SlotSet& inputs, - const SlotSet& outputs); // Compilation Handler can be passed while compiling a shader (in the makeProgram call) to be able to give the hand to // the caller thread if the comilation fails and to prvide a different version of the source for it @@ -180,22 +225,7 @@ public: // @param2 the compilation log containing the error message // @param3 a new string ready to be filled with the new version of the source that could be proposed from the handler functor // @return boolean true if the backend should keep trying to compile the shader with the new source returned or false to stop and fail that shader compilation - using CompilationHandler = std::function; - - // makeProgram(...) make a program shader ready to be used in a Batch. - // It compiles the sub shaders, link them and defines the Slots and their bindings. - // If the shader passed is not a program, nothing happens. - // - // It is possible to provide a set of slot bindings (from the name of the slot to a unit number) allowing - // to make sure slots with the same semantics can be always bound on the same location from shader to shader. - // For example, the "diffuseMap" can always be bound to texture unit #1 for different shaders by specifying a Binding("diffuseMap", 1) - // - // As of now (03/2015), the call to makeProgram is in fact calling gpu::Context::makeProgram and does rely - // on the underneath gpu::Context::Backend available. Since we only support glsl, this means that it relies - // on a gl Context and the driver to compile the glsl shader. - // Hoppefully in a few years the shader compilation will be completely abstracted in a separate shader compiler library - // independant of the graphics api in use underneath (looking at you opengl & vulkan). - static bool makeProgram(Shader& shader, const Shader::BindingSet& bindings = Shader::BindingSet(), const CompilationHandler& handler = nullptr); + using CompilationHandler = std::function; // Check the compilation state bool compilationHasFailed() const { return _compilationHasFailed; } @@ -207,16 +237,14 @@ public: void setCompilationLogs(const CompilationLogs& logs) const; void incrementCompilationAttempt() const; - - const GPUObjectPointer gpuObject {}; + const GPUObjectPointer gpuObject{}; protected: Shader(Type type, const Source& source); Shader(Type type, const Pointer& vertex, const Pointer& geometry, const Pointer& pixel); - Shader(const Shader& shader); // deep copy of the sysmem shader - Shader& operator=(const Shader& shader); // deep copy of the sysmem texture - + Shader(const Shader& shader); // deep copy of the sysmem shader + Shader& operator=(const Shader& shader); // deep copy of the sysmem texture // Source contains the actual source code or nothing if the shader is a program Source _source; @@ -245,32 +273,29 @@ protected: mutable CompilationLogs _compilationLogs; // Whether or not the shader compilation failed - bool _compilationHasFailed { false }; + bool _compilationHasFailed{ false }; - - // Global maps of the shaders + // Global maps of the shaders // Unique shader ID static std::atomic _nextShaderID; - using ShaderMap = std::map, Source::Less>; + using ShaderMap = std::map, Source::Less>; using DomainShaderMaps = std::array; static DomainShaderMaps _domainShaderMaps; static ShaderPointer createOrReuseDomainShader(Type type, const Source& source); - using ProgramMapKey = glm::uvec3; // The IDs of the shaders in a program make its key + using ProgramMapKey = glm::uvec3; // The IDs of the shaders in a program make its key class ProgramKeyLess { public: - bool operator() (const ProgramMapKey& l, const ProgramMapKey& r) const { + bool operator()(const ProgramMapKey& l, const ProgramMapKey& r) const { if (l.x == r.x) { if (l.y == r.y) { return (l.z < r.z); - } - else { + } else { return (l.y < r.y); } - } - else { + } else { return (l.x < r.x); } } @@ -278,13 +303,15 @@ protected: using ProgramMap = std::map, ProgramKeyLess>; static ProgramMap _programMap; - static ShaderPointer createOrReuseProgramShader(Type type, const Pointer& vertexShader, const Pointer& geometryShader, const Pointer& pixelShader); + static ShaderPointer createOrReuseProgramShader(Type type, + const Pointer& vertexShader, + const Pointer& geometryShader, + const Pointer& pixelShader); }; typedef Shader::Pointer ShaderPointer; -typedef std::vector< ShaderPointer > Shaders; - -}; +typedef std::vector Shaders; +}; // namespace gpu #endif diff --git a/libraries/gpu/src/gpu/ShaderConstants.h b/libraries/gpu/src/gpu/ShaderConstants.h new file mode 100644 index 0000000000..dc5879e7ad --- /dev/null +++ b/libraries/gpu/src/gpu/ShaderConstants.h @@ -0,0 +1,129 @@ +// + +// <@if not GPU_SHADER_CONSTANTS_H@> +// <@def GPU_SHADER_CONSTANTS_H@> + +// Hack comment to absorb the extra '//' scribe prepends + +#ifndef GPU_SHADER_CONSTANTS_H +#define GPU_SHADER_CONSTANTS_H + +#define GPU_BUFFER_TRANSFORM_CAMERA 15 +#define GPU_BUFFER_TEXTURE_TABLE0 16 +#define GPU_BUFFER_TEXTURE_TABLE1 17 +#define GPU_BUFFER_CAMERA_CORRECTION 18 + +#define GPU_TEXTURE_TRANSFORM_OBJECT 31 + +#define GPU_STORAGE_TRANSFORM_OBJECT 7 + +#define GPU_ATTR_POSITION 0 +#define GPU_ATTR_NORMAL 1 +#define GPU_ATTR_COLOR 2 +#define GPU_ATTR_TEXCOORD0 3 +#define GPU_ATTR_TANGENT 4 +#define GPU_ATTR_SKIN_CLUSTER_INDEX 5 +#define GPU_ATTR_SKIN_CLUSTER_WEIGHT 6 +#define GPU_ATTR_TEXCOORD1 7 +#define GPU_ATTR_TEXCOORD2 8 +#define GPU_ATTR_TEXCOORD3 9 +#define GPU_ATTR_TEXCOORD4 10 +#define GPU_ATTR_STEREO_SIDE 14 +#define GPU_ATTR_DRAW_CALL_INFO 15 + +// OSX seems to have an issue using 14 as an attribute location for passing from the vertex to the fragment shader +#define GPU_ATTR_V2F_STEREO_SIDE 8 + +#define GPU_UNIFORM_COLOR 101 +#define GPU_UNIFORM_TEXCOORD_RECT 102 +#define GPU_UNIFORM_EXTRA0 110 +#define GPU_UNIFORM_EXTRA1 111 +#define GPU_UNIFORM_EXTRA2 112 +#define GPU_UNIFORM_EXTRA3 113 +#define GPU_UNIFORM_EXTRA4 114 +#define GPU_UNIFORM_EXTRA5 115 +#define GPU_UNIFORM_EXTRA6 116 +#define GPU_UNIFORM_EXTRA7 117 +#define GPU_UNIFORM_EXTRA8 118 +#define GPU_UNIFORM_EXTRA9 119 + +// + +// Hack Comment +#endif // GPU_SHADER_CONSTANTS_H + +// <@if 1@> +// Trigger Scribe include +// <@endif@> + +// <@endif@> + +// Hack Comment diff --git a/libraries/gpu/src/gpu/StandardShaderLib.cpp b/libraries/gpu/src/gpu/StandardShaderLib.cpp deleted file mode 100755 index 0d829fb21f..0000000000 --- a/libraries/gpu/src/gpu/StandardShaderLib.cpp +++ /dev/null @@ -1,163 +0,0 @@ -// -// StandardShaderLib.cpp -// libraries/gpu/src/gpu -// -// Collection of standard shaders that can be used all over the place -// -// Created by Sam Gateau on 6/22/2015. -// Copyright 2015 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// -#include "StandardShaderLib.h" - -#include "DrawUnitQuadTexcoord_vert.h" -#include "DrawTransformUnitQuad_vert.h" -#include "DrawTexcoordRectTransformUnitQuad_vert.h" -#include "DrawViewportQuadTransformTexcoord_vert.h" -#include "DrawVertexPosition_vert.h" -#include "DrawTransformVertexPosition_vert.h" - -const char DrawNada_frag[] = "void main(void) {}"; // DrawNada is really simple... - -#include "DrawWhite_frag.h" -#include "DrawColor_frag.h" -#include "DrawTexture_frag.h" -#include "DrawTextureMirroredX_frag.h" -#include "DrawTextureOpaque_frag.h" -#include "DrawColoredTexture_frag.h" - -using namespace gpu; - -ShaderPointer StandardShaderLib::_drawUnitQuadTexcoordVS; -ShaderPointer StandardShaderLib::_drawTransformUnitQuadVS; -ShaderPointer StandardShaderLib::_drawTexcoordRectTransformUnitQuadVS; -ShaderPointer StandardShaderLib::_drawViewportQuadTransformTexcoordVS; -ShaderPointer StandardShaderLib::_drawVertexPositionVS; -ShaderPointer StandardShaderLib::_drawTransformVertexPositionVS; -ShaderPointer StandardShaderLib::_drawNadaPS; -ShaderPointer StandardShaderLib::_drawWhitePS; -ShaderPointer StandardShaderLib::_drawColorPS; -ShaderPointer StandardShaderLib::_drawTexturePS; -ShaderPointer StandardShaderLib::_drawTextureMirroredXPS; -ShaderPointer StandardShaderLib::_drawTextureOpaquePS; -ShaderPointer StandardShaderLib::_drawColoredTexturePS; -StandardShaderLib::ProgramMap StandardShaderLib::_programs; - -ShaderPointer StandardShaderLib::getProgram(GetShader getVS, GetShader getPS) { - - auto programIt = _programs.find(std::pair(getVS, getPS)); - if (programIt != _programs.end()) { - return (*programIt).second; - } else { - auto vs = (getVS)(); - auto ps = (getPS)(); - auto program = gpu::Shader::createProgram(vs, ps); - if (program) { - // Program created, let's try to make it - if (gpu::Shader::makeProgram((*program))) { - // All good, backup and return that program - _programs.insert(ProgramMap::value_type(std::pair(getVS, getPS), program)); - return program; - } else { - // Failed to make the program probably because vs and ps cannot work together? - } - } else { - // Failed to create the program maybe because ps and vs are not true vertex and pixel shaders? - } - } - return ShaderPointer(); -} - - -ShaderPointer StandardShaderLib::getDrawUnitQuadTexcoordVS() { - if (!_drawUnitQuadTexcoordVS) { - _drawUnitQuadTexcoordVS = DrawUnitQuadTexcoord_vert::getShader(); - } - return _drawUnitQuadTexcoordVS; -} - -ShaderPointer StandardShaderLib::getDrawTransformUnitQuadVS() { - if (!_drawTransformUnitQuadVS) { - _drawTransformUnitQuadVS = DrawTransformUnitQuad_vert::getShader(); - } - return _drawTransformUnitQuadVS; -} - -ShaderPointer StandardShaderLib::getDrawTexcoordRectTransformUnitQuadVS() { - if (!_drawTexcoordRectTransformUnitQuadVS) { - _drawTexcoordRectTransformUnitQuadVS = DrawTexcoordRectTransformUnitQuad_vert::getShader(); - } - return _drawTexcoordRectTransformUnitQuadVS; -} - -ShaderPointer StandardShaderLib::getDrawViewportQuadTransformTexcoordVS() { - if (!_drawViewportQuadTransformTexcoordVS) { - _drawViewportQuadTransformTexcoordVS = DrawViewportQuadTransformTexcoord_vert::getShader(); - } - return _drawViewportQuadTransformTexcoordVS; -} - -ShaderPointer StandardShaderLib::getDrawVertexPositionVS() { - if (!_drawVertexPositionVS) { - _drawVertexPositionVS = DrawVertexPosition_vert::getShader(); - } - return _drawVertexPositionVS; -} - -ShaderPointer StandardShaderLib::getDrawTransformVertexPositionVS() { - if (!_drawTransformVertexPositionVS) { - _drawTransformVertexPositionVS = DrawTransformVertexPosition_vert::getShader(); - } - return _drawTransformVertexPositionVS; -} - -ShaderPointer StandardShaderLib::getDrawNadaPS() { - if (!_drawNadaPS) { - _drawNadaPS = gpu::Shader::createPixel(std::string(DrawNada_frag)); - } - return _drawNadaPS; -} - -ShaderPointer StandardShaderLib::getDrawWhitePS() { - if (!_drawWhitePS) { - _drawWhitePS = DrawWhite_frag::getShader(); - } - return _drawWhitePS; -} - -ShaderPointer StandardShaderLib::getDrawColorPS() { - if (!_drawColorPS) { - _drawColorPS = DrawColor_frag::getShader(); - } - return _drawColorPS; -} - -ShaderPointer StandardShaderLib::getDrawTexturePS() { - if (!_drawTexturePS) { - _drawTexturePS = DrawTexture_frag::getShader(); - } - return _drawTexturePS; -} - -ShaderPointer StandardShaderLib::getDrawTextureMirroredXPS() { - if (!_drawTextureMirroredXPS) { - _drawTextureMirroredXPS = DrawTextureMirroredX_frag::getShader(); - } - return _drawTextureMirroredXPS; -} - -ShaderPointer StandardShaderLib::getDrawTextureOpaquePS() { - if (!_drawTextureOpaquePS) { - _drawTextureOpaquePS = DrawTextureOpaque_frag::getShader(); - } - return _drawTextureOpaquePS; -} - -ShaderPointer StandardShaderLib::getDrawColoredTexturePS() { - if (!_drawColoredTexturePS) { - _drawColoredTexturePS = DrawColoredTexture_frag::getShader(); - } - return _drawColoredTexturePS; -} diff --git a/libraries/gpu/src/gpu/StandardShaderLib.h b/libraries/gpu/src/gpu/StandardShaderLib.h deleted file mode 100755 index 9c11f6cc3a..0000000000 --- a/libraries/gpu/src/gpu/StandardShaderLib.h +++ /dev/null @@ -1,84 +0,0 @@ -// -// StandardShaderLib.h -// libraries/gpu/src/gpu -// -// Collection of standard shaders that can be used all over the place -// -// Created by Sam Gateau on 6/22/2015. -// Copyright 2015 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// -#ifndef hifi_gpu_StandardShaderLib_h -#define hifi_gpu_StandardShaderLib_h - -#include -#include - -#include "Shader.h" - -namespace gpu { - -class StandardShaderLib { -public: - - // Shader draws the unit quad in the full viewport clipPos = ([(-1,-1),(1,1)]) and the unit texcoord = [(0,0),(1,1)]. - static ShaderPointer getDrawUnitQuadTexcoordVS(); - - // Shader draw the unit quad objectPos = ([(-1,-1),(1,1)]) and transform it by the full model transform stack (Model, View, Proj). - // A texcoord attribute is also generated texcoord = [(0,0),(1,1)] - static ShaderPointer getDrawTransformUnitQuadVS(); - - // Shader draw the unit quad objectPos = ([(-1,-1),(1,1)]) and transform it by the full model transform stack (Model, View, Proj). - // A texcoord attribute is also generated covering a rect defined from the uniform vec4 texcoordRect: texcoord = [texcoordRect.xy,texcoordRect.xy + texcoordRect.zw] - static ShaderPointer getDrawTexcoordRectTransformUnitQuadVS(); - - // Shader draws the unit quad in the full viewport clipPos = ([(-1,-1),(1,1)]) and transform the texcoord = [(0,0),(1,1)] by the model transform. - static ShaderPointer getDrawViewportQuadTransformTexcoordVS(); - - // Shader draw the fed vertex position and transform it by the full model transform stack (Model, View, Proj). - // simply output the world pos and the clip pos to the next stage - static ShaderPointer getDrawVertexPositionVS(); - static ShaderPointer getDrawTransformVertexPositionVS(); - - // PShader does nothing, no really nothing, but still needed for defining a program triggering rasterization - static ShaderPointer getDrawNadaPS(); - - static ShaderPointer getDrawWhitePS(); - static ShaderPointer getDrawColorPS(); - static ShaderPointer getDrawTexturePS(); - static ShaderPointer getDrawTextureMirroredXPS(); - static ShaderPointer getDrawTextureOpaquePS(); - static ShaderPointer getDrawColoredTexturePS(); - - // The shader program combining the shaders available above, so they are unique - typedef ShaderPointer (*GetShader) (); - static ShaderPointer getProgram(GetShader vs, GetShader ps); - -protected: - - static ShaderPointer _drawUnitQuadTexcoordVS; - static ShaderPointer _drawTransformUnitQuadVS; - static ShaderPointer _drawTexcoordRectTransformUnitQuadVS; - static ShaderPointer _drawViewportQuadTransformTexcoordVS; - - static ShaderPointer _drawVertexPositionVS; - static ShaderPointer _drawTransformVertexPositionVS; - - static ShaderPointer _drawNadaPS; - static ShaderPointer _drawWhitePS; - static ShaderPointer _drawColorPS; - static ShaderPointer _drawTexturePS; - static ShaderPointer _drawTextureMirroredXPS; - static ShaderPointer _drawTextureOpaquePS; - static ShaderPointer _drawColoredTexturePS; - - typedef std::map, ShaderPointer> ProgramMap; - static ProgramMap _programs; -}; - - -}; - -#endif diff --git a/libraries/gpu/src/gpu/Texture.cpp b/libraries/gpu/src/gpu/Texture.cpp index 34262b0cd9..c6f3cd9b9a 100755 --- a/libraries/gpu/src/gpu/Texture.cpp +++ b/libraries/gpu/src/gpu/Texture.cpp @@ -43,14 +43,6 @@ bool recommendedSparseTextures = (QThread::idealThreadCount() >= MIN_CORES_FOR_I std::atomic Texture::_enableSparseTextures { recommendedSparseTextures }; -struct ReportTextureState { - ReportTextureState() { - qCDebug(gpulogging) << "[TEXTURE TRANSFER SUPPORT]" - << "\n\tidealThreadCount:" << QThread::idealThreadCount() - << "\n\tRECOMMENDED enableSparseTextures:" << recommendedSparseTextures; - } -} report; - void Texture::setEnableSparseTextures(bool enabled) { #ifdef Q_OS_WIN qCDebug(gpulogging) << "[TEXTURE TRANSFER SUPPORT] SETTING - Enable Sparse Textures and Dynamic Texture Management:" << enabled; @@ -510,7 +502,7 @@ void Texture::setSampler(const Sampler& sampler) { } -bool Texture::generateIrradiance() { +bool Texture::generateIrradiance(gpu::BackendTarget target) { if (getType() != TEX_CUBE) { return false; } @@ -521,7 +513,7 @@ bool Texture::generateIrradiance() { _irradiance = std::make_shared(); } - _irradiance->evalFromTexture(*this); + _irradiance->evalFromTexture(*this, target); return true; } @@ -684,7 +676,7 @@ void sphericalHarmonicsEvaluateDirection(float * result, int order, const glm:: result[8] = P_2_2 * ((double)dir.x * (double)dir.x - (double)dir.y * (double)dir.y); } -bool sphericalHarmonicsFromTexture(const gpu::Texture& cubeTexture, std::vector & output, const uint order) { +bool sphericalHarmonicsFromTexture(const gpu::Texture& cubeTexture, std::vector & output, const uint order, gpu::BackendTarget target) { int width = cubeTexture.getWidth(); if(width != cubeTexture.getHeight()) { return false; @@ -692,22 +684,6 @@ bool sphericalHarmonicsFromTexture(const gpu::Texture& cubeTexture, std::vector< PROFILE_RANGE(render_gpu, "sphericalHarmonicsFromTexture"); -#ifndef USE_GLES - auto mipFormat = cubeTexture.getStoredMipFormat(); - std::function unpackFunc; - switch (mipFormat.getSemantic()) { - case gpu::R11G11B10: - unpackFunc = glm::unpackF2x11_1x10; - break; - case gpu::RGB9E5: - unpackFunc = glm::unpackF3x9_E1x5; - break; - default: - assert(false); - break; - } -#endif - const uint sqOrder = order*order; // allocate memory for calculations @@ -741,11 +717,7 @@ bool sphericalHarmonicsFromTexture(const gpu::Texture& cubeTexture, std::vector< for(int face=0; face < gpu::Texture::NUM_CUBE_FACES; face++) { PROFILE_RANGE(render_gpu, "ProcessFace"); -#ifndef USE_GLES - auto data = reinterpret_cast( cubeTexture.accessStoredMipFace(0, face)->readData() ); -#else auto data = cubeTexture.accessStoredMipFace(0, face)->readData(); -#endif if (data == nullptr) { continue; } @@ -827,20 +799,40 @@ bool sphericalHarmonicsFromTexture(const gpu::Texture& cubeTexture, std::vector< // get color from texture glm::vec3 color{ 0.0f, 0.0f, 0.0f }; - for (int i = 0; i < stride; ++i) { - for (int j = 0; j < stride; ++j) { -#ifndef USE_GLES - int k = (int)(x + i - halfStride + (y + j - halfStride) * width); - color += unpackFunc(data[k]); -#else - const int NUM_COMPONENTS_PER_PIXEL = 4; - int k = NUM_COMPONENTS_PER_PIXEL * (int)(x + i - halfStride + (y + j - halfStride) * width); - // BGRA -> RGBA - color += glm::pow(glm::vec3(data[k + 2], data[k + 1], data[k]) / 255.0f, glm::vec3(2.2f)); -#endif + + if (target != gpu::BackendTarget::GLES32) { + auto mipFormat = cubeTexture.getStoredMipFormat(); + std::function unpackFunc; + switch (mipFormat.getSemantic()) { + case gpu::R11G11B10: + unpackFunc = glm::unpackF2x11_1x10; + break; + case gpu::RGB9E5: + unpackFunc = glm::unpackF3x9_E1x5; + break; + default: + assert(false); + break; + } + auto data32 = reinterpret_cast(data); + for (int i = 0; i < stride; ++i) { + for (int j = 0; j < stride; ++j) { + int k = (int)(x + i - halfStride + (y + j - halfStride) * width); + color += unpackFunc(data32[k]); + } + } + } else { + // BGRA -> RGBA + const int NUM_COMPONENTS_PER_PIXEL = 4; + for (int i = 0; i < stride; ++i) { + for (int j = 0; j < stride; ++j) { + int k = NUM_COMPONENTS_PER_PIXEL * (int)(x + i - halfStride + (y + j - halfStride) * width); + color += glm::pow(glm::vec3(data[k + 2], data[k + 1], data[k]) / 255.0f, glm::vec3(2.2f)); + } } } + // scale color and add to previously accumulated coefficients // red sphericalHarmonicsScale(shBuffB.data(), order, shBuff.data(), color.r * fDiffSolid); @@ -869,10 +861,10 @@ bool sphericalHarmonicsFromTexture(const gpu::Texture& cubeTexture, std::vector< return true; } -void SphericalHarmonics::evalFromTexture(const Texture& texture) { +void SphericalHarmonics::evalFromTexture(const Texture& texture, gpu::BackendTarget target) { if (texture.isDefined()) { std::vector< glm::vec3 > coefs; - sphericalHarmonicsFromTexture(texture, coefs, 3); + sphericalHarmonicsFromTexture(texture, coefs, 3, target); L00 = coefs[0]; L1m1 = coefs[1]; diff --git a/libraries/gpu/src/gpu/Texture.h b/libraries/gpu/src/gpu/Texture.h index 9ad5dc0816..73ed1b15dc 100755 --- a/libraries/gpu/src/gpu/Texture.h +++ b/libraries/gpu/src/gpu/Texture.h @@ -43,6 +43,11 @@ namespace khronos { namespace gl { namespace texture { namespace gpu { +enum class BackendTarget { + GL41, + GL45, + GLES32 +}; const std::string SOURCE_HASH_KEY { "hifi.sourceHash" }; @@ -82,7 +87,7 @@ public: void assignPreset(int p); - void evalFromTexture(const Texture& texture); + void evalFromTexture(const Texture& texture, gpu::BackendTarget target); }; typedef std::shared_ptr< SphericalHarmonics > SHPointer; @@ -541,7 +546,7 @@ public: Usage getUsage() const { return _usage; } // For Cube Texture, it's possible to generate the irradiance spherical harmonics and make them availalbe with the texture - bool generateIrradiance(); + bool generateIrradiance(gpu::BackendTarget target); const SHPointer& getIrradiance(uint16 slice = 0) const { return _irradiance; } void overrideIrradiance(SHPointer irradiance) { _irradiance = irradiance; } bool isIrradianceValid() const { return _isIrradianceValid; } diff --git a/libraries/gpu/src/gpu/TextureTable.slh b/libraries/gpu/src/gpu/TextureTable.slh index f2d89e753b..bedbff954b 100644 --- a/libraries/gpu/src/gpu/TextureTable.slh +++ b/libraries/gpu/src/gpu/TextureTable.slh @@ -22,8 +22,7 @@ struct GPUTextureTable { #define tableTex(name, slot) sampler2D(name._textures[slot].xy) #define tableTexMinLod(name, slot) float(name._textures[slot].z) -#define tableTexValue(name, slot, uv) \ - tableTexValueLod(tableTex(matTex, albedoMap), tableTexMinLod(matTex, albedoMap), uv) +#define tableTexValue(name, slot, uv) tableTexValueLod(tableTex(matTex, albedoMap), tableTexMinLod(matTex, albedoMap), uv) vec4 tableTexValueLod(sampler2D sampler, float minLod, vec2 uv) { float queryLod = textureQueryLod(sampler, uv).x; diff --git a/libraries/gpu/src/gpu/Transform.slh b/libraries/gpu/src/gpu/Transform.slh index 50c0bc13ed..d0b7587da6 100644 --- a/libraries/gpu/src/gpu/Transform.slh +++ b/libraries/gpu/src/gpu/Transform.slh @@ -10,12 +10,13 @@ <@if not GPU_TRANSFORM_STATE_SLH@> <@def GPU_TRANSFORM_STATE_SLH@> +<@include gpu/ShaderConstants.h@> <@func declareStandardCameraTransform()@> <@include gpu/TransformCamera_shared.slh@> #define TransformCamera _TransformCamera -layout(std140) uniform transformCameraBuffer { +layout(std140, binding=GPU_BUFFER_TRANSFORM_CAMERA) uniform transformCameraBuffer { #ifdef GPU_TRANSFORM_IS_STEREO #ifdef GPU_TRANSFORM_STEREO_CAMERA TransformCamera _camera[2]; @@ -31,10 +32,10 @@ layout(std140) uniform transformCameraBuffer { #ifdef GPU_TRANSFORM_IS_STEREO #ifdef GPU_TRANSFORM_STEREO_CAMERA #ifdef GPU_TRANSFORM_STEREO_CAMERA_ATTRIBUTED -layout(location=14) in int _inStereoSide; +layout(location=GPU_ATTR_STEREO_SIDE) in int _inStereoSide; #endif -flat out int _stereoSide; +layout(location=GPU_ATTR_V2F_STEREO_SIDE) flat out int _stereoSide; // In stereo drawcall mode Instances are drawn twice (left then right) hence the true InstanceID is the gl_InstanceID / 2 int gpu_InstanceID() { @@ -59,7 +60,7 @@ int gpu_InstanceID() { #ifdef GPU_PIXEL_SHADER #ifdef GPU_TRANSFORM_STEREO_CAMERA -flat in int _stereoSide; +layout(location=GPU_ATTR_V2F_STEREO_SIDE) flat in int _stereoSide; #endif #endif @@ -116,10 +117,10 @@ struct TransformObject { mat4 _modelInverse; }; -layout(location=15) in ivec2 _drawCallInfo; +layout(location=GPU_ATTR_DRAW_CALL_INFO) in ivec2 _drawCallInfo; #if defined(GPU_SSBO_TRANSFORM_OBJECT) -layout(std140) buffer transformObjectBuffer { +layout(std140, binding=GPU_STORAGE_TRANSFORM_OBJECT) buffer transformObjectBuffer { TransformObject _object[]; }; TransformObject getTransformObject() { @@ -127,7 +128,7 @@ TransformObject getTransformObject() { return transformObject; } #else -uniform samplerBuffer transformObjectBuffer; +layout(binding=GPU_TEXTURE_TRANSFORM_OBJECT) uniform samplerBuffer transformObjectBuffer; TransformObject getTransformObject() { int offset = 8 * _drawCallInfo.x; @@ -238,7 +239,7 @@ TransformObject getTransformObject() { <@endfunc@> <@func transformModelToWorldDir(cameraTransform, objectTransform, modelDir, worldDir)@> - { // transformModelToEyeDir + { // transformModelToWorldDir vec3 mr0 = <$objectTransform$>._modelInverse[0].xyz; vec3 mr1 = <$objectTransform$>._modelInverse[1].xyz; vec3 mr2 = <$objectTransform$>._modelInverse[2].xyz; diff --git a/libraries/gpu/src/gpu/drawColor.slp b/libraries/gpu/src/gpu/drawColor.slp new file mode 100644 index 0000000000..1c81242fed --- /dev/null +++ b/libraries/gpu/src/gpu/drawColor.slp @@ -0,0 +1,3 @@ +VERTEX DrawTransformVertexPosition +FRAGMENT DrawColor +r diff --git a/libraries/gpu/src/gpu/drawNothing.slp b/libraries/gpu/src/gpu/drawNothing.slp new file mode 100644 index 0000000000..22e40db8fc --- /dev/null +++ b/libraries/gpu/src/gpu/drawNothing.slp @@ -0,0 +1,2 @@ +VERTEX DrawVertexPosition +FRAGMENT DrawNada diff --git a/libraries/gpu/src/gpu/drawTexture.slp b/libraries/gpu/src/gpu/drawTexture.slp new file mode 100644 index 0000000000..e04be84618 --- /dev/null +++ b/libraries/gpu/src/gpu/drawTexture.slp @@ -0,0 +1,2 @@ +VERTEX DrawUnitQuadTexcoord +FRAGMENT DrawTexture diff --git a/libraries/gpu/src/gpu/drawTextureOpaqueTexcoordRect.slp b/libraries/gpu/src/gpu/drawTextureOpaqueTexcoordRect.slp new file mode 100644 index 0000000000..9b08a99e18 --- /dev/null +++ b/libraries/gpu/src/gpu/drawTextureOpaqueTexcoordRect.slp @@ -0,0 +1,2 @@ +VERTEX DrawTexcoordRectTransformUnitQuad +FRAGMENT DrawTextureOpaque diff --git a/libraries/gpu/src/gpu/drawTransformUnitQuadTextureOpaque.slp b/libraries/gpu/src/gpu/drawTransformUnitQuadTextureOpaque.slp new file mode 100644 index 0000000000..5c76582568 --- /dev/null +++ b/libraries/gpu/src/gpu/drawTransformUnitQuadTextureOpaque.slp @@ -0,0 +1,2 @@ +VERTEX DrawTransformUnitQuad +FRAGMENT DrawTextureOpaque diff --git a/libraries/gpu/src/gpu/drawUnitQuatTextureOpaque.slp b/libraries/gpu/src/gpu/drawUnitQuatTextureOpaque.slp new file mode 100644 index 0000000000..2b03c79f32 --- /dev/null +++ b/libraries/gpu/src/gpu/drawUnitQuatTextureOpaque.slp @@ -0,0 +1,2 @@ +VERTEX DrawUnitQuadTexcoord +FRAGMENT DrawTextureOpaque diff --git a/libraries/gpu/src/gpu/null/NullBackend.h b/libraries/gpu/src/gpu/null/NullBackend.h index 57b8fbafbc..e227b631c7 100644 --- a/libraries/gpu/src/gpu/null/NullBackend.h +++ b/libraries/gpu/src/gpu/null/NullBackend.h @@ -28,7 +28,6 @@ class Backend : public gpu::Backend { friend class gpu::Context; static void init() {} static gpu::Backend* createBackend() { return new Backend(); } - static bool makeProgram(Shader& shader, const Shader::BindingSet& slotBindings, const Shader::CompilationHandler& handler) { return true; } protected: explicit Backend(bool syncCache) : Parent() { } diff --git a/libraries/graphics/CMakeLists.txt b/libraries/graphics/CMakeLists.txt index 2b15604fdf..556d4ef53f 100755 --- a/libraries/graphics/CMakeLists.txt +++ b/libraries/graphics/CMakeLists.txt @@ -1,4 +1,4 @@ set(TARGET_NAME graphics) -AUTOSCRIBE_SHADER_LIB(gpu graphics) setup_hifi_library() -link_hifi_libraries(shared ktx gpu image) \ No newline at end of file + +link_hifi_libraries(shared ktx gpu shaders image) \ No newline at end of file diff --git a/libraries/graphics/src/graphics/Bloom.cpp b/libraries/graphics/src/graphics/Bloom.cpp new file mode 100644 index 0000000000..f8dcda3292 --- /dev/null +++ b/libraries/graphics/src/graphics/Bloom.cpp @@ -0,0 +1,18 @@ +// +// Bloom.cpp +// libraries/graphics/src/graphics +// +// Created by Sam Gondelman on 8/7/2018 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "Bloom.h" + +using namespace graphics; + +const float Bloom::INITIAL_BLOOM_INTENSITY { 0.25f }; +const float Bloom::INITIAL_BLOOM_THRESHOLD { 0.7f }; +const float Bloom::INITIAL_BLOOM_SIZE { 0.9f }; \ No newline at end of file diff --git a/libraries/graphics/src/graphics/Bloom.h b/libraries/graphics/src/graphics/Bloom.h new file mode 100644 index 0000000000..6ab48a35a3 --- /dev/null +++ b/libraries/graphics/src/graphics/Bloom.h @@ -0,0 +1,41 @@ +// +// Bloom.h +// libraries/graphics/src/graphics +// +// Created by Sam Gondelman on 8/7/2018 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#ifndef hifi_model_Bloom_h +#define hifi_model_Bloom_h + +#include + +namespace graphics { + class Bloom { + public: + // Initial values + static const float INITIAL_BLOOM_INTENSITY; + static const float INITIAL_BLOOM_THRESHOLD; + static const float INITIAL_BLOOM_SIZE; + + Bloom() {} + + void setBloomIntensity(const float bloomIntensity) { _bloomIntensity = bloomIntensity; } + void setBloomThreshold(const float bloomThreshold) { _bloomThreshold = bloomThreshold; } + void setBloomSize(const float bloomSize) { _bloomSize = bloomSize; } + + float getBloomIntensity() { return _bloomIntensity; } + float getBloomThreshold() { return _bloomThreshold; } + float getBloomSize() { return _bloomSize; } + + private: + float _bloomIntensity { INITIAL_BLOOM_INTENSITY }; + float _bloomThreshold {INITIAL_BLOOM_THRESHOLD }; + float _bloomSize { INITIAL_BLOOM_SIZE }; + }; + using BloomPointer = std::shared_ptr; +} +#endif // hifi_model_Bloom_h diff --git a/libraries/graphics/src/graphics/BufferViewHelpers.cpp b/libraries/graphics/src/graphics/BufferViewHelpers.cpp index 5c7e6ff892..2fd0d90da3 100644 --- a/libraries/graphics/src/graphics/BufferViewHelpers.cpp +++ b/libraries/graphics/src/graphics/BufferViewHelpers.cpp @@ -20,9 +20,6 @@ #include #include -#include -#include - namespace glm { using hvec2 = glm::tvec2; using hvec4 = glm::tvec4; @@ -62,32 +59,6 @@ namespace { } } -void packNormalAndTangent(glm::vec3 normal, glm::vec3 tangent, glm::uint32& packedNormal, glm::uint32& packedTangent) { - auto absNormal = glm::abs(normal); - auto absTangent = glm::abs(tangent); - normal /= glm::max(1e-6f, glm::max(glm::max(absNormal.x, absNormal.y), absNormal.z)); - tangent /= glm::max(1e-6f, glm::max(glm::max(absTangent.x, absTangent.y), absTangent.z)); - normal = glm::clamp(normal, -1.0f, 1.0f); - tangent = glm::clamp(tangent, -1.0f, 1.0f); - normal *= 511.0f; - tangent *= 511.0f; - normal = glm::round(normal); - tangent = glm::round(tangent); - - glm::detail::i10i10i10i2 normalStruct; - glm::detail::i10i10i10i2 tangentStruct; - normalStruct.data.x = int(normal.x); - normalStruct.data.y = int(normal.y); - normalStruct.data.z = int(normal.z); - normalStruct.data.w = 0; - tangentStruct.data.x = int(tangent.x); - tangentStruct.data.y = int(tangent.y); - tangentStruct.data.z = int(tangent.z); - tangentStruct.data.w = 0; - packedNormal = normalStruct.pack; - packedTangent = tangentStruct.pack; -} - template glm::uint32 forEachGlmVec(const gpu::BufferView& view, std::function func) { QVector result; diff --git a/libraries/graphics/src/graphics/BufferViewHelpers.h b/libraries/graphics/src/graphics/BufferViewHelpers.h index f877341d50..026e7b53a3 100644 --- a/libraries/graphics/src/graphics/BufferViewHelpers.h +++ b/libraries/graphics/src/graphics/BufferViewHelpers.h @@ -9,8 +9,11 @@ #include #include #include +#include +#include #include "GpuHelpers.h" +#include "GLMHelpers.h" namespace graphics { class Mesh; @@ -44,7 +47,29 @@ namespace buffer_helpers { gpu::BufferView clone(const gpu::BufferView& input); gpu::BufferView resized(const gpu::BufferView& input, glm::uint32 numElements); - void packNormalAndTangent(glm::vec3 normal, glm::vec3 tangent, glm::uint32& packedNormal, glm::uint32& packedTangent); + inline void packNormalAndTangent(glm::vec3 normal, glm::vec3 tangent, glm::uint32& packedNormal, glm::uint32& packedTangent) { + auto absNormal = glm::abs(normal); + auto absTangent = glm::abs(tangent); + normal /= glm::max(1e-6f, glm::max(glm::max(absNormal.x, absNormal.y), absNormal.z)); + tangent /= glm::max(1e-6f, glm::max(glm::max(absTangent.x, absTangent.y), absTangent.z)); + normal = glm::clamp(normal, -1.0f, 1.0f); + tangent = glm::clamp(tangent, -1.0f, 1.0f); + normal *= 511.0f; + tangent *= 511.0f; + + glm::detail::i10i10i10i2 normalStruct; + glm::detail::i10i10i10i2 tangentStruct; + normalStruct.data.x = fastLrintf(normal.x); + normalStruct.data.y = fastLrintf(normal.y); + normalStruct.data.z = fastLrintf(normal.z); + normalStruct.data.w = 0; + tangentStruct.data.x = fastLrintf(tangent.x); + tangentStruct.data.y = fastLrintf(tangent.y); + tangentStruct.data.z = fastLrintf(tangent.z); + tangentStruct.data.w = 0; + packedNormal = normalStruct.pack; + packedTangent = tangentStruct.pack; + } namespace mesh { glm::uint32 forEachVertex(const graphics::MeshPointer& mesh, std::function func); diff --git a/libraries/graphics/src/graphics/Light.slh b/libraries/graphics/src/graphics/Light.slh index 53b840f5fb..d22da44c66 100644 --- a/libraries/graphics/src/graphics/Light.slh +++ b/libraries/graphics/src/graphics/Light.slh @@ -11,6 +11,7 @@ <@if not MODEL_LIGHT_SLH@> <@def MODEL_LIGHT_SLH@> +<@include graphics/ShaderConstants.h@> <@include graphics/LightVolume.shared.slh@> <@include graphics/LightIrradiance.shared.slh@> @@ -50,7 +51,7 @@ float getLightAmbientMapNumMips(LightAmbient l) { return l._ambient.y; } <@if N@> -uniform lightBuffer { +layout(binding=GRAPHICS_BUFFER_LIGHT) uniform lightBuffer { Light lightArray[<$N$>]; }; Light getLight(int index) { @@ -58,7 +59,7 @@ Light getLight(int index) { } <@else@> -uniform keyLightBuffer { +layout(binding=GRAPHICS_BUFFER_KEY_LIGHT) uniform keyLightBuffer { Light light; }; Light getKeyLight() { @@ -78,7 +79,7 @@ Light getKeyLight() { <@if N@> -uniform lightAmbientBuffer { +layout(binding=GRAPHICS_BUFFER_AMBIENT_LIGHT) uniform lightAmbientBuffer { LightAmbient lightAmbientArray[<$N$>]; }; @@ -87,7 +88,7 @@ LightAmbient getLightAmbient(int index) { } <@else@> -uniform lightAmbientBuffer { +layout(binding=GRAPHICS_BUFFER_AMBIENT_LIGHT) uniform lightAmbientBuffer { LightAmbient lightAmbient; }; diff --git a/libraries/graphics/src/graphics/Material.slh b/libraries/graphics/src/graphics/Material.slh index dd2985b4da..fe273ed2a9 100644 --- a/libraries/graphics/src/graphics/Material.slh +++ b/libraries/graphics/src/graphics/Material.slh @@ -11,6 +11,8 @@ <@if not MODEL_MATERIAL_SLH@> <@def MODEL_MATERIAL_SLH@> +<@include graphics/ShaderConstants.h@> + // The material values (at least the material key) must be precisely bitwise accurate // to what is provided by the uniform buffer, or the material key has the wrong bits @@ -21,7 +23,7 @@ struct Material { vec4 _scatteringSpare2Key; }; -uniform materialBuffer { +layout(binding=GRAPHICS_BUFFER_MATERIAL) uniform materialBuffer { Material _mat; }; diff --git a/libraries/render-utils/src/MaterialTextures.slh b/libraries/graphics/src/graphics/MaterialTextures.slh similarity index 89% rename from libraries/render-utils/src/MaterialTextures.slh rename to libraries/graphics/src/graphics/MaterialTextures.slh index 0b83fd1334..f76d65da96 100644 --- a/libraries/render-utils/src/MaterialTextures.slh +++ b/libraries/graphics/src/graphics/MaterialTextures.slh @@ -11,6 +11,8 @@ <@if not MODEL_MATERIAL_TEXTURES_SLH@> <@def MODEL_MATERIAL_TEXTURES_SLH@> +<@include graphics/ShaderConstants.h@> + <@func declareMaterialTexMapArrayBuffer()@> const int MAX_TEXCOORDS = 2; @@ -22,7 +24,7 @@ struct TexMapArray { vec4 _lightmapParams; }; -uniform texMapArrayBuffer { +layout(binding=GRAPHICS_BUFFER_TEXMAPARRAY) uniform texMapArrayBuffer { TexMapArray _texMapArray; }; @@ -123,21 +125,21 @@ float fetchScatteringMap(vec2 uv) { #else <@if withAlbedo@> -uniform sampler2D albedoMap; +layout(binding=GRAPHICS_TEXTURE_MATERIAL_ALBEDO) uniform sampler2D albedoMap; vec4 fetchAlbedoMap(vec2 uv) { return texture(albedoMap, uv, TAA_TEXTURE_LOD_BIAS); } <@endif@> <@if withRoughness@> -uniform sampler2D roughnessMap; +layout(binding=GRAPHICS_TEXTURE_MATERIAL_ROUGHNESS) uniform sampler2D roughnessMap; float fetchRoughnessMap(vec2 uv) { return (texture(roughnessMap, uv, TAA_TEXTURE_LOD_BIAS).r); } <@endif@> <@if withNormal@> -uniform sampler2D normalMap; +layout(binding=GRAPHICS_TEXTURE_MATERIAL_NORMAL) uniform sampler2D normalMap; vec3 fetchNormalMap(vec2 uv) { // unpack normal, swizzle to get into hifi tangent space with Y axis pointing out vec2 t = 2.0 * (texture(normalMap, uv, TAA_TEXTURE_LOD_BIAS).rg - vec2(0.5, 0.5)); @@ -147,28 +149,28 @@ vec3 fetchNormalMap(vec2 uv) { <@endif@> <@if withMetallic@> -uniform sampler2D metallicMap; +layout(binding=GRAPHICS_TEXTURE_MATERIAL_METALLIC) uniform sampler2D metallicMap; float fetchMetallicMap(vec2 uv) { return (texture(metallicMap, uv, TAA_TEXTURE_LOD_BIAS).r); } <@endif@> <@if withEmissive@> -uniform sampler2D emissiveMap; +layout(binding=GRAPHICS_TEXTURE_MATERIAL_EMISSIVE_LIGHTMAP) uniform sampler2D emissiveMap; vec3 fetchEmissiveMap(vec2 uv) { return texture(emissiveMap, uv, TAA_TEXTURE_LOD_BIAS).rgb; } <@endif@> <@if withOcclusion@> -uniform sampler2D occlusionMap; +layout(binding=GRAPHICS_TEXTURE_MATERIAL_OCCLUSION) uniform sampler2D occlusionMap; float fetchOcclusionMap(vec2 uv) { return texture(occlusionMap, uv).r; } <@endif@> <@if withScattering@> -uniform sampler2D scatteringMap; +layout(binding=GRAPHICS_TEXTURE_MATERIAL_SCATTERING) uniform sampler2D scatteringMap; float fetchScatteringMap(vec2 uv) { float scattering = texture(scatteringMap, uv, TAA_TEXTURE_LOD_BIAS).r; // boolean scattering for now return max(((scattering - 0.1) / 0.9), 0.0); @@ -217,10 +219,10 @@ float fetchScatteringMap(vec2 uv) { <$declareMaterialTexMapArrayBuffer()$> -uniform sampler2D emissiveMap; +layout(binding=GRAPHICS_TEXTURE_MATERIAL_EMISSIVE_LIGHTMAP) uniform sampler2D emissiveMap; vec3 fetchLightmapMap(vec2 uv) { - vec2 emissiveParams = getTexMapArray()._lightmapParams.xy; - return (vec3(emissiveParams.x) + emissiveParams.y * texture(emissiveMap, uv).rgb); + vec2 lightmapParams = getTexMapArray()._lightmapParams.xy; + return (vec3(lightmapParams.x) + lightmapParams.y * texture(emissiveMap, uv).rgb); } <@endfunc@> @@ -267,7 +269,14 @@ vec3 fetchLightmapMap(vec2 uv) { <@func discardTransparent(opacity)@> { - if (<$opacity$> < 1e-6) { + if (<$opacity$> < 1.0) { + discard; + } +} +<@endfunc@> +<@func discardInvisible(opacity)@> +{ + if (<$opacity$> < 1.e-6) { discard; } } diff --git a/libraries/graphics/src/graphics/ShaderConstants.h b/libraries/graphics/src/graphics/ShaderConstants.h new file mode 100644 index 0000000000..c902185d4f --- /dev/null +++ b/libraries/graphics/src/graphics/ShaderConstants.h @@ -0,0 +1,78 @@ +// + +// <@if not GRAPHICS_SHADER_CONSTANTS_H@> +// <@def GRAPHICS_SHADER_CONSTANTS_H@> + +// Hack comment to absorb the extra '//' scribe prepends + +#ifndef GRAPHICS_SHADER_CONSTANTS_H +#define GRAPHICS_SHADER_CONSTANTS_H + +#define GRAPHICS_BUFFER_SKINNING 0 +#define GRAPHICS_BUFFER_MATERIAL 1 +#define GRAPHICS_BUFFER_TEXMAPARRAY 2 +#define GRAPHICS_BUFFER_KEY_LIGHT 4 +#define GRAPHICS_BUFFER_LIGHT 5 +#define GRAPHICS_BUFFER_AMBIENT_LIGHT 6 + +#define GRAPHICS_TEXTURE_MATERIAL_ALBEDO 0 +#define GRAPHICS_TEXTURE_MATERIAL_NORMAL 1 +#define GRAPHICS_TEXTURE_MATERIAL_METALLIC 2 +#define GRAPHICS_TEXTURE_MATERIAL_EMISSIVE_LIGHTMAP 3 +#define GRAPHICS_TEXTURE_MATERIAL_ROUGHNESS 4 +#define GRAPHICS_TEXTURE_MATERIAL_OCCLUSION 5 +#define GRAPHICS_TEXTURE_MATERIAL_SCATTERING 6 + +// Make sure these match the ones in render-utils/ShaderConstants.h +#define GRAPHICS_TEXTURE_SKYBOX 11 +#define GRAPHICS_BUFFER_SKYBOX_PARAMS 5 + +// +// Hack Comment + +#endif // GRAPHICS_SHADER_CONSTANTS_H + +// <@if 1@> +// Trigger Scribe include +// <@endif@> + +// <@endif@> + +// Hack Comment diff --git a/libraries/graphics/src/graphics/Skybox.cpp b/libraries/graphics/src/graphics/Skybox.cpp index 6ad0045aa9..532b5f1706 100755 --- a/libraries/graphics/src/graphics/Skybox.cpp +++ b/libraries/graphics/src/graphics/Skybox.cpp @@ -14,9 +14,8 @@ #include #include #include - -#include "skybox_vert.h" -#include "skybox_frag.h" +#include +#include "ShaderConstants.h" using namespace graphics; @@ -65,17 +64,12 @@ void Skybox::clear() { _empty = true; } -void Skybox::prepare(gpu::Batch& batch, int textureSlot, int bufferSlot) const { - if (bufferSlot > -1) { - batch.setUniformBuffer(bufferSlot, _schemaBuffer); - } - - if (textureSlot > -1) { - gpu::TexturePointer skymap = getCubemap(); - // FIXME: skymap->isDefined may not be threadsafe - if (skymap && skymap->isDefined()) { - batch.setResourceTexture(textureSlot, skymap); - } +void Skybox::prepare(gpu::Batch& batch) const { + batch.setUniformBuffer(graphics::slot::buffer::SkyboxParams, _schemaBuffer); + gpu::TexturePointer skymap = getCubemap(); + // FIXME: skymap->isDefined may not be threadsafe + if (skymap && skymap->isDefined()) { + batch.setResourceTexture(graphics::slot::texture::Skybox, skymap); } } @@ -91,19 +85,7 @@ void Skybox::render(gpu::Batch& batch, const ViewFrustum& viewFrustum, const Sky static std::once_flag once; std::call_once(once, [&] { { - auto skyVS = skybox_vert::getShader(); - auto skyFS = skybox_frag::getShader(); - auto skyShader = gpu::Shader::createProgram(skyVS, skyFS); - - batch.runLambda([skyShader] { - gpu::Shader::BindingSet bindings; - bindings.insert(gpu::Shader::Binding(std::string("cubeMap"), SKYBOX_SKYMAP_SLOT)); - bindings.insert(gpu::Shader::Binding(std::string("skyboxBuffer"), SKYBOX_CONSTANTS_SLOT)); - if (!gpu::Shader::makeProgram(*skyShader, bindings)) { - - } - }); - + auto skyShader = gpu::Shader::createProgram(shader::graphics::program::skybox); auto skyState = std::make_shared(); // Must match PrepareStencil::STENCIL_BACKGROUND const int8_t STENCIL_BACKGROUND = 0; @@ -133,5 +115,5 @@ void Skybox::render(gpu::Batch& batch, const ViewFrustum& viewFrustum, const Sky skybox.prepare(batch); batch.draw(gpu::TRIANGLE_STRIP, 4); - batch.setResourceTexture(SKYBOX_SKYMAP_SLOT, nullptr); + batch.setResourceTexture(graphics::slot::texture::Skybox, nullptr); } diff --git a/libraries/graphics/src/graphics/Skybox.h b/libraries/graphics/src/graphics/Skybox.h index a739b9a745..50189f4c51 100755 --- a/libraries/graphics/src/graphics/Skybox.h +++ b/libraries/graphics/src/graphics/Skybox.h @@ -43,7 +43,7 @@ public: virtual bool empty() { return _empty; } virtual void clear(); - void prepare(gpu::Batch& batch, int textureSlot = SKYBOX_SKYMAP_SLOT, int bufferSlot = SKYBOX_CONSTANTS_SLOT) const; + void prepare(gpu::Batch& batch) const; virtual void render(gpu::Batch& batch, const ViewFrustum& frustum) const; static void render(gpu::Batch& batch, const ViewFrustum& frustum, const Skybox& skybox); @@ -51,9 +51,6 @@ public: const UniformBufferView& getSchemaBuffer() const { return _schemaBuffer; } protected: - static const int SKYBOX_SKYMAP_SLOT { 0 }; - static const int SKYBOX_CONSTANTS_SLOT { 0 }; - class Schema { public: glm::vec3 color { 0.0f, 0.0f, 0.0f }; diff --git a/libraries/graphics/src/graphics/skybox.slf b/libraries/graphics/src/graphics/skybox.slf index 153e73b9ef..2b81a433f1 100755 --- a/libraries/graphics/src/graphics/skybox.slf +++ b/libraries/graphics/src/graphics/skybox.slf @@ -10,42 +10,22 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +<@include graphics/ShaderConstants.h@> -uniform samplerCube cubeMap; +layout(binding=GRAPHICS_TEXTURE_SKYBOX) uniform samplerCube cubeMap; struct Skybox { vec4 color; }; -uniform skyboxBuffer { +layout(binding=GRAPHICS_BUFFER_SKYBOX_PARAMS) uniform skyboxBuffer { Skybox skybox; }; -in vec3 _normal; -out vec4 _fragColor; +layout(location=0) in vec3 _normal; +layout(location=0) out vec4 _fragColor; -//PROCEDURAL_COMMON_BLOCK - -#line 1001 -//PROCEDURAL_BLOCK - -#line 2033 void main(void) { - -#ifdef PROCEDURAL - - vec3 color = getSkyboxColor(); - // Protect from NaNs and negative values - color = mix(color, vec3(0), isnan(color)); - color = max(color, vec3(0)); - // Procedural Shaders are expected to be Gamma corrected so let's bring back the RGB in linear space for the rest of the pipeline - color = pow(color, vec3(2.2)); - _fragColor = vec4(color, 0.0); - - // FIXME: scribe does not yet scrub out else statements - return; - -#else vec3 coord = normalize(_normal); vec3 color = skybox.color.rgb; @@ -57,7 +37,4 @@ void main(void) { } } _fragColor = vec4(color, 0.0); - -#endif - } diff --git a/libraries/graphics/src/graphics/skybox.slp b/libraries/graphics/src/graphics/skybox.slp new file mode 100644 index 0000000000..e69de29bb2 diff --git a/libraries/graphics/src/graphics/skybox.slv b/libraries/graphics/src/graphics/skybox.slv index 6fd9532fa1..4b14872df2 100755 --- a/libraries/graphics/src/graphics/skybox.slv +++ b/libraries/graphics/src/graphics/skybox.slv @@ -15,7 +15,7 @@ <$declareStandardTransform()$> -out vec3 _normal; +layout(location=0) out vec3 _normal; void main(void) { const float depth = 0.0; diff --git a/libraries/image/CMakeLists.txt b/libraries/image/CMakeLists.txt index 6bc5c762f5..4db39f2152 100644 --- a/libraries/image/CMakeLists.txt +++ b/libraries/image/CMakeLists.txt @@ -3,3 +3,9 @@ setup_hifi_library() link_hifi_libraries(shared gpu) target_nvtt() target_etc2comp() + +if (UNIX AND NOT APPLE) + set(THREADS_PREFER_PTHREAD_FLAG ON) + find_package(Threads REQUIRED) + target_link_libraries(image Threads::Threads) +endif() diff --git a/libraries/image/src/image/Image.cpp b/libraries/image/src/image/Image.cpp index 7fc3a73f87..1355a24bf4 100644 --- a/libraries/image/src/image/Image.cpp +++ b/libraries/image/src/image/Image.cpp @@ -31,17 +31,13 @@ using namespace gpu; #define CPU_MIPMAPS 1 #include -#ifdef USE_GLES +#undef _CRT_SECURE_NO_WARNINGS #include #include -#endif static const glm::uvec2 SPARSE_PAGE_SIZE(128); -#ifdef Q_OS_ANDROID -static const glm::uvec2 MAX_TEXTURE_SIZE(2048); -#else -static const glm::uvec2 MAX_TEXTURE_SIZE(4096); -#endif +static const glm::uvec2 MAX_TEXTURE_SIZE_GLES(2048); +static const glm::uvec2 MAX_TEXTURE_SIZE_GL(4096); bool DEV_DECIMATE_TEXTURES = false; std::atomic DECIMATED_TEXTURE_COUNT{ 0 }; std::atomic RECTIFIED_TEXTURE_COUNT{ 0 }; @@ -83,11 +79,12 @@ const QStringList getSupportedFormats() { // On GLES, we don't use HDR skyboxes -#ifndef USE_GLES -QImage::Format QIMAGE_HDR_FORMAT = QImage::Format_RGB30; -#else -QImage::Format QIMAGE_HDR_FORMAT = QImage::Format_RGB32; -#endif +QImage::Format hdrFormatForTarget(BackendTarget target) { + if (target == BackendTarget::GLES32) { + return QImage::Format_RGB32; + } + return QImage::Format_RGB30; +} TextureUsage::TextureLoader TextureUsage::getTextureLoaderForType(Type type, const QVariantMap& options) { switch (type) { @@ -123,63 +120,63 @@ TextureUsage::TextureLoader TextureUsage::getTextureLoaderForType(Type type, con } gpu::TexturePointer TextureUsage::createStrict2DTextureFromImage(QImage&& srcImage, const std::string& srcImageName, - bool compress, const std::atomic& abortProcessing) { - return process2DTextureColorFromImage(std::move(srcImage), srcImageName, compress, true, abortProcessing); + bool compress, BackendTarget target, const std::atomic& abortProcessing) { + return process2DTextureColorFromImage(std::move(srcImage), srcImageName, compress, target, true, abortProcessing); } gpu::TexturePointer TextureUsage::create2DTextureFromImage(QImage&& srcImage, const std::string& srcImageName, - bool compress, const std::atomic& abortProcessing) { - return process2DTextureColorFromImage(std::move(srcImage), srcImageName, compress, false, abortProcessing); + bool compress, BackendTarget target, const std::atomic& abortProcessing) { + return process2DTextureColorFromImage(std::move(srcImage), srcImageName, compress, target, false, abortProcessing); } gpu::TexturePointer TextureUsage::createAlbedoTextureFromImage(QImage&& srcImage, const std::string& srcImageName, - bool compress, const std::atomic& abortProcessing) { - return process2DTextureColorFromImage(std::move(srcImage), srcImageName, compress, false, abortProcessing); + bool compress, BackendTarget target, const std::atomic& abortProcessing) { + return process2DTextureColorFromImage(std::move(srcImage), srcImageName, compress, target, false, abortProcessing); } gpu::TexturePointer TextureUsage::createEmissiveTextureFromImage(QImage&& srcImage, const std::string& srcImageName, - bool compress, const std::atomic& abortProcessing) { - return process2DTextureColorFromImage(std::move(srcImage), srcImageName, compress, false, abortProcessing); + bool compress, BackendTarget target, const std::atomic& abortProcessing) { + return process2DTextureColorFromImage(std::move(srcImage), srcImageName, compress, target, false, abortProcessing); } gpu::TexturePointer TextureUsage::createLightmapTextureFromImage(QImage&& srcImage, const std::string& srcImageName, - bool compress, const std::atomic& abortProcessing) { - return process2DTextureColorFromImage(std::move(srcImage), srcImageName, compress, false, abortProcessing); + bool compress, BackendTarget target, const std::atomic& abortProcessing) { + return process2DTextureColorFromImage(std::move(srcImage), srcImageName, compress, target, false, abortProcessing); } gpu::TexturePointer TextureUsage::createNormalTextureFromNormalImage(QImage&& srcImage, const std::string& srcImageName, - bool compress, const std::atomic& abortProcessing) { - return process2DTextureNormalMapFromImage(std::move(srcImage), srcImageName, compress, false, abortProcessing); + bool compress, BackendTarget target, const std::atomic& abortProcessing) { + return process2DTextureNormalMapFromImage(std::move(srcImage), srcImageName, compress, target, false, abortProcessing); } gpu::TexturePointer TextureUsage::createNormalTextureFromBumpImage(QImage&& srcImage, const std::string& srcImageName, - bool compress, const std::atomic& abortProcessing) { - return process2DTextureNormalMapFromImage(std::move(srcImage), srcImageName, compress, true, abortProcessing); + bool compress, BackendTarget target, const std::atomic& abortProcessing) { + return process2DTextureNormalMapFromImage(std::move(srcImage), srcImageName, compress, target, true, abortProcessing); } gpu::TexturePointer TextureUsage::createRoughnessTextureFromImage(QImage&& srcImage, const std::string& srcImageName, - bool compress, const std::atomic& abortProcessing) { - return process2DTextureGrayscaleFromImage(std::move(srcImage), srcImageName, compress, false, abortProcessing); + bool compress, BackendTarget target, const std::atomic& abortProcessing) { + return process2DTextureGrayscaleFromImage(std::move(srcImage), srcImageName, compress, target, false, abortProcessing); } gpu::TexturePointer TextureUsage::createRoughnessTextureFromGlossImage(QImage&& srcImage, const std::string& srcImageName, - bool compress, const std::atomic& abortProcessing) { - return process2DTextureGrayscaleFromImage(std::move(srcImage), srcImageName, compress, true, abortProcessing); + bool compress, BackendTarget target, const std::atomic& abortProcessing) { + return process2DTextureGrayscaleFromImage(std::move(srcImage), srcImageName, compress, target, true, abortProcessing); } gpu::TexturePointer TextureUsage::createMetallicTextureFromImage(QImage&& srcImage, const std::string& srcImageName, - bool compress, const std::atomic& abortProcessing) { - return process2DTextureGrayscaleFromImage(std::move(srcImage), srcImageName, compress, false, abortProcessing); + bool compress, BackendTarget target, const std::atomic& abortProcessing) { + return process2DTextureGrayscaleFromImage(std::move(srcImage), srcImageName, compress, target, false, abortProcessing); } gpu::TexturePointer TextureUsage::createCubeTextureFromImage(QImage&& srcImage, const std::string& srcImageName, - bool compress, const std::atomic& abortProcessing) { - return processCubeTextureColorFromImage(std::move(srcImage), srcImageName, compress, true, abortProcessing); + bool compress, BackendTarget target, const std::atomic& abortProcessing) { + return processCubeTextureColorFromImage(std::move(srcImage), srcImageName, compress, target, true, abortProcessing); } gpu::TexturePointer TextureUsage::createCubeTextureFromImageWithoutIrradiance(QImage&& srcImage, const std::string& srcImageName, - bool compress, const std::atomic& abortProcessing) { - return processCubeTextureColorFromImage(std::move(srcImage), srcImageName, compress, false, abortProcessing); + bool compress, BackendTarget target, const std::atomic& abortProcessing) { + return processCubeTextureColorFromImage(std::move(srcImage), srcImageName, compress, target, false, abortProcessing); } static float denormalize(float value, const float minValue) { @@ -228,7 +225,7 @@ QImage processRawImageData(QIODevice& content, const std::string& filename) { gpu::TexturePointer processImage(std::shared_ptr content, const std::string& filename, int maxNumPixels, TextureUsage::Type textureType, - bool compress, const std::atomic& abortProcessing) { + bool compress, BackendTarget target, const std::atomic& abortProcessing) { QImage image = processRawImageData(*content.get(), filename); // Texture content can take up a lot of memory. Here we release our ownership of that content @@ -259,12 +256,12 @@ gpu::TexturePointer processImage(std::shared_ptr content, const std:: } auto loader = TextureUsage::getTextureLoaderForType(textureType); - auto texture = loader(std::move(image), filename, compress, abortProcessing); + auto texture = loader(std::move(image), filename, compress, target, abortProcessing); return texture; } -QImage processSourceImage(QImage&& srcImage, bool cubemap) { +QImage processSourceImage(QImage&& srcImage, bool cubemap, BackendTarget target) { PROFILE_RANGE(resource_parse, "processSourceImage"); // Take a local copy to force move construction @@ -274,7 +271,8 @@ QImage processSourceImage(QImage&& srcImage, bool cubemap) { const glm::uvec2 srcImageSize = toGlm(localCopy.size()); glm::uvec2 targetSize = srcImageSize; - while (glm::any(glm::greaterThan(targetSize, MAX_TEXTURE_SIZE))) { + const auto maxTextureSize = target == BackendTarget::GLES32 ? MAX_TEXTURE_SIZE_GLES : MAX_TEXTURE_SIZE_GL; + while (glm::any(glm::greaterThan(targetSize, maxTextureSize))) { targetSize /= 2; } if (targetSize != srcImageSize) { @@ -406,12 +404,12 @@ public: } }; -void generateHDRMips(gpu::Texture* texture, QImage&& image, const std::atomic& abortProcessing, int face) { +void generateHDRMips(gpu::Texture* texture, QImage&& image, BackendTarget target, const std::atomic& abortProcessing, int face) { // Take a local copy to force move construction // https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#f18-for-consume-parameters-pass-by-x-and-stdmove-the-parameter QImage localCopy = std::move(image); - assert(localCopy.format() == QIMAGE_HDR_FORMAT); + assert(localCopy.format() == hdrFormatForTarget(target)); const int width = localCopy.width(), height = localCopy.height(); std::vector data; @@ -503,220 +501,219 @@ void generateHDRMips(gpu::Texture* texture, QImage&& image, const std::atomic& abortProcessing, int face) { +void generateLDRMips(gpu::Texture* texture, QImage&& image, BackendTarget target, const std::atomic& abortProcessing, int face) { // Take a local copy to force move construction // https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#f18-for-consume-parameters-pass-by-x-and-stdmove-the-parameter QImage localCopy = std::move(image); - if (localCopy.format() != QImage::Format_ARGB32 && localCopy.format() != QIMAGE_HDR_FORMAT) { + if (localCopy.format() != QImage::Format_ARGB32 && localCopy.format() != hdrFormatForTarget(target)) { localCopy = localCopy.convertToFormat(QImage::Format_ARGB32); } const int width = localCopy.width(), height = localCopy.height(); auto mipFormat = texture->getStoredMipFormat(); -#ifndef USE_GLES - const void* data = static_cast(localCopy.constBits()); - nvtt::TextureType textureType = nvtt::TextureType_2D; - nvtt::InputFormat inputFormat = nvtt::InputFormat_BGRA_8UB; - nvtt::WrapMode wrapMode = nvtt::WrapMode_Mirror; - nvtt::RoundMode roundMode = nvtt::RoundMode_None; - nvtt::AlphaMode alphaMode = nvtt::AlphaMode_None; + if (target != BackendTarget::GLES32) { + const void* data = static_cast(localCopy.constBits()); + nvtt::TextureType textureType = nvtt::TextureType_2D; + nvtt::InputFormat inputFormat = nvtt::InputFormat_BGRA_8UB; + nvtt::WrapMode wrapMode = nvtt::WrapMode_Mirror; + nvtt::RoundMode roundMode = nvtt::RoundMode_None; + nvtt::AlphaMode alphaMode = nvtt::AlphaMode_None; - float inputGamma = 2.2f; - float outputGamma = 2.2f; + float inputGamma = 2.2f; + float outputGamma = 2.2f; - nvtt::InputOptions inputOptions; - inputOptions.setTextureLayout(textureType, width, height); + nvtt::InputOptions inputOptions; + inputOptions.setTextureLayout(textureType, width, height); - inputOptions.setMipmapData(data, width, height); - // setMipmapData copies the memory, so free up the memory afterward to avoid bloating the heap - data = nullptr; - localCopy = QImage(); // QImage doesn't have a clear function, so override it with an empty one. + inputOptions.setMipmapData(data, width, height); + // setMipmapData copies the memory, so free up the memory afterward to avoid bloating the heap + data = nullptr; + localCopy = QImage(); // QImage doesn't have a clear function, so override it with an empty one. - inputOptions.setFormat(inputFormat); - inputOptions.setGamma(inputGamma, outputGamma); - inputOptions.setAlphaMode(alphaMode); - inputOptions.setWrapMode(wrapMode); - inputOptions.setRoundMode(roundMode); + inputOptions.setFormat(inputFormat); + inputOptions.setGamma(inputGamma, outputGamma); + inputOptions.setAlphaMode(alphaMode); + inputOptions.setWrapMode(wrapMode); + inputOptions.setRoundMode(roundMode); - inputOptions.setMipmapGeneration(true); - inputOptions.setMipmapFilter(nvtt::MipmapFilter_Box); + inputOptions.setMipmapGeneration(true); + inputOptions.setMipmapFilter(nvtt::MipmapFilter_Box); - nvtt::CompressionOptions compressionOptions; - compressionOptions.setQuality(nvtt::Quality_Production); + nvtt::CompressionOptions compressionOptions; + compressionOptions.setQuality(nvtt::Quality_Production); - if (mipFormat == gpu::Element::COLOR_COMPRESSED_BCX_SRGB) { - compressionOptions.setFormat(nvtt::Format_BC1); - } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_BCX_SRGBA_MASK) { - alphaMode = nvtt::AlphaMode_Transparency; - compressionOptions.setFormat(nvtt::Format_BC1a); - } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_BCX_SRGBA) { - alphaMode = nvtt::AlphaMode_Transparency; - compressionOptions.setFormat(nvtt::Format_BC3); - } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_BCX_RED) { - compressionOptions.setFormat(nvtt::Format_BC4); - } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_BCX_XY) { - compressionOptions.setFormat(nvtt::Format_BC5); - } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_BCX_SRGBA_HIGH) { - alphaMode = nvtt::AlphaMode_Transparency; - compressionOptions.setFormat(nvtt::Format_BC7); - } else if (mipFormat == gpu::Element::COLOR_RGBA_32) { - compressionOptions.setFormat(nvtt::Format_RGBA); - compressionOptions.setPixelType(nvtt::PixelType_UnsignedNorm); - compressionOptions.setPitchAlignment(4); - compressionOptions.setPixelFormat(32, - 0x000000FF, - 0x0000FF00, - 0x00FF0000, - 0xFF000000); - inputGamma = 1.0f; - outputGamma = 1.0f; - } else if (mipFormat == gpu::Element::COLOR_BGRA_32) { - compressionOptions.setFormat(nvtt::Format_RGBA); - compressionOptions.setPixelType(nvtt::PixelType_UnsignedNorm); - compressionOptions.setPitchAlignment(4); - compressionOptions.setPixelFormat(32, - 0x00FF0000, - 0x0000FF00, - 0x000000FF, - 0xFF000000); - inputGamma = 1.0f; - outputGamma = 1.0f; - } else if (mipFormat == gpu::Element::COLOR_SRGBA_32) { - compressionOptions.setFormat(nvtt::Format_RGBA); - compressionOptions.setPixelType(nvtt::PixelType_UnsignedNorm); - compressionOptions.setPitchAlignment(4); - compressionOptions.setPixelFormat(32, - 0x000000FF, - 0x0000FF00, - 0x00FF0000, - 0xFF000000); - } else if (mipFormat == gpu::Element::COLOR_SBGRA_32) { - compressionOptions.setFormat(nvtt::Format_RGBA); - compressionOptions.setPixelType(nvtt::PixelType_UnsignedNorm); - compressionOptions.setPitchAlignment(4); - compressionOptions.setPixelFormat(32, - 0x00FF0000, - 0x0000FF00, - 0x000000FF, - 0xFF000000); - } else if (mipFormat == gpu::Element::COLOR_R_8) { - compressionOptions.setFormat(nvtt::Format_RGB); - compressionOptions.setPixelType(nvtt::PixelType_UnsignedNorm); - compressionOptions.setPitchAlignment(4); - compressionOptions.setPixelFormat(8, 0, 0, 0); - } else if (mipFormat == gpu::Element::VEC2NU8_XY) { - inputOptions.setNormalMap(true); - compressionOptions.setFormat(nvtt::Format_RGBA); - compressionOptions.setPixelType(nvtt::PixelType_UnsignedNorm); - compressionOptions.setPitchAlignment(4); - compressionOptions.setPixelFormat(8, 8, 0, 0); - } else { - qCWarning(imagelogging) << "Unknown mip format"; - Q_UNREACHABLE(); - return; - } - - nvtt::OutputOptions outputOptions; - outputOptions.setOutputHeader(false); - OutputHandler outputHandler(texture, face); - outputOptions.setOutputHandler(&outputHandler); - MyErrorHandler errorHandler; - outputOptions.setErrorHandler(&errorHandler); - - SequentialTaskDispatcher dispatcher(abortProcessing); - nvtt::Compressor compressor; - compressor.setTaskDispatcher(&dispatcher); - compressor.process(inputOptions, compressionOptions, outputOptions); - -#else - int numMips = 1 + (int)log2(std::max(width, height)); - Etc::RawImage *mipMaps = new Etc::RawImage[numMips]; - Etc::Image::Format etcFormat = Etc::Image::Format::DEFAULT; - - if (mipFormat == gpu::Element::COLOR_COMPRESSED_ETC2_RGB) { - etcFormat = Etc::Image::Format::RGB8; - } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_ETC2_SRGB) { - etcFormat = Etc::Image::Format::SRGB8; - } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_ETC2_RGB_PUNCHTHROUGH_ALPHA) { - etcFormat = Etc::Image::Format::RGB8A1; - } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_ETC2_SRGB_PUNCHTHROUGH_ALPHA) { - etcFormat = Etc::Image::Format::SRGB8A1; - } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_ETC2_RGBA) { - etcFormat = Etc::Image::Format::RGBA8; - } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_ETC2_SRGBA) { - etcFormat = Etc::Image::Format::SRGBA8; - } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_EAC_RED) { - etcFormat = Etc::Image::Format::R11; - } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_EAC_RED_SIGNED) { - etcFormat = Etc::Image::Format::SIGNED_R11; - } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_EAC_XY) { - etcFormat = Etc::Image::Format::RG11; - } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_EAC_XY_SIGNED) { - etcFormat = Etc::Image::Format::SIGNED_RG11; - } else { - qCWarning(imagelogging) << "Unknown mip format"; - Q_UNREACHABLE(); - return; - } - - const Etc::ErrorMetric errorMetric = Etc::ErrorMetric::RGBA; - const float effort = 1.0f; - const int numEncodeThreads = 4; - int encodingTime; - const float MAX_COLOR = 255.0f; - - std::vector floatData; - floatData.resize(width * height); - for (int y = 0; y < height; y++) { - QRgb *line = (QRgb *) localCopy.scanLine(y); - for (int x = 0; x < width; x++) { - QRgb &pixel = line[x]; - floatData[x + y * width] = vec4(qRed(pixel), qGreen(pixel), qBlue(pixel), qAlpha(pixel)) / MAX_COLOR; + if (mipFormat == gpu::Element::COLOR_COMPRESSED_BCX_SRGB) { + compressionOptions.setFormat(nvtt::Format_BC1); + } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_BCX_SRGBA_MASK) { + alphaMode = nvtt::AlphaMode_Transparency; + compressionOptions.setFormat(nvtt::Format_BC1a); + } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_BCX_SRGBA) { + alphaMode = nvtt::AlphaMode_Transparency; + compressionOptions.setFormat(nvtt::Format_BC3); + } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_BCX_RED) { + compressionOptions.setFormat(nvtt::Format_BC4); + } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_BCX_XY) { + compressionOptions.setFormat(nvtt::Format_BC5); + } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_BCX_SRGBA_HIGH) { + alphaMode = nvtt::AlphaMode_Transparency; + compressionOptions.setFormat(nvtt::Format_BC7); + } else if (mipFormat == gpu::Element::COLOR_RGBA_32) { + compressionOptions.setFormat(nvtt::Format_RGBA); + compressionOptions.setPixelType(nvtt::PixelType_UnsignedNorm); + compressionOptions.setPitchAlignment(4); + compressionOptions.setPixelFormat(32, + 0x000000FF, + 0x0000FF00, + 0x00FF0000, + 0xFF000000); + inputGamma = 1.0f; + outputGamma = 1.0f; + } else if (mipFormat == gpu::Element::COLOR_BGRA_32) { + compressionOptions.setFormat(nvtt::Format_RGBA); + compressionOptions.setPixelType(nvtt::PixelType_UnsignedNorm); + compressionOptions.setPitchAlignment(4); + compressionOptions.setPixelFormat(32, + 0x00FF0000, + 0x0000FF00, + 0x000000FF, + 0xFF000000); + inputGamma = 1.0f; + outputGamma = 1.0f; + } else if (mipFormat == gpu::Element::COLOR_SRGBA_32) { + compressionOptions.setFormat(nvtt::Format_RGBA); + compressionOptions.setPixelType(nvtt::PixelType_UnsignedNorm); + compressionOptions.setPitchAlignment(4); + compressionOptions.setPixelFormat(32, + 0x000000FF, + 0x0000FF00, + 0x00FF0000, + 0xFF000000); + } else if (mipFormat == gpu::Element::COLOR_SBGRA_32) { + compressionOptions.setFormat(nvtt::Format_RGBA); + compressionOptions.setPixelType(nvtt::PixelType_UnsignedNorm); + compressionOptions.setPitchAlignment(4); + compressionOptions.setPixelFormat(32, + 0x00FF0000, + 0x0000FF00, + 0x000000FF, + 0xFF000000); + } else if (mipFormat == gpu::Element::COLOR_R_8) { + compressionOptions.setFormat(nvtt::Format_RGB); + compressionOptions.setPixelType(nvtt::PixelType_UnsignedNorm); + compressionOptions.setPitchAlignment(4); + compressionOptions.setPixelFormat(8, 0, 0, 0); + } else if (mipFormat == gpu::Element::VEC2NU8_XY) { + inputOptions.setNormalMap(true); + compressionOptions.setFormat(nvtt::Format_RGBA); + compressionOptions.setPixelType(nvtt::PixelType_UnsignedNorm); + compressionOptions.setPitchAlignment(4); + compressionOptions.setPixelFormat(8, 8, 0, 0); + } else { + qCWarning(imagelogging) << "Unknown mip format"; + Q_UNREACHABLE(); + return; } - } - // free up the memory afterward to avoid bloating the heap - localCopy = QImage(); // QImage doesn't have a clear function, so override it with an empty one. + nvtt::OutputOptions outputOptions; + outputOptions.setOutputHeader(false); + OutputHandler outputHandler(texture, face); + outputOptions.setOutputHandler(&outputHandler); + MyErrorHandler errorHandler; + outputOptions.setErrorHandler(&errorHandler); - Etc::EncodeMipmaps( - (float *)floatData.data(), width, height, - etcFormat, errorMetric, effort, - numEncodeThreads, numEncodeThreads, - numMips, Etc::FILTER_WRAP_NONE, - mipMaps, &encodingTime - ); + SequentialTaskDispatcher dispatcher(abortProcessing); + nvtt::Compressor compressor; + compressor.setTaskDispatcher(&dispatcher); + compressor.process(inputOptions, compressionOptions, outputOptions); + } else { + int numMips = 1 + (int)log2(std::max(width, height)); + Etc::RawImage *mipMaps = new Etc::RawImage[numMips]; + Etc::Image::Format etcFormat = Etc::Image::Format::DEFAULT; - for (int i = 0; i < numMips; i++) { - if (mipMaps[i].paucEncodingBits.get()) { - if (face >= 0) { - texture->assignStoredMipFace(i, face, mipMaps[i].uiEncodingBitsBytes, static_cast(mipMaps[i].paucEncodingBits.get())); - } else { - texture->assignStoredMip(i, mipMaps[i].uiEncodingBitsBytes, static_cast(mipMaps[i].paucEncodingBits.get())); + if (mipFormat == gpu::Element::COLOR_COMPRESSED_ETC2_RGB) { + etcFormat = Etc::Image::Format::RGB8; + } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_ETC2_SRGB) { + etcFormat = Etc::Image::Format::SRGB8; + } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_ETC2_RGB_PUNCHTHROUGH_ALPHA) { + etcFormat = Etc::Image::Format::RGB8A1; + } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_ETC2_SRGB_PUNCHTHROUGH_ALPHA) { + etcFormat = Etc::Image::Format::SRGB8A1; + } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_ETC2_RGBA) { + etcFormat = Etc::Image::Format::RGBA8; + } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_ETC2_SRGBA) { + etcFormat = Etc::Image::Format::SRGBA8; + } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_EAC_RED) { + etcFormat = Etc::Image::Format::R11; + } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_EAC_RED_SIGNED) { + etcFormat = Etc::Image::Format::SIGNED_R11; + } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_EAC_XY) { + etcFormat = Etc::Image::Format::RG11; + } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_EAC_XY_SIGNED) { + etcFormat = Etc::Image::Format::SIGNED_RG11; + } else { + qCWarning(imagelogging) << "Unknown mip format"; + Q_UNREACHABLE(); + return; + } + + const Etc::ErrorMetric errorMetric = Etc::ErrorMetric::RGBA; + const float effort = 1.0f; + const int numEncodeThreads = 4; + int encodingTime; + const float MAX_COLOR = 255.0f; + + std::vector floatData; + floatData.resize(width * height); + for (int y = 0; y < height; y++) { + QRgb *line = (QRgb *)localCopy.scanLine(y); + for (int x = 0; x < width; x++) { + QRgb &pixel = line[x]; + floatData[x + y * width] = vec4(qRed(pixel), qGreen(pixel), qBlue(pixel), qAlpha(pixel)) / MAX_COLOR; } } - } - delete[] mipMaps; -#endif + // free up the memory afterward to avoid bloating the heap + localCopy = QImage(); // QImage doesn't have a clear function, so override it with an empty one. + + Etc::EncodeMipmaps( + (float *)floatData.data(), width, height, + etcFormat, errorMetric, effort, + numEncodeThreads, numEncodeThreads, + numMips, Etc::FILTER_WRAP_NONE, + mipMaps, &encodingTime + ); + + for (int i = 0; i < numMips; i++) { + if (mipMaps[i].paucEncodingBits.get()) { + if (face >= 0) { + texture->assignStoredMipFace(i, face, mipMaps[i].uiEncodingBitsBytes, static_cast(mipMaps[i].paucEncodingBits.get())); + } else { + texture->assignStoredMip(i, mipMaps[i].uiEncodingBitsBytes, static_cast(mipMaps[i].paucEncodingBits.get())); + } + } + } + + delete[] mipMaps; + } } #endif -void generateMips(gpu::Texture* texture, QImage&& image, const std::atomic& abortProcessing = false, int face = -1) { +void generateMips(gpu::Texture* texture, QImage&& image, BackendTarget target, const std::atomic& abortProcessing = false, int face = -1) { #if CPU_MIPMAPS PROFILE_RANGE(resource_parse, "generateMips"); -#ifndef USE_GLES - if (image.format() == QIMAGE_HDR_FORMAT) { - generateHDRMips(texture, std::move(image), abortProcessing, face); - } else { - generateLDRMips(texture, std::move(image), abortProcessing, face); + if (target == BackendTarget::GLES32) { + generateLDRMips(texture, std::move(image), target, abortProcessing, face); + } else { + if (image.format() == hdrFormatForTarget(target)) { + generateHDRMips(texture, std::move(image), target, abortProcessing, face); + } else { + generateLDRMips(texture, std::move(image), target, abortProcessing, face); + } } -#else - generateLDRMips(texture, std::move(image), abortProcessing, face); -#endif #else texture->setAutoGenerateMips(true); #endif @@ -750,9 +747,9 @@ void processTextureAlpha(const QImage& srcImage, bool& validAlpha, bool& alphaAs } gpu::TexturePointer TextureUsage::process2DTextureColorFromImage(QImage&& srcImage, const std::string& srcImageName, bool compress, - bool isStrict, const std::atomic& abortProcessing) { + BackendTarget target, bool isStrict, const std::atomic& abortProcessing) { PROFILE_RANGE(resource_parse, "process2DTextureColorFromImage"); - QImage image = processSourceImage(std::move(srcImage), false); + QImage image = processSourceImage(std::move(srcImage), false, target); bool validAlpha = image.hasAlphaChannel(); bool alphaAsMask = false; @@ -771,23 +768,26 @@ gpu::TexturePointer TextureUsage::process2DTextureColorFromImage(QImage&& srcIma gpu::Element formatMip; gpu::Element formatGPU; if (compress) { - if (validAlpha) { - // NOTE: This disables BC1a compression because it was producing odd artifacts on text textures - // for the tutorial. Instead we use BC3 (which is larger) but doesn't produce the same artifacts). - formatGPU = gpu::Element::COLOR_COMPRESSED_BCX_SRGBA; + if (target == BackendTarget::GLES32) { + // GLES does not support GL_BGRA + formatGPU = gpu::Element::COLOR_COMPRESSED_ETC2_SRGBA; + formatMip = formatGPU; } else { - formatGPU = gpu::Element::COLOR_COMPRESSED_BCX_SRGB; + if (validAlpha) { + // NOTE: This disables BC1a compression because it was producing odd artifacts on text textures + // for the tutorial. Instead we use BC3 (which is larger) but doesn't produce the same artifacts). + formatGPU = gpu::Element::COLOR_COMPRESSED_BCX_SRGBA; + } else { + formatGPU = gpu::Element::COLOR_COMPRESSED_BCX_SRGB; + } + formatMip = formatGPU; } - formatMip = formatGPU; } else { -#ifdef USE_GLES - // GLES does not support GL_BGRA - formatGPU = gpu::Element::COLOR_COMPRESSED_ETC2_SRGBA; - formatMip = formatGPU; -#else - formatGPU = gpu::Element::COLOR_SRGBA_32; - formatMip = gpu::Element::COLOR_SBGRA_32; -#endif + if (target == BackendTarget::GLES32) { + } else { + formatGPU = gpu::Element::COLOR_SRGBA_32; + formatMip = gpu::Element::COLOR_SBGRA_32; + } } if (isStrict) { @@ -806,7 +806,7 @@ gpu::TexturePointer TextureUsage::process2DTextureColorFromImage(QImage&& srcIma theTexture->setUsage(usage.build()); theTexture->setStoredMipFormat(formatMip); theTexture->assignStoredMip(0, image.byteCount(), image.constBits()); - generateMips(theTexture.get(), std::move(image), abortProcessing); + generateMips(theTexture.get(), std::move(image), target, abortProcessing); } return theTexture; @@ -887,10 +887,10 @@ QImage processBumpMap(QImage&& image) { return result; } gpu::TexturePointer TextureUsage::process2DTextureNormalMapFromImage(QImage&& srcImage, const std::string& srcImageName, - bool compress, bool isBumpMap, + bool compress, BackendTarget target, bool isBumpMap, const std::atomic& abortProcessing) { PROFILE_RANGE(resource_parse, "process2DTextureNormalMapFromImage"); - QImage image = processSourceImage(std::move(srcImage), false); + QImage image = processSourceImage(std::move(srcImage), false, target); if (isBumpMap) { image = processBumpMap(std::move(image)); @@ -906,13 +906,13 @@ gpu::TexturePointer TextureUsage::process2DTextureNormalMapFromImage(QImage&& sr gpu::Element formatMip; gpu::Element formatGPU; if (compress) { - formatGPU = gpu::Element::COLOR_COMPRESSED_BCX_XY; + if (target == BackendTarget::GLES32) { + formatGPU = gpu::Element::COLOR_COMPRESSED_EAC_XY; + } else { + formatGPU = gpu::Element::COLOR_COMPRESSED_BCX_XY; + } } else { -#ifdef USE_GLES - formatGPU = gpu::Element::COLOR_COMPRESSED_EAC_XY; -#else formatGPU = gpu::Element::VEC2NU8_XY; -#endif } formatMip = formatGPU; @@ -920,17 +920,17 @@ gpu::TexturePointer TextureUsage::process2DTextureNormalMapFromImage(QImage&& sr theTexture->setSource(srcImageName); theTexture->setStoredMipFormat(formatMip); theTexture->assignStoredMip(0, image.byteCount(), image.constBits()); - generateMips(theTexture.get(), std::move(image), abortProcessing); + generateMips(theTexture.get(), std::move(image), target, abortProcessing); } return theTexture; } gpu::TexturePointer TextureUsage::process2DTextureGrayscaleFromImage(QImage&& srcImage, const std::string& srcImageName, - bool compress, bool isInvertedPixels, + bool compress, BackendTarget target, bool isInvertedPixels, const std::atomic& abortProcessing) { PROFILE_RANGE(resource_parse, "process2DTextureGrayscaleFromImage"); - QImage image = processSourceImage(std::move(srcImage), false); + QImage image = processSourceImage(std::move(srcImage), false, target); if (image.format() != QImage::Format_ARGB32) { image = image.convertToFormat(QImage::Format_ARGB32); @@ -946,13 +946,13 @@ gpu::TexturePointer TextureUsage::process2DTextureGrayscaleFromImage(QImage&& sr gpu::Element formatMip; gpu::Element formatGPU; if (compress) { - formatGPU = gpu::Element::COLOR_COMPRESSED_BCX_RED; + if (target == BackendTarget::GLES32) { + formatGPU = gpu::Element::COLOR_COMPRESSED_EAC_RED; + } else { + formatGPU = gpu::Element::COLOR_COMPRESSED_BCX_RED; + } } else { -#ifdef USE_GLES - formatGPU = gpu::Element::COLOR_COMPRESSED_EAC_RED; -#else formatGPU = gpu::Element::COLOR_R_8; -#endif } formatMip = formatGPU; @@ -960,7 +960,7 @@ gpu::TexturePointer TextureUsage::process2DTextureGrayscaleFromImage(QImage&& sr theTexture->setSource(srcImageName); theTexture->setStoredMipFormat(formatMip); theTexture->assignStoredMip(0, image.byteCount(), image.constBits()); - generateMips(theTexture.get(), std::move(image), abortProcessing); + generateMips(theTexture.get(), std::move(image), target, abortProcessing); } return theTexture; @@ -1233,12 +1233,12 @@ const int CubeLayout::NUM_CUBEMAP_LAYOUTS = sizeof(CubeLayout::CUBEMAP_LAYOUTS) //#define DEBUG_COLOR_PACKING -QImage convertToHDRFormat(QImage&& srcImage, gpu::Element format) { +QImage convertToHDRFormat(QImage&& srcImage, gpu::Element format, BackendTarget target) { // Take a local copy to force move construction // https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#f18-for-consume-parameters-pass-by-x-and-stdmove-the-parameter QImage localCopy = std::move(srcImage); - QImage hdrImage(localCopy.width(), localCopy.height(), (QImage::Format)QIMAGE_HDR_FORMAT); + QImage hdrImage(localCopy.width(), localCopy.height(), hdrFormatForTarget(target)); std::function packFunc; #ifdef DEBUG_COLOR_PACKING std::function unpackFunc; @@ -1292,7 +1292,7 @@ QImage convertToHDRFormat(QImage&& srcImage, gpu::Element format) { } gpu::TexturePointer TextureUsage::processCubeTextureColorFromImage(QImage&& srcImage, const std::string& srcImageName, - bool compress, bool generateIrradiance, + bool compress, BackendTarget target, bool generateIrradiance, const std::atomic& abortProcessing) { PROFILE_RANGE(resource_parse, "processCubeTextureColorFromImage"); @@ -1308,27 +1308,28 @@ gpu::TexturePointer TextureUsage::processCubeTextureColorFromImage(QImage&& srcI gpu::TexturePointer theTexture = nullptr; - QImage image = processSourceImage(std::move(localCopy), true); + QImage image = processSourceImage(std::move(localCopy), true, target); - if (image.format() != QIMAGE_HDR_FORMAT) { -#ifndef USE_GLES - image = convertToHDRFormat(std::move(image), HDR_FORMAT); -#else - image = image.convertToFormat(QImage::Format_RGB32); -#endif + if (image.format() != hdrFormatForTarget(target)) { + if (target == BackendTarget::GLES32) { + image = image.convertToFormat(QImage::Format_RGB32); + } else { + image = convertToHDRFormat(std::move(image), HDR_FORMAT, target); + } } gpu::Element formatMip; gpu::Element formatGPU; if (compress) { - formatGPU = gpu::Element::COLOR_COMPRESSED_BCX_HDR_RGB; + if (target == BackendTarget::GLES32) { + formatGPU = gpu::Element::COLOR_COMPRESSED_ETC2_SRGB; + } else { + formatGPU = gpu::Element::COLOR_COMPRESSED_BCX_HDR_RGB; + } } else { -#ifdef USE_GLES - formatGPU = gpu::Element::COLOR_COMPRESSED_ETC2_SRGB; -#else formatGPU = HDR_FORMAT; -#endif } + formatMip = formatGPU; // Find the layout of the cubemap in the 2D image @@ -1378,11 +1379,12 @@ gpu::TexturePointer TextureUsage::processCubeTextureColorFromImage(QImage&& srcI PROFILE_RANGE(resource_parse, "generateIrradiance"); gpu::Element irradianceFormat; // TODO: we could locally compress the irradiance texture on Android, but we don't need to -#ifndef USE_GLES - irradianceFormat = HDR_FORMAT; -#else - irradianceFormat = gpu::Element::COLOR_SRGBA_32; -#endif + if (target == BackendTarget::GLES32) { + irradianceFormat = gpu::Element::COLOR_SRGBA_32; + } else { + irradianceFormat = HDR_FORMAT; + } + auto irradianceTexture = gpu::Texture::createCube(irradianceFormat, faces[0].width(), gpu::Texture::MAX_NUM_MIPS, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR, gpu::Sampler::WRAP_CLAMP)); irradianceTexture->setSource(srcImageName); irradianceTexture->setStoredMipFormat(irradianceFormat); @@ -1390,14 +1392,14 @@ gpu::TexturePointer TextureUsage::processCubeTextureColorFromImage(QImage&& srcI irradianceTexture->assignStoredMipFace(0, face, faces[face].byteCount(), faces[face].constBits()); } - irradianceTexture->generateIrradiance(); + irradianceTexture->generateIrradiance(target); auto irradiance = irradianceTexture->getIrradiance(); theTexture->overrideIrradiance(irradiance); } for (uint8 face = 0; face < faces.size(); ++face) { - generateMips(theTexture.get(), std::move(faces[face]), abortProcessing, face); + generateMips(theTexture.get(), std::move(faces[face]), target, abortProcessing, face); } } diff --git a/libraries/image/src/image/Image.h b/libraries/image/src/image/Image.h index ccf4845fca..ae72a183b3 100644 --- a/libraries/image/src/image/Image.h +++ b/libraries/image/src/image/Image.h @@ -41,42 +41,41 @@ enum Type { UNUSED_TEXTURE }; -using TextureLoader = std::function&)>; +using TextureLoader = std::function&)>; TextureLoader getTextureLoaderForType(Type type, const QVariantMap& options = QVariantMap()); gpu::TexturePointer create2DTextureFromImage(QImage&& image, const std::string& srcImageName, - bool compress, const std::atomic& abortProcessing); + bool compress, gpu::BackendTarget target, const std::atomic& abortProcessing); gpu::TexturePointer createStrict2DTextureFromImage(QImage&& image, const std::string& srcImageName, - bool compress, const std::atomic& abortProcessing); + bool compress, gpu::BackendTarget target, const std::atomic& abortProcessing); gpu::TexturePointer createAlbedoTextureFromImage(QImage&& image, const std::string& srcImageName, - bool compress, const std::atomic& abortProcessing); + bool compress, gpu::BackendTarget target, const std::atomic& abortProcessing); gpu::TexturePointer createEmissiveTextureFromImage(QImage&& image, const std::string& srcImageName, - bool compress, const std::atomic& abortProcessing); + bool compress, gpu::BackendTarget target, const std::atomic& abortProcessing); gpu::TexturePointer createNormalTextureFromNormalImage(QImage&& image, const std::string& srcImageName, - bool compress, const std::atomic& abortProcessing); + bool compress, gpu::BackendTarget target, const std::atomic& abortProcessing); gpu::TexturePointer createNormalTextureFromBumpImage(QImage&& image, const std::string& srcImageName, - bool compress, const std::atomic& abortProcessing); + bool compress, gpu::BackendTarget target, const std::atomic& abortProcessing); gpu::TexturePointer createRoughnessTextureFromImage(QImage&& image, const std::string& srcImageName, - bool compress, const std::atomic& abortProcessing); + bool compress, gpu::BackendTarget target, const std::atomic& abortProcessing); gpu::TexturePointer createRoughnessTextureFromGlossImage(QImage&& image, const std::string& srcImageName, - bool compress, const std::atomic& abortProcessing); + bool compress, gpu::BackendTarget target, const std::atomic& abortProcessing); gpu::TexturePointer createMetallicTextureFromImage(QImage&& image, const std::string& srcImageName, - bool compress, const std::atomic& abortProcessing); + bool compress, gpu::BackendTarget target, const std::atomic& abortProcessing); gpu::TexturePointer createCubeTextureFromImage(QImage&& image, const std::string& srcImageName, - bool compress, const std::atomic& abortProcessing); + bool compress, gpu::BackendTarget target, const std::atomic& abortProcessing); gpu::TexturePointer createCubeTextureFromImageWithoutIrradiance(QImage&& image, const std::string& srcImageName, - bool compress, const std::atomic& abortProcessing); + bool compress, gpu::BackendTarget target, const std::atomic& abortProcessing); gpu::TexturePointer createLightmapTextureFromImage(QImage&& image, const std::string& srcImageName, - bool compress, const std::atomic& abortProcessing); - + bool compress, gpu::BackendTarget target, const std::atomic& abortProcessing); gpu::TexturePointer process2DTextureColorFromImage(QImage&& srcImage, const std::string& srcImageName, bool compress, - bool isStrict, const std::atomic& abortProcessing); + gpu::BackendTarget target, bool isStrict, const std::atomic& abortProcessing); gpu::TexturePointer process2DTextureNormalMapFromImage(QImage&& srcImage, const std::string& srcImageName, bool compress, - bool isBumpMap, const std::atomic& abortProcessing); + gpu::BackendTarget target, bool isBumpMap, const std::atomic& abortProcessing); gpu::TexturePointer process2DTextureGrayscaleFromImage(QImage&& srcImage, const std::string& srcImageName, bool compress, - bool isInvertedPixels, const std::atomic& abortProcessing); + gpu::BackendTarget target, bool isInvertedPixels, const std::atomic& abortProcessing); gpu::TexturePointer processCubeTextureColorFromImage(QImage&& srcImage, const std::string& srcImageName, bool compress, - bool generateIrradiance, const std::atomic& abortProcessing); + gpu::BackendTarget target, bool generateIrradiance, const std::atomic& abortProcessing); } // namespace TextureUsage @@ -84,7 +83,7 @@ const QStringList getSupportedFormats(); gpu::TexturePointer processImage(std::shared_ptr content, const std::string& url, int maxNumPixels, TextureUsage::Type textureType, - bool compress = false, const std::atomic& abortProcessing = false); + bool compress, gpu::BackendTarget target, const std::atomic& abortProcessing = false); } // namespace image diff --git a/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.cpp b/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.cpp index 650c9675a7..ddd51e9b9b 100755 --- a/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.cpp +++ b/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.cpp @@ -302,6 +302,8 @@ controller::Input::NamedVector KeyboardMouseDevice::InputDevice::getAvailableInp availableInputs.append(Input::NamedPair(makeInput(Qt::Key_PageDown), QKeySequence(Qt::Key_PageDown).toString())); availableInputs.append(Input::NamedPair(makeInput(Qt::Key_Tab), QKeySequence(Qt::Key_Tab).toString())); availableInputs.append(Input::NamedPair(makeInput(Qt::Key_Control), "Control")); + availableInputs.append(Input::NamedPair(makeInput(Qt::Key_Delete), "Delete")); + availableInputs.append(Input::NamedPair(makeInput(Qt::Key_Backspace), QKeySequence(Qt::Key_Backspace).toString())); availableInputs.append(Input::NamedPair(makeInput(Qt::LeftButton), "LeftMouseButton")); availableInputs.append(Input::NamedPair(makeInput(Qt::MiddleButton), "MiddleMouseButton")); diff --git a/libraries/model-networking/CMakeLists.txt b/libraries/model-networking/CMakeLists.txt index 696f4feb9a..9a4bc780a6 100644 --- a/libraries/model-networking/CMakeLists.txt +++ b/libraries/model-networking/CMakeLists.txt @@ -1,4 +1,4 @@ set(TARGET_NAME model-networking) setup_hifi_library() -link_hifi_libraries(shared networking graphics fbx ktx image) +link_hifi_libraries(shared networking graphics fbx ktx image gl) include_hifi_library_headers(gpu) diff --git a/libraries/model-networking/src/model-networking/ModelCache.h b/libraries/model-networking/src/model-networking/ModelCache.h index ee13d6666c..eea6c93786 100644 --- a/libraries/model-networking/src/model-networking/ModelCache.h +++ b/libraries/model-networking/src/model-networking/ModelCache.h @@ -140,58 +140,6 @@ class ModelCache : public ResourceCache, public Dependency { public: - // Properties are copied over from ResourceCache (see ResourceCache.h for reason). - - /**jsdoc - * API to manage model cache resources. - * @namespace ModelCache - * - * @hifi-interface - * @hifi-client-entity - * - * @property {number} numTotal - Total number of total resources. Read-only. - * @property {number} numCached - Total number of cached resource. Read-only. - * @property {number} sizeTotal - Size in bytes of all resources. Read-only. - * @property {number} sizeCached - Size in bytes of all cached resources. Read-only. - */ - - - // Functions are copied over from ResourceCache (see ResourceCache.h for reason). - - /**jsdoc - * Get the list of all resource URLs. - * @function ModelCache.getResourceList - * @returns {string[]} - */ - - /**jsdoc - * @function ModelCache.dirty - * @returns {Signal} - */ - - /**jsdoc - * @function ModelCache.updateTotalSize - * @param {number} deltaSize - */ - - /**jsdoc - * Prefetches a resource. - * @function ModelCache.prefetch - * @param {string} url - URL of the resource to prefetch. - * @param {object} [extra=null] - * @returns {ResourceObject} - */ - - /**jsdoc - * Asynchronously loads a resource from the specified URL and returns it. - * @function ModelCache.getResource - * @param {string} url - URL of the resource to load. - * @param {string} [fallback=""] - Fallback URL if load of the desired URL fails. - * @param {} [extra=null] - * @returns {object} - */ - - GeometryResource::Pointer getGeometryResource(const QUrl& url, const QVariantHash& mapping = QVariantHash(), const QUrl& textureBaseUrl = QUrl()); diff --git a/libraries/model-networking/src/model-networking/ModelCacheScriptingInterface.cpp b/libraries/model-networking/src/model-networking/ModelCacheScriptingInterface.cpp new file mode 100644 index 0000000000..cdf75be9ca --- /dev/null +++ b/libraries/model-networking/src/model-networking/ModelCacheScriptingInterface.cpp @@ -0,0 +1,16 @@ +// +// ModelCacheScriptingInterface.cpp +// libraries/mmodel-networking/src/model-networking +// +// Created by David Rowe on 25 Jul 2018. +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "ModelCacheScriptingInterface.h" + +ModelCacheScriptingInterface::ModelCacheScriptingInterface() : + ScriptableResourceCache::ScriptableResourceCache(DependencyManager::get()) +{ } diff --git a/libraries/model-networking/src/model-networking/ModelCacheScriptingInterface.h b/libraries/model-networking/src/model-networking/ModelCacheScriptingInterface.h new file mode 100644 index 0000000000..5ac7ac1e50 --- /dev/null +++ b/libraries/model-networking/src/model-networking/ModelCacheScriptingInterface.h @@ -0,0 +1,49 @@ +// +// ModelCacheScriptingInterface.h +// libraries/mmodel-networking/src/model-networking +// +// Created by David Rowe on 25 Jul 2018. +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#pragma once + +#ifndef hifi_ModelCacheScriptingInterface_h +#define hifi_ModelCacheScriptingInterface_h + +#include + +#include + +#include "ModelCache.h" + +class ModelCacheScriptingInterface : public ScriptableResourceCache, public Dependency { + Q_OBJECT + + // Properties are copied over from ResourceCache (see ResourceCache.h for reason). + + /**jsdoc + * API to manage model cache resources. + * @namespace ModelCache + * + * @hifi-interface + * @hifi-client-entity + * + * @property {number} numTotal - Total number of total resources. Read-only. + * @property {number} numCached - Total number of cached resource. Read-only. + * @property {number} sizeTotal - Size in bytes of all resources. Read-only. + * @property {number} sizeCached - Size in bytes of all cached resources. Read-only. + * + * @borrows ResourceCache.getResourceList as getResourceList + * @borrows ResourceCache.updateTotalSize as updateTotalSize + * @borrows ResourceCache.prefetch as prefetch + * @borrows ResourceCache.dirty as dirty + */ + +public: + ModelCacheScriptingInterface(); +}; + +#endif // hifi_ModelCacheScriptingInterface_h diff --git a/libraries/model-networking/src/model-networking/TextureCache.cpp b/libraries/model-networking/src/model-networking/TextureCache.cpp index 40b31cac53..e8aec5e60e 100644 --- a/libraries/model-networking/src/model-networking/TextureCache.cpp +++ b/libraries/model-networking/src/model-networking/TextureCache.cpp @@ -31,6 +31,7 @@ #include #include +#include #include #include @@ -271,6 +272,20 @@ gpu::TexturePointer getFallbackTextureForType(image::TextureUsage::Type type) { return result; } +gpu::BackendTarget getBackendTarget() { +#if defined(USE_GLES) + gpu::BackendTarget target = gpu::BackendTarget::GLES32; +#elif defined(Q_OS_MAC) + gpu::BackendTarget target = gpu::BackendTarget::GL41; +#else + gpu::BackendTarget target = gpu::BackendTarget::GL45; + if (gl::disableGl45()) { + target = gpu::BackendTarget::GL41; + } +#endif + return target; +} + /// Returns a texture version of an image file gpu::TexturePointer TextureCache::getImageTexture(const QString& path, image::TextureUsage::Type type, QVariantMap options) { QImage image = QImage(path); @@ -279,7 +294,15 @@ gpu::TexturePointer TextureCache::getImageTexture(const QString& path, image::Te return nullptr; } auto loader = image::TextureUsage::getTextureLoaderForType(type, options); - return gpu::TexturePointer(loader(std::move(image), path.toStdString(), false, false)); + +#ifdef USE_GLES + constexpr bool shouldCompress = true; +#else + constexpr bool shouldCompress = false; +#endif + auto target = getBackendTarget(); + + return gpu::TexturePointer(loader(std::move(image), path.toStdString(), shouldCompress, target, false)); } QSharedPointer TextureCache::createResource(const QUrl& url, const QSharedPointer& fallback, @@ -1160,7 +1183,14 @@ void ImageReader::read() { // IMPORTANT: _content is empty past this point auto buffer = std::shared_ptr((QIODevice*)new OwningBuffer(std::move(_content))); - texture = image::processImage(std::move(buffer), _url.toString().toStdString(), _maxNumPixels, networkTexture->getTextureType()); + +#ifdef USE_GLES + constexpr bool shouldCompress = true; +#else + constexpr bool shouldCompress = false; +#endif + auto target = getBackendTarget(); + texture = image::processImage(std::move(buffer), _url.toString().toStdString(), _maxNumPixels, networkTexture->getTextureType(), shouldCompress, target); if (!texture) { qCWarning(modelnetworking) << "Could not process:" << _url; diff --git a/libraries/model-networking/src/model-networking/TextureCache.h b/libraries/model-networking/src/model-networking/TextureCache.h index bca64806c4..c914ad91af 100644 --- a/libraries/model-networking/src/model-networking/TextureCache.h +++ b/libraries/model-networking/src/model-networking/TextureCache.h @@ -156,58 +156,6 @@ class TextureCache : public ResourceCache, public Dependency { public: - // Properties are copied over from ResourceCache (see ResourceCache.h for reason). - - /**jsdoc - * API to manage texture cache resources. - * @namespace TextureCache - * - * @hifi-interface - * @hifi-client-entity - * - * @property {number} numTotal - Total number of total resources. Read-only. - * @property {number} numCached - Total number of cached resource. Read-only. - * @property {number} sizeTotal - Size in bytes of all resources. Read-only. - * @property {number} sizeCached - Size in bytes of all cached resources. Read-only. - */ - - - // Functions are copied over from ResourceCache (see ResourceCache.h for reason). - - /**jsdoc - * Get the list of all resource URLs. - * @function TextureCache.getResourceList - * @returns {string[]} - */ - - /**jsdoc - * @function TextureCache.dirty - * @returns {Signal} - */ - - /**jsdoc - * @function TextureCache.updateTotalSize - * @param {number} deltaSize - */ - - /**jsdoc - * Prefetches a resource. - * @function TextureCache.prefetch - * @param {string} url - URL of the resource to prefetch. - * @param {object} [extra=null] - * @returns {ResourceObject} - */ - - /**jsdoc - * Asynchronously loads a resource from the specified URL and returns it. - * @function TextureCache.getResource - * @param {string} url - URL of the resource to load. - * @param {string} [fallback=""] - Fallback URL if load of the desired URL fails. - * @param {} [extra=null] - * @returns {object} - */ - - /// Returns the ID of the permutation/normal texture used for Perlin noise shader programs. This texture /// has two lines: the first, a set of random numbers in [0, 255] to be used as permutation offsets, and /// the second, a set of random unit vectors to be used as noise gradients. @@ -248,21 +196,10 @@ public: gpu::ContextPointer getGPUContext() const { return _gpuContext; } signals: - /**jsdoc - * @function TextureCache.spectatorCameraFramebufferReset - * @returns {Signal} - */ void spectatorCameraFramebufferReset(); protected: - /**jsdoc - * @function TextureCache.prefetch - * @param {string} url - * @param {number} type - * @param {number} [maxNumPixels=67108864] - * @returns {ResourceObject} - */ // Overload ResourceCache::prefetch to allow specifying texture type for loads Q_INVOKABLE ScriptableResource* prefetch(const QUrl& url, int type, int maxNumPixels = ABSOLUTE_MAX_TEXTURE_NUM_PIXELS); @@ -273,6 +210,7 @@ private: friend class ImageReader; friend class NetworkTexture; friend class DilatableNetworkTexture; + friend class TextureCacheScriptingInterface; TextureCache(); virtual ~TextureCache(); diff --git a/libraries/model-networking/src/model-networking/TextureCacheScriptingInterface.cpp b/libraries/model-networking/src/model-networking/TextureCacheScriptingInterface.cpp new file mode 100644 index 0000000000..ff5c7ca298 --- /dev/null +++ b/libraries/model-networking/src/model-networking/TextureCacheScriptingInterface.cpp @@ -0,0 +1,23 @@ +// +// TextureCacheScriptingInterface.cpp +// libraries/mmodel-networking/src/model-networking +// +// Created by David Rowe on 25 Jul 2018. +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "TextureCacheScriptingInterface.h" + +TextureCacheScriptingInterface::TextureCacheScriptingInterface() : + ScriptableResourceCache::ScriptableResourceCache(DependencyManager::get()) +{ + connect(DependencyManager::get().data(), &TextureCache::spectatorCameraFramebufferReset, + this, &TextureCacheScriptingInterface::spectatorCameraFramebufferReset); +} + +ScriptableResource* TextureCacheScriptingInterface::prefetch(const QUrl& url, int type, int maxNumPixels) { + return DependencyManager::get()->prefetch(url, type, maxNumPixels); +} diff --git a/libraries/model-networking/src/model-networking/TextureCacheScriptingInterface.h b/libraries/model-networking/src/model-networking/TextureCacheScriptingInterface.h new file mode 100644 index 0000000000..4120840759 --- /dev/null +++ b/libraries/model-networking/src/model-networking/TextureCacheScriptingInterface.h @@ -0,0 +1,65 @@ +// +// TextureCacheScriptingInterface.h +// libraries/mmodel-networking/src/model-networking +// +// Created by David Rowe on 25 Jul 2018. +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#pragma once + +#ifndef hifi_TextureCacheScriptingInterface_h +#define hifi_TextureCacheScriptingInterface_h + +#include + +#include + +#include "TextureCache.h" + +class TextureCacheScriptingInterface : public ScriptableResourceCache, public Dependency { + Q_OBJECT + + // Properties are copied over from ResourceCache (see ResourceCache.h for reason). + + /**jsdoc + * API to manage texture cache resources. + * @namespace TextureCache + * + * @hifi-interface + * @hifi-client-entity + * + * @property {number} numTotal - Total number of total resources. Read-only. + * @property {number} numCached - Total number of cached resource. Read-only. + * @property {number} sizeTotal - Size in bytes of all resources. Read-only. + * @property {number} sizeCached - Size in bytes of all cached resources. Read-only. + * + * @borrows ResourceCache.getResourceList as getResourceList + * @borrows ResourceCache.updateTotalSize as updateTotalSize + * @borrows ResourceCache.prefetch as prefetch + * @borrows ResourceCache.dirty as dirty + */ + +public: + TextureCacheScriptingInterface(); + + /**jsdoc + * @function TextureCache.prefetch + * @param {string} url + * @param {number} type + * @param {number} [maxNumPixels=67108864] + * @returns {ResourceObject} + */ + Q_INVOKABLE ScriptableResource* prefetch(const QUrl& url, int type, int maxNumPixels = ABSOLUTE_MAX_TEXTURE_NUM_PIXELS); + +signals: + /**jsdoc + * @function TextureCache.spectatorCameraFramebufferReset + * @returns {Signal} + */ + void spectatorCameraFramebufferReset(); +}; + +#endif // hifi_TextureCacheScriptingInterface_h diff --git a/libraries/networking/src/AccountManager.h b/libraries/networking/src/AccountManager.h index a79b69fe2b..b122115dd0 100644 --- a/libraries/networking/src/AccountManager.h +++ b/libraries/networking/src/AccountManager.h @@ -96,6 +96,8 @@ public: QUrl getMetaverseServerURL() { return NetworkingConstants::METAVERSE_SERVER_URL(); } + void removeAccountFromFile(); + public slots: void requestAccessToken(const QString& login, const QString& password); void requestAccessTokenWithSteam(QByteArray authSessionTicket); @@ -133,7 +135,6 @@ private: void operator=(AccountManager const& other) = delete; void persistAccountToFile(); - void removeAccountFromFile(); void passSuccessToCallback(QNetworkReply* reply); void passErrorToCallback(QNetworkReply* reply); diff --git a/libraries/networking/src/ExtendedIODevice.h b/libraries/networking/src/ExtendedIODevice.h new file mode 100644 index 0000000000..7df1af74b6 --- /dev/null +++ b/libraries/networking/src/ExtendedIODevice.h @@ -0,0 +1,39 @@ +// +// ExtendedIODevice.h +// libraries/networking/src +// +// Created by Stephen Birarda on 8/7/18. +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_ExtendedIODevice_h +#define hifi_ExtendedIODevice_h + +#include + +class ExtendedIODevice : public QIODevice { +public: + ExtendedIODevice(QObject* parent = nullptr) : QIODevice(parent) {}; + + template qint64 peekPrimitive(T* data); + template qint64 readPrimitive(T* data); + template qint64 writePrimitive(const T& data); +}; + +template qint64 ExtendedIODevice::peekPrimitive(T* data) { + return peek(reinterpret_cast(data), sizeof(T)); +} + +template qint64 ExtendedIODevice::readPrimitive(T* data) { + return read(reinterpret_cast(data), sizeof(T)); +} + +template qint64 ExtendedIODevice::writePrimitive(const T& data) { + static_assert(!std::is_pointer::value, "T must not be a pointer"); + return write(reinterpret_cast(&data), sizeof(T)); +} + +#endif // hifi_ExtendedIODevice_h diff --git a/libraries/networking/src/LimitedNodeList.cpp b/libraries/networking/src/LimitedNodeList.cpp index 502874fbfb..b6b2369703 100644 --- a/libraries/networking/src/LimitedNodeList.cpp +++ b/libraries/networking/src/LimitedNodeList.cpp @@ -328,9 +328,10 @@ bool LimitedNodeList::packetSourceAndHashMatchAndTrackBandwidth(const udt::Packe if (sourceNode) { bool verifiedPacket = !PacketTypeEnum::getNonVerifiedPackets().contains(headerType); - bool ignoreVerification = isDomainServer() && PacketTypeEnum::getDomainIgnoredVerificationPackets().contains(headerType); + bool verificationEnabled = !(isDomainServer() && PacketTypeEnum::getDomainIgnoredVerificationPackets().contains(headerType)) + && _useAuthentication; - if (verifiedPacket && !ignoreVerification) { + if (verifiedPacket && verificationEnabled) { QByteArray packetHeaderHash = NLPacket::verificationHashInHeader(packet); QByteArray expectedHash; @@ -383,7 +384,7 @@ void LimitedNodeList::fillPacketHeader(const NLPacket& packet, HMACAuth* hmacAut packet.writeSourceID(getSessionLocalID()); } - if (hmacAuth + if (_useAuthentication && hmacAuth && !PacketTypeEnum::getNonSourcedPackets().contains(packet.getType()) && !PacketTypeEnum::getNonVerifiedPackets().contains(packet.getType())) { packet.writeVerificationHash(*hmacAuth); diff --git a/libraries/networking/src/LimitedNodeList.h b/libraries/networking/src/LimitedNodeList.h index 05374bbfbb..cffc49521a 100644 --- a/libraries/networking/src/LimitedNodeList.h +++ b/libraries/networking/src/LimitedNodeList.h @@ -307,6 +307,8 @@ public: bool isPacketVerifiedWithSource(const udt::Packet& packet, Node* sourceNode = nullptr); bool isPacketVerified(const udt::Packet& packet) { return isPacketVerifiedWithSource(packet); } + void setAuthenticatePackets(bool useAuthentication) { _useAuthentication = useAuthentication; } + bool getAuthenticatePackets() const { return _useAuthentication; } static void makeSTUNRequestPacket(char* stunRequestPacket); @@ -394,6 +396,7 @@ protected: HifiSockAddr _publicSockAddr; HifiSockAddr _stunSockAddr { STUN_SERVER_HOSTNAME, STUN_SERVER_PORT }; bool _hasTCPCheckedLocalSocket { false }; + bool _useAuthentication { true }; PacketReceiver* _packetReceiver; diff --git a/libraries/networking/src/NetworkingConstants.h b/libraries/networking/src/NetworkingConstants.h index 8eb1e71ed6..31ff6da873 100644 --- a/libraries/networking/src/NetworkingConstants.h +++ b/libraries/networking/src/NetworkingConstants.h @@ -30,6 +30,7 @@ namespace NetworkingConstants { QUrl METAVERSE_SERVER_URL(); } +const QString URL_SCHEME_ABOUT = "about"; const QString URL_SCHEME_HIFI = "hifi"; const QString URL_SCHEME_QRC = "qrc"; const QString URL_SCHEME_FILE = "file"; diff --git a/libraries/networking/src/NodeData.cpp b/libraries/networking/src/NodeData.cpp index 300c76ca28..d22b970154 100644 --- a/libraries/networking/src/NodeData.cpp +++ b/libraries/networking/src/NodeData.cpp @@ -11,13 +11,10 @@ #include "NodeData.h" -NodeData::NodeData(const QUuid& nodeID) : +NodeData::NodeData(const QUuid& nodeID, NetworkPeer::LocalID nodeLocalID) : _mutex(), - _nodeID(nodeID) + _nodeID(nodeID), + _nodeLocalID(nodeLocalID) { } - -NodeData::~NodeData() { - -} diff --git a/libraries/networking/src/NodeData.h b/libraries/networking/src/NodeData.h index 9aeac03837..b4cb87d0c2 100644 --- a/libraries/networking/src/NodeData.h +++ b/libraries/networking/src/NodeData.h @@ -16,6 +16,7 @@ #include #include +#include "NetworkPeer.h" #include "NLPacket.h" #include "ReceivedMessage.h" @@ -24,17 +25,19 @@ class Node; class NodeData : public QObject { Q_OBJECT public: - NodeData(const QUuid& nodeID = QUuid()); - virtual ~NodeData() = 0; + NodeData(const QUuid& nodeID = QUuid(), NetworkPeer::LocalID localID = NetworkPeer::NULL_LOCAL_ID); + virtual ~NodeData() = default; virtual int parseData(ReceivedMessage& message) { return 0; } const QUuid& getNodeID() const { return _nodeID; } + NetworkPeer::LocalID getNodeLocalID() const { return _nodeLocalID; } QMutex& getMutex() { return _mutex; } private: QMutex _mutex; QUuid _nodeID; + NetworkPeer::LocalID _nodeLocalID; }; #endif // hifi_NodeData_h diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index 2ce734dd26..e458ffab7e 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -273,6 +273,7 @@ void NodeList::reset(bool skipDomainHandlerReset) { // refresh the owner UUID to the NULL UUID setSessionUUID(QUuid()); + setSessionLocalID(Node::NULL_LOCAL_ID); // if we setup the DTLS socket, also disconnect from the DTLS socket readyRead() so it can handle handshaking if (_dtlsSocket) { @@ -647,6 +648,23 @@ void NodeList::processDomainServerList(QSharedPointer message) Node::LocalID newLocalID; packetStream >> newUUID; packetStream >> newLocalID; + + // when connected, if the session ID or local ID were not null and changed, we should reset + auto currentLocalID = getSessionLocalID(); + auto currentSessionID = getSessionUUID(); + if (_domainHandler.isConnected() && + ((currentLocalID != Node::NULL_LOCAL_ID && newLocalID != currentLocalID) || + (!currentSessionID.isNull() && newUUID != currentSessionID))) { + qCDebug(networking) << "Local ID or Session ID changed while connected to domain - forcing NodeList reset"; + + // reset the nodelist, but don't do a domain handler reset since we're about to process a good domain list + reset(true); + + // tell the domain handler that we're no longer connected so that below + // it can re-perform actions as if we just connected + _domainHandler.setIsConnected(false); + } + setSessionLocalID(newLocalID); setSessionUUID(newUUID); @@ -665,6 +683,10 @@ void NodeList::processDomainServerList(QSharedPointer message) NodePermissions newPermissions; packetStream >> newPermissions; setPermissions(newPermissions); + // Is packet authentication enabled? + bool isAuthenticated; + packetStream >> isAuthenticated; + setAuthenticatePackets(isAuthenticated); // pull each node in the packet while (packetStream.device()->pos() < message->getSize()) { diff --git a/libraries/networking/src/NodeList.h b/libraries/networking/src/NodeList.h index 78d3fad696..e135bc937d 100644 --- a/libraries/networking/src/NodeList.h +++ b/libraries/networking/src/NodeList.h @@ -185,7 +185,7 @@ private: #if defined(Q_OS_ANDROID) Setting::Handle _ignoreRadiusEnabled { "IgnoreRadiusEnabled", false }; #else - Setting::Handle _ignoreRadiusEnabled { "IgnoreRadiusEnabled", true }; + Setting::Handle _ignoreRadiusEnabled { "IgnoreRadiusEnabled", false }; // False, until such time as it is made to work better. #endif #if (PR_BUILD || DEV_BUILD) diff --git a/libraries/networking/src/ResourceCache.cpp b/libraries/networking/src/ResourceCache.cpp index d07420f87e..aed9f3b0e5 100644 --- a/libraries/networking/src/ResourceCache.cpp +++ b/libraries/networking/src/ResourceCache.cpp @@ -131,6 +131,24 @@ QSharedPointer ResourceCacheSharedItems::getHighestPendingRequest() { return highestResource; } + +ScriptableResourceCache::ScriptableResourceCache(QSharedPointer resourceCache) { + _resourceCache = resourceCache; +} + +QVariantList ScriptableResourceCache::getResourceList() { + return _resourceCache->getResourceList(); +} + +void ScriptableResourceCache::updateTotalSize(const qint64& deltaSize) { + _resourceCache->updateTotalSize(deltaSize); +} + +ScriptableResource* ScriptableResourceCache::prefetch(const QUrl& url, void* extra) { + return _resourceCache->prefetch(url, extra); +} + + ScriptableResource::ScriptableResource(const QUrl& url) : QObject(nullptr), _url(url) { } diff --git a/libraries/networking/src/ResourceCache.h b/libraries/networking/src/ResourceCache.h index a4bd352563..2c0baad3f7 100644 --- a/libraries/networking/src/ResourceCache.h +++ b/libraries/networking/src/ResourceCache.h @@ -124,9 +124,9 @@ public: virtual ~ScriptableResource() = default; /**jsdoc - * Release this resource. - * @function ResourceObject#release - */ + * Release this resource. + * @function ResourceObject#release + */ Q_INVOKABLE void release(); const QUrl& getURL() const { return _url; } @@ -186,15 +186,6 @@ Q_DECLARE_METATYPE(ScriptableResource*); class ResourceCache : public QObject { Q_OBJECT - // JSDoc 3.5.5 doesn't augment namespaces with @property or @function definitions. - // The ResourceCache properties and functions are copied to the different exposed cache classes. - - /**jsdoc - * @property {number} numTotal - Total number of total resources. Read-only. - * @property {number} numCached - Total number of cached resource. Read-only. - * @property {number} sizeTotal - Size in bytes of all resources. Read-only. - * @property {number} sizeCached - Size in bytes of all cached resources. Read-only. - */ Q_PROPERTY(size_t numTotal READ getNumTotalResources NOTIFY dirty) Q_PROPERTY(size_t numCached READ getNumCachedResources NOTIFY dirty) Q_PROPERTY(size_t sizeTotal READ getSizeTotalResources NOTIFY dirty) @@ -207,11 +198,6 @@ public: size_t getNumCachedResources() const { return _numUnusedResources; } size_t getSizeCachedResources() const { return _unusedResourcesSize; } - /**jsdoc - * Get the list of all resource URLs. - * @function ResourceCache.getResourceList - * @returns {string[]} - */ Q_INVOKABLE QVariantList getResourceList(); static void setRequestLimit(int limit); @@ -237,40 +223,17 @@ public: signals: - /**jsdoc - * @function ResourceCache.dirty - * @returns {Signal} - */ void dirty(); protected slots: - /**jsdoc - * @function ResourceCache.updateTotalSize - * @param {number} deltaSize - */ void updateTotalSize(const qint64& deltaSize); - /**jsdoc - * Prefetches a resource. - * @function ResourceCache.prefetch - * @param {string} url - URL of the resource to prefetch. - * @param {object} [extra=null] - * @returns {ResourceObject} - */ // Prefetches a resource to be held by the QScriptEngine. // Left as a protected member so subclasses can overload prefetch // and delegate to it (see TextureCache::prefetch(const QUrl&, int). ScriptableResource* prefetch(const QUrl& url, void* extra); - /**jsdoc - * Asynchronously loads a resource from the specified URL and returns it. - * @function ResourceCache.getResource - * @param {string} url - URL of the resource to load. - * @param {string} [fallback=""] - Fallback URL if load of the desired URL fails. - * @param {} [extra=null] - * @returns {object} - */ // FIXME: The return type is not recognized by JavaScript. /// Loads a resource from the specified URL and returns it. /// If the caller is on a different thread than the ResourceCache, @@ -306,6 +269,7 @@ protected: private: friend class Resource; + friend class ScriptableResourceCache; void reserveUnusedResource(qint64 resourceSize); void resetResourceCounters(); @@ -335,6 +299,66 @@ private: QReadWriteLock _resourcesToBeGottenLock { QReadWriteLock::Recursive }; }; +/// Wrapper to expose resource caches to JS/QML +class ScriptableResourceCache : public QObject { + Q_OBJECT + + // JSDoc 3.5.5 doesn't augment name spaces with @property definitions so the following properties JSDoc is copied to the + // different exposed cache classes. + + /**jsdoc + * @property {number} numTotal - Total number of total resources. Read-only. + * @property {number} numCached - Total number of cached resource. Read-only. + * @property {number} sizeTotal - Size in bytes of all resources. Read-only. + * @property {number} sizeCached - Size in bytes of all cached resources. Read-only. + */ + Q_PROPERTY(size_t numTotal READ getNumTotalResources NOTIFY dirty) + Q_PROPERTY(size_t numCached READ getNumCachedResources NOTIFY dirty) + Q_PROPERTY(size_t sizeTotal READ getSizeTotalResources NOTIFY dirty) + Q_PROPERTY(size_t sizeCached READ getSizeCachedResources NOTIFY dirty) + +public: + ScriptableResourceCache(QSharedPointer resourceCache); + + /**jsdoc + * Get the list of all resource URLs. + * @function ResourceCache.getResourceList + * @returns {string[]} + */ + Q_INVOKABLE QVariantList getResourceList(); + + /**jsdoc + * @function ResourceCache.updateTotalSize + * @param {number} deltaSize + */ + Q_INVOKABLE void updateTotalSize(const qint64& deltaSize); + + /**jsdoc + * Prefetches a resource. + * @function ResourceCache.prefetch + * @param {string} url - URL of the resource to prefetch. + * @param {object} [extra=null] + * @returns {ResourceObject} + */ + Q_INVOKABLE ScriptableResource* prefetch(const QUrl& url, void* extra = nullptr); + +signals: + + /**jsdoc + * @function ResourceCache.dirty + * @returns {Signal} + */ + void dirty(); + +private: + QSharedPointer _resourceCache; + + size_t getNumTotalResources() const { return _resourceCache->getNumTotalResources(); } + size_t getSizeTotalResources() const { return _resourceCache->getSizeTotalResources(); } + size_t getNumCachedResources() const { return _resourceCache->getNumCachedResources(); } + size_t getSizeCachedResources() const { return _resourceCache->getSizeCachedResources(); } +}; + /// Base class for resources. class Resource : public QObject { Q_OBJECT diff --git a/libraries/networking/src/ThreadedAssignment.cpp b/libraries/networking/src/ThreadedAssignment.cpp index 9a69d9b3d8..13d4e0bf8b 100644 --- a/libraries/networking/src/ThreadedAssignment.cpp +++ b/libraries/networking/src/ThreadedAssignment.cpp @@ -120,7 +120,7 @@ void ThreadedAssignment::checkInWithDomainServerOrExit() { if (_numQueuedCheckIns >= MAX_SILENT_DOMAIN_SERVER_CHECK_INS) { qCDebug(networking) << "At least" << MAX_SILENT_DOMAIN_SERVER_CHECK_INS << "have been queued without a response from domain-server" << "Stopping the current assignment"; - setFinished(true); + stop(); } else { auto nodeList = DependencyManager::get(); QMetaObject::invokeMethod(nodeList.data(), "sendDomainServerCheckIn"); @@ -132,5 +132,5 @@ void ThreadedAssignment::checkInWithDomainServerOrExit() { void ThreadedAssignment::domainSettingsRequestFailed() { qCDebug(networking) << "Failed to retreive settings object from domain-server. Bailing on assignment."; - setFinished(true); + stop(); } diff --git a/libraries/networking/src/ThreadedAssignment.h b/libraries/networking/src/ThreadedAssignment.h index 9372cfa667..e76533b2a1 100644 --- a/libraries/networking/src/ThreadedAssignment.h +++ b/libraries/networking/src/ThreadedAssignment.h @@ -24,43 +24,25 @@ public: ThreadedAssignment(ReceivedMessage& message); ~ThreadedAssignment() { stop(); } - void setFinished(bool isFinished); virtual void aboutToFinish() { }; void addPacketStatsAndSendStatsPacket(QJsonObject statsObject); public slots: - // JSDoc: Overridden in Agent.h. /// threaded run of assignment virtual void run() = 0; - /**jsdoc - * @function Agent.stop - * @deprecated This function is being removed from the API. - */ Q_INVOKABLE virtual void stop() { setFinished(true); } - /**jsdoc - * @function Agent.sendStatsPacket - * @deprecated This function is being removed from the API. - */ virtual void sendStatsPacket(); - /**jsdoc - * @function Agent.clearQueuedCheckIns - * @deprecated This function is being removed from the API. - */ void clearQueuedCheckIns() { _numQueuedCheckIns = 0; } signals: - /**jsdoc - * @function Agent.finished - * @returns {Signal} - * @deprecated This function is being removed from the API. - */ void finished(); protected: void commonInit(const QString& targetName, NodeType_t nodeType); + void setFinished(bool isFinished); bool _isFinished; QTimer _domainServerTimer; @@ -68,10 +50,6 @@ protected: int _numQueuedCheckIns { 0 }; protected slots: - /**jsdoc - * @function Agent.domainSettingsRequestFailed - * @deprecated This function is being removed from the API. - */ void domainSettingsRequestFailed(); private slots: diff --git a/libraries/networking/src/udt/BasePacket.cpp b/libraries/networking/src/udt/BasePacket.cpp index 0540e60a0e..12a174b7d3 100644 --- a/libraries/networking/src/udt/BasePacket.cpp +++ b/libraries/networking/src/udt/BasePacket.cpp @@ -77,12 +77,6 @@ BasePacket::BasePacket(std::unique_ptr data, qint64 size, const HifiSock } -BasePacket::BasePacket(const BasePacket& other) : - QIODevice() -{ - *this = other; -} - BasePacket& BasePacket::operator=(const BasePacket& other) { _packetSize = other._packetSize; _packet = std::unique_ptr(new char[_packetSize]); diff --git a/libraries/networking/src/udt/BasePacket.h b/libraries/networking/src/udt/BasePacket.h index d9b624b595..4981cb4720 100644 --- a/libraries/networking/src/udt/BasePacket.h +++ b/libraries/networking/src/udt/BasePacket.h @@ -16,16 +16,15 @@ #include -#include - #include #include "../HifiSockAddr.h" #include "Constants.h" +#include "../ExtendedIODevice.h" namespace udt { -class BasePacket : public QIODevice { +class BasePacket : public ExtendedIODevice { Q_OBJECT public: static const qint64 PACKET_WRITE_ERROR; @@ -85,15 +84,11 @@ public: void setReceiveTime(p_high_resolution_clock::time_point receiveTime) { _receiveTime = receiveTime; } p_high_resolution_clock::time_point getReceiveTime() const { return _receiveTime; } - - template qint64 peekPrimitive(T* data); - template qint64 readPrimitive(T* data); - template qint64 writePrimitive(const T& data); protected: BasePacket(qint64 size); BasePacket(std::unique_ptr data, qint64 size, const HifiSockAddr& senderSockAddr); - BasePacket(const BasePacket& other); + BasePacket(const BasePacket& other) : ExtendedIODevice() { *this = other; } BasePacket& operator=(const BasePacket& other); BasePacket(BasePacket&& other); BasePacket& operator=(BasePacket&& other); @@ -116,19 +111,6 @@ protected: p_high_resolution_clock::time_point _receiveTime; // captures the time the packet received (only used on receiving end) }; - -template qint64 BasePacket::peekPrimitive(T* data) { - return peek(reinterpret_cast(data), sizeof(T)); -} - -template qint64 BasePacket::readPrimitive(T* data) { - return read(reinterpret_cast(data), sizeof(T)); -} - -template qint64 BasePacket::writePrimitive(const T& data) { - static_assert(!std::is_pointer::value, "T must not be a pointer"); - return write(reinterpret_cast(&data), sizeof(T)); -} } // namespace udt diff --git a/libraries/networking/src/udt/CongestionControl.cpp b/libraries/networking/src/udt/CongestionControl.cpp index 7ade4f004f..c0ad89e804 100644 --- a/libraries/networking/src/udt/CongestionControl.cpp +++ b/libraries/networking/src/udt/CongestionControl.cpp @@ -39,191 +39,3 @@ void CongestionControl::setPacketSendPeriod(double newSendPeriod) { _packetSendPeriod = newSendPeriod; } } - -DefaultCC::DefaultCC() : - _lastDecreaseMaxSeq(SequenceNumber {SequenceNumber::MAX }) -{ - _mss = udt::MAX_PACKET_SIZE_WITH_UDP_HEADER; - - _congestionWindowSize = 16; - setPacketSendPeriod(1.0); -} - -bool DefaultCC::onACK(SequenceNumber ackNum, p_high_resolution_clock::time_point receiveTime) { - double increase = 0; - - // Note from UDT original code: - // The minimum increase parameter is increased from "1.0 / _mss" to 0.01 - // because the original was too small and caused sending rate to stay at low level - // for long time. - const double minimumIncrease = 0.01; - - // we will only adjust once per sync interval so check that it has been at least that long now - auto now = p_high_resolution_clock::now(); - if (duration_cast(now - _lastRCTime).count() < synInterval()) { - return false; - } - - // our last rate increase time is now - _lastRCTime = now; - - if (_slowStart) { - // we are in slow start phase - increase the congestion window size by the number of packets just ACKed - _congestionWindowSize += seqlen(_lastACK, ackNum); - - // update the last ACK - _lastACK = ackNum; - - // check if we can get out of slow start (is our new congestion window size bigger than the max) - if (_congestionWindowSize > _maxCongestionWindowSize) { - _slowStart = false; - - if (_receiveRate > 0) { - // if we have a valid receive rate we set the send period to whatever the receive rate dictates - setPacketSendPeriod(USECS_PER_SECOND / _receiveRate); - } else { - // no valid receive rate, packet send period is dictated by estimated RTT and current congestion window size - setPacketSendPeriod((_rtt + synInterval()) / _congestionWindowSize); - } - } - } else { - // not in slow start - window size should be arrival rate * (RTT + SYN) + 16 - _congestionWindowSize = _receiveRate / USECS_PER_SECOND * (_rtt + synInterval()) + 16; - } - - // during slow start we perform no rate increases - if (_slowStart) { - return false; - } - - // if loss has happened since the last rate increase we do not perform another increase - if (_loss) { - _loss = false; - return false; - } - - double capacitySpeedDelta = (_bandwidth - USECS_PER_SECOND / _packetSendPeriod); - - // UDT uses what they call DAIMD - additive increase multiplicative decrease with decreasing increases - // This factor is a protocol parameter that is part of the DAIMD algorithim - static const int AIMD_DECREASING_INCREASE_FACTOR = 9; - - if ((_packetSendPeriod > _lastDecreasePeriod) && ((_bandwidth / AIMD_DECREASING_INCREASE_FACTOR) < capacitySpeedDelta)) { - capacitySpeedDelta = _bandwidth / AIMD_DECREASING_INCREASE_FACTOR; - } - - if (capacitySpeedDelta <= 0) { - increase = minimumIncrease; - } else { - // use UDTs DAIMD algorithm to figure out what the send period increase factor should be - - // inc = max(10 ^ ceil(log10(B * MSS * 8 ) * Beta / MSS, minimumIncrease) - // B = estimated link capacity - // Beta = 1.5 * 10^(-6) - - static const double BETA = 0.0000015; - static const double BITS_PER_BYTE = 8.0; - - increase = pow(10.0, ceil(log10(capacitySpeedDelta * _mss * BITS_PER_BYTE))) * BETA / _mss; - - if (increase < minimumIncrease) { - increase = minimumIncrease; - } - } - - setPacketSendPeriod((_packetSendPeriod * synInterval()) / (_packetSendPeriod * increase + synInterval())); - - return false; -} - -void DefaultCC::onLoss(SequenceNumber rangeStart, SequenceNumber rangeEnd) { - // stop the slow start if we haven't yet - if (_slowStart) { - stopSlowStart(); - - // if the change to send rate was driven by a known receive rate, then we don't continue with the decrease - if (_receiveRate > 0) { - return; - } - } - - _loss = true; - ++_nakCount; - - static const double INTER_PACKET_ARRIVAL_INCREASE = 1.125; - static const int MAX_DECREASES_PER_CONGESTION_EPOCH = 5; - - // check if this NAK starts a new congestion period - this will be the case if the - // NAK received occured for a packet sent after the last decrease - if (rangeStart > _lastDecreaseMaxSeq) { - _delayedDecrease = (rangeStart == rangeEnd); - - _lastDecreasePeriod = _packetSendPeriod; - - if (!_delayedDecrease) { - setPacketSendPeriod(ceil(_packetSendPeriod * INTER_PACKET_ARRIVAL_INCREASE)); - } else { - _loss = false; - } - - // use EWMA to update the average number of NAKs per congestion - static const double NAK_EWMA_ALPHA = 0.125; - _avgNAKNum = (int)ceil(_avgNAKNum * (1 - NAK_EWMA_ALPHA) + _nakCount * NAK_EWMA_ALPHA); - - // update the count of NAKs and count of decreases in this interval - _nakCount = 1; - _decreaseCount = 1; - - _lastDecreaseMaxSeq = _sendCurrSeqNum; - - if (_avgNAKNum < 1) { - _randomDecreaseThreshold = 1; - } else { - // avoid synchronous rate decrease across connections using randomization - std::random_device rd; - std::mt19937 generator(rd()); - std::uniform_int_distribution<> distribution(1, std::max(1, _avgNAKNum)); - - _randomDecreaseThreshold = distribution(generator); - } - } else if (_delayedDecrease && _nakCount == 2) { - setPacketSendPeriod(ceil(_packetSendPeriod * INTER_PACKET_ARRIVAL_INCREASE)); - } else if ((_decreaseCount++ < MAX_DECREASES_PER_CONGESTION_EPOCH) && ((_nakCount % _randomDecreaseThreshold) == 0)) { - // there have been fewer than MAX_DECREASES_PER_CONGESTION_EPOCH AND this NAK matches the random count at which we - // decided we would decrease the packet send period - - setPacketSendPeriod(ceil(_packetSendPeriod * INTER_PACKET_ARRIVAL_INCREASE)); - _lastDecreaseMaxSeq = _sendCurrSeqNum; - } -} - -void DefaultCC::onTimeout() { - if (_slowStart) { - stopSlowStart(); - } else { - // UDT used to do the following on timeout if not in slow start - we should check if it could be helpful - _lastDecreasePeriod = _packetSendPeriod; - _packetSendPeriod = ceil(_packetSendPeriod * 2); - - // this seems odd - the last ack they were setting _lastDecreaseMaxSeq to only applies to slow start - _lastDecreaseMaxSeq = _lastACK; - } -} - -void DefaultCC::stopSlowStart() { - _slowStart = false; - - if (_receiveRate > 0) { - // Set the sending rate to the receiving rate. - setPacketSendPeriod(USECS_PER_SECOND / _receiveRate); - } else { - // If no receiving rate is observed, we have to compute the sending - // rate according to the current window size, and decrease it - // using the method below. - setPacketSendPeriod(double(_congestionWindowSize) / (_rtt + synInterval())); - } -} - -void DefaultCC::setInitialSendSequenceNumber(udt::SequenceNumber seqNum) { - _lastACK = _lastDecreaseMaxSeq = seqNum - 1; -} diff --git a/libraries/networking/src/udt/CongestionControl.h b/libraries/networking/src/udt/CongestionControl.h index e6a462651e..7093e8bd96 100644 --- a/libraries/networking/src/udt/CongestionControl.h +++ b/libraries/networking/src/udt/CongestionControl.h @@ -32,11 +32,9 @@ class CongestionControl { friend class Connection; public: - CongestionControl() {}; - CongestionControl(int synInterval) : _synInterval(synInterval) {} - virtual ~CongestionControl() {} - - int synInterval() const { return _synInterval; } + CongestionControl() = default; + virtual ~CongestionControl() = default; + void setMaxBandwidth(int maxBandwidth); virtual void init() {} @@ -44,50 +42,28 @@ public: // return value specifies if connection should perform a fast re-transmit of ACK + 1 (used in TCP style congestion control) virtual bool onACK(SequenceNumber ackNum, p_high_resolution_clock::time_point receiveTime) { return false; } - virtual void onLoss(SequenceNumber rangeStart, SequenceNumber rangeEnd) {} virtual void onTimeout() {} - virtual bool shouldNAK() { return true; } - virtual bool shouldACK2() { return true; } - virtual bool shouldProbe() { return true; } - virtual void onPacketSent(int wireSize, SequenceNumber seqNum, p_high_resolution_clock::time_point timePoint) {} + + virtual int estimatedTimeout() const = 0; protected: - void setAckInterval(int ackInterval) { _ackInterval = ackInterval; } - void setRTO(int rto) { _userDefinedRTO = true; _rto = rto; } - void setMSS(int mss) { _mss = mss; } - void setMaxCongestionWindowSize(int window) { _maxCongestionWindowSize = window; } - void setBandwidth(int bandwidth) { _bandwidth = bandwidth; } virtual void setInitialSendSequenceNumber(SequenceNumber seqNum) = 0; void setSendCurrentSequenceNumber(SequenceNumber seqNum) { _sendCurrSeqNum = seqNum; } - void setReceiveRate(int rate) { _receiveRate = rate; } - void setRTT(int rtt) { _rtt = rtt; } void setPacketSendPeriod(double newSendPeriod); // call this internally to ensure send period doesn't go past max bandwidth double _packetSendPeriod { 1.0 }; // Packet sending period, in microseconds int _congestionWindowSize { 16 }; // Congestion window size, in packets - - int _bandwidth { 0 }; // estimated bandwidth, packets per second + std::atomic _maxBandwidth { -1 }; // Maximum desired bandwidth, bits per second - int _maxCongestionWindowSize { 0 }; // maximum cwnd size, in packets int _mss { 0 }; // Maximum Packet Size, including all packet headers SequenceNumber _sendCurrSeqNum; // current maximum seq num sent out - int _receiveRate { 0 }; // packet arrive rate at receiver side, packets per second - int _rtt { 0 }; // current estimated RTT, microsecond private: CongestionControl(const CongestionControl& other) = delete; CongestionControl& operator=(const CongestionControl& other) = delete; - - int _ackInterval { 0 }; // How many packets to send one ACK, in packets - int _lightACKInterval { 64 }; // How many packets to send one light ACK, in packets - - int _synInterval { DEFAULT_SYN_INTERVAL }; - - bool _userDefinedRTO { false }; // if the RTO value is defined by users - int _rto { -1 }; // RTO value, microseconds }; @@ -95,8 +71,6 @@ class CongestionControlVirtualFactory { public: virtual ~CongestionControlVirtualFactory() {} - static int synInterval() { return DEFAULT_SYN_INTERVAL; } - virtual std::unique_ptr create() = 0; }; @@ -105,35 +79,6 @@ public: virtual ~CongestionControlFactory() {} virtual std::unique_ptr create() override { return std::unique_ptr(new T()); } }; - -class DefaultCC: public CongestionControl { -public: - DefaultCC(); - -public: - virtual bool onACK(SequenceNumber ackNum, p_high_resolution_clock::time_point receiveTime) override; - virtual void onLoss(SequenceNumber rangeStart, SequenceNumber rangeEnd) override; - virtual void onTimeout() override; - -protected: - virtual void setInitialSendSequenceNumber(SequenceNumber seqNum) override; - -private: - void stopSlowStart(); // stops the slow start on loss or timeout - - p_high_resolution_clock::time_point _lastRCTime = p_high_resolution_clock::now(); // last rate increase time - - bool _slowStart { true }; // if in slow start phase - SequenceNumber _lastACK; // last ACKed sequence number from previous - bool _loss { false }; // if loss happened since last rate increase - SequenceNumber _lastDecreaseMaxSeq; // max pkt seq num sent out when last decrease happened - double _lastDecreasePeriod { 1 }; // value of _packetSendPeriod when last decrease happened - int _nakCount { 0 }; // number of NAKs in congestion epoch - int _randomDecreaseThreshold { 1 }; // random threshold on decrease by number of loss events - int _avgNAKNum { 0 }; // average number of NAKs per congestion - int _decreaseCount { 0 }; // number of decreases in a congestion epoch - bool _delayedDecrease { false }; -}; } diff --git a/libraries/networking/src/udt/Connection.cpp b/libraries/networking/src/udt/Connection.cpp index 0bc86a28ad..24e294881a 100644 --- a/libraries/networking/src/udt/Connection.cpp +++ b/libraries/networking/src/udt/Connection.cpp @@ -39,28 +39,12 @@ Connection::Connection(Socket* parentSocket, HifiSockAddr destination, std::uniq Q_ASSERT_X(_congestionControl, "Connection::Connection", "Must be called with a valid CongestionControl object"); _congestionControl->init(); - - // setup default SYN, RTT and RTT Variance based on the SYN interval in CongestionControl object - _synInterval = _congestionControl->synInterval(); - - resetRTT(); - - // set the initial RTT and flow window size on congestion control object - _congestionControl->setRTT(_rtt); - _congestionControl->setMaxCongestionWindowSize(_flowWindowSize); // Setup packets - static const int ACK_PACKET_PAYLOAD_BYTES = sizeof(_lastSentACK) + sizeof(_currentACKSubSequenceNumber) - + sizeof(_rtt) + sizeof(int32_t) + sizeof(int32_t) + sizeof(int32_t); - static const int LIGHT_ACK_PACKET_PAYLOAD_BYTES = sizeof(SequenceNumber); - static const int ACK2_PAYLOAD_BYTES = sizeof(SequenceNumber); - static const int NAK_PACKET_PAYLOAD_BYTES = 2 * sizeof(SequenceNumber); + static const int ACK_PACKET_PAYLOAD_BYTES = sizeof(SequenceNumber); static const int HANDSHAKE_ACK_PAYLOAD_BYTES = sizeof(SequenceNumber); _ackPacket = ControlPacket::create(ControlPacket::ACK, ACK_PACKET_PAYLOAD_BYTES); - _lightACKPacket = ControlPacket::create(ControlPacket::LightACK, LIGHT_ACK_PACKET_PAYLOAD_BYTES); - _ack2Packet = ControlPacket::create(ControlPacket::ACK2, ACK2_PAYLOAD_BYTES); - _lossReport = ControlPacket::create(ControlPacket::NAK, NAK_PACKET_PAYLOAD_BYTES); _handshakeACK = ControlPacket::create(ControlPacket::HandshakeACK, HANDSHAKE_ACK_PAYLOAD_BYTES); @@ -101,11 +85,6 @@ void Connection::stopSendQueue() { } } -void Connection::resetRTT() { - _rtt = _synInterval * 10; - _rttVariance = _rtt / 2; -} - void Connection::setMaxBandwidth(int maxBandwidth) { _congestionControl->setMaxBandwidth(maxBandwidth); } @@ -135,15 +114,12 @@ SendQueue& Connection::getSendQueue() { QObject::connect(_sendQueue.get(), &SendQueue::packetRetransmitted, this, &Connection::recordRetransmission); QObject::connect(_sendQueue.get(), &SendQueue::queueInactive, this, &Connection::queueInactive); QObject::connect(_sendQueue.get(), &SendQueue::timeout, this, &Connection::queueTimeout); - QObject::connect(_sendQueue.get(), &SendQueue::shortCircuitLoss, this, &Connection::queueShortCircuitLoss); // set defaults on the send queue from our congestion control object and estimatedTimeout() _sendQueue->setPacketSendPeriod(_congestionControl->_packetSendPeriod); - _sendQueue->setSyncInterval(_synInterval); - _sendQueue->setEstimatedTimeout(estimatedTimeout()); - _sendQueue->setFlowWindowSize(std::min(_flowWindowSize, (int) _congestionControl->_congestionWindowSize)); - _sendQueue->setProbePacketEnabled(_congestionControl->shouldProbe()); + _sendQueue->setEstimatedTimeout(_congestionControl->estimatedTimeout()); + _sendQueue->setFlowWindowSize(_congestionControl->_congestionWindowSize); // give the randomized sequence number to the congestion control object _congestionControl->setInitialSendSequenceNumber(_sendQueue->getCurrentSequenceNumber()); @@ -167,12 +143,6 @@ void Connection::queueTimeout() { }); } -void Connection::queueShortCircuitLoss(quint32 sequenceNumber) { - updateCongestionControlAndSendQueue([this, sequenceNumber] { - _congestionControl->onLoss(SequenceNumber { sequenceNumber }, SequenceNumber { sequenceNumber }); - }); -} - void Connection::sendReliablePacket(std::unique_ptr packet) { Q_ASSERT_X(packet->isReliable(), "Connection::send", "Trying to send an unreliable packet reliably."); getSendQueue().queuePacket(std::move(packet)); @@ -213,43 +183,6 @@ void Connection::queueReceivedMessagePacket(std::unique_ptr packet) { } void Connection::sync() { - if (_isReceivingData) { - - // check if we should expire the receive portion of this connection - // this occurs if it has been 16 timeouts since the last data received and at least 5 seconds - static const int NUM_TIMEOUTS_BEFORE_EXPIRY = 16; - static const int MIN_SECONDS_BEFORE_EXPIRY = 5; - - auto now = p_high_resolution_clock::now(); - - auto sincePacketReceive = now - _lastReceiveTime; - - if (duration_cast(sincePacketReceive).count() >= NUM_TIMEOUTS_BEFORE_EXPIRY * estimatedTimeout() - && duration_cast(sincePacketReceive).count() >= MIN_SECONDS_BEFORE_EXPIRY ) { - // the receive side of this connection is expired - _isReceivingData = false; - } - - // reset the number of light ACKs or non SYN ACKs during this sync interval - _lightACKsDuringSYN = 1; - _acksDuringSYN = 1; - - if (_congestionControl->_ackInterval > 1) { - // we send out a periodic ACK every rate control interval - sendACK(); - } - - if (_congestionControl->shouldNAK() && _lossList.getLength() > 0) { - // check if we need to re-transmit a loss list - // we do this if it has been longer than the current nakInterval since we last sent - auto now = p_high_resolution_clock::now(); - - if (duration_cast(now - _lastNAKTime).count() >= _nakInterval) { - // Send a timeout NAK packet - sendTimeoutNAK(); - } - } - } } void Connection::recordSentPackets(int wireSize, int payloadSize, @@ -265,159 +198,23 @@ void Connection::recordRetransmission(int wireSize, SequenceNumber seqNum, p_hig _congestionControl->onPacketSent(wireSize, seqNum, timePoint); } -void Connection::sendACK(bool wasCausedBySyncTimeout) { - static p_high_resolution_clock::time_point lastACKSendTime; - auto currentTime = p_high_resolution_clock::now(); - +void Connection::sendACK() { SequenceNumber nextACKNumber = nextACK(); - Q_ASSERT_X(nextACKNumber >= _lastSentACK, "Connection::sendACK", "Sending lower ACK, something is wrong"); - // if our congestion control doesn't want to send an ACK for every packet received - // check if we already sent this ACK - if (_congestionControl->_ackInterval > 1 && nextACKNumber == _lastSentACK) { - - // if we use ACK2s, check if the receiving side already confirmed receipt of this ACK - if (_congestionControl->shouldACK2() && nextACKNumber < _lastReceivedAcknowledgedACK) { - // we already got an ACK2 for this ACK we would be sending, don't bother - return; - } - - // We will re-send if it has been more than the estimated timeout since the last ACK - microseconds sinceLastACK = duration_cast(currentTime - lastACKSendTime); - - if (sinceLastACK.count() < estimatedTimeout()) { - return; - } - } // we have received new packets since the last sent ACK // or our congestion control dictates that we always send ACKs - - // update the last sent ACK - _lastSentACK = nextACKNumber; _ackPacket->reset(); // We need to reset it every time. - - // pack in the ACK sub-sequence number - _ackPacket->writePrimitive(++_currentACKSubSequenceNumber); - + // pack in the ACK number _ackPacket->writePrimitive(nextACKNumber); - - // pack in the RTT and variance - _ackPacket->writePrimitive(_rtt); - - // pack the available buffer size, in packets - // in our implementation we have no hard limit on receive buffer size, send the default value - _ackPacket->writePrimitive((int32_t) udt::MAX_PACKETS_IN_FLIGHT); - if (wasCausedBySyncTimeout) { - // grab the up to date packet receive speed and estimated bandwidth - int32_t packetReceiveSpeed = _receiveWindow.getPacketReceiveSpeed(); - int32_t estimatedBandwidth = _receiveWindow.getEstimatedBandwidth(); - - // update those values in our connection stats - _stats.recordReceiveRate(packetReceiveSpeed); - _stats.recordEstimatedBandwidth(estimatedBandwidth); - - // pack in the receive speed and estimatedBandwidth - _ackPacket->writePrimitive(packetReceiveSpeed); - _ackPacket->writePrimitive(estimatedBandwidth); - } - - // record this as the last ACK send time - lastACKSendTime = p_high_resolution_clock::now(); - // have the socket send off our packet _parentSocket->writeBasePacket(*_ackPacket, _destination); - Q_ASSERT_X(_sentACKs.empty() || _sentACKs.back().first + 1 == _currentACKSubSequenceNumber, - "Connection::sendACK", "Adding an invalid ACK to _sentACKs"); - - // write this ACK to the map of sent ACKs - _sentACKs.push_back({ _currentACKSubSequenceNumber, { nextACKNumber, p_high_resolution_clock::now() }}); - - // reset the number of data packets received since last ACK - _packetsSinceACK = 0; - _stats.record(ConnectionStats::Stats::SentACK); } -void Connection::sendLightACK() { - SequenceNumber nextACKNumber = nextACK(); - - if (nextACKNumber == _lastReceivedAcknowledgedACK) { - // we already got an ACK2 for this ACK we would be sending, don't bother - return; - } - - // reset the lightACKPacket before we go to write the ACK to it - _lightACKPacket->reset(); - - // pack in the ACK - _lightACKPacket->writePrimitive(nextACKNumber); - - // have the socket send off our packet immediately - _parentSocket->writeBasePacket(*_lightACKPacket, _destination); - - _stats.record(ConnectionStats::Stats::SentLightACK); -} - -void Connection::sendACK2(SequenceNumber currentACKSubSequenceNumber) { - // reset the ACK2 Packet before writing the sub-sequence number to it - _ack2Packet->reset(); - - // write the sub sequence number for this ACK2 - _ack2Packet->writePrimitive(currentACKSubSequenceNumber); - - // send the ACK2 packet - _parentSocket->writeBasePacket(*_ack2Packet, _destination); - - // update the last sent ACK2 and the last ACK2 send time - _lastSentACK2 = currentACKSubSequenceNumber; - - _stats.record(ConnectionStats::Stats::SentACK2); -} - -void Connection::sendNAK(SequenceNumber sequenceNumberRecieved) { - _lossReport->reset(); // We need to reset it every time. - - // pack in the loss report - _lossReport->writePrimitive(_lastReceivedSequenceNumber + 1); - if (_lastReceivedSequenceNumber + 1 != sequenceNumberRecieved - 1) { - _lossReport->writePrimitive(sequenceNumberRecieved - 1); - } - - // have the parent socket send off our packet immediately - _parentSocket->writeBasePacket(*_lossReport, _destination); - - // record our last NAK time - _lastNAKTime = p_high_resolution_clock::now(); - - _stats.record(ConnectionStats::Stats::SentNAK); -} - -void Connection::sendTimeoutNAK() { - if (_lossList.getLength() > 0) { - - int timeoutPayloadSize = std::min((int) (_lossList.getLength() * 2 * sizeof(SequenceNumber)), - ControlPacket::maxPayloadSize()); - - // construct a NAK packet that will hold all of the lost sequence numbers - auto lossListPacket = ControlPacket::create(ControlPacket::TimeoutNAK, timeoutPayloadSize); - - // Pack in the lost sequence numbers - _lossList.write(*lossListPacket, timeoutPayloadSize / (2 * sizeof(SequenceNumber))); - - // have our parent socket send off this control packet - _parentSocket->writeBasePacket(*lossListPacket, _destination); - - // record this as the last NAK time - _lastNAKTime = p_high_resolution_clock::now(); - - _stats.record(ConnectionStats::Stats::SentTimeoutNAK); - } -} - SequenceNumber Connection::nextACK() const { if (_lossList.getLength() > 0) { return _lossList.getFirstSequenceNumber() - 1; @@ -447,27 +244,8 @@ bool Connection::processReceivedSequenceNumber(SequenceNumber sequenceNumber, in return false; } - _isReceivingData = true; - // mark our last receive time as now (to push the potential expiry farther) _lastReceiveTime = p_high_resolution_clock::now(); - - if (_congestionControl->shouldProbe()) { - // check if this is a packet pair we should estimate bandwidth from, or just a regular packet - if (((uint32_t) sequenceNumber & 0xF) == 0) { - _receiveWindow.onProbePair1Arrival(); - } else if (((uint32_t) sequenceNumber & 0xF) == 1) { - // only use this packet for bandwidth estimation if we didn't just receive a control packet in its place - if (!_receivedControlProbeTail) { - _receiveWindow.onProbePair2Arrival(); - } else { - // reset our control probe tail marker so the next probe that comes with data can be used - _receivedControlProbeTail = false; - } - } - } - - _receiveWindow.onPacketArrival(); // If this is not the next sequence number, report loss if (sequenceNumber > _lastReceivedSequenceNumber + 1) { @@ -476,24 +254,6 @@ bool Connection::processReceivedSequenceNumber(SequenceNumber sequenceNumber, in } else { _lossList.append(_lastReceivedSequenceNumber + 1, sequenceNumber - 1); } - - if (_congestionControl->shouldNAK()) { - // Send a NAK packet - sendNAK(sequenceNumber); - - // figure out when we should send the next loss report, if we haven't heard anything back - _nakInterval = estimatedTimeout(); - - int receivedPacketsPerSecond = _receiveWindow.getPacketReceiveSpeed(); - if (receivedPacketsPerSecond > 0) { - // the NAK interval is at least the _minNAKInterval - // but might be the time required for all lost packets to be retransmitted - _nakInterval += (int) (_lossList.getLength() * (USECS_PER_SECOND / receivedPacketsPerSecond)); - } - - // the NAK interval is at least the _minNAKInterval but might be the value calculated above, if that is larger - _nakInterval = std::max(_nakInterval, _minNAKInterval); - } } bool wasDuplicate = false; @@ -505,22 +265,9 @@ bool Connection::processReceivedSequenceNumber(SequenceNumber sequenceNumber, in // Otherwise, it could be a resend, try and remove it from the loss list wasDuplicate = !_lossList.remove(sequenceNumber); } - - // increment the counters for data packets received - ++_packetsSinceACK; - - // check if we need to send an ACK, according to CC params - if (_congestionControl->_ackInterval == 1) { - // using a congestion control that ACKs every packet (like TCP Vegas) - sendACK(true); - } else if (_congestionControl->_ackInterval > 0 && _packetsSinceACK >= _congestionControl->_ackInterval * _acksDuringSYN) { - _acksDuringSYN++; - sendACK(false); - } else if (_congestionControl->_lightACKInterval > 0 - && _packetsSinceACK >= _congestionControl->_lightACKInterval * _lightACKsDuringSYN) { - sendLightACK(); - ++_lightACKsDuringSYN; - } + + // using a congestion control that ACKs every packet (like TCP Vegas) + sendACK(); if (wasDuplicate) { _stats.record(ConnectionStats::Stats::Duplicate); @@ -544,37 +291,12 @@ void Connection::processControl(ControlPacketPointer controlPacket) { processACK(move(controlPacket)); } break; - case ControlPacket::LightACK: - if (_hasReceivedHandshakeACK) { - processLightACK(move(controlPacket)); - } - break; - case ControlPacket::ACK2: - if (_hasReceivedHandshake) { - processACK2(move(controlPacket)); - } - break; - case ControlPacket::NAK: - if (_hasReceivedHandshakeACK) { - processNAK(move(controlPacket)); - } - break; - case ControlPacket::TimeoutNAK: - if (_hasReceivedHandshakeACK) { - processTimeoutNAK(move(controlPacket)); - } - break; case ControlPacket::Handshake: processHandshake(move(controlPacket)); break; case ControlPacket::HandshakeACK: processHandshakeACK(move(controlPacket)); break; - case ControlPacket::ProbeTail: - if (_isReceivingData) { - processProbeTail(move(controlPacket)); - } - break; case ControlPacket::HandshakeRequest: if (_hasReceivedHandshakeACK) { // We're already in a state where we've received a handshake ack, so we are likely in a state @@ -591,27 +313,6 @@ void Connection::processControl(ControlPacketPointer controlPacket) { } void Connection::processACK(ControlPacketPointer controlPacket) { - // read the ACK sub-sequence number - SequenceNumber currentACKSubSequenceNumber; - controlPacket->readPrimitive(¤tACKSubSequenceNumber); - - // Check if we need send an ACK2 for this ACK - // This will be the case if it has been longer than the sync interval OR - // it looks like they haven't received our ACK2 for this ACK - auto currentTime = p_high_resolution_clock::now(); - static p_high_resolution_clock::time_point lastACK2SendTime = - p_high_resolution_clock::now() - std::chrono::microseconds(_synInterval); - - microseconds sinceLastACK2 = duration_cast(currentTime - lastACK2SendTime); - - if (_congestionControl->shouldACK2() - && (sinceLastACK2.count() >= _synInterval || currentACKSubSequenceNumber == _lastSentACK2)) { - // Send ACK2 packet - sendACK2(currentACKSubSequenceNumber); - - lastACK2SendTime = p_high_resolution_clock::now(); - } - // read the ACKed sequence number SequenceNumber ack; controlPacket->readPrimitive(&ack); @@ -626,22 +327,9 @@ void Connection::processACK(ControlPacketPointer controlPacket) { return; } - // read the RTT - int32_t rtt; - controlPacket->readPrimitive(&rtt); - - if (ack < _lastReceivedACK) { + if (ack <= _lastReceivedACK) { // this is an out of order ACK, bail - return; - } - - // this is a valid ACKed sequence number - update the flow window size and the last received ACK - int32_t packedFlowWindow; - controlPacket->readPrimitive(&packedFlowWindow); - - _flowWindowSize = packedFlowWindow; - - if (ack == _lastReceivedACK) { + // or // processing an already received ACK, bail return; } @@ -650,39 +338,7 @@ void Connection::processACK(ControlPacketPointer controlPacket) { // ACK the send queue so it knows what was received getSendQueue().ack(ack); - - // update the RTT - updateRTT(rtt); - - // write this RTT to stats - _stats.recordRTT(rtt); - - // set the RTT for congestion control - _congestionControl->setRTT(_rtt); - - if (controlPacket->bytesLeftToRead() > 0) { - int32_t receiveRate, bandwidth; - - Q_ASSERT_X(controlPacket->bytesLeftToRead() == sizeof(receiveRate) + sizeof(bandwidth), - "Connection::processACK", "sync interval ACK packet does not contain expected data"); - - controlPacket->readPrimitive(&receiveRate); - controlPacket->readPrimitive(&bandwidth); - - // set the delivery rate and bandwidth for congestion control - // these are calculated using an EWMA - static const int EMWA_ALPHA_NUMERATOR = 8; - - // record these samples in connection stats - _stats.recordSendRate(receiveRate); - _stats.recordEstimatedBandwidth(bandwidth); - - _deliveryRate = (_deliveryRate * (EMWA_ALPHA_NUMERATOR - 1) + receiveRate) / EMWA_ALPHA_NUMERATOR; - _bandwidth = (_bandwidth * (EMWA_ALPHA_NUMERATOR - 1) + bandwidth) / EMWA_ALPHA_NUMERATOR; - - _congestionControl->setReceiveRate(_deliveryRate); - _congestionControl->setBandwidth(_bandwidth); - } + // give this ACK to the congestion control and update the send queue parameters updateCongestionControlAndSendQueue([this, ack, &controlPacket] { @@ -695,92 +351,6 @@ void Connection::processACK(ControlPacketPointer controlPacket) { _stats.record(ConnectionStats::Stats::ProcessedACK); } -void Connection::processLightACK(ControlPacketPointer controlPacket) { - // read the ACKed sequence number - SequenceNumber ack; - controlPacket->readPrimitive(&ack); - - // must be larger than the last received ACK to be processed - if (ack > _lastReceivedACK) { - // NOTE: the following makes sense in UDT where there is a dynamic receive buffer. - // Since we have a receive buffer that is always of a default size, we don't use this light ACK to - // drop the flow window size. - - // decrease the flow window size by the offset between the last received ACK and this ACK - // _flowWindowSize -= seqoff(_lastReceivedACK, ack); - - // update the last received ACK to the this one - _lastReceivedACK = ack; - - // send light ACK to the send queue - getSendQueue().ack(ack); - } - - _stats.record(ConnectionStats::Stats::ReceivedLightACK); -} - -void Connection::processACK2(ControlPacketPointer controlPacket) { - // pull the sub sequence number from the packet - SequenceNumber subSequenceNumber; - controlPacket->readPrimitive(&subSequenceNumber); - - // check if we had that subsequence number in our map - auto it = std::find_if_not(_sentACKs.begin(), _sentACKs.end(), [&subSequenceNumber](const ACKListPair& pair){ - return pair.first < subSequenceNumber; - }); - - if (it != _sentACKs.end()) { - if (it->first == subSequenceNumber){ - // update the RTT using the ACK window - - // calculate the RTT (time now - time ACK sent) - auto now = p_high_resolution_clock::now(); - int rtt = duration_cast(now - it->second.second).count(); - - updateRTT(rtt); - // write this RTT to stats - _stats.recordRTT(rtt); - - // set the RTT for congestion control - _congestionControl->setRTT(_rtt); - - // update the last ACKed ACK - if (it->second.first > _lastReceivedAcknowledgedACK) { - _lastReceivedAcknowledgedACK = it->second.first; - } - } else if (it->first < subSequenceNumber) { - Q_UNREACHABLE(); - } - } - - // erase this sub-sequence number and anything below it now that we've gotten our timing information - _sentACKs.erase(_sentACKs.begin(), it); - - _stats.record(ConnectionStats::Stats::ReceivedACK2); -} - -void Connection::processNAK(ControlPacketPointer controlPacket) { - // read the loss report - SequenceNumber start, end; - controlPacket->readPrimitive(&start); - - end = start; - - if (controlPacket->bytesLeftToRead() >= (qint64)sizeof(SequenceNumber)) { - controlPacket->readPrimitive(&end); - } - - // send that off to the send queue so it knows there was loss - getSendQueue().nak(start, end); - - // give the loss to the congestion control object and update the send queue parameters - updateCongestionControlAndSendQueue([this, start, end] { - _congestionControl->onLoss(start, end); - }); - - _stats.record(ConnectionStats::Stats::ReceivedNAK); -} - void Connection::processHandshake(ControlPacketPointer controlPacket) { SequenceNumber initialSequenceNumber; controlPacket->readPrimitive(&initialSequenceNumber); @@ -797,7 +367,6 @@ void Connection::processHandshake(ControlPacketPointer controlPacket) { resetReceiveState(); _initialReceiveSequenceNumber = initialSequenceNumber; _lastReceivedSequenceNumber = initialSequenceNumber - 1; - _lastSentACK = initialSequenceNumber - 1; } _handshakeACK->reset(); @@ -829,33 +398,6 @@ void Connection::processHandshakeACK(ControlPacketPointer controlPacket) { } } -void Connection::processTimeoutNAK(ControlPacketPointer controlPacket) { - // Override SendQueue's LossList with the timeout NAK list - getSendQueue().overrideNAKListFromPacket(*controlPacket); - - // we don't tell the congestion control object there was loss here - this matches UDTs implementation - // a possible improvement would be to tell it which new loss this timeout packet told us about - - _stats.record(ConnectionStats::Stats::ReceivedTimeoutNAK); -} - -void Connection::processProbeTail(ControlPacketPointer controlPacket) { - if (((uint32_t) _lastReceivedSequenceNumber & 0xF) == 0) { - // this is the second packet in a probe set so we can estimate bandwidth - // the sender sent this to us in lieu of sending new data (because they didn't have any) - -#ifdef UDT_CONNECTION_DEBUG - qCDebug(networking) << "Processing second packet of probe from control packet instead of data packet"; -#endif - - _receiveWindow.onProbePair2Arrival(); - - // mark that we processed a control packet for the second in the pair and we should not mark - // the next data packet received - _receivedControlProbeTail = true; - } -} - void Connection::resetReceiveState() { // reset all SequenceNumber member variables back to default @@ -863,35 +405,12 @@ void Connection::resetReceiveState() { _lastReceivedSequenceNumber = defaultSequenceNumber; - _lastReceivedAcknowledgedACK = defaultSequenceNumber; - _currentACKSubSequenceNumber = defaultSequenceNumber; - - _lastSentACK = defaultSequenceNumber; - - // clear the sent ACKs - _sentACKs.clear(); - - // clear the loss list and _lastNAKTime + // clear the loss list _lossList.clear(); - _lastNAKTime = p_high_resolution_clock::now(); - - // the _nakInterval need not be reset, that will happen on loss // clear sync variables - _isReceivingData = false; _connectionStart = p_high_resolution_clock::now(); - _acksDuringSYN = 1; - _lightACKsDuringSYN = 1; - _packetsSinceACK = 0; - - // reset RTT to initial value - resetRTT(); - - // clear the intervals in the receive window - _receiveWindow.reset(); - _receivedControlProbeTail = false; - // clear any pending received messages for (auto& pendingMessage : _pendingReceivedMessages) { _parentSocket->messageFailed(this, pendingMessage.first); @@ -899,30 +418,6 @@ void Connection::resetReceiveState() { _pendingReceivedMessages.clear(); } -void Connection::updateRTT(int rtt) { - // This updates the RTT using exponential weighted moving average - // This is the Jacobson's forumla for RTT estimation - // http://www.mathcs.emory.edu/~cheung/Courses/455/Syllabus/7-transport/Jacobson-88.pdf - - // Estimated RTT = (1 - x)(estimatedRTT) + (x)(sampleRTT) - // (where x = 0.125 via Jacobson) - - // Deviation = (1 - x)(deviation) + x |sampleRTT - estimatedRTT| - // (where x = 0.25 via Jacobson) - - static const int RTT_ESTIMATION_ALPHA_NUMERATOR = 8; - static const int RTT_ESTIMATION_VARIANCE_ALPHA_NUMERATOR = 4; - - _rtt = (_rtt * (RTT_ESTIMATION_ALPHA_NUMERATOR - 1) + rtt) / RTT_ESTIMATION_ALPHA_NUMERATOR; - - _rttVariance = (_rttVariance * (RTT_ESTIMATION_VARIANCE_ALPHA_NUMERATOR - 1) - + abs(rtt - _rtt)) / RTT_ESTIMATION_VARIANCE_ALPHA_NUMERATOR; -} - -int Connection::estimatedTimeout() const { - return _congestionControl->_userDefinedRTO ? _congestionControl->_rto : _rtt + _rttVariance * 4; -} - void Connection::updateCongestionControlAndSendQueue(std::function congestionCallback) { // update the last sent sequence number in congestion control _congestionControl->setSendCurrentSequenceNumber(getSendQueue().getCurrentSequenceNumber()); @@ -934,8 +429,8 @@ void Connection::updateCongestionControlAndSendQueue(std::function cong // now that we've updated the congestion control, update the packet send period and flow window size sendQueue.setPacketSendPeriod(_congestionControl->_packetSendPeriod); - sendQueue.setEstimatedTimeout(estimatedTimeout()); - sendQueue.setFlowWindowSize(std::min(_flowWindowSize, (int) _congestionControl->_congestionWindowSize)); + sendQueue.setEstimatedTimeout(_congestionControl->estimatedTimeout()); + sendQueue.setFlowWindowSize(_congestionControl->_congestionWindowSize); // record connection stats _stats.recordPacketSendPeriod(_congestionControl->_packetSendPeriod); diff --git a/libraries/networking/src/udt/Connection.h b/libraries/networking/src/udt/Connection.h index 0017eb204a..17e8a9b1f9 100644 --- a/libraries/networking/src/udt/Connection.h +++ b/libraries/networking/src/udt/Connection.h @@ -22,7 +22,6 @@ #include "ConnectionStats.h" #include "Constants.h" #include "LossList.h" -#include "PacketTimeWindow.h" #include "SendQueue.h" #include "../HifiSockAddr.h" @@ -51,9 +50,6 @@ private: class Connection : public QObject { Q_OBJECT public: - using SequenceNumberTimePair = std::pair; - using ACKListPair = std::pair; - using SentACKList = std::list; using ControlPacketPointer = std::unique_ptr; Connection(Socket* parentSocket, HifiSockAddr destination, std::unique_ptr congestionControl); @@ -87,51 +83,29 @@ private slots: void recordRetransmission(int wireSize, SequenceNumber sequenceNumber, p_high_resolution_clock::time_point timePoint); void queueInactive(); void queueTimeout(); - void queueShortCircuitLoss(quint32 sequenceNumber); private: - void sendACK(bool wasCausedBySyncTimeout = true); - void sendLightACK(); - void sendACK2(SequenceNumber currentACKSubSequenceNumber); - void sendNAK(SequenceNumber sequenceNumberRecieved); - void sendTimeoutNAK(); + void sendACK(); void processACK(ControlPacketPointer controlPacket); - void processLightACK(ControlPacketPointer controlPacket); - void processACK2(ControlPacketPointer controlPacket); - void processNAK(ControlPacketPointer controlPacket); - void processTimeoutNAK(ControlPacketPointer controlPacket); void processHandshake(ControlPacketPointer controlPacket); void processHandshakeACK(ControlPacketPointer controlPacket); - void processProbeTail(ControlPacketPointer controlPacket); void resetReceiveState(); - void resetRTT(); SendQueue& getSendQueue(); SequenceNumber nextACK() const; - void updateRTT(int rtt); - - int estimatedTimeout() const; void updateCongestionControlAndSendQueue(std::function congestionCallback); void stopSendQueue(); - int _synInterval; // Periodical Rate Control Interval, in microseconds - - int _nakInterval { -1 }; // NAK timeout interval, in microseconds, set on loss - int _minNAKInterval { 100000 }; // NAK timeout interval lower bound, default of 100ms - p_high_resolution_clock::time_point _lastNAKTime = p_high_resolution_clock::now(); - bool _hasReceivedHandshake { false }; // flag for receipt of handshake from server bool _hasReceivedHandshakeACK { false }; // flag for receipt of handshake ACK from client bool _didRequestHandshake { false }; // flag for request of handshake from server p_high_resolution_clock::time_point _connectionStart = p_high_resolution_clock::now(); // holds the time_point for creation of this connection p_high_resolution_clock::time_point _lastReceiveTime; // holds the last time we received anything from sender - - bool _isReceivingData { false }; // flag used for expiry of receipt portion of connection SequenceNumber _initialSequenceNumber; // Randomized on Connection creation, identifies connection during re-connect requests SequenceNumber _initialReceiveSequenceNumber; // Randomized by peer Connection on creation, identifies connection during re-connect requests @@ -141,43 +115,18 @@ private: LossList _lossList; // List of all missing packets SequenceNumber _lastReceivedSequenceNumber; // The largest sequence number received from the peer SequenceNumber _lastReceivedACK; // The last ACK received - SequenceNumber _lastReceivedAcknowledgedACK; // The last sent ACK that has been acknowledged via an ACK2 from the peer - SequenceNumber _currentACKSubSequenceNumber; // The current ACK sub-sequence number (used for Acknowledgment of ACKs) - - SequenceNumber _lastSentACK; // The last sent ACK - SequenceNumber _lastSentACK2; // The last sent ACK sub-sequence number in an ACK2 - - int _acksDuringSYN { 1 }; // The number of non-SYN ACKs sent during SYN - int _lightACKsDuringSYN { 1 }; // The number of lite ACKs sent during SYN interval - - int32_t _rtt; // RTT, in microseconds - int32_t _rttVariance; // RTT variance - int _flowWindowSize { udt::MAX_PACKETS_IN_FLIGHT }; // Flow control window size - - int _bandwidth { 1 }; // Exponential moving average for estimated bandwidth, in packets per second - int _deliveryRate { 16 }; // Exponential moving average for receiver's receive rate, in packets per second - - SentACKList _sentACKs; // Map of ACK sub-sequence numbers to ACKed sequence number and sent time Socket* _parentSocket { nullptr }; HifiSockAddr _destination; - - PacketTimeWindow _receiveWindow { 16, 64 }; // Window of interval between packets (16) and probes (64) for timing - bool _receivedControlProbeTail { false }; // Marker for receipt of control packet probe tail (in lieu of probe with data) std::unique_ptr _congestionControl; std::unique_ptr _sendQueue; std::map _pendingReceivedMessages; - - int _packetsSinceACK { 0 }; // The number of packets that have been received during the current ACK interval // Re-used control packets ControlPacketPointer _ackPacket; - ControlPacketPointer _lightACKPacket; - ControlPacketPointer _ack2Packet; - ControlPacketPointer _lossReport; ControlPacketPointer _handshakeACK; ConnectionStats _stats; diff --git a/libraries/networking/src/udt/ConnectionStats.cpp b/libraries/networking/src/udt/ConnectionStats.cpp index 986da062f2..e30c588dba 100644 --- a/libraries/networking/src/udt/ConnectionStats.cpp +++ b/libraries/networking/src/udt/ConnectionStats.cpp @@ -95,11 +95,6 @@ void ConnectionStats::recordReceiveRate(int sample) { _total.receiveRate = (int)((_total.receiveRate * EWMA_PREVIOUS_SAMPLES_WEIGHT) + (sample * EWMA_CURRENT_SAMPLE_WEIGHT)); } -void ConnectionStats::recordEstimatedBandwidth(int sample) { - _currentSample.estimatedBandwith = sample; - _total.estimatedBandwith = (int)((_total.estimatedBandwith * EWMA_PREVIOUS_SAMPLES_WEIGHT) + (sample * EWMA_CURRENT_SAMPLE_WEIGHT)); -} - void ConnectionStats::recordRTT(int sample) { _currentSample.rtt = sample; _total.rtt = (int)((_total.rtt * EWMA_PREVIOUS_SAMPLES_WEIGHT) + (sample * EWMA_CURRENT_SAMPLE_WEIGHT)); @@ -122,14 +117,6 @@ QDebug& operator<<(QDebug&& debug, const udt::ConnectionStats::Stats& stats) { HIFI_LOG_EVENT(SentACK) HIFI_LOG_EVENT(ReceivedACK) HIFI_LOG_EVENT(ProcessedACK) - HIFI_LOG_EVENT(SentLightACK) - HIFI_LOG_EVENT(ReceivedLightACK) - HIFI_LOG_EVENT(SentACK2) - HIFI_LOG_EVENT(ReceivedACK2) - HIFI_LOG_EVENT(SentNAK) - HIFI_LOG_EVENT(ReceivedNAK) - HIFI_LOG_EVENT(SentTimeoutNAK) - HIFI_LOG_EVENT(ReceivedTimeoutNAK) HIFI_LOG_EVENT(Retransmission) HIFI_LOG_EVENT(Duplicate) ; diff --git a/libraries/networking/src/udt/ConnectionStats.h b/libraries/networking/src/udt/ConnectionStats.h index 7ec7b163ee..0fdd1636b3 100644 --- a/libraries/networking/src/udt/ConnectionStats.h +++ b/libraries/networking/src/udt/ConnectionStats.h @@ -24,14 +24,6 @@ public: SentACK, ReceivedACK, ProcessedACK, - SentLightACK, - ReceivedLightACK, - SentACK2, - ReceivedACK2, - SentNAK, - ReceivedNAK, - SentTimeoutNAK, - ReceivedTimeoutNAK, Retransmission, Duplicate, @@ -89,7 +81,6 @@ public: void recordSendRate(int sample); void recordReceiveRate(int sample); - void recordEstimatedBandwidth(int sample); void recordRTT(int sample); void recordCongestionWindowSize(int sample); void recordPacketSendPeriod(int sample); diff --git a/libraries/networking/src/udt/ControlPacket.h b/libraries/networking/src/udt/ControlPacket.h index 3c770de9bb..46b9cdbd40 100644 --- a/libraries/networking/src/udt/ControlPacket.h +++ b/libraries/networking/src/udt/ControlPacket.h @@ -28,13 +28,8 @@ public: enum Type : uint16_t { ACK, - ACK2, - LightACK, - NAK, - TimeoutNAK, Handshake, HandshakeACK, - ProbeTail, HandshakeRequest }; diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index 13ffcb5120..8cb7c9aaa4 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -27,20 +27,20 @@ PacketVersion versionForPacketType(PacketType packetType) { case PacketType::StunResponse: return 17; case PacketType::DomainList: - return static_cast(DomainListVersion::GetMachineFingerprintFromUUIDSupport); + return static_cast(DomainListVersion::AuthenticationOptional); case PacketType::EntityAdd: case PacketType::EntityClone: case PacketType::EntityEdit: case PacketType::EntityData: case PacketType::EntityPhysics: - return static_cast(EntityVersion::ParticleEntityFix); + return static_cast(EntityVersion::BloomEffect); case PacketType::EntityQuery: return static_cast(EntityQueryPacketVersion::ConicalFrustums); case PacketType::AvatarIdentity: case PacketType::AvatarData: case PacketType::BulkAvatarData: case PacketType::KillAvatar: - return static_cast(AvatarMixerPacketVersion::ProceduralFaceMovementFlagsAndBlendshapes); + return static_cast(AvatarMixerPacketVersion::MigrateAvatarEntitiesToTraits); case PacketType::MessagesData: return static_cast(MessageDataVersion::TextOrBinaryData); // ICE packets @@ -94,8 +94,10 @@ PacketVersion versionForPacketType(PacketType packetType) { return static_cast(AvatarQueryVersion::ConicalFrustums); case PacketType::AvatarIdentityRequest: return 22; + case PacketType::EntityQueryInitialResultsComplete: + return static_cast(EntityVersion::ParticleSpin); default: - return 21; + return 22; } } diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index 6e1aca83e5..84a93ac939 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -56,7 +56,7 @@ public: ICEServerPeerInformation, ICEServerQuery, OctreeStats, - UNUSED_PACKET_TYPE_1, + SetAvatarTraits, AvatarIdentityRequest, AssignmentClientStatus, NoisyMute, @@ -132,6 +132,8 @@ public: OctreeDataPersist, EntityClone, + EntityQueryInitialResultsComplete, + BulkAvatarTraits, NUM_PACKET_TYPE }; @@ -238,7 +240,9 @@ enum class EntityVersion : PacketVersion { CloneableData, CollisionMask16Bytes, YieldSimulationOwnership, - ParticleEntityFix + ParticleEntityFix, + ParticleSpin, + BloomEffect }; enum class EntityScriptCallMethodVersion : PacketVersion { @@ -286,7 +290,10 @@ enum class AvatarMixerPacketVersion : PacketVersion { AvatarJointDefaultPoseFlags, FBXReaderNodeReparenting, FixMannequinDefaultAvatarFeet, - ProceduralFaceMovementFlagsAndBlendshapes + ProceduralFaceMovementFlagsAndBlendshapes, + FarGrabJoints, + MigrateSkeletonURLToTraits, + MigrateAvatarEntitiesToTraits }; enum class DomainConnectRequestVersion : PacketVersion { @@ -313,7 +320,8 @@ enum class DomainListVersion : PacketVersion { PrePermissionsGrid = 18, PermissionsGrid, GetUsernameFromUUIDSupport, - GetMachineFingerprintFromUUIDSupport + GetMachineFingerprintFromUUIDSupport, + AuthenticationOptional }; enum class AudioVersion : PacketVersion { diff --git a/libraries/networking/src/udt/PacketList.h b/libraries/networking/src/udt/PacketList.h index ff1860c3d1..b9bd6a8c15 100644 --- a/libraries/networking/src/udt/PacketList.h +++ b/libraries/networking/src/udt/PacketList.h @@ -14,8 +14,7 @@ #include -#include - +#include "../ExtendedIODevice.h" #include "Packet.h" #include "PacketHeaders.h" @@ -25,7 +24,7 @@ namespace udt { class Packet; -class PacketList : public QIODevice { +class PacketList : public ExtendedIODevice { Q_OBJECT public: using MessageNumber = uint32_t; @@ -59,9 +58,6 @@ public: virtual bool isSequential() const override { return false; } virtual qint64 size() const override { return getDataSize(); } - template qint64 readPrimitive(T* data); - template qint64 writePrimitive(const T& data); - qint64 writeString(const QString& string); protected: @@ -105,16 +101,6 @@ private: QByteArray _extendedHeader; }; -template qint64 PacketList::readPrimitive(T* data) { - static_assert(!std::is_pointer::value, "T must not be a pointer"); - return read(reinterpret_cast(data), sizeof(T)); -} - -template qint64 PacketList::writePrimitive(const T& data) { - static_assert(!std::is_pointer::value, "T must not be a pointer"); - return write(reinterpret_cast(&data), sizeof(T)); -} - template std::unique_ptr PacketList::takeFront() { static_assert(std::is_base_of::value, "T must derive from Packet."); diff --git a/libraries/networking/src/udt/PacketTimeWindow.cpp b/libraries/networking/src/udt/PacketTimeWindow.cpp deleted file mode 100644 index 0c95d21bc6..0000000000 --- a/libraries/networking/src/udt/PacketTimeWindow.cpp +++ /dev/null @@ -1,125 +0,0 @@ -// -// PacketTimeWindow.cpp -// libraries/networking/src/udt -// -// Created by Stephen Birarda on 2015-07-28. -// Copyright 2015 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#include "PacketTimeWindow.h" - -#include -#include - -#include - -using namespace udt; -using namespace std::chrono; - -static const int DEFAULT_PACKET_INTERVAL_MICROSECONDS = 1000000; // 1s -static const int DEFAULT_PROBE_INTERVAL_MICROSECONDS = 1000; // 1ms - -PacketTimeWindow::PacketTimeWindow(int numPacketIntervals, int numProbeIntervals) : - _numPacketIntervals(numPacketIntervals), - _numProbeIntervals(numProbeIntervals), - _packetIntervals(_numPacketIntervals, DEFAULT_PACKET_INTERVAL_MICROSECONDS), - _probeIntervals(_numProbeIntervals, DEFAULT_PROBE_INTERVAL_MICROSECONDS) -{ - -} - -void PacketTimeWindow::reset() { - _packetIntervals.assign(_numPacketIntervals, DEFAULT_PACKET_INTERVAL_MICROSECONDS); - _probeIntervals.assign(_numProbeIntervals, DEFAULT_PROBE_INTERVAL_MICROSECONDS); -} - -template -int median(Iterator begin, Iterator end) { - // use std::nth_element to grab the middle - for an even number of elements this is the upper middle - Iterator middle = begin + (end - begin) / 2; - std::nth_element(begin, middle, end); - - if ((end - begin) % 2 != 0) { - // odd number of elements, just return the middle - return *middle; - } else { - // even number of elements, return the mean of the upper middle and the lower middle - Iterator lowerMiddle = std::max_element(begin, middle); - return (*middle + *lowerMiddle) / 2; - } -} - -int32_t meanOfMedianFilteredValues(std::vector intervals, int numValues, int valuesRequired = 0) { - // grab the median value of the intervals vector - int intervalsMedian = median(intervals.begin(), intervals.end()); - - // figure out our bounds for median filtering - static const int MEDIAN_FILTERING_BOUND_MULTIPLIER = 8; - int upperBound = intervalsMedian * MEDIAN_FILTERING_BOUND_MULTIPLIER; - int lowerBound = intervalsMedian / MEDIAN_FILTERING_BOUND_MULTIPLIER; - - int sum = 0; - int count = 0; - - // sum the values that are inside the median filtered bounds - for (auto& interval : intervals) { - if ((interval < upperBound) && (interval > lowerBound)) { - ++count; - sum += interval; - } - } - - // make sure we hit our threshold of values required - if (count >= valuesRequired) { - // return the frequency (per second) for the mean interval - static const double USECS_PER_SEC = 1000000.0; - return (int32_t) ceil(USECS_PER_SEC / (((double) sum) / ((double) count))); - } else { - return 0; - } -} - -int32_t PacketTimeWindow::getPacketReceiveSpeed() const { - // return the mean value of median filtered values (per second) - or zero if there are too few filtered values - return meanOfMedianFilteredValues(_packetIntervals, _numPacketIntervals, _numPacketIntervals / 2); -} - -int32_t PacketTimeWindow::getEstimatedBandwidth() const { - // return mean value of median filtered values (per second) - return meanOfMedianFilteredValues(_probeIntervals, _numProbeIntervals); -} - -void PacketTimeWindow::onPacketArrival() { - - // take the current time - auto now = p_high_resolution_clock::now(); - - if (_packetIntervals.size() > 0) { - // record the interval between this packet and the last one - _packetIntervals[_currentPacketInterval++] = duration_cast(now - _lastPacketTime).count(); - - // reset the currentPacketInterval index when it wraps - _currentPacketInterval %= _numPacketIntervals; - } - - // remember this as the last packet arrival time - _lastPacketTime = now; -} - -void PacketTimeWindow::onProbePair1Arrival() { - // take the current time as the first probe time - _firstProbeTime = p_high_resolution_clock::now(); -} - -void PacketTimeWindow::onProbePair2Arrival() { - // store the interval between the two probes - auto now = p_high_resolution_clock::now(); - - _probeIntervals[_currentProbeInterval++] = duration_cast(now - _firstProbeTime).count(); - - // reset the currentProbeInterval index when it wraps - _currentProbeInterval %= _numProbeIntervals; -} diff --git a/libraries/networking/src/udt/PacketTimeWindow.h b/libraries/networking/src/udt/PacketTimeWindow.h deleted file mode 100644 index 6f7ed9f70f..0000000000 --- a/libraries/networking/src/udt/PacketTimeWindow.h +++ /dev/null @@ -1,51 +0,0 @@ -// -// PacketTimeWindow.h -// libraries/networking/src/udt -// -// Created by Stephen Birarda on 2015-07-28. -// Copyright 2015 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#pragma once - -#ifndef hifi_PacketTimeWindow_h -#define hifi_PacketTimeWindow_h - -#include - -#include - -namespace udt { - -class PacketTimeWindow { -public: - PacketTimeWindow(int numPacketIntervals = 16, int numProbeIntervals = 16); - - void onPacketArrival(); - void onProbePair1Arrival(); - void onProbePair2Arrival(); - - int32_t getPacketReceiveSpeed() const; - int32_t getEstimatedBandwidth() const; - - void reset(); -private: - int _numPacketIntervals { 0 }; // the number of packet intervals to store - int _numProbeIntervals { 0 }; // the number of probe intervals to store - - int _currentPacketInterval { 0 }; // index for the current packet interval - int _currentProbeInterval { 0 }; // index for the current probe interval - - std::vector _packetIntervals; // vector of microsecond intervals between packet arrivals - std::vector _probeIntervals; // vector of microsecond intervals between probe pair arrivals - - p_high_resolution_clock::time_point _lastPacketTime = p_high_resolution_clock::now(); // the time_point when last packet arrived - p_high_resolution_clock::time_point _firstProbeTime = p_high_resolution_clock::now(); // the time_point when first probe in pair arrived -}; - -} - -#endif // hifi_PacketTimeWindow_h diff --git a/libraries/networking/src/udt/SendQueue.cpp b/libraries/networking/src/udt/SendQueue.cpp index 0df54d539d..b1dfb9a8cf 100644 --- a/libraries/networking/src/udt/SendQueue.cpp +++ b/libraries/networking/src/udt/SendQueue.cpp @@ -164,16 +164,6 @@ void SendQueue::ack(SequenceNumber ack) { _emptyCondition.notify_one(); } -void SendQueue::nak(SequenceNumber start, SequenceNumber end) { - { - std::lock_guard nakLocker(_naksLock); - _naks.insert(start, end); - } - - // call notify_one on the condition_variable_any in case the send thread is sleeping waiting for losses to re-send - _emptyCondition.notify_one(); -} - void SendQueue::fastRetransmit(udt::SequenceNumber ack) { { std::lock_guard nakLocker(_naksLock); @@ -184,28 +174,6 @@ void SendQueue::fastRetransmit(udt::SequenceNumber ack) { _emptyCondition.notify_one(); } -void SendQueue::overrideNAKListFromPacket(ControlPacket& packet) { - { - std::lock_guard nakLocker(_naksLock); - _naks.clear(); - - SequenceNumber first, second; - while (packet.bytesLeftToRead() >= (qint64)(2 * sizeof(SequenceNumber))) { - packet.readPrimitive(&first); - packet.readPrimitive(&second); - - if (first == second) { - _naks.append(first); - } else { - _naks.append(first, second); - } - } - } - - // call notify_one on the condition_variable_any in case the send thread is sleeping waiting for losses to re-send - _emptyCondition.notify_one(); -} - void SendQueue::sendHandshake() { std::unique_lock handshakeLock { _handshakeMutex }; if (!_hasReceivedHandshakeACK) { @@ -268,8 +236,6 @@ bool SendQueue::sendNewPacketAndAddToSentList(std::unique_ptr newPacket, _naks.append(sequenceNumber); } - emit shortCircuitLoss(quint32(sequenceNumber)); - return false; } else { return true; @@ -385,10 +351,6 @@ void SendQueue::run() { } } -void SendQueue::setProbePacketEnabled(bool enabled) { - _shouldSendProbes = enabled; -} - int SendQueue::maybeSendNewPacket() { if (!isFlowWindowFull()) { // we didn't re-send a packet, so time to send a new one @@ -397,40 +359,15 @@ int SendQueue::maybeSendNewPacket() { SequenceNumber nextNumber = getNextSequenceNumber(); // grab the first packet we will send - std::unique_ptr firstPacket = _packets.takePacket(); - Q_ASSERT(firstPacket); + std::unique_ptr packet = _packets.takePacket(); + Q_ASSERT(packet); - // attempt to send the first packet - if (sendNewPacketAndAddToSentList(move(firstPacket), nextNumber)) { - std::unique_ptr secondPacket; - bool shouldSendPairTail = false; + // attempt to send the packet + sendNewPacketAndAddToSentList(move(packet), nextNumber); - if (_shouldSendProbes && ((uint32_t) nextNumber & 0xF) == 0) { - // the first packet is the first in a probe pair - every 16 (rightmost 16 bits = 0) packets - // pull off a second packet if we can before we unlock - shouldSendPairTail = true; - - secondPacket = _packets.takePacket(); - } - - // do we have a second in a pair to send as well? - if (secondPacket) { - sendNewPacketAndAddToSentList(move(secondPacket), getNextSequenceNumber()); - } else if (shouldSendPairTail) { - // we didn't get a second packet to send in the probe pair - // send a control packet of type ProbePairTail so the receiver can still do - // proper bandwidth estimation - static auto pairTailPacket = ControlPacket::create(ControlPacket::ProbeTail); - _socket->writeBasePacket(*pairTailPacket, _destination); - } - - // return the number of attempted packet sends - return shouldSendPairTail ? 2 : 1; - } else { - // we attempted to send a single packet, return 1 - return 1; - } + // we attempted to send a packet, return 1 + return 1; } } diff --git a/libraries/networking/src/udt/SendQueue.h b/libraries/networking/src/udt/SendQueue.h index a11aacdb91..65b1b89c7e 100644 --- a/libraries/networking/src/udt/SendQueue.h +++ b/libraries/networking/src/udt/SendQueue.h @@ -69,16 +69,12 @@ public: void setEstimatedTimeout(int estimatedTimeout) { _estimatedTimeout = estimatedTimeout; } void setSyncInterval(int syncInterval) { _syncInterval = syncInterval; } - - void setProbePacketEnabled(bool enabled); public slots: void stop(); void ack(SequenceNumber ack); - void nak(SequenceNumber start, SequenceNumber end); void fastRetransmit(SequenceNumber ack); - void overrideNAKListFromPacket(ControlPacket& packet); void handshakeACK(); signals: @@ -87,7 +83,6 @@ signals: void queueInactive(); - void shortCircuitLoss(quint32 sequenceNumber); void timeout(); private slots: @@ -145,9 +140,6 @@ private: std::condition_variable _handshakeACKCondition; std::condition_variable_any _emptyCondition; - - - std::atomic _shouldSendProbes { true }; }; } diff --git a/libraries/networking/src/udt/Socket.cpp b/libraries/networking/src/udt/Socket.cpp index 4d4303698b..c378987cd0 100644 --- a/libraries/networking/src/udt/Socket.cpp +++ b/libraries/networking/src/udt/Socket.cpp @@ -33,18 +33,11 @@ using namespace udt; Socket::Socket(QObject* parent, bool shouldChangeSocketOptions) : QObject(parent), - _synTimer(new QTimer(this)), _readyReadBackupTimer(new QTimer(this)), _shouldChangeSocketOptions(shouldChangeSocketOptions) { connect(&_udpSocket, &QUdpSocket::readyRead, this, &Socket::readPendingDatagrams); - // make sure our synchronization method is called every SYN interval - connect(_synTimer, &QTimer::timeout, this, &Socket::rateControlSync); - - // start our timer for the synchronization time interval - _synTimer->start(_synInterval); - // make sure we hear about errors and state changes from the underlying socket connect(&_udpSocket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(handleSocketError(QAbstractSocket::SocketError))); @@ -229,7 +222,7 @@ qint64 Socket::writeDatagram(const QByteArray& datagram, const HifiSockAddr& soc if (bytesWritten < 0) { // when saturating a link this isn't an uncommon message - suppress it so it doesn't bomb the debug - HIFI_FCDEBUG(networking(), "Socket::writeDatagram" << _udpSocket.error() << "-" << qPrintable(_udpSocket.errorString()) ); + HIFI_FCDEBUG(networking(), "Socket::writeDatagram" << _udpSocket.error()); } return bytesWritten; @@ -427,49 +420,9 @@ void Socket::connectToSendSignal(const HifiSockAddr& destinationAddr, QObject* r } } -void Socket::rateControlSync() { - - // enumerate our list of connections and ask each of them to send off periodic ACK packet for rate control - - // the way we do this is a little funny looking - we need to avoid the case where we call sync and - // (because of our Qt direct connection to the Connection's signal that it has been deactivated) - // an iterator on _connectionsHash would be invalidated by our own call to cleanupConnection - - // collect the sockets for all connections in a vector - - std::vector sockAddrVector; - sockAddrVector.reserve(_connectionsHash.size()); - - for (auto& connection : _connectionsHash) { - sockAddrVector.emplace_back(connection.first); - } - - // enumerate that vector of HifiSockAddr objects - for (auto& sockAddr : sockAddrVector) { - // pull out the respective connection via a quick find on the unordered_map - auto it = _connectionsHash.find(sockAddr); - - if (it != _connectionsHash.end()) { - // if the connection is erased while calling sync since we are re-using the iterator that was invalidated - // we're good to go - auto& connection = _connectionsHash[sockAddr]; - connection->sync(); - } - } - - if (_synTimer->interval() != _synInterval) { - // if the _synTimer interval doesn't match the current _synInterval (changes when the CC factory is changed) - // then restart it now with the right interval - _synTimer->start(_synInterval); - } -} - void Socket::setCongestionControlFactory(std::unique_ptr ccFactory) { // swap the current unique_ptr for the new factory _ccFactory.swap(ccFactory); - - // update the _synInterval to the value from the factory - _synInterval = _ccFactory->synInterval(); } @@ -513,7 +466,7 @@ std::vector Socket::getConnectionSockAddrs() { } void Socket::handleSocketError(QAbstractSocket::SocketError socketError) { - HIFI_FCDEBUG(networking(), "udt::Socket error - " << socketError << _udpSocket.errorString()); + HIFI_FCDEBUG(networking(), "udt::Socket error - " << socketError); } void Socket::handleStateChanged(QAbstractSocket::SocketState socketState) { diff --git a/libraries/networking/src/udt/Socket.h b/libraries/networking/src/udt/Socket.h index 1919e00b41..1f28592c83 100644 --- a/libraries/networking/src/udt/Socket.h +++ b/libraries/networking/src/udt/Socket.h @@ -102,7 +102,6 @@ public slots: private slots: void readPendingDatagrams(); void checkForReadyReadBackup(); - void rateControlSync(); void handleSocketError(QAbstractSocket::SocketError socketError); void handleStateChanged(QAbstractSocket::SocketState socketState); @@ -133,9 +132,6 @@ private: std::unordered_map _unfilteredHandlers; std::unordered_map _unreliableSequenceNumbers; std::unordered_map> _connectionsHash; - - int _synInterval { 10 }; // 10ms - QTimer* _synTimer { nullptr }; QTimer* _readyReadBackupTimer { nullptr }; diff --git a/libraries/networking/src/udt/TCPVegasCC.cpp b/libraries/networking/src/udt/TCPVegasCC.cpp index 5738ea8421..4842e5a204 100644 --- a/libraries/networking/src/udt/TCPVegasCC.cpp +++ b/libraries/networking/src/udt/TCPVegasCC.cpp @@ -21,8 +21,6 @@ TCPVegasCC::TCPVegasCC() { _packetSendPeriod = 0.0; _congestionWindowSize = 2; - setAckInterval(1); // TCP sends an ACK for every packet received - // set our minimum RTT variables to the maximum possible value // we can't do this as a member initializer until our VS has support for constexpr _currentMinRTT = std::numeric_limits::max(); @@ -103,12 +101,11 @@ bool TCPVegasCC::onACK(SequenceNumber ack, p_high_resolution_clock::time_point r auto it = _sentPacketTimes.find(ack + 1); if (it != _sentPacketTimes.end()) { - auto estimatedTimeout = _ewmaRTT + _rttVariance * 4; auto now = p_high_resolution_clock::now(); auto sinceSend = duration_cast(now - it->second).count(); - if (sinceSend >= estimatedTimeout) { + if (sinceSend >= estimatedTimeout()) { // break out of slow start, we've decided this is loss _slowStart = false; @@ -215,6 +212,11 @@ void TCPVegasCC::performCongestionAvoidance(udt::SequenceNumber ack) { _numACKs = 0; } + +int TCPVegasCC::estimatedTimeout() const { + return _ewmaRTT == -1 ? DEFAULT_SYN_INTERVAL : _ewmaRTT + _rttVariance * 4; +} + bool TCPVegasCC::isCongestionWindowLimited() { if (_slowStart) { return true; diff --git a/libraries/networking/src/udt/TCPVegasCC.h b/libraries/networking/src/udt/TCPVegasCC.h index 862ea36d8f..bb14728d4b 100644 --- a/libraries/networking/src/udt/TCPVegasCC.h +++ b/libraries/networking/src/udt/TCPVegasCC.h @@ -27,14 +27,11 @@ public: TCPVegasCC(); virtual bool onACK(SequenceNumber ackNum, p_high_resolution_clock::time_point receiveTime) override; - virtual void onLoss(SequenceNumber rangeStart, SequenceNumber rangeEnd) override {}; virtual void onTimeout() override {}; - virtual bool shouldNAK() override { return false; } - virtual bool shouldACK2() override { return false; } - virtual bool shouldProbe() override { return false; } - virtual void onPacketSent(int wireSize, SequenceNumber seqNum, p_high_resolution_clock::time_point timePoint) override; + + virtual int estimatedTimeout() const override; protected: virtual void performCongestionAvoidance(SequenceNumber ack); @@ -65,7 +62,6 @@ private: int _duplicateACKCount { 0 }; // Counter for duplicate ACKs received int _slowStartOddAdjust { 0 }; // Marker for every window adjustment every other RTT in slow-start - }; } diff --git a/libraries/octree/src/Octree.cpp b/libraries/octree/src/Octree.cpp index 9bb0e25982..3d387e0956 100644 --- a/libraries/octree/src/Octree.cpp +++ b/libraries/octree/src/Octree.cpp @@ -68,55 +68,12 @@ Octree::~Octree() { eraseAllOctreeElements(false); } - -// Inserts the value and key into three arrays sorted by the key array, the first array is the value, -// the second array is a sorted key for the value, the third array is the index for the value in it original -// non-sorted array -// returns -1 if size exceeded -// originalIndexArray is optional -int insertOctreeElementIntoSortedArrays(const OctreeElementPointer& value, float key, int originalIndex, - OctreeElementPointer* valueArray, float* keyArray, int* originalIndexArray, - int currentCount, int maxCount) { - - if (currentCount < maxCount) { - int i = 0; - if (currentCount > 0) { - while (i < currentCount && key > keyArray[i]) { - i++; - } - // i is our desired location - // shift array elements to the right - if (i < currentCount && i+1 < maxCount) { - for (int j = currentCount - 1; j > i; j--) { - valueArray[j] = valueArray[j - 1]; - keyArray[j] = keyArray[j - 1]; - } - } - } - // place new element at i - valueArray[i] = value; - keyArray[i] = key; - if (originalIndexArray) { - originalIndexArray[i] = originalIndex; - } - return currentCount + 1; - } - return -1; // error case -} - - - // Recurses voxel tree calling the RecurseOctreeOperation function for each element. // stops recursion if operation function returns false. void Octree::recurseTreeWithOperation(const RecurseOctreeOperation& operation, void* extraData) { recurseElementWithOperation(_rootElement, operation, extraData); } -// Recurses voxel tree calling the RecurseOctreePostFixOperation function for each element in post-fix order. -void Octree::recurseTreeWithPostOperation(const RecurseOctreeOperation& operation, void* extraData) { - recurseElementWithPostOperation(_rootElement, operation, extraData); -} - // Recurses voxel element with an operation function void Octree::recurseElementWithOperation(const OctreeElementPointer& element, const RecurseOctreeOperation& operation, void* extraData, int recursionCount) { @@ -129,71 +86,53 @@ void Octree::recurseElementWithOperation(const OctreeElementPointer& element, co for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { OctreeElementPointer child = element->getChildAtIndex(i); if (child) { - recurseElementWithOperation(child, operation, extraData, recursionCount+1); + recurseElementWithOperation(child, operation, extraData, recursionCount + 1); } } } } -// Recurses voxel element with an operation function -void Octree::recurseElementWithPostOperation(const OctreeElementPointer& element, const RecurseOctreeOperation& operation, - void* extraData, int recursionCount) { +void Octree::recurseTreeWithOperationSorted(const RecurseOctreeOperation& operation, const RecurseOctreeSortingOperation& sortingOperation, void* extraData) { + recurseElementWithOperationSorted(_rootElement, operation, sortingOperation, extraData); +} + +// Recurses voxel element with an operation function, calling operation on its children in a specific order +bool Octree::recurseElementWithOperationSorted(const OctreeElementPointer& element, const RecurseOctreeOperation& operation, + const RecurseOctreeSortingOperation& sortingOperation, void* extraData, int recursionCount) { if (recursionCount > DANGEROUSLY_DEEP_RECURSION) { - HIFI_FCDEBUG(octree(), "Octree::recurseElementWithPostOperation() reached DANGEROUSLY_DEEP_RECURSION, bailing!"); - return; + HIFI_FCDEBUG(octree(), "Octree::recurseElementWithOperationSorted() reached DANGEROUSLY_DEEP_RECURSION, bailing!"); + // If we go too deep, we want to keep searching other paths + return true; } + bool keepSearching = operation(element, extraData); + + std::vector sortedChildren; for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { OctreeElementPointer child = element->getChildAtIndex(i); if (child) { - recurseElementWithPostOperation(child, operation, extraData, recursionCount+1); - } - } - operation(element, extraData); -} - -// Recurses voxel tree calling the RecurseOctreeOperation function for each element. -// stops recursion if operation function returns false. -void Octree::recurseTreeWithOperationDistanceSorted(const RecurseOctreeOperation& operation, - const glm::vec3& point, void* extraData) { - - recurseElementWithOperationDistanceSorted(_rootElement, operation, point, extraData); -} - -// Recurses voxel element with an operation function -void Octree::recurseElementWithOperationDistanceSorted(const OctreeElementPointer& element, const RecurseOctreeOperation& operation, - const glm::vec3& point, void* extraData, int recursionCount) { - - if (recursionCount > DANGEROUSLY_DEEP_RECURSION) { - HIFI_FCDEBUG(octree(), "Octree::recurseElementWithOperationDistanceSorted() reached DANGEROUSLY_DEEP_RECURSION, bailing!"); - return; - } - - if (operation(element, extraData)) { - // determine the distance sorted order of our children - OctreeElementPointer sortedChildren[NUMBER_OF_CHILDREN] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }; - float distancesToChildren[NUMBER_OF_CHILDREN] = { 0, 0, 0, 0, 0, 0, 0, 0 }; - int indexOfChildren[NUMBER_OF_CHILDREN] = { 0, 0, 0, 0, 0, 0, 0, 0 }; - int currentCount = 0; - - for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { - OctreeElementPointer childElement = element->getChildAtIndex(i); - if (childElement) { - // chance to optimize, doesn't need to be actual distance!! Could be distance squared - float distanceSquared = childElement->distanceSquareToPoint(point); - currentCount = insertOctreeElementIntoSortedArrays(childElement, distanceSquared, i, - sortedChildren, (float*)&distancesToChildren, - (int*)&indexOfChildren, currentCount, NUMBER_OF_CHILDREN); - } - } - - for (int i = 0; i < currentCount; i++) { - OctreeElementPointer childElement = sortedChildren[i]; - if (childElement) { - recurseElementWithOperationDistanceSorted(childElement, operation, point, extraData); + float priority = sortingOperation(child, extraData); + if (priority < FLT_MAX) { + sortedChildren.emplace_back(priority, child); } } } + + if (sortedChildren.size() > 1) { + static auto comparator = [](const SortedChild& left, const SortedChild& right) { return left.first < right.first; }; + std::sort(sortedChildren.begin(), sortedChildren.end(), comparator); + } + + for (auto it = sortedChildren.begin(); it != sortedChildren.end(); ++it) { + const SortedChild& sortedChild = *it; + // Our children were sorted, so if one hits something, we don't need to check the others + if (!recurseElementWithOperationSorted(sortedChild.second, operation, sortingOperation, extraData, recursionCount + 1)) { + return false; + } + } + // We checked all our children and didn't find anything. + // Stop if we hit something in this element. Continue if we didn't. + return keepSearching; } void Octree::recurseTreeWithOperator(RecurseOctreeOperator* operatorObject) { diff --git a/libraries/octree/src/Octree.h b/libraries/octree/src/Octree.h index b827ed7cd0..a2b2f227cb 100644 --- a/libraries/octree/src/Octree.h +++ b/libraries/octree/src/Octree.h @@ -49,6 +49,9 @@ public: // Callback function, for recuseTreeWithOperation using RecurseOctreeOperation = std::function; +// Function for sorting octree children during recursion. If return value == FLT_MAX, child is discarded +using RecurseOctreeSortingOperation = std::function; +using SortedChild = std::pair; typedef QHash CubeList; const bool NO_EXISTS_BITS = false; @@ -163,17 +166,10 @@ public: OctreeElementPointer getOrCreateChildElementContaining(const AACube& box); void recurseTreeWithOperation(const RecurseOctreeOperation& operation, void* extraData = NULL); - void recurseTreeWithPostOperation(const RecurseOctreeOperation& operation, void* extraData = NULL); - - /// \param operation type of operation - /// \param point point in world-frame (meters) - /// \param extraData hook for user data to be interpreted by special context - void recurseTreeWithOperationDistanceSorted(const RecurseOctreeOperation& operation, - const glm::vec3& point, void* extraData = NULL); + void recurseTreeWithOperationSorted(const RecurseOctreeOperation& operation, const RecurseOctreeSortingOperation& sortingOperation, void* extraData = NULL); void recurseTreeWithOperator(RecurseOctreeOperator* operatorObject); - bool isDirty() const { return _isDirty; } void clearDirtyBit() { _isDirty = false; } void setDirtyBit() { _isDirty = true; } @@ -227,14 +223,8 @@ public: void recurseElementWithOperation(const OctreeElementPointer& element, const RecurseOctreeOperation& operation, void* extraData, int recursionCount = 0); - - /// Traverse child nodes of node applying operation in post-fix order - /// - void recurseElementWithPostOperation(const OctreeElementPointer& element, const RecurseOctreeOperation& operation, - void* extraData, int recursionCount = 0); - - void recurseElementWithOperationDistanceSorted(const OctreeElementPointer& element, const RecurseOctreeOperation& operation, - const glm::vec3& point, void* extraData, int recursionCount = 0); + bool recurseElementWithOperationSorted(const OctreeElementPointer& element, const RecurseOctreeOperation& operation, + const RecurseOctreeSortingOperation& sortingOperation, void* extraData, int recursionCount = 0); bool recurseElementWithOperator(const OctreeElementPointer& element, RecurseOctreeOperator* operatorObject, int recursionCount = 0); diff --git a/libraries/octree/src/OctreeElement.h b/libraries/octree/src/OctreeElement.h index b7857c3e6c..e9e7504e24 100644 --- a/libraries/octree/src/OctreeElement.h +++ b/libraries/octree/src/OctreeElement.h @@ -99,7 +99,7 @@ public: virtual bool deleteApproved() const { return true; } - virtual bool canRayIntersect() const { return isLeaf(); } + virtual bool canPickIntersect() const { return isLeaf(); } /// \param center center of sphere in meters /// \param radius radius of sphere in meters /// \param[out] penetration pointing into cube from sphere diff --git a/libraries/octree/src/OctreeProcessor.cpp b/libraries/octree/src/OctreeProcessor.cpp index beaac1198c..206ff399d9 100644 --- a/libraries/octree/src/OctreeProcessor.cpp +++ b/libraries/octree/src/OctreeProcessor.cpp @@ -192,6 +192,8 @@ void OctreeProcessor::processDatagram(ReceivedMessage& message, SharedNodePointe _elementsInLastWindow = 0; _entitiesInLastWindow = 0; } + + _lastOctreeMessageSequence = sequence; } } diff --git a/libraries/octree/src/OctreeProcessor.h b/libraries/octree/src/OctreeProcessor.h index 325b33cd15..1bc3bd10f9 100644 --- a/libraries/octree/src/OctreeProcessor.h +++ b/libraries/octree/src/OctreeProcessor.h @@ -56,6 +56,8 @@ public: float getAverageUncompressPerPacket() const { return _uncompressPerPacket.getAverage(); } float getAverageReadBitstreamPerPacket() const { return _readBitstreamPerPacket.getAverage(); } + OCTREE_PACKET_SEQUENCE getLastOctreeMessageSequence() const { return _lastOctreeMessageSequence; } + protected: virtual OctreePointer createTree() = 0; @@ -77,6 +79,7 @@ protected: int _packetsInLastWindow = 0; int _elementsInLastWindow = 0; int _entitiesInLastWindow = 0; + std::atomic _lastOctreeMessageSequence; }; diff --git a/libraries/octree/src/OctreeQuery.cpp b/libraries/octree/src/OctreeQuery.cpp index 0d56dbb88f..8c3685dc69 100644 --- a/libraries/octree/src/OctreeQuery.cpp +++ b/libraries/octree/src/OctreeQuery.cpp @@ -27,6 +27,10 @@ OctreeQuery::OctreeQuery(bool randomizeConnectionID) { } } +OctreeQuery::OctreeQueryFlags operator|=(OctreeQuery::OctreeQueryFlags& lhs, int rhs) { + return lhs = OctreeQuery::OctreeQueryFlags(lhs | rhs); +} + int OctreeQuery::getBroadcastData(unsigned char* destinationBuffer) { unsigned char* bufferStart = destinationBuffer; @@ -76,7 +80,12 @@ int OctreeQuery::getBroadcastData(unsigned char* destinationBuffer) { memcpy(destinationBuffer, binaryParametersDocument.data(), binaryParametersBytes); destinationBuffer += binaryParametersBytes; } - + + OctreeQueryFlags queryFlags { NoFlags }; + queryFlags |= (_reportInitialCompletion ? OctreeQuery::WantInitialCompletion : 0); + memcpy(destinationBuffer, &queryFlags, sizeof(queryFlags)); + destinationBuffer += sizeof(queryFlags); + return destinationBuffer - bufferStart; } @@ -150,6 +159,12 @@ int OctreeQuery::parseData(ReceivedMessage& message) { QWriteLocker jsonParameterLocker { &_jsonParametersLock }; _jsonParameters = newJsonDocument.object(); } - + + OctreeQueryFlags queryFlags; + memcpy(&queryFlags, sourceBuffer, sizeof(queryFlags)); + sourceBuffer += sizeof(queryFlags); + + _reportInitialCompletion = bool(queryFlags & OctreeQueryFlags::WantInitialCompletion); + return sourceBuffer - startPosition; } diff --git a/libraries/octree/src/OctreeQuery.h b/libraries/octree/src/OctreeQuery.h index 0ca75bdeb0..2c3c00ef05 100644 --- a/libraries/octree/src/OctreeQuery.h +++ b/libraries/octree/src/OctreeQuery.h @@ -52,6 +52,10 @@ public: bool hasReceivedFirstQuery() const { return _hasReceivedFirstQuery; } + // Want a report when the initial query is complete. + bool wantReportInitialCompletion() const { return _reportInitialCompletion; } + void setReportInitialCompletion(bool reportInitialCompletion) { _reportInitialCompletion = reportInitialCompletion; } + signals: void incomingConnectionIDChanged(); @@ -73,8 +77,12 @@ protected: QJsonObject _jsonParameters; QReadWriteLock _jsonParametersLock; + + enum OctreeQueryFlags : uint16_t { NoFlags = 0x0, WantInitialCompletion = 0x1 }; + friend OctreeQuery::OctreeQueryFlags operator|=(OctreeQuery::OctreeQueryFlags& lhs, const int rhs); bool _hasReceivedFirstQuery { false }; + bool _reportInitialCompletion { false }; }; #endif // hifi_OctreeQuery_h diff --git a/libraries/octree/src/OctreeUtils.cpp b/libraries/octree/src/OctreeUtils.cpp index 8eaf22e198..7ed9c2ed3c 100644 --- a/libraries/octree/src/OctreeUtils.cpp +++ b/libraries/octree/src/OctreeUtils.cpp @@ -64,10 +64,14 @@ float boundaryDistanceForRenderLevel(unsigned int renderLevel, float voxelSizeSc return voxelSizeScale / powf(2.0f, renderLevel); } -float getPerspectiveAccuracyAngle(float octreeSizeScale, int boundaryLevelAdjust) { +float getPerspectiveAccuracyAngleTan(float octreeSizeScale, int boundaryLevelAdjust) { const float maxScale = (float)TREE_SCALE; float visibleDistanceAtMaxScale = boundaryDistanceForRenderLevel(boundaryLevelAdjust, octreeSizeScale) / OCTREE_TO_MESH_RATIO; - return atan(maxScale / visibleDistanceAtMaxScale); + return (maxScale / visibleDistanceAtMaxScale); +} + +float getPerspectiveAccuracyAngle(float octreeSizeScale, int boundaryLevelAdjust) { + return atan(getPerspectiveAccuracyAngleTan(octreeSizeScale, boundaryLevelAdjust)); } float getOrthographicAccuracySize(float octreeSizeScale, int boundaryLevelAdjust) { @@ -75,9 +79,3 @@ float getOrthographicAccuracySize(float octreeSizeScale, int boundaryLevelAdjust const float smallestSize = 0.01f; return (smallestSize * MAX_VISIBILITY_DISTANCE_FOR_UNIT_ELEMENT) / boundaryDistanceForRenderLevel(boundaryLevelAdjust, octreeSizeScale); } - -bool isAngularSizeBigEnough(glm::vec3 position, const AACube& cube, float lodScaleFactor, float minDiameter) { - float distance = glm::distance(cube.calcCenter(), position) + MIN_VISIBLE_DISTANCE; - float angularDiameter = cube.getScale() / distance; - return angularDiameter > minDiameter * lodScaleFactor; -} diff --git a/libraries/octree/src/OctreeUtils.h b/libraries/octree/src/OctreeUtils.h index dff56cad64..eedbfe8bda 100644 --- a/libraries/octree/src/OctreeUtils.h +++ b/libraries/octree/src/OctreeUtils.h @@ -29,6 +29,7 @@ float calculateRenderAccuracy(const glm::vec3& position, float boundaryDistanceForRenderLevel(unsigned int renderLevel, float voxelSizeScale); +float getPerspectiveAccuracyAngleTan(float octreeSizeScale, int boundaryLevelAdjust); float getPerspectiveAccuracyAngle(float octreeSizeScale, int boundaryLevelAdjust); float getOrthographicAccuracySize(float octreeSizeScale, int boundaryLevelAdjust); @@ -38,6 +39,4 @@ const float MIN_ELEMENT_ANGULAR_DIAMETER = 0.0043301f; // radians const float MIN_ENTITY_ANGULAR_DIAMETER = MIN_ELEMENT_ANGULAR_DIAMETER * SQRT_THREE; const float MIN_VISIBLE_DISTANCE = 0.0001f; // helps avoid divide-by-zero check -bool isAngularSizeBigEnough(glm::vec3 position, const AACube& cube, float lodScaleFactor, float minDiameter); - #endif // hifi_OctreeUtils_h diff --git a/libraries/physics/src/CharacterController.cpp b/libraries/physics/src/CharacterController.cpp index cee0e6a1fa..b716d5c0d1 100755 --- a/libraries/physics/src/CharacterController.cpp +++ b/libraries/physics/src/CharacterController.cpp @@ -121,6 +121,8 @@ void CharacterController::setDynamicsWorld(btDynamicsWorld* world) { _dynamicsWorld->addAction(this); // restore gravity settings because adding an object to the world overwrites its gravity setting _rigidBody->setGravity(_currentGravity * _currentUp); + // set flag to enable custom contactAddedCallback + _rigidBody->setCollisionFlags(btCollisionObject::CF_CUSTOM_MATERIAL_CALLBACK); btCollisionShape* shape = _rigidBody->getCollisionShape(); assert(shape && shape->getShapeType() == CONVEX_HULL_SHAPE_PROXYTYPE); _ghost.setCharacterShape(static_cast(shape)); @@ -294,14 +296,14 @@ void CharacterController::playerStep(btCollisionWorld* collisionWorld, btScalar // compute the angle we will resolve for this dt, but don't overshoot float angle = 2.0f * acosf(qDot); - if ( dt < _followTimeRemaining) { + if (dt < _followTimeRemaining) { angle *= dt / _followTimeRemaining; } - + // accumulate rotation deltaRot = btQuaternion(axis, angle); _followAngularDisplacement = (deltaRot * _followAngularDisplacement).normalize(); - + // in order to accumulate displacement of avatar position, we need to take _shapeLocalOffset into account. btVector3 shapeLocalOffset = glmToBullet(_shapeLocalOffset); diff --git a/libraries/physics/src/CollisionRenderMeshCache.cpp b/libraries/physics/src/CollisionRenderMeshCache.cpp deleted file mode 100644 index 6f66b9af10..0000000000 --- a/libraries/physics/src/CollisionRenderMeshCache.cpp +++ /dev/null @@ -1,217 +0,0 @@ -// -// CollisionRenderMeshCache.cpp -// libraries/physics/src -// -// Created by Andrew Meadows 2016.07.13 -// Copyright 2016 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#include "CollisionRenderMeshCache.h" - -#include - -#include -#include - -#include // for MAX_HULL_POINTS - -const int32_t MAX_HULL_INDICES = 6 * MAX_HULL_POINTS; -const int32_t MAX_HULL_NORMALS = MAX_HULL_INDICES; -float tempVertices[MAX_HULL_NORMALS]; -graphics::Index tempIndexBuffer[MAX_HULL_INDICES]; - -bool copyShapeToMesh(const btTransform& transform, const btConvexShape* shape, - gpu::BufferView& vertices, gpu::BufferView& indices, gpu::BufferView& parts, - gpu::BufferView& normals) { - assert(shape); - - btShapeHull hull(shape); - if (!hull.buildHull(shape->getMargin())) { - return false; - } - - int32_t numHullIndices = hull.numIndices(); - assert(numHullIndices <= MAX_HULL_INDICES); - - int32_t numHullVertices = hull.numVertices(); - assert(numHullVertices <= MAX_HULL_POINTS); - - { // new part - graphics::Mesh::Part part; - part._startIndex = (graphics::Index)indices.getNumElements(); - part._numIndices = (graphics::Index)numHullIndices; - // FIXME: the render code cannot handle the case where part._baseVertex != 0 - //part._baseVertex = vertices.getNumElements(); // DOES NOT WORK - part._baseVertex = 0; - - gpu::BufferView::Size numBytes = sizeof(graphics::Mesh::Part); - const gpu::Byte* data = reinterpret_cast(&part); - parts._buffer->append(numBytes, data); - parts._size = parts._buffer->getSize(); - } - - const int32_t SIZE_OF_VEC3 = 3 * sizeof(float); - graphics::Index indexOffset = (graphics::Index)vertices.getNumElements(); - - { // new indices - const uint32_t* hullIndices = hull.getIndexPointer(); - // FIXME: the render code cannot handle the case where part._baseVertex != 0 - // so we must add an offset to each index - for (int32_t i = 0; i < numHullIndices; ++i) { - tempIndexBuffer[i] = hullIndices[i] + indexOffset; - } - const gpu::Byte* data = reinterpret_cast(tempIndexBuffer); - gpu::BufferView::Size numBytes = (gpu::BufferView::Size)(sizeof(graphics::Index) * numHullIndices); - indices._buffer->append(numBytes, data); - indices._size = indices._buffer->getSize(); - } - { // new vertices - const btVector3* hullVertices = hull.getVertexPointer(); - assert(numHullVertices <= MAX_HULL_POINTS); - for (int32_t i = 0; i < numHullVertices; ++i) { - btVector3 transformedPoint = transform * hullVertices[i]; - memcpy(tempVertices + 3 * i, transformedPoint.m_floats, SIZE_OF_VEC3); - } - gpu::BufferView::Size numBytes = sizeof(float) * (3 * numHullVertices); - const gpu::Byte* data = reinterpret_cast(tempVertices); - vertices._buffer->append(numBytes, data); - vertices._size = vertices._buffer->getSize(); - } - { // new normals - // compute average point - btVector3 avgVertex(0.0f, 0.0f, 0.0f); - const btVector3* hullVertices = hull.getVertexPointer(); - for (int i = 0; i < numHullVertices; ++i) { - avgVertex += hullVertices[i]; - } - avgVertex = transform * (avgVertex * (1.0f / (float)numHullVertices)); - - for (int i = 0; i < numHullVertices; ++i) { - btVector3 norm = transform * hullVertices[i] - avgVertex; - btScalar normLength = norm.length(); - if (normLength > FLT_EPSILON) { - norm /= normLength; - } - memcpy(tempVertices + 3 * i, norm.m_floats, SIZE_OF_VEC3); - } - gpu::BufferView::Size numBytes = sizeof(float) * (3 * numHullVertices); - const gpu::Byte* data = reinterpret_cast(tempVertices); - normals._buffer->append(numBytes, data); - normals._size = vertices._buffer->getSize(); - } - return true; -} - -graphics::MeshPointer createMeshFromShape(const void* pointer) { - graphics::MeshPointer mesh; - if (!pointer) { - return mesh; - } - - // pointer must be a const btCollisionShape* (cast to void*), but it only - // needs to be valid here when its render mesh is created, after this call - // the cache doesn't care what happens to the shape behind the pointer - const btCollisionShape* shape = static_cast(pointer); - - int32_t shapeType = shape->getShapeType(); - if (shapeType == (int32_t)COMPOUND_SHAPE_PROXYTYPE || shape->isConvex()) { - // allocate buffers for it - gpu::BufferView vertices(new gpu::Buffer(), gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ)); - gpu::BufferView indices(new gpu::Buffer(), gpu::Element(gpu::SCALAR, gpu::UINT32, gpu::INDEX)); - gpu::BufferView parts(new gpu::Buffer(), gpu::Element(gpu::VEC4, gpu::UINT32, gpu::PART)); - gpu::BufferView normals(new gpu::Buffer(), gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ)); - - int32_t numSuccesses = 0; - if (shapeType == (int32_t)COMPOUND_SHAPE_PROXYTYPE) { - const btCompoundShape* compoundShape = static_cast(shape); - int32_t numSubShapes = compoundShape->getNumChildShapes(); - for (int32_t i = 0; i < numSubShapes; ++i) { - const btCollisionShape* childShape = compoundShape->getChildShape(i); - if (childShape->isConvex()) { - const btConvexShape* convexShape = static_cast(childShape); - if (copyShapeToMesh(compoundShape->getChildTransform(i), convexShape, vertices, indices, parts, normals)) { - numSuccesses++; - } - } - } - } else { - // shape is convex - const btConvexShape* convexShape = static_cast(shape); - btTransform transform; - transform.setIdentity(); - if (copyShapeToMesh(transform, convexShape, vertices, indices, parts, normals)) { - numSuccesses++; - } - } - if (numSuccesses > 0) { - mesh = std::make_shared(); - mesh->setVertexBuffer(vertices); - mesh->setIndexBuffer(indices); - mesh->setPartBuffer(parts); - mesh->addAttribute(gpu::Stream::NORMAL, normals); - } else { - // TODO: log failure message here - } - } - return mesh; -} - -CollisionRenderMeshCache::CollisionRenderMeshCache() { -} - -CollisionRenderMeshCache::~CollisionRenderMeshCache() { - _meshMap.clear(); - _pendingGarbage.clear(); -} - -graphics::MeshPointer CollisionRenderMeshCache::getMesh(CollisionRenderMeshCache::Key key) { - graphics::MeshPointer mesh; - if (key) { - CollisionMeshMap::const_iterator itr = _meshMap.find(key); - if (itr == _meshMap.end()) { - // make mesh and add it to map - mesh = createMeshFromShape(key); - if (mesh) { - _meshMap.insert(std::make_pair(key, mesh)); - } - } else { - mesh = itr->second; - } - } - const uint32_t MAX_NUM_PENDING_GARBAGE = 20; - if (_pendingGarbage.size() > MAX_NUM_PENDING_GARBAGE) { - collectGarbage(); - } - return mesh; -} - -bool CollisionRenderMeshCache::releaseMesh(CollisionRenderMeshCache::Key key) { - if (!key) { - return false; - } - CollisionMeshMap::const_iterator itr = _meshMap.find(key); - if (itr != _meshMap.end()) { - _pendingGarbage.push_back(key); - return true; - } - return false; -} - -void CollisionRenderMeshCache::collectGarbage() { - uint32_t numShapes = (uint32_t)_pendingGarbage.size(); - for (uint32_t i = 0; i < numShapes; ++i) { - CollisionRenderMeshCache::Key key = _pendingGarbage[i]; - CollisionMeshMap::const_iterator itr = _meshMap.find(key); - if (itr != _meshMap.end()) { - if ((*itr).second.use_count() == 1) { - // we hold the only reference - _meshMap.erase(itr); - } - } - } - _pendingGarbage.clear(); -} - diff --git a/libraries/physics/src/CollisionRenderMeshCache.h b/libraries/physics/src/CollisionRenderMeshCache.h deleted file mode 100644 index c5b643c0cc..0000000000 --- a/libraries/physics/src/CollisionRenderMeshCache.h +++ /dev/null @@ -1,48 +0,0 @@ -// -// CollisionRenderMeshCache.h -// libraries/physics/src -// -// Created by Andrew Meadows 2016.07.13 -// Copyright 2016 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#ifndef hifi_CollisionRenderMeshCache_h -#define hifi_CollisionRenderMeshCache_h - -#include -#include -#include - -#include - - -class CollisionRenderMeshCache { -public: - using Key = const void*; // must actually be a const btCollisionShape* - - CollisionRenderMeshCache(); - ~CollisionRenderMeshCache(); - - /// \return pointer to geometry - graphics::MeshPointer getMesh(Key key); - - /// \return true if geometry was found and released - bool releaseMesh(Key key); - - /// delete geometries that have zero references - void collectGarbage(); - - // validation methods - uint32_t getNumMeshes() const { return (uint32_t)_meshMap.size(); } - bool hasMesh(Key key) const { return _meshMap.find(key) == _meshMap.end(); } - -private: - using CollisionMeshMap = std::unordered_map; - CollisionMeshMap _meshMap; - std::vector _pendingGarbage; -}; - -#endif // hifi_CollisionRenderMeshCache_h diff --git a/libraries/physics/src/EntityMotionState.cpp b/libraries/physics/src/EntityMotionState.cpp index a610a6f2a6..2c130274bc 100644 --- a/libraries/physics/src/EntityMotionState.cpp +++ b/libraries/physics/src/EntityMotionState.cpp @@ -307,13 +307,6 @@ const btCollisionShape* EntityMotionState::computeNewShape() { return getShapeManager()->getShape(shapeInfo); } -void EntityMotionState::setShape(const btCollisionShape* shape) { - if (_shape != shape) { - ObjectMotionState::setShape(shape); - _entity->setCollisionShape(_shape); - } -} - bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) { // NOTE: this method is only ever called when the entity simulation is locally owned DETAILED_PROFILE_RANGE(simulation_physics, "CheckOutOfSync"); @@ -785,8 +778,10 @@ void EntityMotionState::computeCollisionGroupAndMask(int32_t& group, int32_t& ma bool EntityMotionState::shouldSendBid() const { // NOTE: this method is only ever called when the entity's simulation is NOT locally owned - return _body->isActive() && (_region == workload::Region::R1) && - glm::max(glm::max(VOLUNTEER_SIMULATION_PRIORITY, _bumpedPriority), _entity->getScriptSimulationPriority()) >= _entity->getSimulationPriority(); + return _body->isActive() + && (_region == workload::Region::R1) + && glm::max(glm::max(VOLUNTEER_SIMULATION_PRIORITY, _bumpedPriority), _entity->getScriptSimulationPriority()) >= _entity->getSimulationPriority() + && !_entity->getLocked(); } uint8_t EntityMotionState::computeFinalBidPriority() const { diff --git a/libraries/physics/src/EntityMotionState.h b/libraries/physics/src/EntityMotionState.h index b215537d55..653e3f4252 100644 --- a/libraries/physics/src/EntityMotionState.h +++ b/libraries/physics/src/EntityMotionState.h @@ -118,7 +118,6 @@ protected: bool isReadyToComputeShape() const override; const btCollisionShape* computeNewShape() override; - void setShape(const btCollisionShape* shape) override; void setMotionType(PhysicsMotionType motionType) override; // EntityMotionState keeps a SharedPointer to its EntityItem which is only set in the CTOR diff --git a/libraries/physics/src/ObjectMotionState.cpp b/libraries/physics/src/ObjectMotionState.cpp index 0e6eb2511d..c814140930 100644 --- a/libraries/physics/src/ObjectMotionState.cpp +++ b/libraries/physics/src/ObjectMotionState.cpp @@ -64,9 +64,9 @@ ShapeManager* ObjectMotionState::getShapeManager() { } ObjectMotionState::ObjectMotionState(const btCollisionShape* shape) : - _shape(shape), _lastKinematicStep(worldSimulationStep) { + setShape(shape); } ObjectMotionState::~ObjectMotionState() { @@ -155,26 +155,25 @@ void ObjectMotionState::setMotionType(PhysicsMotionType motionType) { // Update the Continuous Collision Detection (CCD) configuration settings of our RigidBody so that // CCD will be enabled automatically when its speed surpasses a certain threshold. void ObjectMotionState::updateCCDConfiguration() { - if (_body) { - if (_shape) { - // If this object moves faster than its bounding radius * RADIUS_MOTION_THRESHOLD_MULTIPLIER, - // CCD will be enabled for this object. - const auto RADIUS_MOTION_THRESHOLD_MULTIPLIER = 0.5f; + assert(_body); + if (_shape && _shape->getShapeType() != TRIANGLE_MESH_SHAPE_PROXYTYPE) { + // find minumum dimension of shape + btVector3 aabbMin, aabbMax; + btTransform transform; + transform.setIdentity(); + _shape->getAabb(transform, aabbMin, aabbMax); + aabbMin = aabbMax - aabbMin; + btScalar radius = *((btScalar*)(aabbMin) + aabbMin.minAxis()); - btVector3 center; - btScalar radius; - _shape->getBoundingSphere(center, radius); - _body->setCcdMotionThreshold(radius * RADIUS_MOTION_THRESHOLD_MULTIPLIER); + // use the minimum dimension as the radius of the CCD proxy sphere + _body->setCcdSweptSphereRadius(radius); - // TODO: Ideally the swept sphere radius would be contained by the object. Using the bounding sphere - // radius works well for spherical objects, but may cause issues with other shapes. For arbitrary - // objects we may want to consider a different approach, such as grouping rigid bodies together. - - _body->setCcdSweptSphereRadius(radius); - } else { - // Disable CCD - _body->setCcdMotionThreshold(0); - } + // also use the radius as the motion threshold for enabling CCD + _body->setCcdMotionThreshold(radius); + } else { + // disable CCD + _body->setCcdSweptSphereRadius(0.0f); + _body->setCcdMotionThreshold(0.0f); } } @@ -188,8 +187,8 @@ void ObjectMotionState::setRigidBody(btRigidBody* body) { if (_body) { _body->setUserPointer(this); assert(_body->getCollisionShape() == _shape); + updateCCDConfiguration(); } - updateCCDConfiguration(); } } @@ -199,6 +198,9 @@ void ObjectMotionState::setShape(const btCollisionShape* shape) { getShapeManager()->releaseShape(_shape); } _shape = shape; + if (_body) { + updateCCDConfiguration(); + } } } @@ -312,7 +314,6 @@ bool ObjectMotionState::handleHardAndEasyChanges(uint32_t& flags, PhysicsEngine* } else { _body->setCollisionShape(const_cast(newShape)); setShape(newShape); - updateCCDConfiguration(); } } if (flags & EASY_DIRTY_PHYSICS_FLAGS) { diff --git a/libraries/physics/src/ObjectMotionState.h b/libraries/physics/src/ObjectMotionState.h index 7439c1c38d..74173c3f47 100644 --- a/libraries/physics/src/ObjectMotionState.h +++ b/libraries/physics/src/ObjectMotionState.h @@ -175,13 +175,13 @@ protected: virtual void setMotionType(PhysicsMotionType motionType); void updateCCDConfiguration(); - void setRigidBody(btRigidBody* body); + virtual void setRigidBody(btRigidBody* body); virtual void setShape(const btCollisionShape* shape); MotionStateType _type { MOTIONSTATE_TYPE_INVALID }; // type of MotionState PhysicsMotionType _motionType { MOTION_TYPE_STATIC }; // type of motion: KINEMATIC, DYNAMIC, or STATIC - const btCollisionShape* _shape; + const btCollisionShape* _shape { nullptr }; btRigidBody* _body { nullptr }; float _density { 1.0f }; diff --git a/libraries/physics/src/PhysicsEngine.cpp b/libraries/physics/src/PhysicsEngine.cpp index 66a4edb486..58c197d6f4 100644 --- a/libraries/physics/src/PhysicsEngine.cpp +++ b/libraries/physics/src/PhysicsEngine.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include "CharacterController.h" #include "ObjectMotionState.h" @@ -26,6 +27,7 @@ #include "ThreadSafeDynamicsWorld.h" #include "PhysicsLogging.h" + PhysicsEngine::PhysicsEngine(const glm::vec3& offset) : _originOffset(offset), _myAvatarController(nullptr) { @@ -75,6 +77,10 @@ uint32_t PhysicsEngine::getNumSubsteps() const { return _dynamicsWorld->getNumSubsteps(); } +int32_t PhysicsEngine::getNumCollisionObjects() const { + return _dynamicsWorld ? _dynamicsWorld->getNumCollisionObjects() : 0; +} + // private void PhysicsEngine::addObjectToDynamicsWorld(ObjectMotionState* motionState) { assert(motionState); @@ -277,6 +283,57 @@ void PhysicsEngine::reinsertObject(ObjectMotionState* object) { } } +void PhysicsEngine::processTransaction(PhysicsEngine::Transaction& transaction) { + // removes + for (auto object : transaction.objectsToRemove) { + btRigidBody* body = object->getRigidBody(); + if (body) { + removeDynamicsForBody(body); + _dynamicsWorld->removeRigidBody(body); + + // NOTE: setRigidBody() modifies body->m_userPointer so we should clear the MotionState's body BEFORE deleting it. + object->setRigidBody(nullptr); + body->setMotionState(nullptr); + delete body; + } + object->clearIncomingDirtyFlags(); + } + + // adds + for (auto object : transaction.objectsToAdd) { + addObjectToDynamicsWorld(object); + } + + // changes + std::vector failedChanges; + for (auto object : transaction.objectsToChange) { + uint32_t flags = object->getIncomingDirtyFlags() & DIRTY_PHYSICS_FLAGS; + if (flags & HARD_DIRTY_PHYSICS_FLAGS) { + if (object->handleHardAndEasyChanges(flags, this)) { + object->clearIncomingDirtyFlags(); + } else { + failedChanges.push_back(object); + } + } else if (flags & EASY_DIRTY_PHYSICS_FLAGS) { + object->handleEasyChanges(flags); + object->clearIncomingDirtyFlags(); + } + if (object->getMotionType() == MOTION_TYPE_STATIC && object->isActive()) { + _activeStaticBodies.insert(object->getRigidBody()); + } + } + // activeStaticBodies have changed (in an Easy way) and need their Aabbs updated + // but we've configured Bullet to NOT update them automatically (for improved performance) + // so we must do it ourselves + std::set::const_iterator itr = _activeStaticBodies.begin(); + while (itr != _activeStaticBodies.end()) { + _dynamicsWorld->updateSingleAabb(*itr); + ++itr; + } + // we replace objectsToChange with any that failed + transaction.objectsToChange.swap(failedChanges); +} + void PhysicsEngine::removeContacts(ObjectMotionState* motionState) { // trigger events for new/existing/old contacts ContactMap::iterator contactItr = _contactMap.begin(); @@ -840,3 +897,93 @@ void PhysicsEngine::setShowBulletConstraintLimits(bool value) { } } +struct AllContactsCallback : public btCollisionWorld::ContactResultCallback { + AllContactsCallback(int32_t mask, int32_t group, const ShapeInfo& shapeInfo, const Transform& transform, btCollisionObject* myAvatarCollisionObject, float threshold) : + btCollisionWorld::ContactResultCallback(), + collisionObject(), + contacts(), + myAvatarCollisionObject(myAvatarCollisionObject), + threshold(threshold) { + const btCollisionShape* collisionShape = ObjectMotionState::getShapeManager()->getShape(shapeInfo); + + collisionObject.setCollisionShape(const_cast(collisionShape)); + + btTransform bulletTransform; + bulletTransform.setOrigin(glmToBullet(transform.getTranslation())); + bulletTransform.setRotation(glmToBullet(transform.getRotation())); + + collisionObject.setWorldTransform(bulletTransform); + + m_collisionFilterMask = mask; + m_collisionFilterGroup = group; + } + + ~AllContactsCallback() { + ObjectMotionState::getShapeManager()->releaseShape(collisionObject.getCollisionShape()); + } + + btCollisionObject collisionObject; + std::vector contacts; + btCollisionObject* myAvatarCollisionObject; + btScalar threshold; + + btScalar addSingleResult(btManifoldPoint& cp, const btCollisionObjectWrapper* colObj0, int partId0, int index0, const btCollisionObjectWrapper* colObj1, int partId1, int index1) override { + if (cp.m_distance1 > -threshold) { + return 0; + } + + const btCollisionObject* otherBody; + btVector3 penetrationPoint; + btVector3 otherPenetrationPoint; + if (colObj0->m_collisionObject == &collisionObject) { + otherBody = colObj1->m_collisionObject; + penetrationPoint = getWorldPoint(cp.m_localPointB, colObj1->getWorldTransform()); + otherPenetrationPoint = getWorldPoint(cp.m_localPointA, colObj0->getWorldTransform()); + } else { + otherBody = colObj0->m_collisionObject; + penetrationPoint = getWorldPoint(cp.m_localPointA, colObj0->getWorldTransform()); + otherPenetrationPoint = getWorldPoint(cp.m_localPointB, colObj1->getWorldTransform()); + } + + // TODO: Give MyAvatar a motion state so we don't have to do this + if ((m_collisionFilterMask & BULLET_COLLISION_GROUP_MY_AVATAR) && myAvatarCollisionObject && myAvatarCollisionObject == otherBody) { + contacts.emplace_back(Physics::getSessionUUID(), bulletToGLM(penetrationPoint), bulletToGLM(otherPenetrationPoint)); + return 0; + } + + if (!(otherBody->getInternalType() & btCollisionObject::CO_RIGID_BODY)) { + return 0; + } + const btRigidBody* collisionCandidate = static_cast(otherBody); + + const btMotionState* motionStateCandidate = collisionCandidate->getMotionState(); + const ObjectMotionState* candidate = dynamic_cast(motionStateCandidate); + if (!candidate) { + return 0; + } + + // This is the correct object type. Add it to the list. + contacts.emplace_back(candidate->getObjectID(), bulletToGLM(penetrationPoint), bulletToGLM(otherPenetrationPoint)); + + return 0; + } + +protected: + static btVector3 getWorldPoint(const btVector3& localPoint, const btTransform& transform) { + return quatRotate(transform.getRotation(), localPoint) + transform.getOrigin(); + } +}; + +std::vector PhysicsEngine::contactTest(uint16_t mask, const ShapeInfo& regionShapeInfo, const Transform& regionTransform, uint16_t group, float threshold) const { + // TODO: Give MyAvatar a motion state so we don't have to do this + btCollisionObject* myAvatarCollisionObject = nullptr; + if ((mask & USER_COLLISION_GROUP_MY_AVATAR) && _myAvatarController) { + myAvatarCollisionObject = _myAvatarController->getCollisionObject(); + } + + auto contactCallback = AllContactsCallback((int32_t)mask, (int32_t)group, regionShapeInfo, regionTransform, myAvatarCollisionObject, threshold); + _dynamicsWorld->contactTest(&contactCallback.collisionObject, contactCallback); + + return contactCallback.contacts; +} + diff --git a/libraries/physics/src/PhysicsEngine.h b/libraries/physics/src/PhysicsEngine.h index 2ac195956a..09b68d9a8b 100644 --- a/libraries/physics/src/PhysicsEngine.h +++ b/libraries/physics/src/PhysicsEngine.h @@ -43,16 +43,51 @@ public: void* _b; // ObjectMotionState pointer }; +struct ContactTestResult { + ContactTestResult() = delete; + + ContactTestResult(const ContactTestResult& contactTestResult) : + foundID(contactTestResult.foundID), + testCollisionPoint(contactTestResult.testCollisionPoint), + foundCollisionPoint(contactTestResult.foundCollisionPoint) { + } + + ContactTestResult(QUuid foundID, glm::vec3 testCollisionPoint, glm::vec3 otherCollisionPoint) : + foundID(foundID), + testCollisionPoint(testCollisionPoint), + foundCollisionPoint(otherCollisionPoint) { + } + + QUuid foundID; + // The deepest point of an intersection within the volume of the test shape, in world space. + glm::vec3 testCollisionPoint; + // The deepest point of an intersection within the volume of the found object, in world space. + glm::vec3 foundCollisionPoint; +}; + using ContactMap = std::map; using CollisionEvents = std::vector; class PhysicsEngine { public: + class Transaction { + public: + void clear() { + objectsToRemove.clear(); + objectsToAdd.clear(); + objectsToChange.clear(); + } + std::vector objectsToRemove; + std::vector objectsToAdd; + std::vector objectsToChange; + }; + PhysicsEngine(const glm::vec3& offset); ~PhysicsEngine(); void init(); uint32_t getNumSubsteps() const; + int32_t getNumCollisionObjects() const; void removeObjects(const VectorOfMotionStates& objects); void removeSetOfObjects(const SetOfMotionStates& objects); // only called during teardown @@ -61,6 +96,8 @@ public: VectorOfMotionStates changeObjects(const VectorOfMotionStates& objects); void reinsertObject(ObjectMotionState* object); + void processTransaction(Transaction& transaction); + void stepSimulation(); void harvestPerformanceStats(); void printPerformanceStatsToFile(const QString& filename); @@ -103,6 +140,10 @@ public: void setShowBulletConstraints(bool value); void setShowBulletConstraintLimits(bool value); + // Function for getting colliding objects in the world of specified type + // See PhysicsCollisionGroups.h for mask flags. + std::vector contactTest(uint16_t mask, const ShapeInfo& regionShapeInfo, const Transform& regionTransform, uint16_t group = USER_COLLISION_GROUP_DYNAMIC, float threshold = 0.0f) const; + private: QList removeDynamicsForBody(btRigidBody* body); void addObjectToDynamicsWorld(ObjectMotionState* motionState); @@ -134,7 +175,7 @@ private: CharacterController* _myAvatarController; - uint32_t _numContactFrames = 0; + uint32_t _numContactFrames { 0 }; bool _dumpNextStats { false }; bool _saveNextStats { false }; diff --git a/libraries/pointers/src/Pick.cpp b/libraries/pointers/src/Pick.cpp index 4315409bdb..7ac53a2643 100644 --- a/libraries/pointers/src/Pick.cpp +++ b/libraries/pointers/src/Pick.cpp @@ -72,11 +72,15 @@ PickResultPointer PickQuery::getPrevPickResult() const { void PickQuery::setIgnoreItems(const QVector& ignoreItems) { withWriteLock([&] { _ignoreItems = ignoreItems; + // We sort these items here so the PickCacheOptimizer can catch cases where two picks have the same ignoreItems in a different order + std::sort(_ignoreItems.begin(), _ignoreItems.end(), std::less()); }); } void PickQuery::setIncludeItems(const QVector& includeItems) { withWriteLock([&] { _includeItems = includeItems; + // We sort these items here so the PickCacheOptimizer can catch cases where two picks have the same includeItems in a different order + std::sort(_includeItems.begin(), _includeItems.end(), std::less()); }); } \ No newline at end of file diff --git a/libraries/pointers/src/Pick.h b/libraries/pointers/src/Pick.h index 53606b154f..099a791407 100644 --- a/libraries/pointers/src/Pick.h +++ b/libraries/pointers/src/Pick.h @@ -17,6 +17,7 @@ #include #include +#include enum IntersectionType { NONE = 0, @@ -145,6 +146,7 @@ public: * * @property {number} Ray Ray Picks intersect a ray with the nearest object in front of them, along a given direction. * @property {number} Stylus Stylus Picks provide "tapping" functionality on/into flat surfaces. + * @property {number} Parabola Parabola Picks intersect a parabola with the nearest object in front of them, with a given initial velocity and acceleration. */ /**jsdoc * @@ -154,6 +156,7 @@ public: * * * + * * *
{@link PickType(0)|PickType.Ray}
{@link PickType(0)|PickType.Stylus}
{@link PickType(0)|PickType.Parabola}
* @typedef {number} PickType @@ -161,7 +164,8 @@ public: enum PickType { Ray = 0, Stylus, - + Parabola, + Collision, NUM_PICK_TYPES }; Q_ENUM(PickType) @@ -210,6 +214,10 @@ public: virtual bool isRightHand() const { return false; } virtual bool isMouse() const { return false; } + virtual Transform getResultTransform() const = 0; + + std::shared_ptr parentTransform; + private: PickFilter _filter; const float _maxDistance; diff --git a/libraries/pointers/src/PickCacheOptimizer.h b/libraries/pointers/src/PickCacheOptimizer.h index 49a039935c..c74e84937b 100644 --- a/libraries/pointers/src/PickCacheOptimizer.h +++ b/libraries/pointers/src/PickCacheOptimizer.h @@ -37,7 +37,7 @@ template class PickCacheOptimizer { public: - void update(std::unordered_map>& picks, uint32_t& nextToUpdate, uint64_t expiry, bool shouldPickHUD); + QVector4D update(std::unordered_map>& picks, uint32_t& nextToUpdate, uint64_t expiry, bool shouldPickHUD); protected: typedef std::unordered_map> PickCache; @@ -57,8 +57,8 @@ bool PickCacheOptimizer::checkAndCompareCachedResults(T& pick, PickCache& cac } template -void PickCacheOptimizer::cacheResult(const bool intersects, const PickResultPointer& resTemp, const PickCacheKey& key, PickResultPointer& res, T& mathPick, PickCache& cache, const std::shared_ptr> pick) { - if (intersects) { +void PickCacheOptimizer::cacheResult(const bool needToCompareResults, const PickResultPointer& resTemp, const PickCacheKey& key, PickResultPointer& res, T& mathPick, PickCache& cache, const std::shared_ptr> pick) { + if (needToCompareResults) { cache[mathPick][key] = resTemp; res = res->compareAndProcessNewResult(resTemp); } else { @@ -67,8 +67,9 @@ void PickCacheOptimizer::cacheResult(const bool intersects, const PickResultP } template -void PickCacheOptimizer::update(std::unordered_map>& picks, +QVector4D PickCacheOptimizer::update(std::unordered_map>& picks, uint32_t& nextToUpdate, uint64_t expiry, bool shouldPickHUD) { + QVector4D numIntersectionsComputed; PickCache results; const uint32_t INVALID_PICK_ID = 0; auto itr = picks.begin(); @@ -91,6 +92,7 @@ void PickCacheOptimizer::update(std::unordered_mapgetFilter().getEntityFlags(), pick->getIncludeItems(), pick->getIgnoreItems() }; if (!checkAndCompareCachedResults(mathematicalPick, results, res, entityKey)) { PickResultPointer entityRes = pick->getEntityIntersection(mathematicalPick); + numIntersectionsComputed[0]++; if (entityRes) { cacheResult(entityRes->doesIntersect(), entityRes, entityKey, res, mathematicalPick, results, pick); } @@ -101,6 +103,7 @@ void PickCacheOptimizer::update(std::unordered_mapgetFilter().getOverlayFlags(), pick->getIncludeItems(), pick->getIgnoreItems() }; if (!checkAndCompareCachedResults(mathematicalPick, results, res, overlayKey)) { PickResultPointer overlayRes = pick->getOverlayIntersection(mathematicalPick); + numIntersectionsComputed[1]++; if (overlayRes) { cacheResult(overlayRes->doesIntersect(), overlayRes, overlayKey, res, mathematicalPick, results, pick); } @@ -111,6 +114,7 @@ void PickCacheOptimizer::update(std::unordered_mapgetFilter().getAvatarFlags(), pick->getIncludeItems(), pick->getIgnoreItems() }; if (!checkAndCompareCachedResults(mathematicalPick, results, res, avatarKey)) { PickResultPointer avatarRes = pick->getAvatarIntersection(mathematicalPick); + numIntersectionsComputed[2]++; if (avatarRes) { cacheResult(avatarRes->doesIntersect(), avatarRes, avatarKey, res, mathematicalPick, results, pick); } @@ -122,6 +126,7 @@ void PickCacheOptimizer::update(std::unordered_mapgetFilter().getHUDFlags(), QVector(), QVector() }; if (!checkAndCompareCachedResults(mathematicalPick, results, res, hudKey)) { PickResultPointer hudRes = pick->getHUDIntersection(mathematicalPick); + numIntersectionsComputed[3]++; if (hudRes) { cacheResult(true, hudRes, hudKey, res, mathematicalPick, results, pick); } @@ -145,6 +150,7 @@ void PickCacheOptimizer::update(std::unordered_mapsecond].erase(uid); _typeMap.erase(uid); + _totalPickCounts[type->second]--; } }); } @@ -88,6 +90,14 @@ void PickManager::setIncludeItems(unsigned int uid, const QVector& includ } } +Transform PickManager::getResultTransform(unsigned int uid) const { + auto pick = findPick(uid); + if (pick) { + return pick->getResultTransform(); + } + return Transform(); +} + void PickManager::update() { uint64_t expiry = usecTimestampNow() + _perFrameTimeBudget; std::unordered_map>> cachedPicks; @@ -96,10 +106,12 @@ void PickManager::update() { }); bool shouldPickHUD = _shouldPickHUDOperator(); - // we pass the same expiry to both updates, but the stylus updates are relatively cheap - // and the rayPicks updae will ALWAYS update at least one ray even when there is no budget - _stylusPickCacheOptimizer.update(cachedPicks[PickQuery::Stylus], _nextPickToUpdate[PickQuery::Stylus], expiry, false); - _rayPickCacheOptimizer.update(cachedPicks[PickQuery::Ray], _nextPickToUpdate[PickQuery::Ray], expiry, shouldPickHUD); + // FIXME: give each type its own expiry + // Each type will update at least one pick, regardless of the expiry + _updatedPickCounts[PickQuery::Stylus] = _stylusPickCacheOptimizer.update(cachedPicks[PickQuery::Stylus], _nextPickToUpdate[PickQuery::Stylus], expiry, false); + _updatedPickCounts[PickQuery::Ray] = _rayPickCacheOptimizer.update(cachedPicks[PickQuery::Ray], _nextPickToUpdate[PickQuery::Ray], expiry, shouldPickHUD); + _updatedPickCounts[PickQuery::Parabola] = _parabolaPickCacheOptimizer.update(cachedPicks[PickQuery::Parabola], _nextPickToUpdate[PickQuery::Parabola], expiry, shouldPickHUD); + _updatedPickCounts[PickQuery::Collision] = _collisionPickCacheOptimizer.update(cachedPicks[PickQuery::Collision], _nextPickToUpdate[PickQuery::Collision], expiry, false); } bool PickManager::isLeftHand(unsigned int uid) { diff --git a/libraries/pointers/src/PickManager.h b/libraries/pointers/src/PickManager.h index 3b466be2bc..cf911236a1 100644 --- a/libraries/pointers/src/PickManager.h +++ b/libraries/pointers/src/PickManager.h @@ -16,7 +16,10 @@ #include -class PickManager : public Dependency, protected ReadWriteLockable { +#include + +class PickManager : public QObject, public Dependency, protected ReadWriteLockable { + Q_OBJECT SINGLETON_DEPENDENCY public: @@ -40,6 +43,8 @@ public: void setIgnoreItems(unsigned int uid, const QVector& ignore) const; void setIncludeItems(unsigned int uid, const QVector& include) const; + Transform getResultTransform(unsigned int uid) const; + bool isLeftHand(unsigned int uid); bool isRightHand(unsigned int uid); bool isMouse(unsigned int uid); @@ -53,20 +58,34 @@ public: unsigned int getPerFrameTimeBudget() const { return _perFrameTimeBudget; } void setPerFrameTimeBudget(unsigned int numUsecs) { _perFrameTimeBudget = numUsecs; } + bool getForceCoarsePicking() { return _forceCoarsePicking; } + + const std::vector& getUpdatedPickCounts() { return _updatedPickCounts; } + const std::vector& getTotalPickCounts() { return _totalPickCounts; } + +public slots: + void setForceCoarsePicking(bool forceCoarsePicking) { _forceCoarsePicking = forceCoarsePicking; } + protected: + std::vector _updatedPickCounts { PickQuery::NUM_PICK_TYPES }; + std::vector _totalPickCounts { 0, 0, 0, 0 }; + + bool _forceCoarsePicking { false }; std::function _shouldPickHUDOperator; std::function _calculatePos2DFromHUDOperator; std::shared_ptr findPick(unsigned int uid) const; std::unordered_map>> _picks; - unsigned int _nextPickToUpdate[PickQuery::NUM_PICK_TYPES] { 0, 0 }; + unsigned int _nextPickToUpdate[PickQuery::NUM_PICK_TYPES] { 0, 0, 0, 0 }; std::unordered_map _typeMap; unsigned int _nextPickID { INVALID_PICK_ID + 1 }; PickCacheOptimizer _rayPickCacheOptimizer; PickCacheOptimizer _stylusPickCacheOptimizer; + PickCacheOptimizer _parabolaPickCacheOptimizer; + PickCacheOptimizer _collisionPickCacheOptimizer; - static const unsigned int DEFAULT_PER_FRAME_TIME_BUDGET = 2 * USECS_PER_MSEC; + static const unsigned int DEFAULT_PER_FRAME_TIME_BUDGET = 3 * USECS_PER_MSEC; unsigned int _perFrameTimeBudget { DEFAULT_PER_FRAME_TIME_BUDGET }; }; diff --git a/libraries/pointers/src/PickTransformNode.cpp b/libraries/pointers/src/PickTransformNode.cpp new file mode 100644 index 0000000000..fe011b7fcd --- /dev/null +++ b/libraries/pointers/src/PickTransformNode.cpp @@ -0,0 +1,26 @@ +// +// Created by Sabrina Shanman 8/22/2018 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "PickTransformNode.h" + +#include "DependencyManager.h" +#include "PickManager.h" + +PickTransformNode::PickTransformNode(unsigned int uid) : + _uid(uid) +{ +} + +Transform PickTransformNode::getTransform() { + auto pickManager = DependencyManager::get(); + if (!pickManager) { + return Transform(); + } + + return pickManager->getResultTransform(_uid); +} \ No newline at end of file diff --git a/libraries/pointers/src/PickTransformNode.h b/libraries/pointers/src/PickTransformNode.h new file mode 100644 index 0000000000..b4c92f3ba7 --- /dev/null +++ b/libraries/pointers/src/PickTransformNode.h @@ -0,0 +1,23 @@ +// +// Created by Sabrina Shanman 8/22/2018 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#ifndef hifi_PickTransformNode_h +#define hifi_PickTransformNode_h + +#include "TransformNode.h" + +// TODO: Remove this class when Picks are converted to SpatiallyNestables +class PickTransformNode : public TransformNode { +public: + PickTransformNode(unsigned int uid); + Transform getTransform() override; + +protected: + unsigned int _uid; +}; + +#endif // hifi_PickTransformNode_h \ No newline at end of file diff --git a/libraries/pointers/src/Pointer.h b/libraries/pointers/src/Pointer.h index 0c842dbd88..4264a60079 100644 --- a/libraries/pointers/src/Pointer.h +++ b/libraries/pointers/src/Pointer.h @@ -50,6 +50,8 @@ public: virtual void setRenderState(const std::string& state) = 0; virtual void editRenderState(const std::string& state, const QVariant& startProps, const QVariant& pathProps, const QVariant& endProps) = 0; + virtual QVariantMap toVariantMap() const = 0; + virtual void setPrecisionPicking(bool precisionPicking); virtual void setIgnoreItems(const QVector& ignoreItems) const; virtual void setIncludeItems(const QVector& includeItems) const; diff --git a/libraries/pointers/src/PointerManager.cpp b/libraries/pointers/src/PointerManager.cpp index be890da392..922f0bb5bc 100644 --- a/libraries/pointers/src/PointerManager.cpp +++ b/libraries/pointers/src/PointerManager.cpp @@ -77,6 +77,15 @@ PickResultPointer PointerManager::getPrevPickResult(unsigned int uid) const { return result; } +QVariantMap PointerManager::getPointerProperties(unsigned int uid) const { + auto pointer = find(uid); + if (pointer) { + return pointer->toVariantMap(); + } else { + return QVariantMap(); + } +} + void PointerManager::update() { auto cachedPointers = resultWithReadLock>>([&] { return _pointers; diff --git a/libraries/pointers/src/PointerManager.h b/libraries/pointers/src/PointerManager.h index b98558622f..2d0b2a107e 100644 --- a/libraries/pointers/src/PointerManager.h +++ b/libraries/pointers/src/PointerManager.h @@ -30,6 +30,7 @@ public: void setRenderState(unsigned int uid, const std::string& renderState) const; void editRenderState(unsigned int uid, const std::string& state, const QVariant& startProps, const QVariant& pathProps, const QVariant& endProps) const; PickResultPointer getPrevPickResult(unsigned int uid) const; + QVariantMap getPointerProperties(unsigned int uid) const; void setPrecisionPicking(unsigned int uid, bool precisionPicking) const; void setIgnoreItems(unsigned int uid, const QVector& ignoreEntities) const; diff --git a/libraries/procedural/CMakeLists.txt b/libraries/procedural/CMakeLists.txt index 9ec7cb6431..6d6610a323 100644 --- a/libraries/procedural/CMakeLists.txt +++ b/libraries/procedural/CMakeLists.txt @@ -1,5 +1,4 @@ set(TARGET_NAME procedural) -AUTOSCRIBE_SHADER_LIB(gpu graphics) setup_hifi_library() -link_hifi_libraries(shared gpu networking graphics model-networking ktx image) +link_hifi_libraries(shared gpu shaders networking graphics model-networking ktx image) diff --git a/libraries/procedural/src/procedural/Logging.h b/libraries/procedural/src/procedural/Logging.h index 3684d7c78f..77a25c305d 100644 --- a/libraries/procedural/src/procedural/Logging.h +++ b/libraries/procedural/src/procedural/Logging.h @@ -11,6 +11,6 @@ #include -Q_DECLARE_LOGGING_CATEGORY(procedural) +Q_DECLARE_LOGGING_CATEGORY(proceduralLog) #endif // hifi_octree_Logging_h diff --git a/libraries/procedural/src/procedural/Procedural.cpp b/libraries/procedural/src/procedural/Procedural.cpp index 7bf020094a..79c0b31dff 100644 --- a/libraries/procedural/src/procedural/Procedural.cpp +++ b/libraries/procedural/src/procedural/Procedural.cpp @@ -20,12 +20,12 @@ #include #include #include -#include "ProceduralCommon_frag.h" +#include +#include "ShaderConstants.h" #include "Logging.h" -Q_LOGGING_CATEGORY(procedural, "hifi.gpu.procedural") - +Q_LOGGING_CATEGORY(proceduralLog, "hifi.gpu.procedural") // Userdata parsing constants static const QString PROCEDURAL_USER_DATA_KEY = "ProceduralEntity"; @@ -36,20 +36,9 @@ static const QString CHANNELS_KEY = "channels"; // Shader replace strings static const std::string PROCEDURAL_BLOCK = "//PROCEDURAL_BLOCK"; -static const std::string PROCEDURAL_COMMON_BLOCK = "//PROCEDURAL_COMMON_BLOCK"; static const std::string PROCEDURAL_VERSION = "//PROCEDURAL_VERSION"; -static const std::string STANDARD_UNIFORM_NAMES[Procedural::NUM_STANDARD_UNIFORMS] = { - "iDate", - "iGlobalTime", - "iFrameCount", - "iWorldScale", - "iWorldPosition", - "iWorldOrientation", - "iChannelResolution" -}; - -bool operator ==(const ProceduralData& a, const ProceduralData& b) { +bool operator==(const ProceduralData& a, const ProceduralData& b) { return ( (a.version == b.version) && (a.shaderUrl == b.shaderUrl) && @@ -57,7 +46,6 @@ bool operator ==(const ProceduralData& a, const ProceduralData& b) { (a.channels == b.channels)); } - QJsonValue ProceduralData::getProceduralData(const QString& proceduralJson) { if (proceduralJson.isEmpty()) { return QJsonValue(); @@ -99,7 +87,7 @@ void ProceduralData::parse(const QJsonObject& proceduralData) { auto rawShaderUrl = proceduralData[URL_KEY].toString(); shaderUrl = DependencyManager::get()->normalizeURL(rawShaderUrl); - + // Empty shader URL isn't valid if (shaderUrl.isEmpty()) { return; @@ -119,10 +107,8 @@ void ProceduralData::parse(const QJsonObject& proceduralData) { Procedural::Procedural() { _transparentState->setCullMode(gpu::State::CULL_NONE); _transparentState->setDepthTest(true, true, gpu::LESS_EQUAL); - _transparentState->setBlendFunction(true, - gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, - gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); - + _transparentState->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, + gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); } void Procedural::setProceduralData(const ProceduralData& proceduralData) { @@ -133,6 +119,11 @@ void Procedural::setProceduralData(const ProceduralData& proceduralData) { _dirty = true; _enabled = false; + if (proceduralData.version != _data.version ) { + _data.version = proceduralData.version; + _shaderDirty = true; + } + if (proceduralData.uniforms != _data.uniforms) { _data.uniforms = proceduralData.uniforms; _uniformsDirty = true; @@ -168,13 +159,13 @@ void Procedural::setProceduralData(const ProceduralData& proceduralData) { } if (!shaderUrl.isValid()) { - qCWarning(procedural) << "Invalid shader URL: " << shaderUrl; + qCWarning(proceduralLog) << "Invalid shader URL: " << shaderUrl; return; } if (shaderUrl.isLocalFile()) { if (!QFileInfo(shaderUrl.toLocalFile()).exists()) { - qCWarning(procedural) << "Invalid shader URL, missing local file: " << shaderUrl; + qCWarning(proceduralLog) << "Invalid shader URL, missing local file: " << shaderUrl; return; } _shaderPath = shaderUrl.toLocalFile(); @@ -222,28 +213,27 @@ bool Procedural::isReady() const { } std::string Procedural::replaceProceduralBlock(const std::string& fragmentSource) { - std::string fragmentShaderSource = fragmentSource; - size_t replaceIndex = fragmentShaderSource.find(PROCEDURAL_COMMON_BLOCK); - if (replaceIndex != std::string::npos) { - fragmentShaderSource.replace(replaceIndex, PROCEDURAL_COMMON_BLOCK.size(), ProceduralCommon_frag::getSource()); - } - - replaceIndex = fragmentShaderSource.find(PROCEDURAL_VERSION); + std::string result = fragmentSource; + auto replaceIndex = result.find(PROCEDURAL_VERSION); if (replaceIndex != std::string::npos) { if (_data.version == 1) { - fragmentShaderSource.replace(replaceIndex, PROCEDURAL_VERSION.size(), "#define PROCEDURAL_V1 1"); + result.replace(replaceIndex, PROCEDURAL_VERSION.size(), "#define PROCEDURAL_V1 1"); } else if (_data.version == 2) { - fragmentShaderSource.replace(replaceIndex, PROCEDURAL_VERSION.size(), "#define PROCEDURAL_V2 1"); + result.replace(replaceIndex, PROCEDURAL_VERSION.size(), "#define PROCEDURAL_V2 1"); } } - replaceIndex = fragmentShaderSource.find(PROCEDURAL_BLOCK); + replaceIndex = result.find(PROCEDURAL_BLOCK); if (replaceIndex != std::string::npos) { - fragmentShaderSource.replace(replaceIndex, PROCEDURAL_BLOCK.size(), _shaderSource.toLocal8Bit().data()); + result.replace(replaceIndex, PROCEDURAL_BLOCK.size(), _shaderSource.toLocal8Bit().data()); } - return fragmentShaderSource; + return result; } -void Procedural::prepare(gpu::Batch& batch, const glm::vec3& position, const glm::vec3& size, const glm::quat& orientation, const glm::vec4& color) { +void Procedural::prepare(gpu::Batch& batch, + const glm::vec3& position, + const glm::vec3& size, + const glm::quat& orientation, + const glm::vec4& color) { _entityDimensions = size; _entityPosition = position; _entityOrientation = glm::mat3_cast(orientation); @@ -266,31 +256,31 @@ void Procedural::prepare(gpu::Batch& batch, const glm::vec3& position, const glm } // Build the fragment shader - std::string opaqueShaderSource = replaceProceduralBlock(_opaquefragmentSource); - std::string transparentShaderSource = replaceProceduralBlock(_transparentfragmentSource); + std::string opaqueShaderSource = replaceProceduralBlock(_opaquefragmentSource.getCode()); + auto opaqueReflection = _opaquefragmentSource.getReflection(); + auto& opaqueUniforms = opaqueReflection[gpu::Shader::BindingType::UNIFORM]; + std::string transparentShaderSource = replaceProceduralBlock(_transparentfragmentSource.getCode()); + auto transparentReflection = _transparentfragmentSource.getReflection(); + auto& transparentUniforms = transparentReflection[gpu::Shader::BindingType::UNIFORM]; + + // Set any userdata specified uniforms + int customSlot = procedural::slot::uniform::Custom; + for (const auto& key : _data.uniforms.keys()) { + std::string uniformName = key.toLocal8Bit().data(); + opaqueUniforms[uniformName] = customSlot; + transparentUniforms[uniformName] = customSlot; + ++customSlot; + } // Leave this here for debugging // qCDebug(procedural) << "FragmentShader:\n" << fragmentShaderSource.c_str(); - gpu::Shader::BindingSet slotBindings; - - slotBindings.insert(gpu::Shader::Binding(std::string("iChannel0"), 0)); - slotBindings.insert(gpu::Shader::Binding(std::string("iChannel1"), 1)); - slotBindings.insert(gpu::Shader::Binding(std::string("iChannel2"), 2)); - slotBindings.insert(gpu::Shader::Binding(std::string("iChannel3"), 3)); - // TODO: THis is a simple fix, we need a cleaner way to provide the "hosting" program for procedural custom shaders to be defined together with the required bindings. - const int PROCEDURAL_PROGRAM_LIGHTING_MODEL_SLOT = 3; - slotBindings.insert(gpu::Shader::Binding(std::string("lightingModelBuffer"), PROCEDURAL_PROGRAM_LIGHTING_MODEL_SLOT)); - - _opaqueFragmentShader = gpu::Shader::createPixel(opaqueShaderSource); + _opaqueFragmentShader = gpu::Shader::createPixel({ opaqueShaderSource, opaqueReflection }); _opaqueShader = gpu::Shader::createProgram(_vertexShader, _opaqueFragmentShader); - gpu::Shader::makeProgram(*_opaqueShader, slotBindings); - if (!transparentShaderSource.empty() && transparentShaderSource != opaqueShaderSource) { - _transparentFragmentShader = gpu::Shader::createPixel(transparentShaderSource); + _transparentFragmentShader = gpu::Shader::createPixel({ transparentShaderSource, transparentReflection }); _transparentShader = gpu::Shader::createProgram(_vertexShader, _transparentFragmentShader); - gpu::Shader::makeProgram(*_transparentShader, slotBindings); } else { _transparentFragmentShader = _opaqueFragmentShader; _transparentShader = _opaqueShader; @@ -298,11 +288,6 @@ void Procedural::prepare(gpu::Batch& batch, const glm::vec3& position, const glm _opaquePipeline = gpu::Pipeline::create(_opaqueShader, _opaqueState); _transparentPipeline = gpu::Pipeline::create(_transparentShader, _transparentState); - for (size_t i = 0; i < NUM_STANDARD_UNIFORMS; ++i) { - const std::string& name = STANDARD_UNIFORM_NAMES[i]; - _standardOpaqueUniformSlots[i] = _opaqueShader->getUniforms().findLocation(name); - _standardTransparentUniformSlots[i] = _transparentShader->getUniforms().findLocation(name); - } _start = usecTimestampNow(); _frameCount = 0; } @@ -346,19 +331,22 @@ void Procedural::prepare(gpu::Batch& batch, const glm::vec3& position, const glm void Procedural::setupUniforms(bool transparent) { _uniforms.clear(); - // Set any userdata specified uniforms - foreach(QString key, _data.uniforms.keys()) { - std::string uniformName = key.toLocal8Bit().data(); - int32_t slot = (transparent ? _transparentShader : _opaqueShader)->getUniforms().findLocation(uniformName); - if (gpu::Shader::INVALID_LOCATION == slot) { + auto& pipeline = transparent ? _transparentShader : _opaqueShader; + const auto& uniformSlots = pipeline->getUniforms(); + auto customUniformCount = _data.uniforms.keys().size(); + + // Set any userdata specified uniforms + for (int i = 0; i < customUniformCount; ++i) { + int slot = procedural::slot::uniform::Custom + i; + if (!uniformSlots.isValid(slot)) { continue; } + QString key = _data.uniforms.keys().at(i); + std::string uniformName = key.toLocal8Bit().data(); QJsonValue value = _data.uniforms[key]; if (value.isDouble()) { float v = value.toDouble(); - _uniforms.push_back([=](gpu::Batch& batch) { - batch._glUniform1f(slot, v); - }); + _uniforms.push_back([=](gpu::Batch& batch) { batch._glUniform1f(slot, v); }); } else if (value.isArray()) { auto valueArray = value.toArray(); switch (valueArray.size()) { @@ -367,17 +355,13 @@ void Procedural::setupUniforms(bool transparent) { case 1: { float v = valueArray[0].toDouble(); - _uniforms.push_back([=](gpu::Batch& batch) { - batch._glUniform1f(slot, v); - }); + _uniforms.push_back([=](gpu::Batch& batch) { batch._glUniform1f(slot, v); }); break; } case 2: { glm::vec2 v{ valueArray[0].toDouble(), valueArray[1].toDouble() }; - _uniforms.push_back([=](gpu::Batch& batch) { - batch._glUniform2f(slot, v.x, v.y); - }); + _uniforms.push_back([=](gpu::Batch& batch) { batch._glUniform2f(slot, v.x, v.y); }); break; } @@ -387,9 +371,7 @@ void Procedural::setupUniforms(bool transparent) { valueArray[1].toDouble(), valueArray[2].toDouble(), }; - _uniforms.push_back([=](gpu::Batch& batch) { - batch._glUniform3f(slot, v.x, v.y, v.z); - }); + _uniforms.push_back([=](gpu::Batch& batch) { batch._glUniform3f(slot, v.x, v.y, v.z); }); break; } @@ -401,26 +383,22 @@ void Procedural::setupUniforms(bool transparent) { valueArray[2].toDouble(), valueArray[3].toDouble(), }; - _uniforms.push_back([=](gpu::Batch& batch) { - batch._glUniform4f(slot, v.x, v.y, v.z, v.w); - }); + _uniforms.push_back([=](gpu::Batch& batch) { batch._glUniform4f(slot, v.x, v.y, v.z, v.w); }); break; } } } } - auto uniformSlots = transparent ? _standardTransparentUniformSlots : _standardOpaqueUniformSlots; - - if (gpu::Shader::INVALID_LOCATION != uniformSlots[TIME]) { + if (uniformSlots.isValid(procedural::slot::uniform::Time)) { _uniforms.push_back([=](gpu::Batch& batch) { // Minimize floating point error by doing an integer division to milliseconds, before the floating point division to seconds float time = (float)((usecTimestampNow() - _start) / USECS_PER_MSEC) / MSECS_PER_SECOND; - batch._glUniform(uniformSlots[TIME], time); + batch._glUniform(procedural::slot::uniform::Time, time); }); } - if (gpu::Shader::INVALID_LOCATION != uniformSlots[DATE]) { + if (uniformSlots.isValid(procedural::slot::uniform::Date)) { _uniforms.push_back([=](gpu::Batch& batch) { QDateTime now = QDateTime::currentDateTimeUtc(); QDate date = now.date(); @@ -433,41 +411,37 @@ void Procedural::setupUniforms(bool transparent) { v.z = date.day(); float fractSeconds = (time.msec() / 1000.0f); v.w = (time.hour() * 3600) + (time.minute() * 60) + time.second() + fractSeconds; - batch._glUniform(uniformSlots[DATE], v); + batch._glUniform(procedural::slot::uniform::Date, v); }); } - if (gpu::Shader::INVALID_LOCATION != uniformSlots[FRAME_COUNT]) { - _uniforms.push_back([=](gpu::Batch& batch) { - batch._glUniform(uniformSlots[FRAME_COUNT], ++_frameCount); - }); + if (uniformSlots.isValid(procedural::slot::uniform::FrameCount)) { + _uniforms.push_back([=](gpu::Batch& batch) { batch._glUniform(procedural::slot::uniform::FrameCount, ++_frameCount); }); } - if (gpu::Shader::INVALID_LOCATION != uniformSlots[SCALE]) { + if (uniformSlots.isValid(procedural::slot::uniform::Scale)) { // FIXME move into the 'set once' section, since this doesn't change over time - _uniforms.push_back([=](gpu::Batch& batch) { - batch._glUniform(uniformSlots[SCALE], _entityDimensions); - }); + _uniforms.push_back([=](gpu::Batch& batch) { batch._glUniform(procedural::slot::uniform::Scale, _entityDimensions); }); } - if (gpu::Shader::INVALID_LOCATION != uniformSlots[ORIENTATION]) { + if (uniformSlots.isValid(procedural::slot::uniform::Orientation)) { // FIXME move into the 'set once' section, since this doesn't change over time - _uniforms.push_back([=](gpu::Batch& batch) { - batch._glUniform(uniformSlots[ORIENTATION], _entityOrientation); - }); + _uniforms.push_back( + [=](gpu::Batch& batch) { batch._glUniform(procedural::slot::uniform::Orientation, _entityOrientation); }); } - if (gpu::Shader::INVALID_LOCATION != uniformSlots[POSITION]) { + if (uniformSlots.isValid(procedural::slot::uniform::Position)) { // FIXME move into the 'set once' section, since this doesn't change over time - _uniforms.push_back([=](gpu::Batch& batch) { - batch._glUniform(uniformSlots[POSITION], _entityPosition); - }); + _uniforms.push_back( + [=](gpu::Batch& batch) { batch._glUniform(procedural::slot::uniform::Orientation, _entityPosition); }); } } void Procedural::setupChannels(bool shouldCreate, bool transparent) { - auto uniformSlots = transparent ? _standardTransparentUniformSlots : _standardOpaqueUniformSlots; - if (gpu::Shader::INVALID_LOCATION != uniformSlots[CHANNEL_RESOLUTION]) { + auto& pipeline = transparent ? _transparentShader : _opaqueShader; + const auto& uniformSlots = pipeline->getUniforms(); + + if (uniformSlots.isValid(procedural::slot::uniform::ChannelResolution)) { if (!shouldCreate) { // Instead of modifying the last element, just remove and recreate it. _uniforms.pop_back(); @@ -479,7 +453,8 @@ void Procedural::setupChannels(bool shouldCreate, bool transparent) { channelSizes[i] = vec3(_channels[i]->getWidth(), _channels[i]->getHeight(), 1.0); } } - batch._glUniform3fv(uniformSlots[CHANNEL_RESOLUTION], MAX_PROCEDURAL_TEXTURE_CHANNELS, &channelSizes[0].x); + batch._glUniform3fv(procedural::slot::uniform::ChannelResolution, MAX_PROCEDURAL_TEXTURE_CHANNELS, + &channelSizes[0].x); }); } } diff --git a/libraries/procedural/src/procedural/Procedural.h b/libraries/procedural/src/procedural/Procedural.h index 1d3b0b3b5a..973b323f60 100644 --- a/libraries/procedural/src/procedural/Procedural.h +++ b/libraries/procedural/src/procedural/Procedural.h @@ -64,23 +64,13 @@ public: void setIsFading(bool isFading) { _isFading = isFading; } void setDoesFade(bool doesFade) { _doesFade = doesFade; } - std::string _vertexSource; - std::string _opaquefragmentSource; - std::string _transparentfragmentSource; + gpu::Shader::Source _vertexSource; + gpu::Shader::Source _opaquefragmentSource; + gpu::Shader::Source _transparentfragmentSource; gpu::StatePointer _opaqueState { std::make_shared() }; gpu::StatePointer _transparentState { std::make_shared() }; - enum StandardUniforms { - DATE, - TIME, - FRAME_COUNT, - SCALE, - POSITION, - ORIENTATION, - CHANNEL_RESOLUTION, - NUM_STANDARD_UNIFORMS - }; protected: // Procedural metadata @@ -102,8 +92,6 @@ protected: // Rendering objects UniformLambdas _uniforms; - int32_t _standardOpaqueUniformSlots[NUM_STANDARD_UNIFORMS]; - int32_t _standardTransparentUniformSlots[NUM_STANDARD_UNIFORMS]; NetworkTexturePointer _channels[MAX_PROCEDURAL_TEXTURE_CHANNELS]; gpu::PipelinePointer _opaquePipeline; gpu::PipelinePointer _transparentPipeline; diff --git a/libraries/procedural/src/procedural/ProceduralCommon.slf b/libraries/procedural/src/procedural/ProceduralCommon.slf deleted file mode 100644 index de226e6dae..0000000000 --- a/libraries/procedural/src/procedural/ProceduralCommon.slf +++ /dev/null @@ -1,52 +0,0 @@ -<@include gpu/Config.slh@> -// Generated on <$_SCRIBE_DATE$> -// -// Created by Bradley Austin Davis on 2015/09/05 -// Copyright 2013-2015 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -<@include gpu/Transform.slh@> -<@include gpu/Noise.slh@> - -<$declareStandardCameraTransform()$> - -#define PROCEDURAL 1 - -//PROCEDURAL_VERSION - -#ifdef PROCEDURAL_V1 - -// shader playback time (in seconds) -uniform float iGlobalTime; -// the dimensions of the object being rendered -uniform vec3 iWorldScale; - -#else - -// Unimplemented uniforms -// Resolution doesn't make sense in the VR context -const vec3 iResolution = vec3(1.0); -// Mouse functions not enabled currently -const vec4 iMouse = vec4(0.0); -// No support for audio input -const float iSampleRate = 1.0; -// No support for video input -const vec4 iChannelTime = vec4(0.0); - - -uniform float iGlobalTime; // shader playback time (in seconds) -uniform vec4 iDate; -uniform int iFrameCount; -uniform vec3 iWorldPosition; // the position of the object being rendered -uniform vec3 iWorldScale; // the dimensions of the object being rendered -uniform mat3 iWorldOrientation; // the orientation of the object being rendered -uniform vec3 iChannelResolution[4]; -uniform sampler2D iChannel0; -uniform sampler2D iChannel1; -uniform sampler2D iChannel2; -uniform sampler2D iChannel3; - -#endif \ No newline at end of file diff --git a/libraries/procedural/src/procedural/ProceduralCommon.slh b/libraries/procedural/src/procedural/ProceduralCommon.slh new file mode 100644 index 0000000000..c36f2da1d3 --- /dev/null +++ b/libraries/procedural/src/procedural/ProceduralCommon.slh @@ -0,0 +1,56 @@ +<@include gpu/Config.slh@> +// Generated on <$_SCRIBE_DATE$> +// +// Created by Bradley Austin Davis on 2015/09/05 +// Copyright 2013-2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +<@include gpu/Transform.slh@> +<@include gpu/Noise.slh@> +<@include procedural/ShaderConstants.h@> + +<$declareStandardCameraTransform()$> + +#define PROCEDURAL 1 + +//PROCEDURAL_VERSION + +#ifdef PROCEDURAL_V1 + +// shader playback time (in seconds) +layout(location=PROCEDURAL_UNIFORM_TIME) uniform float iGlobalTime; +// the dimensions of the object being rendered +layout(location=PROCEDURAL_UNIFORM_SCALE) uniform vec3 iWorldScale; + +#else + +// Unimplemented uniforms +// Resolution doesn't make sense in the VR context +const vec3 iResolution = vec3(1.0); +// Mouse functions not enabled currently +const vec4 iMouse = vec4(0.0); +// No support for audio input +const float iSampleRate = 1.0; +// No support for video input +const vec4 iChannelTime = vec4(0.0); + + +layout(location=PROCEDURAL_UNIFORM_TIME) uniform float iGlobalTime; // shader playback time (in seconds) +layout(location=PROCEDURAL_UNIFORM_DATE) uniform vec4 iDate; +layout(location=PROCEDURAL_UNIFORM_FRAME_COUNT) uniform int iFrameCount; +layout(location=PROCEDURAL_UNIFORM_POSITION) uniform vec3 iWorldPosition; // the position of the object being rendered +layout(location=PROCEDURAL_UNIFORM_SCALE) uniform vec3 iWorldScale; // the dimensions of the object being rendered +layout(location=PROCEDURAL_UNIFORM_ORIENTATION) uniform mat3 iWorldOrientation; // the orientation of the object being rendered +layout(location=PROCEDURAL_UNIFORM_CHANNEL_RESOLUTION) uniform vec3 iChannelResolution[4]; +layout(binding=PROCEDURAL_TEXTURE_CHANNEL0) uniform sampler2D iChannel0; +layout(binding=PROCEDURAL_TEXTURE_CHANNEL1) uniform sampler2D iChannel1; +layout(binding=PROCEDURAL_TEXTURE_CHANNEL2) uniform sampler2D iChannel2; +layout(binding=PROCEDURAL_TEXTURE_CHANNEL3) uniform sampler2D iChannel3; + +#endif + +// hack comment for extra whitespace + diff --git a/libraries/procedural/src/procedural/ProceduralSkybox.cpp b/libraries/procedural/src/procedural/ProceduralSkybox.cpp index 0c6501928b..0addb57fcf 100644 --- a/libraries/procedural/src/procedural/ProceduralSkybox.cpp +++ b/libraries/procedural/src/procedural/ProceduralSkybox.cpp @@ -13,14 +13,13 @@ #include #include +#include #include - -#include -#include +#include ProceduralSkybox::ProceduralSkybox() : graphics::Skybox() { - _procedural._vertexSource = skybox_vert::getSource(); - _procedural._opaquefragmentSource = skybox_frag::getSource(); + _procedural._vertexSource = gpu::Shader::createVertex(shader::graphics::vertex::skybox)->getSource(); + _procedural._opaquefragmentSource = gpu::Shader::createPixel(shader::procedural::fragment::proceduralSkybox)->getSource(); // Adjust the pipeline state for background using the stencil test _procedural.setDoesFade(false); // Must match PrepareStencil::STENCIL_BACKGROUND @@ -61,9 +60,7 @@ void ProceduralSkybox::render(gpu::Batch& batch, const ViewFrustum& viewFrustum, auto& procedural = skybox._procedural; procedural.prepare(batch, glm::vec3(0), glm::vec3(1), glm::quat()); - auto textureSlot = procedural.getOpaqueShader()->getTextures().findLocation("cubeMap"); - auto bufferSlot = procedural.getOpaqueShader()->getUniformBuffers().findLocation("skyboxBuffer"); - skybox.prepare(batch, textureSlot, bufferSlot); + skybox.prepare(batch); batch.draw(gpu::TRIANGLE_STRIP, 4); } diff --git a/libraries/procedural/src/procedural/ShaderConstants.h b/libraries/procedural/src/procedural/ShaderConstants.h new file mode 100644 index 0000000000..1995996c97 --- /dev/null +++ b/libraries/procedural/src/procedural/ShaderConstants.h @@ -0,0 +1,72 @@ +// + +// <@if not PROCEDURAL_SHADER_CONSTANTS_H@> +// <@def PROCEDURAL_SHADER_CONSTANTS_H@> + +// Hack comment to absorb the extra '//' scribe prepends + +#ifndef PROCEDURAL_SHADER_CONSTANTS_H +#define PROCEDURAL_SHADER_CONSTANTS_H + +// Polyvox +#define PROCEDURAL_UNIFORM_TIME 200 +#define PROCEDURAL_UNIFORM_DATE 201 +#define PROCEDURAL_UNIFORM_FRAME_COUNT 202 +#define PROCEDURAL_UNIFORM_POSITION 203 +#define PROCEDURAL_UNIFORM_SCALE 204 +#define PROCEDURAL_UNIFORM_ORIENTATION 205 +// Additional space because orientation will take up 3-4 locations, being a matrix +#define PROCEDURAL_UNIFORM_CHANNEL_RESOLUTION 209 +#define PROCEDURAL_UNIFORM_CUSTOM 220 + +#define PROCEDURAL_TEXTURE_CHANNEL0 0 +#define PROCEDURAL_TEXTURE_CHANNEL1 1 +#define PROCEDURAL_TEXTURE_CHANNEL2 2 +#define PROCEDURAL_TEXTURE_CHANNEL3 3 + +// +// Hack Comment + +#endif // PROCEDURAL_SHADER_CONSTANTS_H + +// <@if 1@> +// Trigger Scribe include +// <@endif@> + +// <@endif@> + +// Hack Comment diff --git a/libraries/procedural/src/procedural/proceduralSkybox.slf b/libraries/procedural/src/procedural/proceduralSkybox.slf new file mode 100644 index 0000000000..e18b7abef6 --- /dev/null +++ b/libraries/procedural/src/procedural/proceduralSkybox.slf @@ -0,0 +1,42 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// proceduralSkybox.frag +// fragment shader +// +// Created by Sam Gateau on 5/5/2015. +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +<@include graphics/ShaderConstants.h@> + +layout(binding=GRAPHICS_TEXTURE_SKYBOX) uniform samplerCube cubeMap; + +struct Skybox { + vec4 color; +}; + +layout(binding=GRAPHICS_BUFFER_SKYBOX_PARAMS) uniform skyboxBuffer { + Skybox skybox; +}; + +layout(location=0) in vec3 _normal; +layout(location=0) out vec4 _fragColor; + +<@include procedural/ProceduralCommon.slh@> + +#line 1001 +//PROCEDURAL_BLOCK + +#line 2033 +void main(void) { + vec3 color = getSkyboxColor(); + // Protect from NaNs and negative values + color = mix(color, vec3(0), isnan(color)); + color = max(color, vec3(0)); + // Procedural Shaders are expected to be Gamma corrected so let's bring back the RGB in linear space for the rest of the pipeline + color = pow(color, vec3(2.2)); + _fragColor = vec4(color, 0.0); +} diff --git a/libraries/qml/src/qml/OffscreenSurface.cpp b/libraries/qml/src/qml/OffscreenSurface.cpp index ea6f1ce324..cbcafe9c7d 100644 --- a/libraries/qml/src/qml/OffscreenSurface.cpp +++ b/libraries/qml/src/qml/OffscreenSurface.cpp @@ -40,7 +40,8 @@ static QSize clampSize(const QSize& qsize, uint32_t maxDimension) { return fromGlm(clampSize(toGlm(qsize), maxDimension)); } -const QmlContextObjectCallback OffscreenSurface::DEFAULT_CONTEXT_CALLBACK = [](QQmlContext*, QQuickItem*) {}; +const QmlContextObjectCallback OffscreenSurface::DEFAULT_CONTEXT_OBJECT_CALLBACK = [](QQmlContext*, QQuickItem*) {}; +const QmlContextCallback OffscreenSurface::DEFAULT_CONTEXT_CALLBACK = [](QQmlContext*) {}; void OffscreenSurface::initializeEngine(QQmlEngine* engine) { } @@ -99,7 +100,7 @@ QPointF OffscreenSurface::mapToVirtualScreen(const QPointF& originalPoint) { // bool OffscreenSurface::filterEnabled(QObject* originalDestination, QEvent* event) const { - if (!_sharedObject || _sharedObject->getWindow() == originalDestination) { + if (!_sharedObject || !_sharedObject->getWindow() || _sharedObject->getWindow() == originalDestination) { return false; } // Only intercept events while we're in an active state @@ -266,8 +267,8 @@ void OffscreenSurface::load(const QUrl& qmlSource, bool createNewContext, const loadInternal(qmlSource, createNewContext, nullptr, callback); } -void OffscreenSurface::loadInNewContext(const QUrl& qmlSource, const QmlContextObjectCallback& callback) { - load(qmlSource, true, callback); +void OffscreenSurface::loadInNewContext(const QUrl& qmlSource, const QmlContextObjectCallback& callback, const QmlContextCallback& contextCallback) { + loadInternal(qmlSource, true, nullptr, callback, contextCallback); } void OffscreenSurface::load(const QUrl& qmlSource, const QmlContextObjectCallback& callback) { @@ -281,7 +282,8 @@ void OffscreenSurface::load(const QString& qmlSourceFile, const QmlContextObject void OffscreenSurface::loadInternal(const QUrl& qmlSource, bool createNewContext, QQuickItem* parent, - const QmlContextObjectCallback& callback) { + const QmlContextObjectCallback& callback, + const QmlContextCallback& contextCallback) { PROFILE_RANGE_EX(app, "OffscreenSurface::loadInternal", 0xffff00ff, 0, { std::make_pair("url", qmlSource.toDisplayString()) }); if (QThread::currentThread() != thread()) { qFatal("Called load on a non-surface thread"); @@ -310,6 +312,7 @@ void OffscreenSurface::loadInternal(const QUrl& qmlSource, } auto targetContext = contextForUrl(finalQmlSource, parent, createNewContext); + contextCallback(targetContext); QQmlComponent* qmlComponent; { PROFILE_RANGE(app, "new QQmlComponent"); diff --git a/libraries/qml/src/qml/OffscreenSurface.h b/libraries/qml/src/qml/OffscreenSurface.h index 555f2ee6a4..b3539e7709 100644 --- a/libraries/qml/src/qml/OffscreenSurface.h +++ b/libraries/qml/src/qml/OffscreenSurface.h @@ -37,13 +37,15 @@ namespace impl { class SharedObject; } +using QmlContextCallback = ::std::function; using QmlContextObjectCallback = ::std::function; class OffscreenSurface : public QObject { Q_OBJECT public: - static const QmlContextObjectCallback DEFAULT_CONTEXT_CALLBACK; + static const QmlContextObjectCallback DEFAULT_CONTEXT_OBJECT_CALLBACK; + static const QmlContextCallback DEFAULT_CONTEXT_CALLBACK; using TextureAndFence = std::pair; using MouseTranslator = std::function; @@ -85,10 +87,15 @@ public: Q_INVOKABLE void load(const QUrl& qmlSource, QQuickItem* parent, const QJSValue& callback); // For use from C++ - Q_INVOKABLE void load(const QUrl& qmlSource, const QmlContextObjectCallback& callback = DEFAULT_CONTEXT_CALLBACK); - Q_INVOKABLE void load(const QUrl& qmlSource, bool createNewContext, const QmlContextObjectCallback& callback = DEFAULT_CONTEXT_CALLBACK); - Q_INVOKABLE void load(const QString& qmlSourceFile, const QmlContextObjectCallback& callback = DEFAULT_CONTEXT_CALLBACK); - Q_INVOKABLE void loadInNewContext(const QUrl& qmlSource, const QmlContextObjectCallback& callback = DEFAULT_CONTEXT_CALLBACK); + Q_INVOKABLE void load(const QUrl& qmlSource, const QmlContextObjectCallback& callback = DEFAULT_CONTEXT_OBJECT_CALLBACK); + Q_INVOKABLE void load(const QUrl& qmlSource, + bool createNewContext, + const QmlContextObjectCallback& callback = DEFAULT_CONTEXT_OBJECT_CALLBACK); + Q_INVOKABLE void load(const QString& qmlSourceFile, + const QmlContextObjectCallback& callback = DEFAULT_CONTEXT_OBJECT_CALLBACK); + Q_INVOKABLE void loadInNewContext(const QUrl& qmlSource, + const QmlContextObjectCallback& callback = DEFAULT_CONTEXT_OBJECT_CALLBACK, + const QmlContextCallback& contextCallback = DEFAULT_CONTEXT_CALLBACK); public slots: virtual void onFocusObjectChanged(QObject* newFocus) {} @@ -103,19 +110,21 @@ protected: virtual void initializeEngine(QQmlEngine* engine); virtual void loadInternal(const QUrl& qmlSource, - bool createNewContext, - QQuickItem* parent, - const QmlContextObjectCallback& callback) final; + bool createNewContext, + QQuickItem* parent, + const QmlContextObjectCallback& callback, + const QmlContextCallback& contextCallback = DEFAULT_CONTEXT_CALLBACK) final; virtual void finishQmlLoad(QQmlComponent* qmlComponent, - QQmlContext* qmlContext, - QQuickItem* parent, - const QmlContextObjectCallback& onQmlLoadedCallback) final; + QQmlContext* qmlContext, + QQuickItem* parent, + const QmlContextObjectCallback& onQmlLoadedCallback) final; virtual void onRootCreated() {} virtual void onItemCreated(QQmlContext* context, QQuickItem* newItem) {} virtual void onRootContextCreated(QQmlContext* qmlContext) {} virtual QQmlContext* contextForUrl(const QUrl& qmlSource, QQuickItem* parent, bool forceNewContext); + private: MouseTranslator _mouseTranslator{ [](const QPointF& p) { return p.toPoint(); } }; friend class hifi::qml::impl::SharedObject; diff --git a/libraries/qml/src/qml/impl/RenderEventHandler.cpp b/libraries/qml/src/qml/impl/RenderEventHandler.cpp index 945a469611..39f3123d40 100644 --- a/libraries/qml/src/qml/impl/RenderEventHandler.cpp +++ b/libraries/qml/src/qml/impl/RenderEventHandler.cpp @@ -8,6 +8,8 @@ #include "RenderEventHandler.h" +#ifndef DISABLE_QML + #include #include @@ -165,3 +167,4 @@ void RenderEventHandler::onQuit() { moveToThread(qApp->thread()); QThread::currentThread()->quit(); } +#endif \ No newline at end of file diff --git a/libraries/qml/src/qml/impl/RenderEventHandler.h b/libraries/qml/src/qml/impl/RenderEventHandler.h index d1e079cc85..1e2f9945f3 100644 --- a/libraries/qml/src/qml/impl/RenderEventHandler.h +++ b/libraries/qml/src/qml/impl/RenderEventHandler.h @@ -7,6 +7,8 @@ // #pragma once +#ifndef DISABLE_QML + #include #include #include @@ -54,3 +56,5 @@ private: }; }}} // namespace hifi::qml::impl + +#endif \ No newline at end of file diff --git a/libraries/qml/src/qml/impl/SharedObject.cpp b/libraries/qml/src/qml/impl/SharedObject.cpp index 2fde057ca8..3b8d0bb743 100644 --- a/libraries/qml/src/qml/impl/SharedObject.cpp +++ b/libraries/qml/src/qml/impl/SharedObject.cpp @@ -55,6 +55,8 @@ QOpenGLContext* SharedObject::getSharedContext() { } SharedObject::SharedObject() { +#ifndef DISABLE_QML + // Create render control _renderControl = new RenderControl(); @@ -68,6 +70,7 @@ SharedObject::SharedObject() { _quickWindow->setColor(QColor(255, 255, 255, 0)); _quickWindow->setClearBeforeRendering(true); +#endif QObject::connect(qApp, &QCoreApplication::aboutToQuit, this, &SharedObject::onAboutToQuit); } @@ -78,7 +81,7 @@ SharedObject::~SharedObject() { destroy(); // _renderTimer is created with `this` as the parent, so need no explicit destruction - +#ifndef DISABLE_QML // Destroy the event hand if (_renderObject) { delete _renderObject; @@ -89,18 +92,20 @@ SharedObject::~SharedObject() { delete _renderControl; _renderControl = nullptr; } +#endif if (_rootItem) { delete _rootItem; _rootItem = nullptr; } +#ifndef DISABLE_QML if (_quickWindow) { _quickWindow->destroy(); delete _quickWindow; _quickWindow = nullptr; } - +#endif if (_qmlContext) { auto engine = _qmlContext->engine(); delete _qmlContext; @@ -114,7 +119,9 @@ void SharedObject::create(OffscreenSurface* surface) { qFatal("QML surface root item already set"); } +#ifndef DISABLE_QML QObject::connect(_quickWindow, &QQuickWindow::focusObjectChanged, surface, &OffscreenSurface::onFocusObjectChanged); +#endif // Create a QML engine. auto qmlEngine = acquireEngine(surface); @@ -125,10 +132,12 @@ void SharedObject::create(OffscreenSurface* surface) { surface->onRootContextCreated(_qmlContext); emit surface->rootContextCreated(_qmlContext); +#ifndef DISABLE_QML if (!qmlEngine->incubationController()) { qmlEngine->setIncubationController(_quickWindow->incubationController()); } _qmlContext->setContextProperty("offscreenWindow", QVariant::fromValue(_quickWindow)); +#endif } void SharedObject::setRootItem(QQuickItem* rootItem) { @@ -137,6 +146,7 @@ void SharedObject::setRootItem(QQuickItem* rootItem) { } _rootItem = rootItem; +#ifndef DISABLE_QML _rootItem->setSize(_quickWindow->size()); // Create the render thread @@ -150,6 +160,8 @@ void SharedObject::setRootItem(QQuickItem* rootItem) { QObject::connect(_renderControl, &QQuickRenderControl::renderRequested, this, &SharedObject::requestRender); QObject::connect(_renderControl, &QQuickRenderControl::sceneChanged, this, &SharedObject::requestRenderSync); +#endif + } void SharedObject::destroy() { @@ -163,6 +175,7 @@ void SharedObject::destroy() { } _paused = true; +#ifndef DISABLE_QML if (_renderTimer) { _renderTimer->stop(); QObject::disconnect(_renderTimer); @@ -171,9 +184,11 @@ void SharedObject::destroy() { if (_renderControl) { QObject::disconnect(_renderControl); } +#endif QObject::disconnect(qApp); +#ifndef DISABLE_QML { QMutexLocker lock(&_mutex); _quit = true; @@ -190,6 +205,7 @@ void SharedObject::destroy() { delete _renderThread; _renderThread = nullptr; } +#endif } @@ -240,6 +256,7 @@ void SharedObject::releaseEngine(QQmlEngine* engine) { } bool SharedObject::event(QEvent* e) { +#ifndef DISABLE_QML switch (static_cast(e->type())) { case OffscreenEvent::Initialize: onInitialize(); @@ -252,6 +269,7 @@ bool SharedObject::event(QEvent* e) { default: break; } +#endif return QObject::event(e); } @@ -261,22 +279,28 @@ void SharedObject::initializeRenderControl(QOpenGLContext* context) { qFatal("QML rendering context has no share context"); } +#ifndef DISABLE_QML if (!nsightActive()) { _renderControl->initialize(context); } +#endif } void SharedObject::releaseTextureAndFence() { +#ifndef DISABLE_QML QMutexLocker lock(&_mutex); // If the most recent texture was unused, we can directly recycle it if (_latestTextureAndFence.first) { getTextureCache().releaseTexture(_latestTextureAndFence); _latestTextureAndFence = TextureAndFence{ 0, 0 }; } +#endif } void SharedObject::setRenderTarget(uint32_t fbo, const QSize& size) { +#ifndef DISABLE_QML _quickWindow->setRenderTarget(fbo, size); +#endif } QSize SharedObject::getSize() const { @@ -295,6 +319,7 @@ void SharedObject::setSize(const QSize& size) { } qCDebug(qmlLogging) << "Offscreen UI resizing to " << size.width() << "x" << size.height(); +#ifndef DISABLE_QML _quickWindow->setGeometry(QRect(QPoint(), size)); _quickWindow->contentItem()->setSize(size); @@ -304,9 +329,11 @@ void SharedObject::setSize(const QSize& size) { } requestRenderSync(); +#endif } bool SharedObject::preRender() { +#ifndef DISABLE_QML QMutexLocker lock(&_mutex); if (_paused) { if (_syncRequested) { @@ -327,6 +354,7 @@ bool SharedObject::preRender() { } _syncRequested = false; } +#endif return true; } @@ -339,8 +367,10 @@ void SharedObject::shutdownRendering(OffscreenGLCanvas& canvas, const QSize& siz getTextureCache().releaseTexture(_latestTextureAndFence); } } +#ifndef DISABLE_QML _renderControl->invalidate(); canvas.doneCurrent(); +#endif wake(); } @@ -381,8 +411,10 @@ bool SharedObject::fetchTexture(TextureAndFence& textureAndFence) { } void SharedObject::setProxyWindow(QWindow* window) { +#ifndef DISABLE_QML _proxyWindow = window; _renderControl->setRenderWindow(window); +#endif } void SharedObject::wait() { @@ -394,6 +426,7 @@ void SharedObject::wake() { } void SharedObject::onInitialize() { +#ifndef DISABLE_QML // Associate root item with the window. _rootItem->setParentItem(_quickWindow->contentItem()); _renderControl->prepareThread(_renderThread); @@ -410,9 +443,11 @@ void SharedObject::onInitialize() { _renderTimer->setTimerType(Qt::PreciseTimer); _renderTimer->setInterval(MIN_TIMER_MS); // 5ms, Qt::PreciseTimer required _renderTimer->start(); +#endif } void SharedObject::onRender() { +#ifndef DISABLE_QML PROFILE_RANGE(render_qml, __FUNCTION__); if (_quit) { return; @@ -430,6 +465,7 @@ void SharedObject::onRender() { QCoreApplication::postEvent(_renderObject, new OffscreenEvent(OffscreenEvent::Render)); } _renderRequested = false; +#endif } void SharedObject::onTimer() { @@ -455,7 +491,9 @@ void SharedObject::onTimer() { } } +#ifndef DISABLE_QML QCoreApplication::postEvent(this, new OffscreenEvent(OffscreenEvent::Render)); +#endif } void SharedObject::onAboutToQuit() { diff --git a/libraries/qml/src/qml/impl/SharedObject.h b/libraries/qml/src/qml/impl/SharedObject.h index 76dde611fc..002679c44d 100644 --- a/libraries/qml/src/qml/impl/SharedObject.h +++ b/libraries/qml/src/qml/impl/SharedObject.h @@ -93,17 +93,21 @@ private: // Texture management TextureAndFence _latestTextureAndFence{ 0, 0 }; - RenderControl* _renderControl{ nullptr }; - RenderEventHandler* _renderObject{ nullptr }; - QQuickWindow* _quickWindow{ nullptr }; - QWindow* _proxyWindow{ nullptr }; QQuickItem* _item{ nullptr }; QQuickItem* _rootItem{ nullptr }; + QQuickWindow* _quickWindow{ nullptr }; QQmlContext* _qmlContext{ nullptr }; + mutable QMutex _mutex; + QWaitCondition _cond; + +#ifndef DISABLE_QML + QWindow* _proxyWindow{ nullptr }; + RenderControl* _renderControl{ nullptr }; + RenderEventHandler* _renderObject{ nullptr }; + QTimer* _renderTimer{ nullptr }; QThread* _renderThread{ nullptr }; - QWaitCondition _cond; - mutable QMutex _mutex; +#endif uint64_t _lastRenderTime{ 0 }; QSize _size{ 100, 100 }; diff --git a/libraries/render-utils/CMakeLists.txt b/libraries/render-utils/CMakeLists.txt index 319b6ad415..eaa3b4dbf5 100644 --- a/libraries/render-utils/CMakeLists.txt +++ b/libraries/render-utils/CMakeLists.txt @@ -1,9 +1,9 @@ set(TARGET_NAME render-utils) -AUTOSCRIBE_SHADER_LIB(gpu graphics render) + # pull in the resources.qrc file qt5_add_resources(QT_RESOURCES_FILE "${CMAKE_CURRENT_SOURCE_DIR}/res/fonts/fonts.qrc") setup_hifi_library(Gui Network Qml Quick Script) -link_hifi_libraries(shared task ktx gpu graphics graphics-scripting model-networking render animation fbx image procedural) +link_hifi_libraries(shared task ktx gpu shaders graphics graphics-scripting model-networking render animation fbx image procedural) include_hifi_library_headers(audio) include_hifi_library_headers(networking) include_hifi_library_headers(octree) @@ -14,3 +14,4 @@ set_property(SOURCE qrc_fonts.cpp PROPERTY SKIP_AUTOMOC ON) if (NOT ANDROID) target_nsight() endif () + diff --git a/libraries/render-utils/src/AmbientOcclusionEffect.cpp b/libraries/render-utils/src/AmbientOcclusionEffect.cpp index 2ac8e77898..ced1570f37 100644 --- a/libraries/render-utils/src/AmbientOcclusionEffect.cpp +++ b/libraries/render-utils/src/AmbientOcclusionEffect.cpp @@ -18,20 +18,18 @@ #include #include #include -#include +#include +#include + #include "RenderUtilsLogging.h" +#include "render-utils/ShaderConstants.h" #include "DeferredLightingEffect.h" #include "TextureCache.h" #include "FramebufferCache.h" #include "DependencyManager.h" #include "ViewFrustum.h" -#include "ssao_makePyramid_frag.h" -#include "ssao_makeOcclusion_frag.h" -#include "ssao_debugOcclusion_frag.h" -#include "ssao_makeHorizontalBlur_frag.h" -#include "ssao_makeVerticalBlur_frag.h" AmbientOcclusionFramebuffer::AmbientOcclusionFramebuffer() { @@ -166,12 +164,6 @@ public: } }; -const int AmbientOcclusionEffect_FrameTransformSlot = 0; -const int AmbientOcclusionEffect_ParamsSlot = 1; -const int AmbientOcclusionEffect_CameraCorrectionSlot = 2; -const int AmbientOcclusionEffect_LinearDepthMapSlot = 0; -const int AmbientOcclusionEffect_OcclusionMapSlot = 0; - AmbientOcclusionEffect::AmbientOcclusionEffect() { } @@ -261,18 +253,7 @@ void AmbientOcclusionEffect::configure(const Config& config) { const gpu::PipelinePointer& AmbientOcclusionEffect::getOcclusionPipeline() { if (!_occlusionPipeline) { - auto vs = gpu::StandardShaderLib::getDrawViewportQuadTransformTexcoordVS(); - auto ps = ssao_makeOcclusion_frag::getShader(); - gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); - - gpu::Shader::BindingSet slotBindings; - slotBindings.insert(gpu::Shader::Binding(std::string("deferredFrameTransformBuffer"), AmbientOcclusionEffect_FrameTransformSlot)); - slotBindings.insert(gpu::Shader::Binding(std::string("ambientOcclusionParamsBuffer"), AmbientOcclusionEffect_ParamsSlot)); - slotBindings.insert(gpu::Shader::Binding(std::string("cameraCorrectionBuffer"), AmbientOcclusionEffect_CameraCorrectionSlot)); - - slotBindings.insert(gpu::Shader::Binding(std::string("pyramidMap"), AmbientOcclusionEffect_LinearDepthMapSlot)); - gpu::Shader::makeProgram(*program, slotBindings); - + gpu::ShaderPointer program = gpu::Shader::createProgram(shader::render_utils::program::ssao_makeOcclusion); gpu::StatePointer state = gpu::StatePointer(new gpu::State()); state->setColorWriteMask(true, true, true, false); @@ -286,17 +267,7 @@ const gpu::PipelinePointer& AmbientOcclusionEffect::getOcclusionPipeline() { const gpu::PipelinePointer& AmbientOcclusionEffect::getHBlurPipeline() { if (!_hBlurPipeline) { - auto vs = gpu::StandardShaderLib::getDrawViewportQuadTransformTexcoordVS(); - auto ps = ssao_makeHorizontalBlur_frag::getShader(); - gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); - - gpu::Shader::BindingSet slotBindings; - slotBindings.insert(gpu::Shader::Binding(std::string("ambientOcclusionFrameTransformBuffer"), AmbientOcclusionEffect_FrameTransformSlot)); - slotBindings.insert(gpu::Shader::Binding(std::string("cameraCorrectionBuffer"), AmbientOcclusionEffect_CameraCorrectionSlot)); - slotBindings.insert(gpu::Shader::Binding(std::string("ambientOcclusionParamsBuffer"), AmbientOcclusionEffect_ParamsSlot)); - slotBindings.insert(gpu::Shader::Binding(std::string("occlusionMap"), AmbientOcclusionEffect_OcclusionMapSlot)); - gpu::Shader::makeProgram(*program, slotBindings); - + gpu::ShaderPointer program = gpu::Shader::createProgram(shader::render_utils::program::ssao_makeHorizontalBlur); gpu::StatePointer state = gpu::StatePointer(new gpu::State()); state->setColorWriteMask(true, true, true, false); @@ -309,18 +280,7 @@ const gpu::PipelinePointer& AmbientOcclusionEffect::getHBlurPipeline() { const gpu::PipelinePointer& AmbientOcclusionEffect::getVBlurPipeline() { if (!_vBlurPipeline) { - auto vs = gpu::StandardShaderLib::getDrawViewportQuadTransformTexcoordVS(); - auto ps = ssao_makeVerticalBlur_frag::getShader(); - gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); - - gpu::Shader::BindingSet slotBindings; - slotBindings.insert(gpu::Shader::Binding(std::string("ambientOcclusionFrameTransformBuffer"), AmbientOcclusionEffect_FrameTransformSlot)); - slotBindings.insert(gpu::Shader::Binding(std::string("cameraCorrectionBuffer"), AmbientOcclusionEffect_CameraCorrectionSlot)); - slotBindings.insert(gpu::Shader::Binding(std::string("ambientOcclusionParamsBuffer"), AmbientOcclusionEffect_ParamsSlot)); - slotBindings.insert(gpu::Shader::Binding(std::string("occlusionMap"), AmbientOcclusionEffect_OcclusionMapSlot)); - - gpu::Shader::makeProgram(*program, slotBindings); - + gpu::ShaderPointer program = gpu::Shader::createProgram(shader::render_utils::program::ssao_makeVerticalBlur); gpu::StatePointer state = gpu::StatePointer(new gpu::State()); // Vertical blur write just the final result Occlusion value in the alpha channel @@ -398,8 +358,8 @@ void AmbientOcclusionEffect::run(const render::RenderContextPointer& renderConte model.setScale(glm::vec3(sWidth, tHeight, 1.0f)); batch.setModelTransform(model); - batch.setUniformBuffer(AmbientOcclusionEffect_FrameTransformSlot, frameTransform->getFrameTransformBuffer()); - batch.setUniformBuffer(AmbientOcclusionEffect_ParamsSlot, _parametersBuffer); + batch.setUniformBuffer(render_utils::slot::buffer::DeferredFrameTransform, frameTransform->getFrameTransformBuffer()); + batch.setUniformBuffer(render_utils::slot::buffer::SsaoParams, _parametersBuffer); // We need this with the mips levels @@ -409,7 +369,7 @@ void AmbientOcclusionEffect::run(const render::RenderContextPointer& renderConte batch.setFramebuffer(occlusionFBO); batch.clearColorFramebuffer(gpu::Framebuffer::BUFFER_COLOR0, glm::vec4(1.0f)); batch.setPipeline(occlusionPipeline); - batch.setResourceTexture(AmbientOcclusionEffect_LinearDepthMapSlot, _framebuffer->getLinearDepthTexture()); + batch.setResourceTexture(render_utils::slot::texture::SsaoPyramid, _framebuffer->getLinearDepthTexture()); batch.draw(gpu::TRIANGLE_STRIP, 4); @@ -417,19 +377,19 @@ void AmbientOcclusionEffect::run(const render::RenderContextPointer& renderConte // Blur 1st pass batch.setFramebuffer(occlusionBlurredFBO); batch.setPipeline(firstHBlurPipeline); - batch.setResourceTexture(AmbientOcclusionEffect_OcclusionMapSlot, occlusionFBO->getRenderBuffer(0)); + batch.setResourceTexture(render_utils::slot::texture::SsaoOcclusion, occlusionFBO->getRenderBuffer(0)); batch.draw(gpu::TRIANGLE_STRIP, 4); // Blur 2nd pass batch.setFramebuffer(occlusionFBO); batch.setPipeline(lastVBlurPipeline); - batch.setResourceTexture(AmbientOcclusionEffect_OcclusionMapSlot, occlusionBlurredFBO->getRenderBuffer(0)); + batch.setResourceTexture(render_utils::slot::texture::SsaoOcclusion, occlusionBlurredFBO->getRenderBuffer(0)); batch.draw(gpu::TRIANGLE_STRIP, 4); } - batch.setResourceTexture(AmbientOcclusionEffect_LinearDepthMapSlot, nullptr); - batch.setResourceTexture(AmbientOcclusionEffect_OcclusionMapSlot, nullptr); + batch.setResourceTexture(render_utils::slot::texture::SsaoPyramid, nullptr); + batch.setResourceTexture(render_utils::slot::texture::SsaoOcclusion, nullptr); _gpuTimer->end(batch); }); @@ -456,17 +416,7 @@ void DebugAmbientOcclusion::configure(const Config& config) { const gpu::PipelinePointer& DebugAmbientOcclusion::getDebugPipeline() { if (!_debugPipeline) { - auto vs = gpu::StandardShaderLib::getDrawViewportQuadTransformTexcoordVS(); - auto ps = ssao_debugOcclusion_frag::getShader(); - gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); - - gpu::Shader::BindingSet slotBindings; - slotBindings.insert(gpu::Shader::Binding(std::string("deferredFrameTransformBuffer"), AmbientOcclusionEffect_FrameTransformSlot)); - slotBindings.insert(gpu::Shader::Binding(std::string("ambientOcclusionParamsBuffer"), AmbientOcclusionEffect_ParamsSlot)); - slotBindings.insert(gpu::Shader::Binding(std::string("debugAmbientOcclusionBuffer"), 2)); - slotBindings.insert(gpu::Shader::Binding(std::string("pyramidMap"), AmbientOcclusionEffect_LinearDepthMapSlot)); - gpu::Shader::makeProgram(*program, slotBindings); - + gpu::ShaderPointer program = gpu::Shader::createProgram(shader::render_utils::program::ssao_debugOcclusion); gpu::StatePointer state = gpu::StatePointer(new gpu::State()); state->setColorWriteMask(true, true, true, false); @@ -529,16 +479,16 @@ void DebugAmbientOcclusion::run(const render::RenderContextPointer& renderContex model.setScale(glm::vec3(sWidth, tHeight, 1.0f)); batch.setModelTransform(model); - batch.setUniformBuffer(AmbientOcclusionEffect_FrameTransformSlot, frameTransform->getFrameTransformBuffer()); - batch.setUniformBuffer(AmbientOcclusionEffect_ParamsSlot, ambientOcclusionUniforms); + batch.setUniformBuffer(render_utils::slot::buffer::DeferredFrameTransform, frameTransform->getFrameTransformBuffer()); + batch.setUniformBuffer(render_utils::slot::buffer::SsaoParams, ambientOcclusionUniforms); batch.setUniformBuffer(2, _parametersBuffer); batch.setPipeline(debugPipeline); - batch.setResourceTexture(AmbientOcclusionEffect_LinearDepthMapSlot, linearDepthTexture); + batch.setResourceTexture(render_utils::slot::texture::SsaoPyramid, linearDepthTexture); batch.draw(gpu::TRIANGLE_STRIP, 4); - batch.setResourceTexture(AmbientOcclusionEffect_LinearDepthMapSlot, nullptr); + batch.setResourceTexture(render_utils::slot::texture::SsaoPyramid, nullptr); }); } diff --git a/libraries/render-utils/src/AnimDebugDraw.cpp b/libraries/render-utils/src/AnimDebugDraw.cpp index eca500f36c..74afded28f 100644 --- a/libraries/render-utils/src/AnimDebugDraw.cpp +++ b/libraries/render-utils/src/AnimDebugDraw.cpp @@ -12,17 +12,13 @@ #include #include #include +#include #include "AbstractViewStateInterface.h" #include "RenderUtilsLogging.h" #include "DebugDraw.h" #include "StencilMaskPass.h" -#include "animdebugdraw_vert.h" -#include "animdebugdraw_frag.h" - -#include "animdebugdraw_vert.h" -#include "animdebugdraw_frag.h" class AnimDebugDrawData { public: @@ -106,9 +102,7 @@ AnimDebugDraw::AnimDebugDraw() : gpu::State::INV_SRC_ALPHA, gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); PrepareStencil::testMaskDrawShape(*state.get()); - auto vertShader = animdebugdraw_vert::getShader(); - auto fragShader = animdebugdraw_frag::getShader(); - auto program = gpu::Shader::createProgram(vertShader, fragShader); + auto program = gpu::Shader::createProgram(shader::render_utils::program::animdebugdraw); _pipeline = gpu::Pipeline::create(program, state); _animDebugDrawData = std::make_shared(); diff --git a/libraries/render-utils/src/AntialiasingEffect.cpp b/libraries/render-utils/src/AntialiasingEffect.cpp index 8317568fc6..2b17ba3c01 100644 --- a/libraries/render-utils/src/AntialiasingEffect.cpp +++ b/libraries/render-utils/src/AntialiasingEffect.cpp @@ -16,8 +16,10 @@ #include #include #include -#include +#include +#include +#include "render-utils/ShaderConstants.h" #include "StencilMaskPass.h" #include "TextureCache.h" #include "DependencyManager.h" @@ -25,6 +27,18 @@ #include "GeometryCache.h" #include "FramebufferCache.h" + +namespace ru { + using render_utils::slot::uniform::Uniform; + using render_utils::slot::texture::Texture; + using render_utils::slot::buffer::Buffer; +} + +namespace gr { + using graphics::slot::texture::Texture; + using graphics::slot::buffer::Buffer; +} + #define ANTIALIASING_USE_TAA 1 #if !ANTIALIASING_USE_TAA @@ -66,11 +80,6 @@ const gpu::PipelinePointer& Antialiasing::getAntialiasingPipeline(RenderArgs* ar auto ps = fxaa_frag::getShader(); gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); - gpu::Shader::BindingSet slotBindings; - slotBindings.insert(gpu::Shader::Binding(std::string("colorTexture"), 0)); - - gpu::Shader::makeProgram(*program, slotBindings); - _texcoordOffsetLoc = program->getUniforms().findLocation("texcoordOffset"); gpu::StatePointer state = gpu::StatePointer(new gpu::State()); @@ -90,14 +99,7 @@ const gpu::PipelinePointer& Antialiasing::getBlendPipeline() { auto vs = fxaa_vert::getShader(); auto ps = fxaa_blend_frag::getShader(); gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); - - gpu::Shader::BindingSet slotBindings; - slotBindings.insert(gpu::Shader::Binding(std::string("colorTexture"), 0)); - - gpu::Shader::makeProgram(*program, slotBindings); - gpu::StatePointer state = gpu::StatePointer(new gpu::State()); - state->setDepthTest(false, false, gpu::LESS_EQUAL); PrepareStencil::testNoAA(*state); @@ -172,21 +174,6 @@ void Antialiasing::run(const render::RenderContextPointer& renderContext, const } #else -#include "taa_frag.h" -#include "fxaa_blend_frag.h" -#include "taa_blend_frag.h" - -const int AntialiasingPass_ParamsSlot = 0; -const int AntialiasingPass_FrameTransformSlot = 1; - -const int AntialiasingPass_HistoryMapSlot = 0; -const int AntialiasingPass_SourceMapSlot = 1; -const int AntialiasingPass_VelocityMapSlot = 2; -const int AntialiasingPass_DepthMapSlot = 3; - -const int AntialiasingPass_NextMapSlot = 4; - - Antialiasing::Antialiasing(bool isSharpenEnabled) : _isSharpenEnabled{ isSharpenEnabled } { } @@ -200,34 +187,13 @@ Antialiasing::~Antialiasing() { const gpu::PipelinePointer& Antialiasing::getAntialiasingPipeline(const render::RenderContextPointer& renderContext) { if (!_antialiasingPipeline) { - - auto vs = gpu::StandardShaderLib::getDrawUnitQuadTexcoordVS(); - auto ps = taa_frag::getShader(); - gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); - - gpu::Shader::BindingSet slotBindings; - + gpu::ShaderPointer program = gpu::Shader::createProgram(shader::render_utils::program::taa); gpu::StatePointer state = gpu::StatePointer(new gpu::State()); PrepareStencil::testNoAA(*state); // Good to go add the brand new pipeline _antialiasingPipeline = gpu::Pipeline::create(program, state); - - gpu::doInBatch("SurfaceGeometryPass::CurvaturePipeline", renderContext->args->_context, [program](gpu::Batch& batch) { - batch.runLambda([program]() { - gpu::Shader::BindingSet slotBindings; - slotBindings.insert(gpu::Shader::Binding(std::string("taaParamsBuffer"), AntialiasingPass_ParamsSlot)); - - slotBindings.insert(gpu::Shader::Binding(std::string("deferredFrameTransformBuffer"), AntialiasingPass_FrameTransformSlot)); - - slotBindings.insert(gpu::Shader::Binding(std::string("historyMap"), AntialiasingPass_HistoryMapSlot)); - slotBindings.insert(gpu::Shader::Binding(std::string("sourceMap"), AntialiasingPass_SourceMapSlot)); - slotBindings.insert(gpu::Shader::Binding(std::string("velocityMap"), AntialiasingPass_VelocityMapSlot)); - slotBindings.insert(gpu::Shader::Binding(std::string("depthMap"), AntialiasingPass_DepthMapSlot)); - gpu::Shader::makeProgram(*program, slotBindings); - }); - }); } return _antialiasingPipeline; @@ -235,46 +201,18 @@ const gpu::PipelinePointer& Antialiasing::getAntialiasingPipeline(const render:: const gpu::PipelinePointer& Antialiasing::getBlendPipeline() { if (!_blendPipeline) { - auto vs = gpu::StandardShaderLib::getDrawUnitQuadTexcoordVS(); - auto ps = fxaa_blend_frag::getShader(); - gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); - - gpu::Shader::BindingSet slotBindings; - slotBindings.insert(gpu::Shader::Binding(std::string("colorTexture"), AntialiasingPass_NextMapSlot)); - - gpu::Shader::makeProgram(*program, slotBindings); - + gpu::ShaderPointer program = gpu::Shader::createProgram(shader::render_utils::program::fxaa_blend); gpu::StatePointer state = gpu::StatePointer(new gpu::State()); PrepareStencil::testNoAA(*state); - - // Good to go add the brand new pipeline _blendPipeline = gpu::Pipeline::create(program, state); - _sharpenLoc = program->getUniforms().findLocation("sharpenIntensity"); - } return _blendPipeline; } const gpu::PipelinePointer& Antialiasing::getDebugBlendPipeline() { if (!_debugBlendPipeline) { - auto vs = gpu::StandardShaderLib::getDrawUnitQuadTexcoordVS(); - auto ps = taa_blend_frag::getShader(); - gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); - - gpu::Shader::BindingSet slotBindings; - slotBindings.insert(gpu::Shader::Binding(std::string("taaParamsBuffer"), AntialiasingPass_ParamsSlot)); - - slotBindings.insert(gpu::Shader::Binding(std::string("deferredFrameTransformBuffer"), AntialiasingPass_FrameTransformSlot)); - slotBindings.insert(gpu::Shader::Binding(std::string("nextMap"), AntialiasingPass_NextMapSlot)); - slotBindings.insert(gpu::Shader::Binding(std::string("historyMap"), AntialiasingPass_HistoryMapSlot)); - slotBindings.insert(gpu::Shader::Binding(std::string("sourceMap"), AntialiasingPass_SourceMapSlot)); - slotBindings.insert(gpu::Shader::Binding(std::string("velocityMap"), AntialiasingPass_VelocityMapSlot)); - slotBindings.insert(gpu::Shader::Binding(std::string("depthMap"), AntialiasingPass_DepthMapSlot)); - - - gpu::Shader::makeProgram(*program, slotBindings); - + gpu::ShaderPointer program = gpu::Shader::createProgram(shader::render_utils::program::taa_blend); gpu::StatePointer state = gpu::StatePointer(new gpu::State()); PrepareStencil::testNoAA(*state); @@ -351,41 +289,43 @@ void Antialiasing::run(const render::RenderContextPointer& renderContext, const // TAA step getAntialiasingPipeline(renderContext); - batch.setResourceFramebufferSwapChainTexture(AntialiasingPass_HistoryMapSlot, _antialiasingBuffers, 0); - batch.setResourceTexture(AntialiasingPass_SourceMapSlot, sourceBuffer->getRenderBuffer(0)); - batch.setResourceTexture(AntialiasingPass_VelocityMapSlot, velocityBuffer->getVelocityTexture()); + batch.setResourceFramebufferSwapChainTexture(ru::Texture::TaaHistory, _antialiasingBuffers, 0); + batch.setResourceTexture(ru::Texture::TaaSource, sourceBuffer->getRenderBuffer(0)); + batch.setResourceTexture(ru::Texture::TaaVelocity, velocityBuffer->getVelocityTexture()); // This is only used during debug - batch.setResourceTexture(AntialiasingPass_DepthMapSlot, linearDepthBuffer->getLinearDepthTexture()); + batch.setResourceTexture(ru::Texture::TaaDepth, linearDepthBuffer->getLinearDepthTexture()); - batch.setUniformBuffer(AntialiasingPass_ParamsSlot, _params); - batch.setUniformBuffer(AntialiasingPass_FrameTransformSlot, deferredFrameTransform->getFrameTransformBuffer()); + batch.setUniformBuffer(ru::Buffer::TaaParams, _params); + batch.setUniformBuffer(ru::Buffer::DeferredFrameTransform, deferredFrameTransform->getFrameTransformBuffer()); batch.setFramebufferSwapChain(_antialiasingBuffers, 1); batch.setPipeline(getAntialiasingPipeline(renderContext)); batch.draw(gpu::TRIANGLE_STRIP, 4); // Blend step - batch.setResourceTexture(AntialiasingPass_SourceMapSlot, nullptr); + batch.setResourceTexture(ru::Texture::TaaSource, nullptr); batch.setFramebuffer(sourceBuffer); if (_params->isDebug()) { batch.setPipeline(getDebugBlendPipeline()); + batch.setResourceFramebufferSwapChainTexture(ru::Texture::TaaNext, _antialiasingBuffers, 1); } else { batch.setPipeline(getBlendPipeline()); + // Must match the bindg point in the fxaa_blend.slf shader + batch.setResourceFramebufferSwapChainTexture(0, _antialiasingBuffers, 1); // Disable sharpen if FXAA - batch._glUniform1f(_sharpenLoc, _sharpen * _params.get().regionInfo.z); + batch._glUniform1f(ru::Uniform::TaaSharpenIntensity, _sharpen * _params.get().regionInfo.z); } - batch.setResourceFramebufferSwapChainTexture(AntialiasingPass_NextMapSlot, _antialiasingBuffers, 1); batch.draw(gpu::TRIANGLE_STRIP, 4); batch.advance(_antialiasingBuffers); - batch.setUniformBuffer(AntialiasingPass_ParamsSlot, nullptr); - batch.setUniformBuffer(AntialiasingPass_FrameTransformSlot, nullptr); + batch.setUniformBuffer(ru::Buffer::TaaParams, nullptr); + batch.setUniformBuffer(ru::Buffer::DeferredFrameTransform, nullptr); - batch.setResourceTexture(AntialiasingPass_DepthMapSlot, nullptr); - batch.setResourceTexture(AntialiasingPass_HistoryMapSlot, nullptr); - batch.setResourceTexture(AntialiasingPass_VelocityMapSlot, nullptr); - batch.setResourceTexture(AntialiasingPass_NextMapSlot, nullptr); + batch.setResourceTexture(ru::Texture::TaaDepth, nullptr); + batch.setResourceTexture(ru::Texture::TaaHistory, nullptr); + batch.setResourceTexture(ru::Texture::TaaVelocity, nullptr); + batch.setResourceTexture(ru::Texture::TaaNext, nullptr); }); } @@ -515,5 +455,4 @@ void JitterSample::run(const render::RenderContextPointer& renderContext, Output } } - #endif diff --git a/libraries/render-utils/src/AntialiasingEffect.h b/libraries/render-utils/src/AntialiasingEffect.h index ffce84495e..61da352154 100644 --- a/libraries/render-utils/src/AntialiasingEffect.h +++ b/libraries/render-utils/src/AntialiasingEffect.h @@ -193,7 +193,6 @@ private: TAAParamsBuffer _params; float _sharpen{ 0.15f }; - int _sharpenLoc{ -1 }; bool _isSharpenEnabled{ true }; }; diff --git a/libraries/render-utils/src/BloomApply.slf b/libraries/render-utils/src/BloomApply.slf index 28415643a0..a53894de60 100644 --- a/libraries/render-utils/src/BloomApply.slf +++ b/libraries/render-utils/src/BloomApply.slf @@ -1,6 +1,6 @@ <@include gpu/Config.slh@> <$VERSION_HEADER$> -// BloomApply.slf +// BloomApply.frag // Mix the three gaussian blur textures. // // Created by Olivier Prat on 10/09/2017 @@ -10,17 +10,18 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // <@include BloomApply.shared.slh@> +<@include render-utils/ShaderConstants.h@> -uniform sampler2D blurMap0; -uniform sampler2D blurMap1; -uniform sampler2D blurMap2; +layout(binding=0) uniform sampler2D blurMap0; +layout(binding=1) uniform sampler2D blurMap1; +layout(binding=2) uniform sampler2D blurMap2; -layout(std140) uniform parametersBuffer { +layout(std140, binding=RENDER_UTILS_BUFFER_BLOOM_PARAMS) uniform parametersBuffer { Parameters parameters; }; -in vec2 varTexCoord0; -out vec4 outFragColor; +layout(location=0) in vec2 varTexCoord0; +layout(location=0) out vec4 outFragColor; void main(void) { vec4 blur0 = texture(blurMap0, varTexCoord0); diff --git a/libraries/render-utils/src/BloomEffect.cpp b/libraries/render-utils/src/BloomEffect.cpp index 0e95655370..3a35e736a9 100644 --- a/libraries/render-utils/src/BloomEffect.cpp +++ b/libraries/render-utils/src/BloomEffect.cpp @@ -10,14 +10,13 @@ // #include "BloomEffect.h" -#include "gpu/Context.h" -#include "gpu/StandardShaderLib.h" +#include + +#include #include #include - -#include "BloomThreshold_frag.h" -#include "BloomApply_frag.h" +#include "render-utils/ShaderConstants.h" #define BLOOM_BLUR_LEVEL_COUNT 3 @@ -26,11 +25,7 @@ BloomThreshold::BloomThreshold(unsigned int downsamplingFactor) { _parameters.edit()._sampleCount = downsamplingFactor; } -void BloomThreshold::configure(const Config& config) { - if (_parameters.get()._threshold != config.threshold) { - _parameters.edit()._threshold = config.threshold; - } -} +void BloomThreshold::configure(const Config& config) {} void BloomThreshold::run(const render::RenderContextPointer& renderContext, const Inputs& inputs, Outputs& outputs) { assert(renderContext->args); @@ -40,6 +35,7 @@ void BloomThreshold::run(const render::RenderContextPointer& renderContext, cons const auto frameTransform = inputs.get0(); const auto inputFrameBuffer = inputs.get1(); + const auto bloom = inputs.get2(); assert(inputFrameBuffer->hasColor()); @@ -61,25 +57,21 @@ void BloomThreshold::run(const render::RenderContextPointer& renderContext, cons _parameters.edit()._deltaUV = { 1.0f / bufferSize.x, 1.0f / bufferSize.y }; } - static const int COLOR_MAP_SLOT = 0; - static const int PARAMETERS_SLOT = 1; - if (!_pipeline) { - auto vs = gpu::StandardShaderLib::getDrawTransformUnitQuadVS(); - auto ps = BloomThreshold_frag::getShader(); - gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); - - gpu::Shader::BindingSet slotBindings; - slotBindings.insert(gpu::Shader::Binding("colorMap", COLOR_MAP_SLOT)); - slotBindings.insert(gpu::Shader::Binding("parametersBuffer", PARAMETERS_SLOT)); - gpu::Shader::makeProgram(*program, slotBindings); - + gpu::ShaderPointer program = gpu::Shader::createProgram(shader::render_utils::program::bloomThreshold); gpu::StatePointer state = gpu::StatePointer(new gpu::State()); _pipeline = gpu::Pipeline::create(program, state); } glm::ivec4 viewport{ 0, 0, bufferSize.x, bufferSize.y }; + if (!bloom) { + renderContext->taskFlow.abortTask(); + return; + } + + _parameters.edit()._threshold = bloom->getBloomThreshold(); + gpu::doInBatch("BloomThreshold::run", args->_context, [&](gpu::Batch& batch) { batch.enableStereo(false); @@ -90,28 +82,20 @@ void BloomThreshold::run(const render::RenderContextPointer& renderContext, cons batch.setPipeline(_pipeline); batch.setFramebuffer(_outputBuffer); - batch.setResourceTexture(COLOR_MAP_SLOT, inputBuffer); - batch.setUniformBuffer(PARAMETERS_SLOT, _parameters); + batch.setResourceTexture(render_utils::slot::texture::BloomColor, inputBuffer); + batch.setUniformBuffer(render_utils::slot::buffer::BloomParams, _parameters); batch.draw(gpu::TRIANGLE_STRIP, 4); }); - outputs = _outputBuffer; + outputs.edit0() = _outputBuffer; + outputs.edit1() = 0.5f + bloom->getBloomSize() * 3.5f; } BloomApply::BloomApply() { } -void BloomApply::configure(const Config& config) { - const auto newIntensity = config.intensity / 3.0f; - - if (_parameters.get()._intensities.x != newIntensity) { - auto& parameters = _parameters.edit(); - parameters._intensities.x = newIntensity; - parameters._intensities.y = newIntensity; - parameters._intensities.z = newIntensity; - } -} +void BloomApply::configure(const Config& config) {} void BloomApply::run(const render::RenderContextPointer& renderContext, const Inputs& inputs) { assert(renderContext->args); @@ -121,20 +105,9 @@ void BloomApply::run(const render::RenderContextPointer& renderContext, const In static const auto BLUR0_SLOT = 0; static const auto BLUR1_SLOT = 1; static const auto BLUR2_SLOT = 2; - static const auto PARAMETERS_SLOT = 0; if (!_pipeline) { - auto vs = gpu::StandardShaderLib::getDrawTransformUnitQuadVS(); - auto ps = BloomApply_frag::getShader(); - gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); - - gpu::Shader::BindingSet slotBindings; - slotBindings.insert(gpu::Shader::Binding("blurMap0", BLUR0_SLOT)); - slotBindings.insert(gpu::Shader::Binding("blurMap1", BLUR1_SLOT)); - slotBindings.insert(gpu::Shader::Binding("blurMap2", BLUR2_SLOT)); - slotBindings.insert(gpu::Shader::Binding("parametersBuffer", PARAMETERS_SLOT)); - gpu::Shader::makeProgram(*program, slotBindings); - + gpu::ShaderPointer program = gpu::Shader::createProgram(shader::render_utils::program::bloomApply); gpu::StatePointer state = gpu::StatePointer(new gpu::State()); state->setDepthTest(gpu::State::DepthTest(false, false)); _pipeline = gpu::Pipeline::create(program, state); @@ -145,8 +118,15 @@ void BloomApply::run(const render::RenderContextPointer& renderContext, const In const auto blur0FB = inputs.get1(); const auto blur1FB = inputs.get2(); const auto blur2FB = inputs.get3(); + const auto bloom = inputs.get4(); const glm::ivec4 viewport{ 0, 0, framebufferSize.x, framebufferSize.y }; + const auto newIntensity = bloom->getBloomIntensity() / 3.0f; + auto& parameters = _parameters.edit(); + parameters._intensities.x = newIntensity; + parameters._intensities.y = newIntensity; + parameters._intensities.z = newIntensity; + gpu::doInBatch("BloomApply::run", args->_context, [&](gpu::Batch& batch) { batch.enableStereo(false); @@ -161,7 +141,7 @@ void BloomApply::run(const render::RenderContextPointer& renderContext, const In batch.setResourceTexture(BLUR0_SLOT, blur0FB->getRenderBuffer(0)); batch.setResourceTexture(BLUR1_SLOT, blur1FB->getRenderBuffer(0)); batch.setResourceTexture(BLUR2_SLOT, blur2FB->getRenderBuffer(0)); - batch.setUniformBuffer(PARAMETERS_SLOT, _parameters); + batch.setUniformBuffer(render_utils::slot::buffer::BloomParams, _parameters); batch.draw(gpu::TRIANGLE_STRIP, 4); }); } @@ -178,13 +158,7 @@ void BloomDraw::run(const render::RenderContextPointer& renderContext, const Inp const auto framebufferSize = frameBuffer->getSize(); if (!_pipeline) { - auto vs = gpu::StandardShaderLib::getDrawTransformUnitQuadVS(); - auto ps = gpu::StandardShaderLib::getDrawTextureOpaquePS(); - gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); - - gpu::Shader::BindingSet slotBindings; - gpu::Shader::makeProgram(*program, slotBindings); - + gpu::ShaderPointer program = gpu::Shader::createProgram(shader::gpu::program::drawTransformUnitQuadTextureOpaque); gpu::StatePointer state = gpu::StatePointer(new gpu::State()); state->setDepthTest(gpu::State::DepthTest(false, false)); state->setBlendFunction(true, gpu::State::ONE, gpu::State::BLEND_OP_ADD, gpu::State::ONE, @@ -234,17 +208,8 @@ void DebugBloom::run(const render::RenderContextPointer& renderContext, const In level2FB->getRenderBuffer(0) }; - static auto TEXCOORD_RECT_SLOT = 1; - if (!_pipeline) { - auto vs = gpu::StandardShaderLib::getDrawTexcoordRectTransformUnitQuadVS(); - auto ps = gpu::StandardShaderLib::getDrawTextureOpaquePS(); - gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); - - gpu::Shader::BindingSet slotBindings; - slotBindings.insert(gpu::Shader::Binding(std::string("texcoordRect"), TEXCOORD_RECT_SLOT)); - gpu::Shader::makeProgram(*program, slotBindings); - + gpu::ShaderPointer program = gpu::Shader::createProgram(shader::gpu::program::drawTextureOpaqueTexcoordRect); gpu::StatePointer state = gpu::StatePointer(new gpu::State()); state->setDepthTest(gpu::State::DepthTest(false)); _pipeline = gpu::Pipeline::create(program, state); @@ -262,7 +227,7 @@ void DebugBloom::run(const render::RenderContextPointer& renderContext, const In Transform modelTransform; if (_mode == DebugBloomConfig::MODE_ALL_LEVELS) { - batch._glUniform4f(TEXCOORD_RECT_SLOT, 0.0f, 0.0f, 1.f, 1.f); + batch._glUniform4f(gpu::slot::uniform::TexCoordRect, 0.0f, 0.0f, 1.f, 1.f); modelTransform = gpu::Framebuffer::evalSubregionTexcoordTransform(framebufferSize, args->_viewport / 2); modelTransform.postTranslate(glm::vec3(-1.0f, 1.0f, 0.0f)); @@ -290,7 +255,7 @@ void DebugBloom::run(const render::RenderContextPointer& renderContext, const In viewport.z /= 2; - batch._glUniform4f(TEXCOORD_RECT_SLOT, 0.5f, 0.0f, 0.5f, 1.f); + batch._glUniform4f(gpu::slot::uniform::TexCoordRect, 0.5f, 0.0f, 0.5f, 1.f); modelTransform = gpu::Framebuffer::evalSubregionTexcoordTransform(framebufferSize, viewport); modelTransform.postTranslate(glm::vec3(-1.0f, 0.0f, 0.0f)); @@ -301,44 +266,10 @@ void DebugBloom::run(const render::RenderContextPointer& renderContext, const In }); } -void BloomConfig::setIntensity(float value) { - auto task = static_cast(_task); - auto blurJobIt = task->editJob("BloomApply"); - assert(blurJobIt != task->_jobs.end()); - blurJobIt->getConfiguration()->setProperty("intensity", value); -} +BloomEffect::BloomEffect() {} -float BloomConfig::getIntensity() const { - auto task = static_cast(_task); - auto blurJobIt = task->getJob("BloomApply"); - assert(blurJobIt != task->_jobs.end()); - return blurJobIt->getConfiguration()->property("intensity").toFloat(); -} - -void BloomConfig::setSize(float value) { - std::string blurName{ "BloomBlurN" }; - auto sigma = 0.5f+value*3.5f; - auto task = static_cast(_task); - - for (auto i = 0; i < BLOOM_BLUR_LEVEL_COUNT; i++) { - blurName.back() = '0' + i; - auto blurJobIt = task->editJob(blurName); - assert(blurJobIt != task->_jobs.end()); - auto& gaussianBlur = blurJobIt->edit(); - auto gaussianBlurParams = gaussianBlur.getParameters(); - gaussianBlurParams->setFilterGaussianTaps(9, sigma); - } - auto blurJobIt = task->getJob("BloomApply"); - assert(blurJobIt != task->_jobs.end()); - blurJobIt->getConfiguration()->setProperty("sigma", sigma); -} - -Bloom::Bloom() { - -} - -void Bloom::configure(const Config& config) { - std::string blurName{ "BloomBlurN" }; +void BloomEffect::configure(const Config& config) { + std::string blurName { "BloomBlurN" }; for (auto i = 0; i < BLOOM_BLUR_LEVEL_COUNT; i++) { blurName.back() = '0' + i; @@ -347,25 +278,30 @@ void Bloom::configure(const Config& config) { } } -void Bloom::build(JobModel& task, const render::Varying& inputs, render::Varying& outputs) { +void BloomEffect::build(JobModel& task, const render::Varying& inputs, render::Varying& outputs) { // Start by computing threshold of color buffer input at quarter resolution - const auto bloomInputBuffer = task.addJob("BloomThreshold", inputs, 4U); + const auto bloomOutputs = task.addJob("BloomThreshold", inputs, 4U); // Multi-scale blur, each new blur is half resolution of the previous pass - const auto blurFB0 = task.addJob("BloomBlur0", bloomInputBuffer, true); - const auto blurFB1 = task.addJob("BloomBlur1", blurFB0, true, 2U); - const auto blurFB2 = task.addJob("BloomBlur2", blurFB1, true, 2U); + const auto blurInputBuffer = bloomOutputs.getN(0); + const auto sigma = bloomOutputs.getN(1); + const auto blurInput0 = render::BlurGaussian::Inputs(blurInputBuffer, true, 1U, 9, sigma).asVarying(); + const auto blurFB0 = task.addJob("BloomBlur0", blurInput0); + const auto blurInput1 = render::BlurGaussian::Inputs(blurFB0, true, 2U, 9, sigma).asVarying(); + const auto blurFB1 = task.addJob("BloomBlur1", blurInput1); + const auto blurInput2 = render::BlurGaussian::Inputs(blurFB1, true, 2U, 9, sigma).asVarying(); + const auto blurFB2 = task.addJob("BloomBlur2", blurInput2); - const auto& input = inputs.get(); - const auto& frameBuffer = input[1]; + const auto& frameBuffer = inputs.getN(1); + const auto& bloom = inputs.getN(2); // Mix all blur levels at quarter resolution - const auto applyInput = BloomApply::Inputs(bloomInputBuffer, blurFB0, blurFB1, blurFB2).asVarying(); + const auto applyInput = BloomApply::Inputs(blurInputBuffer, blurFB0, blurFB1, blurFB2, bloom).asVarying(); task.addJob("BloomApply", applyInput); // And then blend result in additive manner on top of final color buffer - const auto drawInput = BloomDraw::Inputs(frameBuffer, bloomInputBuffer).asVarying(); + const auto drawInput = BloomDraw::Inputs(frameBuffer, blurInputBuffer).asVarying(); task.addJob("BloomDraw", drawInput); - const auto debugInput = DebugBloom::Inputs(frameBuffer, blurFB0, blurFB1, blurFB2, bloomInputBuffer).asVarying(); + const auto debugInput = DebugBloom::Inputs(frameBuffer, blurFB0, blurFB1, blurFB2, blurInputBuffer).asVarying(); task.addJob("DebugBloom", debugInput); } diff --git a/libraries/render-utils/src/BloomEffect.h b/libraries/render-utils/src/BloomEffect.h index 04cb4a9474..7789c70190 100644 --- a/libraries/render-utils/src/BloomEffect.h +++ b/libraries/render-utils/src/BloomEffect.h @@ -14,43 +14,22 @@ #include +#include "graphics/Bloom.h" + #include "DeferredFrameTransform.h" class BloomConfig : public render::Task::Config { Q_OBJECT - Q_PROPERTY(float intensity READ getIntensity WRITE setIntensity NOTIFY dirty) - Q_PROPERTY(float size MEMBER size WRITE setSize NOTIFY dirty) - -public: - - BloomConfig() : render::Task::Config(false) {} - - float size{ 0.7f }; - - void setIntensity(float value); - float getIntensity() const; - void setSize(float value); - -signals: - void dirty(); }; class BloomThresholdConfig : public render::Job::Config { Q_OBJECT - Q_PROPERTY(float threshold MEMBER threshold NOTIFY dirty) - -public: - - float threshold{ 0.9f }; - -signals: - void dirty(); }; class BloomThreshold { public: - using Inputs = render::VaryingSet2; - using Outputs = gpu::FramebufferPointer; + using Inputs = render::VaryingSet3; + using Outputs = render::VaryingSet2; using Config = BloomThresholdConfig; using JobModel = render::Job::ModelIO; @@ -71,21 +50,11 @@ private: class BloomApplyConfig : public render::Job::Config { Q_OBJECT - Q_PROPERTY(float intensity MEMBER intensity NOTIFY dirty) - Q_PROPERTY(float sigma MEMBER sigma NOTIFY dirty) - -public: - - float intensity{ 0.25f }; - float sigma{ 1.0f }; - -signals: - void dirty(); }; class BloomApply { public: - using Inputs = render::VaryingSet4; + using Inputs = render::VaryingSet5; using Config = BloomApplyConfig; using JobModel = render::Job::ModelI; @@ -118,7 +87,7 @@ private: class DebugBloomConfig : public render::Job::Config { Q_OBJECT - Q_PROPERTY(int mode MEMBER mode NOTIFY dirty) + Q_PROPERTY(int mode MEMBER mode NOTIFY dirty) public: @@ -155,13 +124,13 @@ private: DebugBloomConfig::Mode _mode; }; -class Bloom { +class BloomEffect { public: - using Inputs = render::VaryingSet2; + using Inputs = render::VaryingSet3; using Config = BloomConfig; - using JobModel = render::Task::ModelI; + using JobModel = render::Task::ModelI; - Bloom(); + BloomEffect(); void configure(const Config& config); void build(JobModel& task, const render::Varying& inputs, render::Varying& outputs); diff --git a/libraries/render-utils/src/BloomStage.cpp b/libraries/render-utils/src/BloomStage.cpp new file mode 100644 index 0000000000..904e27f5da --- /dev/null +++ b/libraries/render-utils/src/BloomStage.cpp @@ -0,0 +1,79 @@ +// +// BloomStage.cpp +// +// Created by Sam Gondelman on 8/7/2018 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#include "BloomStage.h" + +#include "DeferredLightingEffect.h" + +#include + +std::string BloomStage::_stageName { "BLOOM_STAGE"}; +const BloomStage::Index BloomStage::INVALID_INDEX { render::indexed_container::INVALID_INDEX }; + +FetchBloomStage::FetchBloomStage() { + _bloom = std::make_shared(); +} + +void FetchBloomStage::configure(const Config& config) { + _bloom->setBloomIntensity(config.bloomIntensity); + _bloom->setBloomThreshold(config.bloomThreshold); + _bloom->setBloomSize(config.bloomSize); +} + +BloomStage::Index BloomStage::findBloom(const BloomPointer& bloom) const { + auto found = _bloomMap.find(bloom); + if (found != _bloomMap.end()) { + return INVALID_INDEX; + } else { + return (*found).second; + } +} + +BloomStage::Index BloomStage::addBloom(const BloomPointer& bloom) { + auto found = _bloomMap.find(bloom); + if (found == _bloomMap.end()) { + auto bloomId = _blooms.newElement(bloom); + // Avoid failing to allocate a bloom, just pass + if (bloomId != INVALID_INDEX) { + // Insert the bloom and its index in the reverse map + _bloomMap.insert(BloomMap::value_type(bloom, bloomId)); + } + return bloomId; + } else { + return (*found).second; + } +} + +BloomStage::BloomPointer BloomStage::removeBloom(Index index) { + BloomPointer removed = _blooms.freeElement(index); + if (removed) { + _bloomMap.erase(removed); + } + return removed; +} + +BloomStageSetup::BloomStageSetup() {} + +void BloomStageSetup::run(const render::RenderContextPointer& renderContext) { + auto stage = renderContext->_scene->getStage(BloomStage::getName()); + if (!stage) { + renderContext->_scene->resetStage(BloomStage::getName(), std::make_shared()); + } +} + +void FetchBloomStage::run(const render::RenderContextPointer& renderContext, graphics::BloomPointer& bloom) { + auto bloomStage = renderContext->_scene->getStage(); + assert(bloomStage); + + bloom = nullptr; + if (bloomStage->_currentFrame._blooms.size() != 0) { + auto bloomId = bloomStage->_currentFrame._blooms.front(); + bloom = bloomStage->getBloom(bloomId); + } +} diff --git a/libraries/render-utils/src/BloomStage.h b/libraries/render-utils/src/BloomStage.h new file mode 100644 index 0000000000..27bd97dcab --- /dev/null +++ b/libraries/render-utils/src/BloomStage.h @@ -0,0 +1,118 @@ +// +// BloomStage.h + +// Created by Sam Gondelman on 8/7/2018 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_render_utils_BloomStage_h +#define hifi_render_utils_BloomStage_h + +#include +#include +#include +#include +#include + +#include +#include +#include + +// Bloom stage to set up bloom-related rendering tasks +class BloomStage : public render::Stage { +public: + static std::string _stageName; + static const std::string& getName() { return _stageName; } + + using Index = render::indexed_container::Index; + static const Index INVALID_INDEX; + static bool isIndexInvalid(Index index) { return index == INVALID_INDEX; } + + using BloomPointer = graphics::BloomPointer; + using Blooms = render::indexed_container::IndexedPointerVector; + using BloomMap = std::unordered_map; + + using BloomIndices = std::vector; + + Index findBloom(const BloomPointer& bloom) const; + Index addBloom(const BloomPointer& bloom); + + BloomPointer removeBloom(Index index); + + bool checkBloomId(Index index) const { return _blooms.checkIndex(index); } + + Index getNumBlooms() const { return _blooms.getNumElements(); } + Index getNumFreeBlooms() const { return _blooms.getNumFreeIndices(); } + Index getNumAllocatedBlooms() const { return _blooms.getNumAllocatedIndices(); } + + BloomPointer getBloom(Index bloomId) const { + return _blooms.get(bloomId); + } + + Blooms _blooms; + BloomMap _bloomMap; + + class Frame { + public: + Frame() {} + + void clear() { _blooms.clear(); } + + void pushBloom(BloomStage::Index index) { _blooms.emplace_back(index); } + + BloomStage::BloomIndices _blooms; + }; + + Frame _currentFrame; +}; +using BloomStagePointer = std::shared_ptr; + +class BloomStageSetup { +public: + using JobModel = render::Job::Model; + + BloomStageSetup(); + void run(const render::RenderContextPointer& renderContext); + +protected: +}; + +class FetchBloomConfig : public render::Job::Config { + Q_OBJECT + Q_PROPERTY(float bloomIntensity MEMBER bloomIntensity WRITE setBloomIntensity NOTIFY dirty); + Q_PROPERTY(float bloomThreshold MEMBER bloomThreshold WRITE setBloomThreshold NOTIFY dirty); + Q_PROPERTY(float bloomSize MEMBER bloomSize WRITE setBloomSize NOTIFY dirty); + +public: + FetchBloomConfig() : render::Job::Config() {} + + float bloomIntensity { graphics::Bloom::INITIAL_BLOOM_INTENSITY }; + float bloomThreshold { graphics::Bloom::INITIAL_BLOOM_THRESHOLD }; + float bloomSize { graphics::Bloom::INITIAL_BLOOM_SIZE }; + +public slots: + void setBloomIntensity(const float value) { bloomIntensity = value; emit dirty(); } + void setBloomThreshold(const float value) { bloomThreshold = value; emit dirty(); } + void setBloomSize(const float value) { bloomSize = value; emit dirty(); } + +signals: + void dirty(); +}; + +class FetchBloomStage { +public: + using Config = FetchBloomConfig; + using JobModel = render::Job::ModelO; + + FetchBloomStage(); + + void configure(const Config& config); + void run(const render::RenderContextPointer& renderContext, graphics::BloomPointer& bloom); + +private: + graphics::BloomPointer _bloom; +}; +#endif diff --git a/libraries/render-utils/src/BloomThreshold.slf b/libraries/render-utils/src/BloomThreshold.slf index 6eb75fba6e..621aa31622 100644 --- a/libraries/render-utils/src/BloomThreshold.slf +++ b/libraries/render-utils/src/BloomThreshold.slf @@ -1,6 +1,6 @@ <@include gpu/Config.slh@> <$VERSION_HEADER$> -// BloomThreshold.slf +// BloomThreshold.frag // Perform a soft threshold on an input texture and downsample to half size in one go. // // Created by Olivier Prat on 09/26/2017 @@ -10,14 +10,15 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // <@include BloomThreshold.shared.slh@> +<@include render-utils/ShaderConstants.h@> -uniform sampler2D colorMap; -layout(std140) uniform parametersBuffer { +layout(binding=RENDER_UTILS_TEXTURE_BLOOM_COLOR) uniform sampler2D colorMap; +layout(std140, binding=RENDER_UTILS_BUFFER_BLOOM_PARAMS) uniform parametersBuffer { Parameters parameters; }; -in vec2 varTexCoord0; -out vec4 outFragColor; +layout(location=0) in vec2 varTexCoord0; +layout(location=0) out vec4 outFragColor; void main(void) { vec2 startUv = varTexCoord0; diff --git a/libraries/render-utils/src/CauterizedMeshPartPayload.cpp b/libraries/render-utils/src/CauterizedMeshPartPayload.cpp index a4dc56e696..a310c10136 100644 --- a/libraries/render-utils/src/CauterizedMeshPartPayload.cpp +++ b/libraries/render-utils/src/CauterizedMeshPartPayload.cpp @@ -12,6 +12,7 @@ #include "CauterizedMeshPartPayload.h" #include +#include #include "CauterizedModel.h" @@ -58,7 +59,7 @@ void CauterizedMeshPartPayload::bindTransform(gpu::Batch& batch, RenderArgs::Ren bool useCauterizedMesh = (renderMode != RenderArgs::RenderMode::SHADOW_RENDER_MODE && renderMode != RenderArgs::RenderMode::SECONDARY_CAMERA_RENDER_MODE) && _enableCauterization; if (useCauterizedMesh) { if (_cauterizedClusterBuffer) { - batch.setUniformBuffer(ShapePipeline::Slot::BUFFER::SKINNING, _cauterizedClusterBuffer); + batch.setUniformBuffer(graphics::slot::buffer::Skinning, _cauterizedClusterBuffer); } batch.setModelTransform(_cauterizedTransform); } else { diff --git a/libraries/render-utils/src/CauterizedModel.cpp b/libraries/render-utils/src/CauterizedModel.cpp index 80a9c5ccae..c4631c3676 100644 --- a/libraries/render-utils/src/CauterizedModel.cpp +++ b/libraries/render-utils/src/CauterizedModel.cpp @@ -76,6 +76,7 @@ void CauterizedModel::createRenderItemSet() { // Run through all of the meshes, and place them into their segregated, but unsorted buckets int shapeID = 0; uint32_t numMeshes = (uint32_t)meshes.size(); + const FBXGeometry& fbxGeometry = getFBXGeometry(); for (uint32_t i = 0; i < numMeshes; i++) { const auto& mesh = meshes.at(i); if (!mesh) { @@ -85,6 +86,9 @@ void CauterizedModel::createRenderItemSet() { // Create the render payloads int numParts = (int)mesh->getNumParts(); for (int partIndex = 0; partIndex < numParts; partIndex++) { + if (!fbxGeometry.meshes[i].blendshapes.empty()) { + initializeBlendshapes(fbxGeometry.meshes[i], i); + } auto ptr = std::make_shared(shared_from_this(), i, partIndex, shapeID, transform, offset); _modelMeshRenderItems << std::static_pointer_cast(ptr); auto material = getGeometry()->getShapeMaterial(shapeID); @@ -170,9 +174,10 @@ void CauterizedModel::updateClusterMatrices() { } // post the blender if we're not currently waiting for one to finish - if (geometry.hasBlendedMeshes() && _blendshapeCoefficients != _blendedBlendshapeCoefficients) { + auto modelBlender = DependencyManager::get(); + if (_blendedVertexBuffersInitialized && modelBlender->shouldComputeBlendshapes() && geometry.hasBlendedMeshes() && _blendshapeCoefficients != _blendedBlendshapeCoefficients) { _blendedBlendshapeCoefficients = _blendshapeCoefficients; - DependencyManager::get()->noteRequiresBlend(getThisPointer()); + modelBlender->noteRequiresBlend(getThisPointer()); } } diff --git a/libraries/render-utils/src/DebugDeferredBuffer.cpp b/libraries/render-utils/src/DebugDeferredBuffer.cpp index 8575df399e..0e61b36280 100644 --- a/libraries/render-utils/src/DebugDeferredBuffer.cpp +++ b/libraries/render-utils/src/DebugDeferredBuffer.cpp @@ -18,14 +18,13 @@ #include #include #include +#include +#include "render-utils/ShaderConstants.h" #include "GeometryCache.h" #include "TextureCache.h" #include "DeferredLightingEffect.h" -#include "debug_deferred_buffer_vert.h" -#include "debug_deferred_buffer_frag.h" - using namespace render; void DebugDeferredBufferConfig::setMode(int newMode) { @@ -39,53 +38,28 @@ void DebugDeferredBufferConfig::setMode(int newMode) { emit dirty(); } -enum TextureSlot { - Albedo = 0, - Normal, - Specular, - Depth, - Lighting, - Shadow, - LinearDepth, - HalfLinearDepth, - HalfNormal, - Curvature, - DiffusedCurvature, - Scattering, - AmbientOcclusion, - AmbientOcclusionBlurred, - Velocity, -}; - -enum ParamSlot { - CameraCorrection = 0, - DeferredFrameTransform, - ShadowTransform, - DebugParametersBuffer -}; - -static const std::string DEFAULT_ALBEDO_SHADER { +static const std::string DEFAULT_ALBEDO_SHADER{ "vec4 getFragmentColor() {" " DeferredFragment frag = unpackDeferredFragmentNoPosition(uv);" " return vec4(pow(frag.albedo, vec3(1.0 / 2.2)), 1.0);" " }" }; -static const std::string DEFAULT_METALLIC_SHADER { +static const std::string DEFAULT_METALLIC_SHADER{ "vec4 getFragmentColor() {" " DeferredFragment frag = unpackDeferredFragmentNoPosition(uv);" " return vec4(vec3(pow(frag.metallic, 1.0 / 2.2)), 1.0);" " }" }; -static const std::string DEFAULT_ROUGHNESS_SHADER { +static const std::string DEFAULT_ROUGHNESS_SHADER{ "vec4 getFragmentColor() {" " DeferredFragment frag = unpackDeferredFragmentNoPosition(uv);" " return vec4(vec3(pow(frag.roughness, 1.0 / 2.2)), 1.0);" - // " return vec4(vec3(pow(colorRamp(frag.roughness), vec3(1.0 / 2.2))), 1.0);" + // " return vec4(vec3(pow(colorRamp(frag.roughness), vec3(1.0 / 2.2))), 1.0);" " }" }; -static const std::string DEFAULT_NORMAL_SHADER { +static const std::string DEFAULT_NORMAL_SHADER{ "vec4 getFragmentColor() {" " DeferredFragment frag = unpackDeferredFragmentNoPosition(uv);" " return vec4(vec3(0.5) + (frag.normal * 0.5), 1.0);" @@ -94,8 +68,8 @@ static const std::string DEFAULT_NORMAL_SHADER { static const std::string DEFAULT_OCCLUSION_SHADER{ "vec4 getFragmentColor() {" - // " DeferredFragment frag = unpackDeferredFragmentNoPosition(uv);" - // " return vec4(vec3(pow(frag.obscurance, 1.0 / 2.2)), 1.0);" + // " DeferredFragment frag = unpackDeferredFragmentNoPosition(uv);" + // " return vec4(vec3(pow(frag.obscurance, 1.0 / 2.2)), 1.0);" " return vec4(vec3(pow(texture(specularMap, uv).a, 1.0 / 2.2)), 1.0);" " }" }; @@ -103,7 +77,8 @@ static const std::string DEFAULT_OCCLUSION_SHADER{ static const std::string DEFAULT_EMISSIVE_SHADER{ "vec4 getFragmentColor() {" " DeferredFragment frag = unpackDeferredFragmentNoPosition(uv);" - " return (frag.mode == FRAG_MODE_SHADED ? vec4(pow(texture(specularMap, uv).rgb, vec3(1.0 / 2.2)), 1.0) : vec4(vec3(0.0), 1.0));" + " return (frag.mode == FRAG_MODE_SHADED ? vec4(pow(texture(specularMap, uv).rgb, vec3(1.0 / 2.2)), 1.0) : " + "vec4(vec3(0.0), 1.0));" " }" }; @@ -117,7 +92,8 @@ static const std::string DEFAULT_UNLIT_SHADER{ static const std::string DEFAULT_LIGHTMAP_SHADER{ "vec4 getFragmentColor() {" " DeferredFragment frag = unpackDeferredFragmentNoPosition(uv);" - " return (frag.mode == FRAG_MODE_LIGHTMAPPED ? vec4(pow(texture(specularMap, uv).rgb, vec3(1.0 / 2.2)), 1.0) : vec4(vec3(0.0), 1.0));" + " return (frag.mode == FRAG_MODE_LIGHTMAPPED ? vec4(pow(texture(specularMap, uv).rgb, vec3(1.0 / 2.2)), 1.0) : " + "vec4(vec3(0.0), 1.0));" " }" }; @@ -128,13 +104,13 @@ static const std::string DEFAULT_SCATTERING_SHADER{ " }" }; -static const std::string DEFAULT_DEPTH_SHADER { +static const std::string DEFAULT_DEPTH_SHADER{ "vec4 getFragmentColor() {" " return vec4(vec3(texture(depthMap, uv).x), 1.0);" " }" }; -static const std::string DEFAULT_LIGHTING_SHADER { +static const std::string DEFAULT_LIGHTING_SHADER{ "vec4 getFragmentColor() {" " return vec4(pow(texture(lightingMap, uv).xyz, vec3(1.0 / 2.2)), 1.0);" " }" @@ -173,28 +149,28 @@ static const std::string DEFAULT_SHADOW_CASCADE_SHADER{ "}" }; -static const std::string DEFAULT_LINEAR_DEPTH_SHADER { +static const std::string DEFAULT_LINEAR_DEPTH_SHADER{ "vec4 getFragmentColor() {" - " return vec4(vec3(1.0 - texture(linearDepthMap, uv).x * 0.01), 1.0);" + " return vec4(vec3(1.0 - texture(debugTexture0, uv).x * 0.01), 1.0);" "}" }; static const std::string DEFAULT_HALF_LINEAR_DEPTH_SHADER{ "vec4 getFragmentColor() {" - " return vec4(vec3(1.0 - texture(halfLinearDepthMap, uv).x * 0.01), 1.0);" + " return vec4(vec3(1.0 - texture(debugTexture0, uv).x * 0.01), 1.0);" " }" }; static const std::string DEFAULT_HALF_NORMAL_SHADER{ "vec4 getFragmentColor() {" - " return vec4(vec3(texture(halfNormalMap, uv).xyz), 1.0);" + " return vec4(vec3(texture(debugTexture0, uv).xyz), 1.0);" " }" }; static const std::string DEFAULT_CURVATURE_SHADER{ "vec4 getFragmentColor() {" " return vec4(pow(vec3(texture(curvatureMap, uv).a), vec3(1.0 / 2.2)), 1.0);" - // " return vec4(pow(vec3(texture(curvatureMap, uv).xyz), vec3(1.0 / 2.2)), 1.0);" + // " return vec4(pow(vec3(texture(curvatureMap, uv).xyz), vec3(1.0 / 2.2)), 1.0);" //" return vec4(vec3(1.0 - textureLod(pyramidMap, uv, 3).x * 0.01), 1.0);" " }" }; @@ -202,7 +178,7 @@ static const std::string DEFAULT_CURVATURE_SHADER{ static const std::string DEFAULT_NORMAL_CURVATURE_SHADER{ "vec4 getFragmentColor() {" //" return vec4(pow(vec3(texture(curvatureMap, uv).a), vec3(1.0 / 2.2)), 1.0);" - " return vec4(vec3(texture(curvatureMap, uv).xyz), 1.0);" + " return vec4(vec3(texture(curvatureMap, uv).xyz), 1.0);" //" return vec4(vec3(1.0 - textureLod(pyramidMap, uv, 3).x * 0.01), 1.0);" " }" }; @@ -237,31 +213,31 @@ static const std::string DEFAULT_CURVATURE_OCCLUSION_SHADER{ static const std::string DEFAULT_DEBUG_SCATTERING_SHADER{ "vec4 getFragmentColor() {" - " return vec4(pow(vec3(texture(scatteringMap, uv).xyz), vec3(1.0 / 2.2)), 1.0);" - // " return vec4(vec3(texture(scatteringMap, uv).xyz), 1.0);" + " return vec4(pow(vec3(texture(debugTexture0, uv).xyz), vec3(1.0 / 2.2)), 1.0);" + // " return vec4(vec3(texture(debugTexture0, uv).xyz), 1.0);" " }" }; static const std::string DEFAULT_AMBIENT_OCCLUSION_SHADER{ "vec4 getFragmentColor() {" " return vec4(vec3(texture(obscuranceMap, uv).x), 1.0);" - // When drawing color " return vec4(vec3(texture(occlusionMap, uv).xyz), 1.0);" - // when drawing normal" return vec4(normalize(texture(occlusionMap, uv).xyz * 2.0 - vec3(1.0)), 1.0);" + // When drawing color " return vec4(vec3(texture(debugTexture0, uv).xyz), 1.0);" + // when drawing normal" return vec4(normalize(texture(debugTexture0, uv).xyz * 2.0 - vec3(1.0)), 1.0);" " }" }; static const std::string DEFAULT_AMBIENT_OCCLUSION_BLURRED_SHADER{ "vec4 getFragmentColor() {" - " return vec4(vec3(texture(occlusionBlurredMap, uv).xyz), 1.0);" + " return vec4(vec3(texture(debugTexture0, uv).xyz), 1.0);" " }" }; static const std::string DEFAULT_VELOCITY_SHADER{ "vec4 getFragmentColor() {" - " return vec4(vec2(texture(velocityMap, uv).xy), 0.0, 1.0);" + " return vec4(vec2(texture(debugTexture0, uv).xy), 0.0, 1.0);" " }" }; -static const std::string DEFAULT_CUSTOM_SHADER { +static const std::string DEFAULT_CUSTOM_SHADER{ "vec4 getFragmentColor() {" " return vec4(1.0, 0.0, 0.0, 1.0);" " }" @@ -272,12 +248,11 @@ static std::string getFileContent(std::string fileName, std::string defaultConte if (customFile.open(QIODevice::ReadOnly)) { return customFile.readAll().toStdString(); } - qWarning() << "DebugDeferredBuffer::getFileContent(): Could not open" - << QString::fromStdString(fileName); + qWarning() << "DebugDeferredBuffer::getFileContent(): Could not open" << QString::fromStdString(fileName); return defaultContent; } -#include // TODO REMOVE: Temporary until UI +#include // TODO REMOVE: Temporary until UI DebugDeferredBuffer::DebugDeferredBuffer() { // TODO REMOVE: Temporary until UI static const auto DESKTOP_PATH = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation); @@ -363,60 +338,35 @@ bool DebugDeferredBuffer::pipelineNeedsUpdate(Mode mode, std::string customFile) if (mode != CustomMode) { return !_pipelines[mode]; } - + auto it = _customPipelines.find(customFile); if (it != _customPipelines.end() && it->second.pipeline) { auto& info = it->second.info; - + auto lastModified = info.lastModified(); info.refresh(); return lastModified != info.lastModified(); } - + return true; } const gpu::PipelinePointer& DebugDeferredBuffer::getPipeline(Mode mode, std::string customFile) { if (pipelineNeedsUpdate(mode, customFile)) { - static const std::string FRAGMENT_SHADER { debug_deferred_buffer_frag::getSource() }; - static const std::string SOURCE_PLACEHOLDER { "//SOURCE_PLACEHOLDER" }; - static const auto SOURCE_PLACEHOLDER_INDEX = FRAGMENT_SHADER.find(SOURCE_PLACEHOLDER); - Q_ASSERT_X(SOURCE_PLACEHOLDER_INDEX != std::string::npos, Q_FUNC_INFO, - "Could not find source placeholder"); - - auto bakedFragmentShader = FRAGMENT_SHADER; - bakedFragmentShader.replace(SOURCE_PLACEHOLDER_INDEX, SOURCE_PLACEHOLDER.size(), - getShaderSourceCode(mode, customFile)); - - const auto vs = debug_deferred_buffer_vert::getShader(); - const auto ps = gpu::Shader::createPixel(bakedFragmentShader); - const auto program = gpu::Shader::createProgram(vs, ps); - - gpu::Shader::BindingSet slotBindings; - slotBindings.insert(gpu::Shader::Binding("cameraCorrectionBuffer", CameraCorrection)); - slotBindings.insert(gpu::Shader::Binding("deferredFrameTransformBuffer", DeferredFrameTransform)); - slotBindings.insert(gpu::Shader::Binding("shadowTransformBuffer", ShadowTransform)); - slotBindings.insert(gpu::Shader::Binding("parametersBuffer", DebugParametersBuffer)); + static const auto FRAGMENT_SHADER_SOURCE = + gpu::Shader::createPixel(shader::render_utils::fragment::debug_deferred_buffer)->getSource(); + static const std::string SOURCE_PLACEHOLDER{ "//SOURCE_PLACEHOLDER" }; + static const auto SOURCE_PLACEHOLDER_INDEX = FRAGMENT_SHADER_SOURCE.getCode().find(SOURCE_PLACEHOLDER); + Q_ASSERT_X(SOURCE_PLACEHOLDER_INDEX != std::string::npos, Q_FUNC_INFO, "Could not find source placeholder"); - slotBindings.insert(gpu::Shader::Binding("albedoMap", Albedo)); - slotBindings.insert(gpu::Shader::Binding("normalMap", Normal)); - slotBindings.insert(gpu::Shader::Binding("specularMap", Specular)); - slotBindings.insert(gpu::Shader::Binding("depthMap", Depth)); - slotBindings.insert(gpu::Shader::Binding("obscuranceMap", AmbientOcclusion)); - slotBindings.insert(gpu::Shader::Binding("lightingMap", Lighting)); - slotBindings.insert(gpu::Shader::Binding("shadowMaps", Shadow)); - slotBindings.insert(gpu::Shader::Binding("linearDepthMap", LinearDepth)); - slotBindings.insert(gpu::Shader::Binding("halfLinearDepthMap", HalfLinearDepth)); - slotBindings.insert(gpu::Shader::Binding("halfNormalMap", HalfNormal)); - slotBindings.insert(gpu::Shader::Binding("curvatureMap", Curvature)); - slotBindings.insert(gpu::Shader::Binding("diffusedCurvatureMap", DiffusedCurvature)); - slotBindings.insert(gpu::Shader::Binding("scatteringMap", Scattering)); - slotBindings.insert(gpu::Shader::Binding("occlusionBlurredMap", AmbientOcclusionBlurred)); - slotBindings.insert(gpu::Shader::Binding("velocityMap", Velocity)); - gpu::Shader::makeProgram(*program, slotBindings); - + auto bakedFragmentShader = FRAGMENT_SHADER_SOURCE.getCode(); + bakedFragmentShader.replace(SOURCE_PLACEHOLDER_INDEX, SOURCE_PLACEHOLDER.size(), getShaderSourceCode(mode, customFile)); + + const auto vs = gpu::Shader::createVertex(shader::render_utils::vertex::debug_deferred_buffer); + const auto ps = gpu::Shader::createPixel({ bakedFragmentShader, FRAGMENT_SHADER_SOURCE.getReflection() }); + const auto program = gpu::Shader::createProgram(vs, ps); auto pipeline = gpu::Pipeline::create(program, std::make_shared()); - + // Good to go add the brand new pipeline if (mode != CustomMode) { _pipelines[mode] = pipeline; @@ -424,7 +374,7 @@ const gpu::PipelinePointer& DebugDeferredBuffer::getPipeline(Mode mode, std::str _customPipelines[customFile].pipeline = pipeline; } } - + if (mode != CustomMode) { return _pipelines[mode]; } else { @@ -471,70 +421,75 @@ void DebugDeferredBuffer::run(const RenderContextPointer& renderContext, const I batch.setViewTransform(viewMat, true); batch.setModelTransform(Transform()); + using Textures = render_utils::slot::texture::Texture; + using UBOs = render_utils::slot::buffer::Buffer; + // TODO REMOVE: Temporary until UI auto first = _customPipelines.begin()->first; auto pipeline = getPipeline(_mode, first); batch.setPipeline(pipeline); if (deferredFramebuffer) { - batch.setResourceTexture(Albedo, deferredFramebuffer->getDeferredColorTexture()); - batch.setResourceTexture(Normal, deferredFramebuffer->getDeferredNormalTexture()); - batch.setResourceTexture(Specular, deferredFramebuffer->getDeferredSpecularTexture()); - batch.setResourceTexture(Depth, deferredFramebuffer->getPrimaryDepthTexture()); - batch.setResourceTexture(Lighting, deferredFramebuffer->getLightingTexture()); + batch.setResourceTexture(Textures::DeferredColor, deferredFramebuffer->getDeferredColorTexture()); + batch.setResourceTexture(Textures::DeferredNormal, deferredFramebuffer->getDeferredNormalTexture()); + batch.setResourceTexture(Textures::DeferredSpecular, deferredFramebuffer->getDeferredSpecularTexture()); + batch.setResourceTexture(Textures::DeferredDepth, deferredFramebuffer->getPrimaryDepthTexture()); + batch.setResourceTexture(Textures::DeferredLighting, deferredFramebuffer->getLightingTexture()); } - if (velocityFramebuffer) { - batch.setResourceTexture(Velocity, velocityFramebuffer->getVelocityTexture()); + if (velocityFramebuffer && _mode == VelocityMode) { + batch.setResourceTexture(Textures::DebugTexture0, velocityFramebuffer->getVelocityTexture()); } - batch.setUniformBuffer(DebugParametersBuffer, _parameters); - auto lightStage = renderContext->_scene->getStage(); assert(lightStage); assert(lightStage->getNumLights() > 0); auto lightAndShadow = lightStage->getCurrentKeyLightAndShadow(); const auto& globalShadow = lightAndShadow.second; if (globalShadow) { - batch.setResourceTexture(Shadow, globalShadow->map); - batch.setUniformBuffer(ShadowTransform, globalShadow->getBuffer()); - batch.setUniformBuffer(DeferredFrameTransform, frameTransform->getFrameTransformBuffer()); + batch.setResourceTexture(Textures::Shadow, globalShadow->map); + batch.setUniformBuffer(UBOs::ShadowParams, globalShadow->getBuffer()); + batch.setUniformBuffer(UBOs::DeferredFrameTransform, frameTransform->getFrameTransformBuffer()); + batch.setUniformBuffer(UBOs::DebugDeferredParams, _parameters); } if (linearDepthTarget) { - batch.setResourceTexture(LinearDepth, linearDepthTarget->getLinearDepthTexture()); - batch.setResourceTexture(HalfLinearDepth, linearDepthTarget->getHalfLinearDepthTexture()); - batch.setResourceTexture(HalfNormal, linearDepthTarget->getHalfNormalTexture()); + if (_mode == DepthMode) { + batch.setResourceTexture(Textures::DebugTexture0, linearDepthTarget->getLinearDepthTexture()); + } else if (_mode == HalfLinearDepthMode) { + batch.setResourceTexture(Textures::DebugTexture0, linearDepthTarget->getHalfLinearDepthTexture()); + } else if (_mode == HalfNormalMode) { + batch.setResourceTexture(Textures::DebugTexture0, linearDepthTarget->getHalfNormalTexture()); + } } if (surfaceGeometryFramebuffer) { - batch.setResourceTexture(Curvature, surfaceGeometryFramebuffer->getCurvatureTexture()); - batch.setResourceTexture(DiffusedCurvature, surfaceGeometryFramebuffer->getLowCurvatureTexture()); + batch.setResourceTexture(Textures::DeferredCurvature, surfaceGeometryFramebuffer->getCurvatureTexture()); + batch.setResourceTexture(Textures::DeferredDiffusedCurvature, + surfaceGeometryFramebuffer->getLowCurvatureTexture()); } if (ambientOcclusionFramebuffer) { - batch.setResourceTexture(AmbientOcclusion, ambientOcclusionFramebuffer->getOcclusionTexture()); - batch.setResourceTexture(AmbientOcclusionBlurred, ambientOcclusionFramebuffer->getOcclusionBlurredTexture()); + if (_mode == AmbientOcclusionMode) { + batch.setResourceTexture(Textures::DebugTexture0, ambientOcclusionFramebuffer->getOcclusionTexture()); + } else if (_mode == AmbientOcclusionBlurredMode) { + batch.setResourceTexture(Textures::DebugTexture0, ambientOcclusionFramebuffer->getOcclusionBlurredTexture()); + } } const glm::vec4 color(1.0f, 1.0f, 1.0f, 1.0f); const glm::vec2 bottomLeft(_size.x, _size.y); const glm::vec2 topRight(_size.z, _size.w); geometryBuffer->renderQuad(batch, bottomLeft, topRight, color, _geometryId); - batch.setResourceTexture(Albedo, nullptr); - batch.setResourceTexture(Normal, nullptr); - batch.setResourceTexture(Specular, nullptr); - batch.setResourceTexture(Depth, nullptr); - batch.setResourceTexture(Lighting, nullptr); - batch.setResourceTexture(Shadow, nullptr); - batch.setResourceTexture(LinearDepth, nullptr); - batch.setResourceTexture(HalfLinearDepth, nullptr); - batch.setResourceTexture(HalfNormal, nullptr); + batch.setResourceTexture(Textures::DeferredColor, nullptr); + batch.setResourceTexture(Textures::DeferredNormal, nullptr); + batch.setResourceTexture(Textures::DeferredSpecular, nullptr); + batch.setResourceTexture(Textures::DeferredDepth, nullptr); + batch.setResourceTexture(Textures::DeferredCurvature, nullptr); + batch.setResourceTexture(Textures::DeferredDiffusedCurvature, nullptr); + batch.setResourceTexture(Textures::DeferredLighting, nullptr); - batch.setResourceTexture(Curvature, nullptr); - batch.setResourceTexture(DiffusedCurvature, nullptr); - - batch.setResourceTexture(AmbientOcclusion, nullptr); - batch.setResourceTexture(AmbientOcclusionBlurred, nullptr); - - batch.setResourceTexture(Velocity, nullptr); + for (auto i = 0; i < SHADOW_CASCADE_MAX_COUNT; i++) { + batch.setResourceTexture(Textures::Shadow + i, nullptr); + } + batch.setResourceTexture(Textures::DebugTexture0, nullptr); }); } diff --git a/libraries/render-utils/src/DeferredBufferRead.slh b/libraries/render-utils/src/DeferredBufferRead.slh index f7ab0957cc..8c6c76b24a 100644 --- a/libraries/render-utils/src/DeferredBufferRead.slh +++ b/libraries/render-utils/src/DeferredBufferRead.slh @@ -11,26 +11,29 @@ <@if not DEFERRED_BUFFER_READ_SLH@> <@def DEFERRED_BUFFER_READ_SLH@> +<@include render-utils/ShaderConstants.h@> <@include DeferredBuffer.slh@> +// See DeferredShader_MapSlot in DeferredLightingEffect.cpp for constants + // the albedo texture -uniform sampler2D albedoMap; +layout(binding=RENDER_UTILS_TEXTURE_DEFERRRED_COLOR) uniform sampler2D albedoMap; // the normal texture -uniform sampler2D normalMap; +layout(binding=RENDER_UTILS_TEXTURE_DEFERRRED_NORMAL) uniform sampler2D normalMap; // the specular texture -uniform sampler2D specularMap; +layout(binding=RENDER_UTILS_TEXTURE_DEFERRRED_SPECULAR) uniform sampler2D specularMap; // the depth texture -uniform sampler2D depthMap; -uniform sampler2D linearZeyeMap; +layout(binding=RENDER_UTILS_TEXTURE_DEFERRRED_DEPTH) uniform sampler2D depthMap; +layout(binding=RENDER_UTILS_TEXTURE_DEFERRRED_LINEAR_Z_EYE) uniform sampler2D linearZeyeMap; // the obscurance texture -uniform sampler2D obscuranceMap; +layout(binding=RENDER_UTILS_TEXTURE_DEFERRED_OBSCURANCE) uniform sampler2D obscuranceMap; // the lighting texture -uniform sampler2D lightingMap; +layout(binding=RENDER_UTILS_TEXTURE_DEFERRED_LIGHTING) uniform sampler2D lightingMap; struct DeferredFragment { @@ -167,14 +170,14 @@ DeferredFragment unpackDeferredFragment(DeferredFrameTransform deferredTransform <@func declareDeferredCurvature()@> // the curvature texture -uniform sampler2D curvatureMap; +layout(binding=RENDER_UTILS_TEXTURE_DEFERRED_CURVATURE) uniform sampler2D curvatureMap; vec4 fetchCurvature(vec2 texcoord) { return texture(curvatureMap, texcoord); } // the curvature texture -uniform sampler2D diffusedCurvatureMap; +layout(binding=RENDER_UTILS_TEXTURE_DEFERRED_DIFFUSED_CURVATURE) uniform sampler2D diffusedCurvatureMap; vec4 fetchDiffusedCurvature(vec2 texcoord) { return texture(diffusedCurvatureMap, texcoord); diff --git a/libraries/render-utils/src/DeferredBufferWrite.slh b/libraries/render-utils/src/DeferredBufferWrite.slh index ae8d6fa277..769e602dc5 100644 --- a/libraries/render-utils/src/DeferredBufferWrite.slh +++ b/libraries/render-utils/src/DeferredBufferWrite.slh @@ -14,11 +14,10 @@ <@include DeferredBuffer.slh@> -layout(location = 0) out vec4 _fragColor0; -layout(location = 1) out vec4 _fragColor1; -layout(location = 2) out vec4 _fragColor2; - -layout(location = 3) out vec4 _fragColor3; +layout(location=0) out vec4 _fragColor0; // albedo / metallic +layout(location=1) out vec4 _fragColor1; // Normal +layout(location=2) out vec4 _fragColor2; // scattering / emissive / occlusion +layout(location=3) out vec4 _fragColor3; // emissive // the alpha threshold const float alphaThreshold = 0.5; diff --git a/libraries/render-utils/src/DeferredFrameTransform.h b/libraries/render-utils/src/DeferredFrameTransform.h index f8181b6b8a..f7700cb2dc 100644 --- a/libraries/render-utils/src/DeferredFrameTransform.h +++ b/libraries/render-utils/src/DeferredFrameTransform.h @@ -43,20 +43,20 @@ protected: glm::vec4 depthInfo; // Stereo info is { isStereoFrame, halfWidth } glm::vec4 stereoInfo{ 0.0 }; - // Mono proj matrix or Left and Right proj matrix going from Mono Eye space to side clip space - glm::mat4 projection[2]; - // Inverse proj matrix or Left and Right proj matrix going from Mono Eye space to side clip space - glm::mat4 invProjection[2]; - // THe mono projection for sure + // Mono proj matrix or Left and Right proj matrix going from Mono Eye space to side clip space + glm::mat4 projection[2]; + // Inverse proj matrix or Left and Right proj matrix going from Mono Eye space to side clip space + glm::mat4 invProjection[2]; + // THe mono projection for sure glm::mat4 projectionMono; // Inv View matrix from eye space (mono) to world space glm::mat4 invView; // View matrix from world space to eye space (mono) glm::mat4 view; - // Mono proj matrix or Left and Right proj matrix going from Mono Eye space to side clip space without jittering - glm::mat4 projectionUnjittered[2]; - // Inverse proj matrix or Left and Right proj matrix going from Mono Eye space to side clip space without jittering - glm::mat4 invProjectionUnjittered[2]; + // Mono proj matrix or Left and Right proj matrix going from Mono Eye space to side clip space without jittering + glm::mat4 projectionUnjittered[2]; + // Inverse proj matrix or Left and Right proj matrix going from Mono Eye space to side clip space without jittering + glm::mat4 invProjectionUnjittered[2]; FrameTransform() {} }; diff --git a/libraries/render-utils/src/DeferredLightingEffect.cpp b/libraries/render-utils/src/DeferredLightingEffect.cpp index 62d8dffe3a..af5a3f3e63 100644 --- a/libraries/render-utils/src/DeferredLightingEffect.cpp +++ b/libraries/render-utils/src/DeferredLightingEffect.cpp @@ -10,82 +10,46 @@ // #include "DeferredLightingEffect.h" - #include #include #include #include #include - #include #include +#include +#include + +#include "render-utils/ShaderConstants.h" #include "StencilMaskPass.h" #include "AbstractViewStateInterface.h" #include "GeometryCache.h" #include "TextureCache.h" #include "FramebufferCache.h" -#include "deferred_light_vert.h" -#include "deferred_light_point_vert.h" -#include "deferred_light_spot_vert.h" - -#include "directional_ambient_light_frag.h" -#include "directional_skybox_light_frag.h" - -#include "directional_ambient_light_shadow_frag.h" -#include "directional_skybox_light_shadow_frag.h" - -#include "local_lights_shading_frag.h" -#include "local_lights_drawOutline_frag.h" +namespace ru { + using render_utils::slot::texture::Texture; + using render_utils::slot::buffer::Buffer; + using render_utils::slot::uniform::Uniform; +} +namespace gr { + using graphics::slot::texture::Texture; + using graphics::slot::buffer::Buffer; +} using namespace render; struct LightLocations { - int radius{ -1 }; - int keyLightBufferUnit{ -1 }; - int lightBufferUnit{ -1 }; - int ambientBufferUnit { -1 }; - int lightIndexBufferUnit { -1 }; - int texcoordFrameTransform{ -1 }; - int deferredFrameTransformBuffer{ -1 }; - int subsurfaceScatteringParametersBuffer{ -1 }; - int shadowTransformBuffer{ -1 }; + bool shadowTransform{ false }; + void initialize(const gpu::ShaderPointer& program) { + shadowTransform = program->getUniformBuffers().isValid(ru::Buffer::ShadowParams); + } }; -enum DeferredShader_MapSlot { - DEFERRED_BUFFER_COLOR_UNIT = 0, - DEFERRED_BUFFER_NORMAL_UNIT = 1, - DEFERRED_BUFFER_EMISSIVE_UNIT = 2, - DEFERRED_BUFFER_DEPTH_UNIT = 3, - DEFERRED_BUFFER_OBSCURANCE_UNIT = 4, - DEFERRED_BUFFER_LINEAR_DEPTH_UNIT = 5, - DEFERRED_BUFFER_CURVATURE_UNIT = 6, - DEFERRED_BUFFER_DIFFUSED_CURVATURE_UNIT = 7, - SCATTERING_LUT_UNIT = 8, - SCATTERING_SPECULAR_UNIT = 9, - SKYBOX_MAP_UNIT = render::ShapePipeline::Slot::LIGHT_AMBIENT_MAP, // unit = 10 - SHADOW_MAP_UNIT = 11, - nextAvailableUnit = SHADOW_MAP_UNIT -}; -enum DeferredShader_BufferSlot { - DEFERRED_FRAME_TRANSFORM_BUFFER_SLOT = 0, - CAMERA_CORRECTION_BUFFER_SLOT, - SCATTERING_PARAMETERS_BUFFER_SLOT, - LIGHTING_MODEL_BUFFER_SLOT = render::ShapePipeline::Slot::LIGHTING_MODEL, - KEY_LIGHT_SLOT = render::ShapePipeline::Slot::KEY_LIGHT, - LIGHT_ARRAY_SLOT = render::ShapePipeline::Slot::LIGHT_ARRAY_BUFFER, - LIGHT_AMBIENT_SLOT = render::ShapePipeline::Slot::LIGHT_AMBIENT_BUFFER, - HAZE_MODEL_BUFFER_SLOT = render::ShapePipeline::Slot::HAZE_MODEL, - LIGHT_INDEX_GPU_SLOT, - LIGHT_CLUSTER_GRID_FRUSTUM_GRID_SLOT, - LIGHT_CLUSTER_GRID_CLUSTER_GRID_SLOT, - LIGHT_CLUSTER_GRID_CLUSTER_CONTENT_SLOT, -}; - -static void loadLightProgram(const gpu::ShaderPointer& vertShader, const gpu::ShaderPointer& fragShader, bool lightVolume, gpu::PipelinePointer& program, LightLocationsPtr& locations); +static void loadLightProgram(int programId, bool lightVolume, gpu::PipelinePointer& program, LightLocationsPtr& locations); void DeferredLightingEffect::init() { _directionalAmbientSphereLightLocations = std::make_shared(); @@ -97,17 +61,17 @@ void DeferredLightingEffect::init() { _localLightLocations = std::make_shared(); _localLightOutlineLocations = std::make_shared(); - loadLightProgram(deferred_light_vert::getShader(), directional_ambient_light_frag::getShader(), false, _directionalAmbientSphereLight, _directionalAmbientSphereLightLocations); - loadLightProgram(deferred_light_vert::getShader(), directional_skybox_light_frag::getShader(), false, _directionalSkyboxLight, _directionalSkyboxLightLocations); + loadLightProgram(shader::render_utils::program::directional_ambient_light, false, _directionalAmbientSphereLight, _directionalAmbientSphereLightLocations); + loadLightProgram(shader::render_utils::program::directional_skybox_light, false, _directionalSkyboxLight, _directionalSkyboxLightLocations); - loadLightProgram(deferred_light_vert::getShader(), directional_ambient_light_shadow_frag::getShader(), false, _directionalAmbientSphereLightShadow, _directionalAmbientSphereLightShadowLocations); - loadLightProgram(deferred_light_vert::getShader(), directional_skybox_light_shadow_frag::getShader(), false, _directionalSkyboxLightShadow, _directionalSkyboxLightShadowLocations); + loadLightProgram(shader::render_utils::program::directional_ambient_light_shadow, false, _directionalAmbientSphereLightShadow, _directionalAmbientSphereLightShadowLocations); + loadLightProgram(shader::render_utils::program::directional_skybox_light_shadow, false, _directionalSkyboxLightShadow, _directionalSkyboxLightShadowLocations); - loadLightProgram(deferred_light_vert::getShader(), local_lights_shading_frag::getShader(), true, _localLight, _localLightLocations); - loadLightProgram(deferred_light_vert::getShader(), local_lights_drawOutline_frag::getShader(), true, _localLightOutline, _localLightOutlineLocations); + loadLightProgram(shader::render_utils::program::local_lights_shading, true, _localLight, _localLightLocations); + loadLightProgram(shader::render_utils::program::local_lights_drawOutline, true, _localLightOutline, _localLightOutlineLocations); } -void DeferredLightingEffect::setupKeyLightBatch(const RenderArgs* args, gpu::Batch& batch, int lightBufferUnit, int ambientBufferUnit, int skyboxCubemapUnit) { +void DeferredLightingEffect::setupKeyLightBatch(const RenderArgs* args, gpu::Batch& batch) { PerformanceTimer perfTimer("DLE->setupBatch()"); graphics::LightPointer keySunLight; auto lightStage = args->_scene->getStage(); @@ -121,114 +85,49 @@ void DeferredLightingEffect::setupKeyLightBatch(const RenderArgs* args, gpu::Bat } if (keySunLight) { - if (lightBufferUnit >= 0) { - batch.setUniformBuffer(lightBufferUnit, keySunLight->getLightSchemaBuffer()); - } + batch.setUniformBuffer(gr::Buffer::KeyLight, keySunLight->getLightSchemaBuffer()); } if (keyAmbiLight) { - if (ambientBufferUnit >= 0) { - batch.setUniformBuffer(ambientBufferUnit, keyAmbiLight->getAmbientSchemaBuffer()); - } + batch.setUniformBuffer(gr::Buffer::AmbientLight, keyAmbiLight->getAmbientSchemaBuffer()); - if (keyAmbiLight->getAmbientMap() && (skyboxCubemapUnit >= 0)) { - batch.setResourceTexture(skyboxCubemapUnit, keyAmbiLight->getAmbientMap()); + if (keyAmbiLight->getAmbientMap() ) { + batch.setResourceTexture(ru::Texture::Skybox, keyAmbiLight->getAmbientMap()); } } } -void DeferredLightingEffect::unsetKeyLightBatch(gpu::Batch& batch, int lightBufferUnit, int ambientBufferUnit, int skyboxCubemapUnit) { - if (lightBufferUnit >= 0) { - batch.setUniformBuffer(lightBufferUnit, nullptr); - } - if ((ambientBufferUnit >= 0)) { - batch.setUniformBuffer(ambientBufferUnit, nullptr); - } - - if ((skyboxCubemapUnit >= 0)) { - batch.setResourceTexture(skyboxCubemapUnit, nullptr); - } +void DeferredLightingEffect::unsetKeyLightBatch(gpu::Batch& batch) { + batch.setUniformBuffer(gr::Buffer::KeyLight, nullptr); + batch.setUniformBuffer(gr::Buffer::AmbientLight, nullptr); + batch.setResourceTexture(ru::Texture::Skybox, nullptr); } -void DeferredLightingEffect::setupLocalLightsBatch(gpu::Batch& batch, - int lightArrayBufferUnit, int clusterGridBufferUnit, int clusterContentBufferUnit, int frustumGridBufferUnit, - const LightClustersPointer& lightClusters) { +void DeferredLightingEffect::setupLocalLightsBatch(gpu::Batch& batch, const LightClustersPointer& lightClusters) { // Bind the global list of lights and the visible lights this frame - batch.setUniformBuffer(lightArrayBufferUnit, lightClusters->_lightStage->getLightArrayBuffer()); + batch.setUniformBuffer(gr::Buffer::Light, lightClusters->_lightStage->getLightArrayBuffer()); - batch.setUniformBuffer(frustumGridBufferUnit, lightClusters->_frustumGridBuffer); - batch.setUniformBuffer(clusterGridBufferUnit, lightClusters->_clusterGridBuffer); - batch.setUniformBuffer(clusterContentBufferUnit, lightClusters->_clusterContentBuffer); + batch.setUniformBuffer(ru::Buffer::LightClusterFrustumGrid, lightClusters->_frustumGridBuffer); + batch.setUniformBuffer(ru::Buffer::LightClusterGrid, lightClusters->_clusterGridBuffer); + batch.setUniformBuffer(ru::Buffer::LightClusterContent, lightClusters->_clusterContentBuffer); } -void DeferredLightingEffect::unsetLocalLightsBatch(gpu::Batch& batch, int lightArrayBufferUnit, int clusterGridBufferUnit, int clusterContentBufferUnit, int frustumGridBufferUnit) { - if (lightArrayBufferUnit >= 0) { - batch.setUniformBuffer(lightArrayBufferUnit, nullptr); - } - if (clusterGridBufferUnit >= 0) { - batch.setUniformBuffer(clusterGridBufferUnit, nullptr); - } - if (clusterContentBufferUnit >= 0) { - batch.setUniformBuffer(clusterContentBufferUnit, nullptr); - } - if (frustumGridBufferUnit >= 0) { - batch.setUniformBuffer(frustumGridBufferUnit, nullptr); - } +void DeferredLightingEffect::unsetLocalLightsBatch(gpu::Batch& batch) { + batch.setUniformBuffer(gr::Buffer::Light, nullptr); + batch.setUniformBuffer(ru::Buffer::LightClusterGrid, nullptr); + batch.setUniformBuffer(ru::Buffer::LightClusterContent, nullptr); + batch.setUniformBuffer(ru::Buffer::LightClusterFrustumGrid, nullptr); } -static gpu::ShaderPointer makeLightProgram(const gpu::ShaderPointer& vertShader, const gpu::ShaderPointer& fragShader, LightLocationsPtr& locations) { - gpu::ShaderPointer program = gpu::Shader::createProgram(vertShader, fragShader); - gpu::Shader::BindingSet slotBindings; - slotBindings.insert(gpu::Shader::Binding(std::string("colorMap"), DEFERRED_BUFFER_COLOR_UNIT)); - slotBindings.insert(gpu::Shader::Binding(std::string("normalMap"), DEFERRED_BUFFER_NORMAL_UNIT)); - slotBindings.insert(gpu::Shader::Binding(std::string("specularMap"), DEFERRED_BUFFER_EMISSIVE_UNIT)); - slotBindings.insert(gpu::Shader::Binding(std::string("depthMap"), DEFERRED_BUFFER_DEPTH_UNIT)); - slotBindings.insert(gpu::Shader::Binding(std::string("obscuranceMap"), DEFERRED_BUFFER_OBSCURANCE_UNIT)); - slotBindings.insert(gpu::Shader::Binding(std::string("shadowMaps"), SHADOW_MAP_UNIT)); - slotBindings.insert(gpu::Shader::Binding(std::string("skyboxMap"), SKYBOX_MAP_UNIT)); - - slotBindings.insert(gpu::Shader::Binding(std::string("linearZeyeMap"), DEFERRED_BUFFER_LINEAR_DEPTH_UNIT)); - slotBindings.insert(gpu::Shader::Binding(std::string("curvatureMap"), DEFERRED_BUFFER_CURVATURE_UNIT)); - slotBindings.insert(gpu::Shader::Binding(std::string("diffusedCurvatureMap"), DEFERRED_BUFFER_DIFFUSED_CURVATURE_UNIT)); - slotBindings.insert(gpu::Shader::Binding(std::string("scatteringLUT"), SCATTERING_LUT_UNIT)); - slotBindings.insert(gpu::Shader::Binding(std::string("scatteringSpecularBeckmann"), SCATTERING_SPECULAR_UNIT)); - - - slotBindings.insert(gpu::Shader::Binding(std::string("cameraCorrectionBuffer"), CAMERA_CORRECTION_BUFFER_SLOT)); - slotBindings.insert(gpu::Shader::Binding(std::string("deferredFrameTransformBuffer"), DEFERRED_FRAME_TRANSFORM_BUFFER_SLOT)); - slotBindings.insert(gpu::Shader::Binding(std::string("lightingModelBuffer"), LIGHTING_MODEL_BUFFER_SLOT)); - slotBindings.insert(gpu::Shader::Binding(std::string("hazeBuffer"), HAZE_MODEL_BUFFER_SLOT)); - slotBindings.insert(gpu::Shader::Binding(std::string("subsurfaceScatteringParametersBuffer"), SCATTERING_PARAMETERS_BUFFER_SLOT)); - slotBindings.insert(gpu::Shader::Binding(std::string("keyLightBuffer"), KEY_LIGHT_SLOT)); - slotBindings.insert(gpu::Shader::Binding(std::string("lightBuffer"), LIGHT_ARRAY_SLOT)); - slotBindings.insert(gpu::Shader::Binding(std::string("lightAmbientBuffer"), LIGHT_AMBIENT_SLOT)); - slotBindings.insert(gpu::Shader::Binding(std::string("lightIndexBuffer"), LIGHT_INDEX_GPU_SLOT)); - - - slotBindings.insert(gpu::Shader::Binding(std::string("frustumGridBuffer"), LIGHT_CLUSTER_GRID_FRUSTUM_GRID_SLOT)); - slotBindings.insert(gpu::Shader::Binding(std::string("clusterGridBuffer"), LIGHT_CLUSTER_GRID_CLUSTER_GRID_SLOT)); - slotBindings.insert(gpu::Shader::Binding(std::string("clusterContentBuffer"), LIGHT_CLUSTER_GRID_CLUSTER_CONTENT_SLOT)); - - gpu::Shader::makeProgram(*program, slotBindings); - - locations->radius = program->getUniforms().findLocation("radius"); - - locations->texcoordFrameTransform = program->getUniforms().findLocation("texcoordFrameTransform"); - - locations->keyLightBufferUnit = program->getUniformBuffers().findLocation("keyLightBuffer"); - locations->lightBufferUnit = program->getUniformBuffers().findLocation("lightBuffer"); - locations->ambientBufferUnit = program->getUniformBuffers().findLocation("lightAmbientBuffer"); - locations->lightIndexBufferUnit = program->getUniformBuffers().findLocation("lightIndexBuffer"); - locations->deferredFrameTransformBuffer = program->getUniformBuffers().findLocation("deferredFrameTransformBuffer"); - locations->subsurfaceScatteringParametersBuffer = program->getUniformBuffers().findLocation("subsurfaceScatteringParametersBuffer"); - locations->shadowTransformBuffer = program->getUniformBuffers().findLocation("shadowTransformBuffer"); - +static gpu::ShaderPointer makeLightProgram(int programId, LightLocationsPtr& locations) { + gpu::ShaderPointer program = gpu::Shader::createProgram(programId); + locations->initialize(program); return program; } -static void loadLightProgram(const gpu::ShaderPointer& vertShader, const gpu::ShaderPointer& fragShader, bool lightVolume, gpu::PipelinePointer& pipeline, LightLocationsPtr& locations) { +static void loadLightProgram(int programId, bool lightVolume, gpu::PipelinePointer& pipeline, LightLocationsPtr& locations) { - gpu::ShaderPointer program = makeLightProgram(vertShader, fragShader, locations); + gpu::ShaderPointer program = makeLightProgram(programId, locations); auto state = std::make_shared(); state->setColorWriteMask(true, true, true, false); @@ -237,9 +136,8 @@ static void loadLightProgram(const gpu::ShaderPointer& vertShader, const gpu::Sh PrepareStencil::testShape(*state); state->setCullMode(gpu::State::CULL_BACK); - // state->setCullMode(gpu::State::CULL_FRONT); - // state->setDepthTest(true, false, gpu::GREATER_EQUAL); - + //state->setCullMode(gpu::State::CULL_FRONT); + //state->setDepthTest(true, false, gpu::GREATER_EQUAL); //state->setDepthClampEnable(true); // TODO: We should use DepthClamp and avoid changing geometry for inside /outside cases // additive blending @@ -462,7 +360,7 @@ void PrepareDeferred::run(const RenderContextPointer& renderContext, const Input vec4(vec3(0), 0), 1.0, 0, true); // For the rest of the rendering, bind the lighting model - batch.setUniformBuffer(LIGHTING_MODEL_BUFFER_SLOT, lightingModel->getParametersBuffer()); + batch.setUniformBuffer(ru::Buffer::LightModel, lightingModel->getParametersBuffer()); }); @@ -501,35 +399,35 @@ void RenderDeferredSetup::run(const render::RenderContextPointer& renderContext, // Bind the G-Buffer surfaces - batch.setResourceTexture(DEFERRED_BUFFER_COLOR_UNIT, deferredFramebuffer->getDeferredColorTexture()); - batch.setResourceTexture(DEFERRED_BUFFER_NORMAL_UNIT, deferredFramebuffer->getDeferredNormalTexture()); - batch.setResourceTexture(DEFERRED_BUFFER_EMISSIVE_UNIT, deferredFramebuffer->getDeferredSpecularTexture()); - batch.setResourceTexture(DEFERRED_BUFFER_DEPTH_UNIT, deferredFramebuffer->getPrimaryDepthTexture()); + batch.setResourceTexture(ru::Texture::DeferredColor, deferredFramebuffer->getDeferredColorTexture()); + batch.setResourceTexture(ru::Texture::DeferredNormal, deferredFramebuffer->getDeferredNormalTexture()); + batch.setResourceTexture(ru::Texture::DeferredSpecular, deferredFramebuffer->getDeferredSpecularTexture()); + batch.setResourceTexture(ru::Texture::DeferredDepth, deferredFramebuffer->getPrimaryDepthTexture()); // FIXME: Different render modes should have different tasks if (args->_renderMode == RenderArgs::DEFAULT_RENDER_MODE && deferredLightingEffect->isAmbientOcclusionEnabled() && ambientOcclusionFramebuffer) { - batch.setResourceTexture(DEFERRED_BUFFER_OBSCURANCE_UNIT, ambientOcclusionFramebuffer->getOcclusionTexture()); + batch.setResourceTexture(ru::Texture::DeferredObscurance, ambientOcclusionFramebuffer->getOcclusionTexture()); } else { // need to assign the white texture if ao is off - batch.setResourceTexture(DEFERRED_BUFFER_OBSCURANCE_UNIT, textureCache->getWhiteTexture()); + batch.setResourceTexture(ru::Texture::DeferredObscurance, textureCache->getWhiteTexture()); } // The Deferred Frame Transform buffer - batch.setUniformBuffer(DEFERRED_FRAME_TRANSFORM_BUFFER_SLOT, frameTransform->getFrameTransformBuffer()); + batch.setUniformBuffer(ru::Buffer::DeferredFrameTransform, frameTransform->getFrameTransformBuffer()); // THe lighting model - batch.setUniformBuffer(LIGHTING_MODEL_BUFFER_SLOT, lightingModel->getParametersBuffer()); + batch.setUniformBuffer(ru::Buffer::LightModel, lightingModel->getParametersBuffer()); // Subsurface scattering specific if (surfaceGeometryFramebuffer) { - batch.setResourceTexture(DEFERRED_BUFFER_LINEAR_DEPTH_UNIT, surfaceGeometryFramebuffer->getLinearDepthTexture()); - batch.setResourceTexture(DEFERRED_BUFFER_CURVATURE_UNIT, surfaceGeometryFramebuffer->getCurvatureTexture()); - batch.setResourceTexture(DEFERRED_BUFFER_DIFFUSED_CURVATURE_UNIT, surfaceGeometryFramebuffer->getLowCurvatureTexture()); + batch.setResourceTexture(ru::Texture::DeferredLinearZEye, surfaceGeometryFramebuffer->getLinearDepthTexture()); + batch.setResourceTexture(ru::Texture::DeferredCurvature, surfaceGeometryFramebuffer->getCurvatureTexture()); + batch.setResourceTexture(ru::Texture::DeferredDiffusedCurvature, surfaceGeometryFramebuffer->getLowCurvatureTexture()); } if (subsurfaceScatteringResource) { - batch.setUniformBuffer(SCATTERING_PARAMETERS_BUFFER_SLOT, subsurfaceScatteringResource->getParametersBuffer()); - batch.setResourceTexture(SCATTERING_LUT_UNIT, subsurfaceScatteringResource->getScatteringTable()); - batch.setResourceTexture(SCATTERING_SPECULAR_UNIT, subsurfaceScatteringResource->getScatteringSpecular()); + batch.setUniformBuffer(ru::Buffer::SsscParams, subsurfaceScatteringResource->getParametersBuffer()); + batch.setResourceTexture(ru::Texture::SsscLut, subsurfaceScatteringResource->getScatteringTable()); + batch.setResourceTexture(ru::Texture::SsscSpecularBeckmann, subsurfaceScatteringResource->getScatteringSpecular()); } // Global directional light and ambient pass @@ -542,7 +440,7 @@ void RenderDeferredSetup::run(const render::RenderContextPointer& renderContext, // Bind the shadow buffers if (globalShadow) { - batch.setResourceTexture(SHADOW_MAP_UNIT, globalShadow->map); + batch.setResourceTexture(ru::Texture::Shadow, globalShadow->map); } auto program = deferredLightingEffect->_directionalSkyboxLight; @@ -591,33 +489,27 @@ void RenderDeferredSetup::run(const render::RenderContextPointer& renderContext, } } - if (locations->shadowTransformBuffer >= 0) { - if (globalShadow) { - batch.setUniformBuffer(locations->shadowTransformBuffer, globalShadow->getBuffer()); - } + if (locations->shadowTransform && globalShadow) { + batch.setUniformBuffer(ru::Buffer::ShadowParams, globalShadow->getBuffer()); } batch.setPipeline(program); } - // Adjust the texcoordTransform in the case we are rendeirng a sub region(mini mirror) - auto textureFrameTransform = gpu::Framebuffer::evalSubregionTexcoordTransformCoefficients(deferredFramebuffer->getFrameSize(), args->_viewport); - batch._glUniform4fv(locations->texcoordFrameTransform, 1, reinterpret_cast< const float* >(&textureFrameTransform)); - // Setup the global lighting - deferredLightingEffect->setupKeyLightBatch(args, batch, KEY_LIGHT_SLOT, LIGHT_AMBIENT_SLOT, SKYBOX_MAP_UNIT); + deferredLightingEffect->setupKeyLightBatch(args, batch); // Haze if (haze) { - batch.setUniformBuffer(HAZE_MODEL_BUFFER_SLOT, haze->getHazeParametersBuffer()); + batch.setUniformBuffer(ru::Buffer::HazeParams, haze->getHazeParametersBuffer()); } batch.draw(gpu::TRIANGLE_STRIP, 4); - deferredLightingEffect->unsetKeyLightBatch(batch, KEY_LIGHT_SLOT, LIGHT_AMBIENT_SLOT, SKYBOX_MAP_UNIT); + deferredLightingEffect->unsetKeyLightBatch(batch); for (auto i = 0; i < SHADOW_CASCADE_MAX_COUNT; i++) { - batch.setResourceTexture(SHADOW_MAP_UNIT+i, nullptr); + batch.setResourceTexture(ru::Texture::Shadow +i, nullptr); } } } @@ -664,25 +556,18 @@ void RenderDeferredLocals::run(const render::RenderContextPointer& renderContext batch.setViewportTransform(viewport); batch.setStateScissorRect(viewport); - auto textureFrameTransform = gpu::Framebuffer::evalSubregionTexcoordTransformCoefficients(deferredFramebuffer->getFrameSize(), viewport); - - auto& lightIndices = lightClusters->_visibleLightIndices; if (!lightIndices.empty() && lightIndices[0] > 0) { - deferredLightingEffect->setupLocalLightsBatch(batch, - LIGHT_ARRAY_SLOT, LIGHT_CLUSTER_GRID_CLUSTER_GRID_SLOT, LIGHT_CLUSTER_GRID_CLUSTER_CONTENT_SLOT, LIGHT_CLUSTER_GRID_FRUSTUM_GRID_SLOT, - lightClusters); + deferredLightingEffect->setupLocalLightsBatch(batch, lightClusters); // Local light pipeline batch.setPipeline(deferredLightingEffect->_localLight); - batch._glUniform4fv(deferredLightingEffect->_localLightLocations->texcoordFrameTransform, 1, reinterpret_cast(&textureFrameTransform)); batch.draw(gpu::TRIANGLE_STRIP, 4); // Draw outline as well ? if (lightingModel->isShowLightContourEnabled()) { batch.setPipeline(deferredLightingEffect->_localLightOutline); - batch._glUniform4fv(deferredLightingEffect->_localLightOutlineLocations->texcoordFrameTransform, 1, reinterpret_cast(&textureFrameTransform)); batch.draw(gpu::TRIANGLE_STRIP, 4); } @@ -695,25 +580,25 @@ void RenderDeferredCleanup::run(const render::RenderContextPointer& renderContex auto& batch = (*args->_batch); { // Probably not necessary in the long run because the gpu layer would unbound this texture if used as render target - batch.setResourceTexture(DEFERRED_BUFFER_COLOR_UNIT, nullptr); - batch.setResourceTexture(DEFERRED_BUFFER_NORMAL_UNIT, nullptr); - batch.setResourceTexture(DEFERRED_BUFFER_EMISSIVE_UNIT, nullptr); - batch.setResourceTexture(DEFERRED_BUFFER_DEPTH_UNIT, nullptr); - batch.setResourceTexture(DEFERRED_BUFFER_OBSCURANCE_UNIT, nullptr); + batch.setResourceTexture(ru::Texture::DeferredColor, nullptr); + batch.setResourceTexture(ru::Texture::DeferredNormal, nullptr); + batch.setResourceTexture(ru::Texture::DeferredSpecular, nullptr); + batch.setResourceTexture(ru::Texture::DeferredDepth, nullptr); + batch.setResourceTexture(ru::Texture::DeferredObscurance, nullptr); - batch.setResourceTexture(DEFERRED_BUFFER_LINEAR_DEPTH_UNIT, nullptr); - batch.setResourceTexture(DEFERRED_BUFFER_CURVATURE_UNIT, nullptr); - batch.setResourceTexture(DEFERRED_BUFFER_DIFFUSED_CURVATURE_UNIT, nullptr); - batch.setResourceTexture(SCATTERING_LUT_UNIT, nullptr); - batch.setResourceTexture(SCATTERING_SPECULAR_UNIT, nullptr); + batch.setResourceTexture(ru::Texture::DeferredLinearZEye, nullptr); + batch.setResourceTexture(ru::Texture::DeferredCurvature, nullptr); + batch.setResourceTexture(ru::Texture::DeferredDiffusedCurvature, nullptr); + batch.setResourceTexture(ru::Texture::SsscLut, nullptr); + batch.setResourceTexture(ru::Texture::SsscSpecularBeckmann, nullptr); - batch.setUniformBuffer(SCATTERING_PARAMETERS_BUFFER_SLOT, nullptr); + batch.setUniformBuffer(ru::Buffer::SsscParams, nullptr); // batch.setUniformBuffer(LIGHTING_MODEL_BUFFER_SLOT, nullptr); - batch.setUniformBuffer(DEFERRED_FRAME_TRANSFORM_BUFFER_SLOT, nullptr); + batch.setUniformBuffer(ru::Buffer::DeferredFrameTransform, nullptr); - batch.setUniformBuffer(LIGHT_CLUSTER_GRID_FRUSTUM_GRID_SLOT, nullptr); - batch.setUniformBuffer(LIGHT_CLUSTER_GRID_CLUSTER_GRID_SLOT, nullptr); - batch.setUniformBuffer(LIGHT_CLUSTER_GRID_CLUSTER_CONTENT_SLOT, nullptr); + batch.setUniformBuffer(ru::Buffer::LightClusterFrustumGrid, nullptr); + batch.setUniformBuffer(ru::Buffer::LightClusterGrid, nullptr); + batch.setUniformBuffer(ru::Buffer::LightClusterContent, nullptr); } } diff --git a/libraries/render-utils/src/DeferredLightingEffect.h b/libraries/render-utils/src/DeferredLightingEffect.h index 5da2eb22f7..70bfb65f38 100644 --- a/libraries/render-utils/src/DeferredLightingEffect.h +++ b/libraries/render-utils/src/DeferredLightingEffect.h @@ -48,11 +48,11 @@ class DeferredLightingEffect : public Dependency { public: void init(); - void setupKeyLightBatch(const RenderArgs* args, gpu::Batch& batch, int lightBufferUnit, int ambientBufferUnit, int skyboxCubemapUnit); - void unsetKeyLightBatch(gpu::Batch& batch, int lightBufferUnit, int ambientBufferUnit, int skyboxCubemapUnit); + static void setupKeyLightBatch(const RenderArgs* args, gpu::Batch& batch); + static void unsetKeyLightBatch(gpu::Batch& batch); - void setupLocalLightsBatch(gpu::Batch& batch, int lightArrayBufferUnit, int clusterGridBufferUnit, int clusterContentBufferUnit, int frustumGridBufferUnit, const LightClustersPointer& lightClusters); - void unsetLocalLightsBatch(gpu::Batch& batch, int lightArrayBufferUnit, int clusterGridBufferUnit, int clusterContentBufferUnit, int frustumGridBufferUnit); + static void setupLocalLightsBatch(gpu::Batch& batch, const LightClustersPointer& lightClusters); + static void unsetLocalLightsBatch(gpu::Batch& batch); void setShadowMapEnabled(bool enable) { _shadowMapEnabled = enable; }; void setAmbientOcclusionEnabled(bool enable) { _ambientOcclusionEnabled = enable; } diff --git a/libraries/render-utils/src/DeferredTransform.slh b/libraries/render-utils/src/DeferredTransform.slh index 2f015b95fe..6dd92c651b 100644 --- a/libraries/render-utils/src/DeferredTransform.slh +++ b/libraries/render-utils/src/DeferredTransform.slh @@ -11,6 +11,9 @@ <@if not DEFERRED_TRANSFORM_SLH@> <@def DEFERRED_TRANSFORM_SLH@> +<@include gpu/ShaderConstants.h@> +<@include render-utils/ShaderConstants.h@> + <@func declareDeferredFrameTransform()@> struct CameraCorrection { @@ -21,7 +24,7 @@ struct CameraCorrection { mat4 _prevViewInverse; }; -uniform cameraCorrectionBuffer { +layout(binding=GPU_BUFFER_CAMERA_CORRECTION) uniform cameraCorrectionBuffer { CameraCorrection cameraCorrection; }; @@ -39,7 +42,7 @@ struct DeferredFrameTransform { mat4 _invProjectionUnJittered[2]; }; -uniform deferredFrameTransformBuffer { +layout(binding=RENDER_UTILS_BUFFER_DEFERRED_FRAME_TRANSFORM) uniform deferredFrameTransformBuffer { DeferredFrameTransform frameTransform; }; diff --git a/libraries/render-utils/src/DrawHaze.cpp b/libraries/render-utils/src/DrawHaze.cpp index 94bac4e3ac..538a916a06 100644 --- a/libraries/render-utils/src/DrawHaze.cpp +++ b/libraries/render-utils/src/DrawHaze.cpp @@ -12,14 +12,25 @@ #include "DrawHaze.h" #include -#include +#include + +#include +#include "render-utils/ShaderConstants.h" #include "StencilMaskPass.h" #include "FramebufferCache.h" #include "HazeStage.h" #include "LightStage.h" -#include "Haze_frag.h" +namespace ru { + using render_utils::slot::texture::Texture; + using render_utils::slot::buffer::Buffer; +} + +namespace gr { + using graphics::slot::texture::Texture; + using graphics::slot::buffer::Buffer; +} void HazeConfig::setHazeColor(const glm::vec3 value) { hazeColor = value; @@ -107,12 +118,6 @@ void MakeHaze::run(const render::RenderContextPointer& renderContext, graphics:: haze = _haze; } -// Buffer slots -const int HazeEffect_ParamsSlot = 0; -const int HazeEffect_TransformBufferSlot = 1; -// Texture slots -const int HazeEffect_LinearDepthMapSlot = 0; - void DrawHaze::configure(const Config& config) { } @@ -132,10 +137,7 @@ void DrawHaze::run(const render::RenderContextPointer& renderContext, const Inpu RenderArgs* args = renderContext->args; if (!_hazePipeline) { - gpu::ShaderPointer ps = Haze_frag::getShader(); - gpu::ShaderPointer vs = gpu::StandardShaderLib::getDrawViewportQuadTransformTexcoordVS(); - - gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); + gpu::ShaderPointer program = gpu::Shader::createProgram(shader::render_utils::program::haze); gpu::StatePointer state = gpu::StatePointer(new gpu::State()); state->setBlendFunction(true, @@ -144,19 +146,7 @@ void DrawHaze::run(const render::RenderContextPointer& renderContext, const Inpu // Mask out haze on the tablet PrepareStencil::testMask(*state); - _hazePipeline = gpu::PipelinePointer(gpu::Pipeline::create(program, state)); - gpu::doInBatch("DrawHaze::build", args->_context, [program](gpu::Batch& batch) { - batch.runLambda([program]() { - gpu::Shader::BindingSet slotBindings; - slotBindings.insert(gpu::Shader::Binding(std::string("hazeBuffer"), HazeEffect_ParamsSlot)); - slotBindings.insert(gpu::Shader::Binding(std::string("deferredFrameTransformBuffer"), HazeEffect_TransformBufferSlot)); - slotBindings.insert(gpu::Shader::Binding(std::string("lightingModelBuffer"), render::ShapePipeline::Slot::LIGHTING_MODEL)); - slotBindings.insert(gpu::Shader::Binding(std::string("linearDepthMap"), HazeEffect_LinearDepthMapSlot)); - slotBindings.insert(gpu::Shader::Binding(std::string("keyLightBuffer"), render::ShapePipeline::Slot::KEY_LIGHT)); - gpu::Shader::makeProgram(*program, slotBindings); - }); - }); } auto outputFramebufferSize = glm::ivec2(outputBuffer->getSize()); @@ -176,26 +166,25 @@ void DrawHaze::run(const render::RenderContextPointer& renderContext, const Inpu if (hazeStage && hazeStage->_currentFrame._hazes.size() > 0) { graphics::HazePointer hazePointer = hazeStage->getHaze(hazeStage->_currentFrame._hazes.front()); if (hazePointer) { - batch.setUniformBuffer(HazeEffect_ParamsSlot, hazePointer->getHazeParametersBuffer()); + batch.setUniformBuffer(ru::Buffer::HazeParams, hazePointer->getHazeParametersBuffer()); } else { // Something is wrong, so just quit Haze return; } } - batch.setUniformBuffer(HazeEffect_TransformBufferSlot, transformBuffer->getFrameTransformBuffer()); - batch.setUniformBuffer(render::ShapePipeline::Slot::LIGHTING_MODEL, lightingModel->getParametersBuffer()); + batch.setUniformBuffer(ru::Buffer::DeferredFrameTransform, transformBuffer->getFrameTransformBuffer()); + batch.setUniformBuffer(ru::Buffer::LightModel, lightingModel->getParametersBuffer()); + batch.setResourceTexture(ru::Texture::HazeLinearDepth, depthBuffer); + auto lightStage = args->_scene->getStage(); + if (lightStage) { + graphics::LightPointer keyLight; + keyLight = lightStage->getCurrentKeyLight(); + if (keyLight) { + batch.setUniformBuffer(gr::Buffer::KeyLight, keyLight->getLightSchemaBuffer()); + } + } - auto lightStage = args->_scene->getStage(); - if (lightStage) { - graphics::LightPointer keyLight; - keyLight = lightStage->getCurrentKeyLight(); - if (keyLight) { - batch.setUniformBuffer(render::ShapePipeline::Slot::KEY_LIGHT, keyLight->getLightSchemaBuffer()); - } - } - - batch.setResourceTexture(HazeEffect_LinearDepthMapSlot, depthBuffer); batch.draw(gpu::TRIANGLE_STRIP, 4); }); diff --git a/libraries/render-utils/src/Fade.slh b/libraries/render-utils/src/Fade.slh index a4e8fdf1f4..47347ba135 100644 --- a/libraries/render-utils/src/Fade.slh +++ b/libraries/render-utils/src/Fade.slh @@ -9,18 +9,22 @@ <@if not FADE_SLH@> <@def FADE_SLH@> -<@func declareFadeFragmentCommon()@> +<@include render-utils/ShaderConstants.h@> +<@func declareFadeFragmentCommon()@> #define CATEGORY_COUNT 5 <@include Fade_shared.slh@> <@include FadeObjectParams.shared.slh@> -layout(std140) uniform fadeParametersBuffer { +// See ShapePipeline::Slot::BUFFER in ShapePipeline.h +layout(std140, binding=RENDER_UTILS_BUFFER_FADE_PARAMS) uniform fadeParametersBuffer { FadeParameters fadeParameters[CATEGORY_COUNT]; }; -uniform sampler2D fadeMaskMap; + +// See ShapePipeline::Slot::MAP in ShapePipeline.h +layout(binding=RENDER_UTILS_TEXTURE_FADE_MASK) uniform sampler2D fadeMaskMap; vec3 getNoiseInverseSize(int category) { return fadeParameters[category]._noiseInvSizeAndLevel.xyz; @@ -113,7 +117,7 @@ void applyFade(FadeObjectParams params, vec3 position, out vec3 emissive) { <@func declareFadeFragmentUniform()@> -layout(std140) uniform fadeObjectParametersBuffer { +layout(std140, binding=RENDER_UTILS_BUFFER_FADE_OBJECT_PARAMS) uniform fadeObjectParametersBuffer { FadeObjectParams fadeObjectParams; }; @@ -125,9 +129,9 @@ layout(std140) uniform fadeObjectParametersBuffer { <@func declareFadeFragmentVertexInput()@> -in vec4 _fadeData1; -in vec4 _fadeData2; -in vec4 _fadeData3; +layout(location=RENDER_UTILS_ATTR_FADE1) in vec4 _fadeData1; +layout(location=RENDER_UTILS_ATTR_FADE2) in vec4 _fadeData2; +layout(location=RENDER_UTILS_ATTR_FADE3) in vec4 _fadeData3; <@endfunc@> @@ -150,9 +154,9 @@ in vec4 _fadeData3; <@endfunc@> <@func declareFadeVertexInstanced()@> -out vec4 _fadeData1; -out vec4 _fadeData2; -out vec4 _fadeData3; +layout(location=RENDER_UTILS_ATTR_FADE1) out vec4 _fadeData1; +layout(location=RENDER_UTILS_ATTR_FADE2) out vec4 _fadeData2; +layout(location=RENDER_UTILS_ATTR_FADE3) out vec4 _fadeData3; <@endfunc@> <@func passThroughFadeObjectParams()@> diff --git a/libraries/render-utils/src/FadeEffect.cpp b/libraries/render-utils/src/FadeEffect.cpp index 12531d4c9d..402865da4c 100644 --- a/libraries/render-utils/src/FadeEffect.cpp +++ b/libraries/render-utils/src/FadeEffect.cpp @@ -14,7 +14,7 @@ #include "render/TransitionStage.h" #include "FadeObjectParams.shared.slh" - +#include "render-utils/ShaderConstants.h" #include FadeEffect::FadeEffect() { @@ -33,16 +33,16 @@ void FadeEffect::build(render::Task::TaskConcept& task, const task::Varying& edi render::ShapePipeline::BatchSetter FadeEffect::getBatchSetter() const { return [this](const render::ShapePipeline& shapePipeline, gpu::Batch& batch, render::Args*) { - batch.setResourceTexture(render::ShapePipeline::Slot::FADE_MASK, _maskMap); - batch.setUniformBuffer(render::ShapePipeline::Slot::FADE_PARAMETERS, _configurations); + batch.setResourceTexture(render_utils::slot::texture::FadeMask, _maskMap); + batch.setUniformBuffer(render_utils::slot::buffer::FadeParameters, _configurations); }; } render::ShapePipeline::ItemSetter FadeEffect::getItemUniformSetter() const { return [](const render::ShapePipeline& shapePipeline, render::Args* args, const render::Item& item) { if (!render::TransitionStage::isIndexInvalid(item.getTransitionId())) { - auto scene = args->_scene; - auto batch = args->_batch; + const auto& scene = args->_scene; + const auto& batch = args->_batch; auto transitionStage = scene->getStage(render::TransitionStage::getName()); auto& transitionState = transitionStage->getTransition(item.getTransitionId()); @@ -67,7 +67,7 @@ render::ShapePipeline::ItemSetter FadeEffect::getItemUniformSetter() const { params.noiseOffset = glm::vec4(transitionState.noiseOffset, 0.0f); params.baseOffset = glm::vec4(transitionState.baseOffset, 0.0f); } - batch->setUniformBuffer(render::ShapePipeline::Slot::FADE_OBJECT_PARAMETERS, transitionState.paramsBuffer); + batch->setUniformBuffer(render_utils::slot::buffer::FadeObjectParameters, transitionState.paramsBuffer); } }; } diff --git a/libraries/render-utils/src/GeometryCache.cpp b/libraries/render-utils/src/GeometryCache.cpp index f8f49921a3..cbc9b40129 100644 --- a/libraries/render-utils/src/GeometryCache.cpp +++ b/libraries/render-utils/src/GeometryCache.cpp @@ -23,39 +23,32 @@ #include #include +#include +#include +#include +#include +#include +#include "render-utils/ShaderConstants.h" #include "TextureCache.h" #include "RenderUtilsLogging.h" #include "StencilMaskPass.h" #include "FadeEffect.h" -#include "gpu/StandardShaderLib.h" -#include "graphics/TextureMap.h" -#include "graphics/BufferViewHelpers.h" -#include "render/Args.h" - -#include "standardTransformPNTC_vert.h" -#include "standardDrawTexture_frag.h" - -#include "simple_vert.h" -#include "simple_textured_frag.h" -#include "simple_transparent_textured_frag.h" -#include "simple_textured_unlit_frag.h" -#include "simple_fade_vert.h" -#include "simple_textured_fade_frag.h" -#include "simple_textured_unlit_fade_frag.h" -#include "simple_opaque_web_browser_frag.h" -#include "simple_transparent_web_browser_frag.h" -#include "glowLine_vert.h" -#include "glowLine_frag.h" - -#include "forward_simple_textured_frag.h" -#include "forward_simple_textured_transparent_frag.h" -#include "forward_simple_textured_unlit_frag.h" #include "DeferredLightingEffect.h" +namespace gr { + using graphics::slot::texture::Texture; + using graphics::slot::buffer::Buffer; +} + +namespace ru { + using render_utils::slot::texture::Texture; + using render_utils::slot::buffer::Buffer; +} + #if defined(USE_GLES) static bool DISABLE_DEFERRED = true; #else @@ -63,8 +56,6 @@ static const QString RENDER_FORWARD{ "HIFI_RENDER_FORWARD" }; static bool DISABLE_DEFERRED = QProcessEnvironment::systemEnvironment().contains(RENDER_FORWARD); #endif -#include "grid_frag.h" - //#define WANT_DEBUG // @note: Originally size entity::NUM_SHAPES @@ -825,12 +816,9 @@ render::ShapePipelinePointer GeometryCache::getShapePipeline(bool textured, bool return std::make_shared(getSimplePipeline(textured, transparent, culled, unlit, depthBias, false, true), nullptr, [](const render::ShapePipeline& pipeline, gpu::Batch& batch, render::Args* args) { - batch.setResourceTexture(render::ShapePipeline::Slot::MAP::ALBEDO, DependencyManager::get()->getWhiteTexture()); - DependencyManager::get()->setupKeyLightBatch(args, batch, - pipeline.pipeline->getProgram()->getUniformBuffers().findLocation("keyLightBuffer"), - pipeline.pipeline->getProgram()->getUniformBuffers().findLocation("lightAmbientBuffer"), - pipeline.pipeline->getProgram()->getUniformBuffers().findLocation("skyboxMap")); - } + batch.setResourceTexture(gr::Texture::MaterialAlbedo, DependencyManager::get()->getWhiteTexture()); + DependencyManager::get()->setupKeyLightBatch(args, batch); + } ); } @@ -841,7 +829,7 @@ render::ShapePipelinePointer GeometryCache::getFadingShapePipeline(bool textured auto fadeItemSetter = fadeEffect->getItemStoredSetter(); return std::make_shared(getSimplePipeline(textured, transparent, culled, unlit, depthBias, true), nullptr, [fadeBatchSetter, fadeItemSetter](const render::ShapePipeline& shapePipeline, gpu::Batch& batch, render::Args* args) { - batch.setResourceTexture(render::ShapePipeline::Slot::MAP::ALBEDO, DependencyManager::get()->getWhiteTexture()); + batch.setResourceTexture(gr::Texture::MaterialAlbedo, DependencyManager::get()->getWhiteTexture()); fadeBatchSetter(shapePipeline, batch, args); }, fadeItemSetter @@ -2048,13 +2036,10 @@ void GeometryCache::renderGlowLine(gpu::Batch& batch, const glm::vec3& p1, const } // Compile the shaders - static const uint32_t LINE_DATA_SLOT = 1; static std::once_flag once; std::call_once(once, [&] { auto state = std::make_shared(); - auto VS = glowLine_vert::getShader(); - auto PS = glowLine_frag::getShader(); - auto program = gpu::Shader::createProgram(VS, PS); + auto program = gpu::Shader::createProgram(shader::render_utils::program::glowLine); state->setCullMode(gpu::State::CULL_NONE); state->setDepthTest(true, false, gpu::LESS_EQUAL); state->setBlendFunction(true, @@ -2062,10 +2047,6 @@ void GeometryCache::renderGlowLine(gpu::Batch& batch, const glm::vec3& p1, const gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); PrepareStencil::testMask(*state); - - gpu::Shader::BindingSet slotBindings; - slotBindings.insert(gpu::Shader::Binding(std::string("lineData"), LINE_DATA_SLOT)); - gpu::Shader::makeProgram(*program, slotBindings); _glowLinePipeline = gpu::Pipeline::create(program, state); }); @@ -2102,16 +2083,14 @@ void GeometryCache::renderGlowLine(gpu::Batch& batch, const glm::vec3& p1, const } // The shader requires no vertices, only uniforms. - batch.setUniformBuffer(LINE_DATA_SLOT, details.uniformBuffer); + batch.setUniformBuffer(0, details.uniformBuffer); batch.draw(gpu::TRIANGLE_STRIP, NUM_VERTICES, 0); } void GeometryCache::useSimpleDrawPipeline(gpu::Batch& batch, bool noBlend) { static std::once_flag once; std::call_once(once, [&]() { - auto vs = standardTransformPNTC_vert::getShader(); - auto ps = standardDrawTexture_frag::getShader(); - auto program = gpu::Shader::createProgram(vs, ps); + auto program = gpu::Shader::createProgram(shader::render_utils::program::standardDrawTexture); auto state = std::make_shared(); @@ -2125,15 +2104,10 @@ void GeometryCache::useSimpleDrawPipeline(gpu::Batch& batch, bool noBlend) { auto stateNoBlend = std::make_shared(); PrepareStencil::testMaskDrawShape(*stateNoBlend); - auto noBlendPS = gpu::StandardShaderLib::getDrawTextureOpaquePS(); - auto programNoBlend = gpu::Shader::createProgram(vs, noBlendPS); + auto noBlendPS = gpu::Shader::createVertex(shader::gpu::fragment::DrawTextureOpaque); + auto programNoBlend = gpu::Shader::createProgram(shader::render_utils::program::standardDrawTextureNoBlend); _standardDrawPipelineNoBlend = gpu::Pipeline::create(programNoBlend, stateNoBlend); - - batch.runLambda([program, programNoBlend] { - gpu::Shader::makeProgram((*program)); - gpu::Shader::makeProgram((*programNoBlend)); - }); }); if (noBlend) { @@ -2145,12 +2119,8 @@ void GeometryCache::useSimpleDrawPipeline(gpu::Batch& batch, bool noBlend) { void GeometryCache::useGridPipeline(gpu::Batch& batch, GridBuffer gridBuffer, bool isLayered) { if (!_gridPipeline) { - auto vs = standardTransformPNTC_vert::getShader(); - auto ps = grid_frag::getShader(); - auto program = gpu::Shader::createProgram(vs, ps); - gpu::Shader::makeProgram((*program)); - _gridSlot = program->getUniformBuffers().findLocation("gridBuffer"); - + auto program = gpu::Shader::createProgram(shader::render_utils::program::grid); + _gridSlot = 0; auto stateLayered = std::make_shared(); stateLayered->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA); PrepareStencil::testMask(*stateLayered); @@ -2229,12 +2199,9 @@ inline bool operator==(const SimpleProgramKey& a, const SimpleProgramKey& b) { return a.getRaw() == b.getRaw(); } -static void buildWebShader(const gpu::ShaderPointer& vertShader, const gpu::ShaderPointer& fragShader, bool blendEnable, +static void buildWebShader(int programId, bool blendEnable, gpu::ShaderPointer& shaderPointerOut, gpu::PipelinePointer& pipelinePointerOut) { - shaderPointerOut = gpu::Shader::createProgram(vertShader, fragShader); - - gpu::Shader::BindingSet slotBindings; - gpu::Shader::makeProgram(*shaderPointerOut, slotBindings); + shaderPointerOut = gpu::Shader::createProgram(programId); auto state = std::make_shared(); state->setCullMode(gpu::State::CULL_NONE); state->setDepthTest(true, true, gpu::LESS_EQUAL); @@ -2254,8 +2221,8 @@ void GeometryCache::bindWebBrowserProgram(gpu::Batch& batch, bool transparent) { gpu::PipelinePointer GeometryCache::getWebBrowserProgram(bool transparent) { static std::once_flag once; std::call_once(once, [&]() { - buildWebShader(simple_vert::getShader(), simple_opaque_web_browser_frag::getShader(), false, _simpleOpaqueWebBrowserShader, _simpleOpaqueWebBrowserPipeline); - buildWebShader(simple_vert::getShader(), simple_transparent_web_browser_frag::getShader(), true, _simpleTransparentWebBrowserShader, _simpleTransparentWebBrowserPipeline); + buildWebShader(shader::render_utils::program::simple_opaque_web_browser, false, _simpleOpaqueWebBrowserShader, _simpleOpaqueWebBrowserPipeline); + buildWebShader(shader::render_utils::program::simple_transparent_web_browser, true, _simpleTransparentWebBrowserShader, _simpleTransparentWebBrowserPipeline); }); return transparent ? _simpleTransparentWebBrowserPipeline : _simpleOpaqueWebBrowserPipeline; @@ -2266,7 +2233,7 @@ void GeometryCache::bindSimpleProgram(gpu::Batch& batch, bool textured, bool tra // If not textured, set a default albedo map if (!textured) { - batch.setResourceTexture(render::ShapePipeline::Slot::MAP::ALBEDO, + batch.setResourceTexture(gr::Texture::MaterialAlbedo, DependencyManager::get()->getWhiteTexture()); } } @@ -2284,45 +2251,21 @@ gpu::PipelinePointer GeometryCache::getSimplePipeline(bool textured, bool transp if (!fading) { static std::once_flag once; std::call_once(once, [&]() { - auto VS = simple_vert::getShader(); - auto PS = DISABLE_DEFERRED ? forward_simple_textured_frag::getShader() : simple_textured_frag::getShader(); + using namespace shader::render_utils::program; + auto PS = DISABLE_DEFERRED ? forward_simple_textured : simple_textured; // Use the forward pipeline for both here, otherwise transparents will be unlit - auto PSTransparent = DISABLE_DEFERRED ? forward_simple_textured_transparent_frag::getShader() : forward_simple_textured_transparent_frag::getShader(); - auto PSUnlit = DISABLE_DEFERRED ? forward_simple_textured_unlit_frag::getShader() : simple_textured_unlit_frag::getShader(); - - _simpleShader = gpu::Shader::createProgram(VS, PS); - _transparentShader = gpu::Shader::createProgram(VS, PSTransparent); - _unlitShader = gpu::Shader::createProgram(VS, PSUnlit); - - gpu::Shader::BindingSet slotBindings; - slotBindings.insert(gpu::Shader::Binding(std::string("originalTexture"), render::ShapePipeline::Slot::MAP::ALBEDO)); - slotBindings.insert(gpu::Shader::Binding(std::string("lightingModelBuffer"), render::ShapePipeline::Slot::LIGHTING_MODEL)); - slotBindings.insert(gpu::Shader::Binding(std::string("keyLightBuffer"), render::ShapePipeline::Slot::KEY_LIGHT)); - slotBindings.insert(gpu::Shader::Binding(std::string("lightAmbientBuffer"), render::ShapePipeline::Slot::LIGHT_AMBIENT_BUFFER)); - slotBindings.insert(gpu::Shader::Binding(std::string("skyboxMap"), render::ShapePipeline::Slot::MAP::LIGHT_AMBIENT_MAP)); - gpu::Shader::makeProgram(*_simpleShader, slotBindings); - gpu::Shader::makeProgram(*_transparentShader, slotBindings); - gpu::Shader::makeProgram(*_unlitShader, slotBindings); + auto PSTransparent = DISABLE_DEFERRED ? forward_simple_textured_transparent : forward_simple_textured_transparent; + auto PSUnlit = DISABLE_DEFERRED ? forward_simple_textured_unlit : simple_textured_unlit; + _simpleShader = gpu::Shader::createProgram(PS); + _transparentShader = gpu::Shader::createProgram(PSTransparent); + _unlitShader = gpu::Shader::createProgram(PSUnlit); }); } else { static std::once_flag once; std::call_once(once, [&]() { - auto VS = simple_fade_vert::getShader(); - auto PS = DISABLE_DEFERRED ? forward_simple_textured_frag::getShader() : simple_textured_fade_frag::getShader(); - auto PSUnlit = DISABLE_DEFERRED ? forward_simple_textured_unlit_frag::getShader() : simple_textured_unlit_fade_frag::getShader(); - - _simpleFadeShader = gpu::Shader::createProgram(VS, PS); - _unlitFadeShader = gpu::Shader::createProgram(VS, PSUnlit); - - gpu::Shader::BindingSet slotBindings; - slotBindings.insert(gpu::Shader::Binding(std::string("originalTexture"), render::ShapePipeline::Slot::MAP::ALBEDO)); - slotBindings.insert(gpu::Shader::Binding(std::string("lightingModelBuffer"), render::ShapePipeline::Slot::LIGHTING_MODEL)); - slotBindings.insert(gpu::Shader::Binding(std::string("keyLightBuffer"), render::ShapePipeline::Slot::KEY_LIGHT)); - slotBindings.insert(gpu::Shader::Binding(std::string("lightAmbientBuffer"), render::ShapePipeline::Slot::LIGHT_AMBIENT_BUFFER)); - slotBindings.insert(gpu::Shader::Binding(std::string("skyboxMap"), render::ShapePipeline::Slot::MAP::LIGHT_AMBIENT_MAP)); - slotBindings.insert(gpu::Shader::Binding(std::string("fadeMaskMap"), render::ShapePipeline::Slot::MAP::FADE_MASK)); - gpu::Shader::makeProgram(*_simpleFadeShader, slotBindings); - gpu::Shader::makeProgram(*_unlitFadeShader, slotBindings); + using namespace shader::render_utils::program; + _simpleFadeShader = gpu::Shader::createProgram(DISABLE_DEFERRED ? forward_simple_textured : simple_textured_fade); + _unlitFadeShader = gpu::Shader::createProgram(DISABLE_DEFERRED ? forward_simple_textured_unlit : simple_textured_unlit_fade); }); } diff --git a/libraries/render-utils/src/Haze.slf b/libraries/render-utils/src/Haze.slf index 93b66d99ed..bb3c0bc769 100644 --- a/libraries/render-utils/src/Haze.slf +++ b/libraries/render-utils/src/Haze.slf @@ -2,6 +2,8 @@ <$VERSION_HEADER$> // Generated on <$_SCRIBE_DATE$> // +// Haze.frag +// // Created by Nissim Hadar on 9/5/2107. // Copyright 2016 High Fidelity, Inc. // @@ -17,12 +19,9 @@ <@include LightingModel.slh@> <$declareLightBuffer()$> -<@include LightDirectional.slh@> -<$declareLightingDirectional(_SCRIBE_NULL)$> - <@include Haze.slh@> -uniform sampler2D linearDepthMap; +layout(binding=RENDER_UTILS_TEXTURE_HAZE_LINEAR_DEPTH) uniform sampler2D linearDepthMap; vec4 unpackPositionFromZeye(vec2 texcoord) { float Zeye = -texture(linearDepthMap, texcoord).x; @@ -37,8 +36,8 @@ vec4 unpackPositionFromZeye(vec2 texcoord) { return vec4(evalEyePositionFromZeye(side, Zeye, texcoord), 1.0); } -in vec2 varTexCoord0; -out vec4 outFragColor; +layout(location=0) in vec2 varTexCoord0; +layout(location=0) out vec4 outFragColor; void main(void) { if ((isHazeEnabled() == 0.0) || (hazeParams.hazeMode & HAZE_MODE_IS_ACTIVE) != HAZE_MODE_IS_ACTIVE) { diff --git a/libraries/render-utils/src/Haze.slh b/libraries/render-utils/src/Haze.slh index 7854ad08ca..b7bcfcefcd 100644 --- a/libraries/render-utils/src/Haze.slh +++ b/libraries/render-utils/src/Haze.slh @@ -10,6 +10,8 @@ <@if not HAZE_SLH@> <@def HAZE_SLH@> +<@include render-utils/ShaderConstants.h@> + const int HAZE_MODE_IS_ACTIVE = 1 << 0; const int HAZE_MODE_IS_ALTITUDE_BASED = 1 << 1; const int HAZE_MODE_IS_KEYLIGHT_ATTENUATED = 1 << 2; @@ -36,7 +38,8 @@ struct HazeParams { float hazeKeyLightAltitudeFactor; }; -layout(std140) uniform hazeBuffer { +// See ShapePipeline::Slot::BUFFER in ShapePipeline.h +layout(std140, binding=RENDER_UTILS_BUFFER_HAZE_PARAMS) uniform hazeBuffer { HazeParams hazeParams; }; diff --git a/libraries/render-utils/src/HazeStage.h b/libraries/render-utils/src/HazeStage.h index 8f137cb280..b48168e376 100644 --- a/libraries/render-utils/src/HazeStage.h +++ b/libraries/render-utils/src/HazeStage.h @@ -163,6 +163,5 @@ public: private: graphics::HazePointer _haze; - gpu::PipelinePointer _hazePipeline; }; #endif diff --git a/libraries/render-utils/src/Highlight.slf b/libraries/render-utils/src/Highlight.slf index bf65f92613..676df52235 100644 --- a/libraries/render-utils/src/Highlight.slf +++ b/libraries/render-utils/src/Highlight.slf @@ -1,4 +1,4 @@ -// Highlight.slf +// Highlight.frag // Add highlight effect based on two zbuffers : one containing the total scene z and another // with the z of only the objects to be outlined. // This is the version without the fill effect inside the silhouette. diff --git a/libraries/render-utils/src/Highlight.slh b/libraries/render-utils/src/Highlight.slh index f4d4ad0e04..56a999f508 100644 --- a/libraries/render-utils/src/Highlight.slh +++ b/libraries/render-utils/src/Highlight.slh @@ -15,12 +15,12 @@ <@include Highlight_shared.slh@> -uniform highlightParamsBuffer { +layout(binding=RENDER_UTILS_BUFFER_HIGHLIGHT_PARAMS) uniform highlightParamsBuffer { HighlightParameters params; }; -uniform sampler2D sceneDepthMap; -uniform sampler2D highlightedDepthMap; +layout(binding=RENDER_UTILS_TEXTURE_HIGHLIGHT_SCENE_DEPTH) uniform sampler2D sceneDepthMap; +layout(binding=RENDER_UTILS_TEXTURE_HIGHLIGHT_DEPTH) uniform sampler2D highlightedDepthMap; in vec2 varTexCoord0; out vec4 outFragColor; diff --git a/libraries/render-utils/src/HighlightEffect.cpp b/libraries/render-utils/src/HighlightEffect.cpp index 6c8a90da81..7e2e55c768 100644 --- a/libraries/render-utils/src/HighlightEffect.cpp +++ b/libraries/render-utils/src/HighlightEffect.cpp @@ -10,27 +10,31 @@ // #include "HighlightEffect.h" -#include "GeometryCache.h" - -#include "CubeProjectedPolygon.h" +#include +#include #include #include -#include "gpu/Context.h" -#include "gpu/StandardShaderLib.h" +#include +#include -#include +#include "GeometryCache.h" +#include "CubeProjectedPolygon.h" -#include "surfaceGeometry_copyDepth_frag.h" -#include "debug_deferred_buffer_vert.h" -#include "debug_deferred_buffer_frag.h" -#include "Highlight_frag.h" -#include "Highlight_filled_frag.h" -#include "Highlight_aabox_vert.h" -#include "nop_frag.h" +#include "render-utils/ShaderConstants.h" using namespace render; +namespace ru { + using render_utils::slot::texture::Texture; + using render_utils::slot::buffer::Buffer; + using render_utils::slot::uniform::Uniform; +} + +namespace gr { + using graphics::slot::texture::Texture; + using graphics::slot::buffer::Buffer; +} #define OUTLINE_STENCIL_MASK 1 @@ -118,7 +122,7 @@ void DrawHighlightMask::run(const render::RenderContextPointer& renderContext, c auto& inShapes = inputs.get0(); const int BOUNDS_SLOT = 0; - const int PARAMETERS_SLOT = 1; + const int PARAMETERS_SLOT = 0; if (!_stencilMaskPipeline || !_stencilMaskFillPipeline) { gpu::StatePointer state = gpu::StatePointer(new gpu::State()); @@ -133,15 +137,7 @@ void DrawHighlightMask::run(const render::RenderContextPointer& renderContext, c fillState->setColorWriteMask(false, false, false, false); fillState->setCullMode(gpu::State::CULL_FRONT); - auto vs = Highlight_aabox_vert::getShader(); - auto ps = nop_frag::getShader(); - gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); - - gpu::Shader::BindingSet slotBindings; - slotBindings.insert(gpu::Shader::Binding(std::string("ssbo0Buffer"), BOUNDS_SLOT)); - slotBindings.insert(gpu::Shader::Binding(std::string("parametersBuffer"), PARAMETERS_SLOT)); - gpu::Shader::makeProgram(*program, slotBindings); - + gpu::ShaderPointer program = gpu::Shader::createProgram(shader::render_utils::program::highlight_aabox); _stencilMaskPipeline = gpu::Pipeline::create(program, state); _stencilMaskFillPipeline = gpu::Pipeline::create(program, fillState); } @@ -306,10 +302,10 @@ void DrawHighlight::run(const render::RenderContextPointer& renderContext, const batch.setModelTransform(gpu::Framebuffer::evalSubregionTexcoordTransform(framebufferSize, args->_viewport)); batch.setPipeline(pipeline); - batch.setUniformBuffer(HIGHLIGHT_PARAMS_SLOT, _configuration); - batch.setUniformBuffer(FRAME_TRANSFORM_SLOT, frameTransform->getFrameTransformBuffer()); - batch.setResourceTexture(SCENE_DEPTH_MAP_SLOT, sceneDepthBuffer->getPrimaryDepthTexture()); - batch.setResourceTexture(HIGHLIGHTED_DEPTH_MAP_SLOT, highlightedDepthTexture); + batch.setUniformBuffer(ru::Buffer::HighlightParams, _configuration); + batch.setUniformBuffer(ru::Buffer::DeferredFrameTransform, frameTransform->getFrameTransformBuffer()); + batch.setResourceTexture(ru::Texture::HighlightSceneDepth, sceneDepthBuffer->getPrimaryDepthTexture()); + batch.setResourceTexture(ru::Texture::HighlightDepth, highlightedDepthTexture); batch.draw(gpu::TRIANGLE_STRIP, 4); // Reset the framebuffer for overlay drawing @@ -327,22 +323,10 @@ const gpu::PipelinePointer& DrawHighlight::getPipeline(const render::HighlightSt state->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA); state->setStencilTest(true, 0, gpu::State::StencilTest(OUTLINE_STENCIL_MASK, 0xFF, gpu::EQUAL)); - auto vs = gpu::StandardShaderLib::getDrawViewportQuadTransformTexcoordVS(); - auto ps = Highlight_frag::getShader(); - gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); - - gpu::Shader::BindingSet slotBindings; - slotBindings.insert(gpu::Shader::Binding("highlightParamsBuffer", HIGHLIGHT_PARAMS_SLOT)); - slotBindings.insert(gpu::Shader::Binding("deferredFrameTransformBuffer", FRAME_TRANSFORM_SLOT)); - slotBindings.insert(gpu::Shader::Binding("sceneDepthMap", SCENE_DEPTH_MAP_SLOT)); - slotBindings.insert(gpu::Shader::Binding("highlightedDepthMap", HIGHLIGHTED_DEPTH_MAP_SLOT)); - gpu::Shader::makeProgram(*program, slotBindings); - + auto program = gpu::Shader::createProgram(shader::render_utils::program::highlight); _pipeline = gpu::Pipeline::create(program, state); - ps = Highlight_filled_frag::getShader(); - program = gpu::Shader::createProgram(vs, ps); - gpu::Shader::makeProgram(*program, slotBindings); + program = gpu::Shader::createProgram(shader::render_utils::program::highlight_filled); _pipelineFilled = gpu::Pipeline::create(program, state); } return style.isFilled() ? _pipelineFilled : _pipeline; @@ -406,9 +390,9 @@ void DebugHighlight::run(const render::RenderContextPointer& renderContext, cons } void DebugHighlight::initializePipelines() { - static const std::string FRAGMENT_SHADER{ debug_deferred_buffer_frag::getSource() }; + static const auto FRAGMENT_SHADER_SOURCE = gpu::Shader::createPixel(shader::render_utils::fragment::debug_deferred_buffer)->getSource(); static const std::string SOURCE_PLACEHOLDER{ "//SOURCE_PLACEHOLDER" }; - static const auto SOURCE_PLACEHOLDER_INDEX = FRAGMENT_SHADER.find(SOURCE_PLACEHOLDER); + static const auto SOURCE_PLACEHOLDER_INDEX = FRAGMENT_SHADER_SOURCE.getCode().find(SOURCE_PLACEHOLDER); Q_ASSERT_X(SOURCE_PLACEHOLDER_INDEX != std::string::npos, Q_FUNC_INFO, "Could not find source placeholder"); @@ -416,28 +400,23 @@ void DebugHighlight::initializePipelines() { state->setDepthTest(gpu::State::DepthTest(false, false)); state->setStencilTest(true, 0, gpu::State::StencilTest(OUTLINE_STENCIL_MASK, 0xFF, gpu::EQUAL)); - const auto vs = debug_deferred_buffer_vert::getShader(); + const auto vs = gpu::Shader::createVertex(shader::render_utils::vertex::debug_deferred_buffer); // Depth shader { - static const std::string DEPTH_SHADER{ - "vec4 getFragmentColor() {" - " float Zdb = texelFetch(depthMap, ivec2(gl_FragCoord.xy), 0).x;" - " Zdb = 1.0-(1.0-Zdb)*100;" - " return vec4(Zdb, Zdb, Zdb, 1.0); " - "}" - }; + static const std::string DEPTH_SHADER{ R"SHADER( + vec4 getFragmentColor() { + float Zdb = texelFetch(depthMap, ivec2(gl_FragCoord.xy), 0).x; + Zdb = 1.0-(1.0-Zdb)*100; + return vec4(Zdb, Zdb, Zdb, 1.0); + } + )SHADER" }; - auto fragmentShader = FRAGMENT_SHADER; + auto fragmentShader = FRAGMENT_SHADER_SOURCE.getCode(); fragmentShader.replace(SOURCE_PLACEHOLDER_INDEX, SOURCE_PLACEHOLDER.size(), DEPTH_SHADER); - const auto ps = gpu::Shader::createPixel(fragmentShader); + const auto ps = gpu::Shader::createPixel({ fragmentShader, FRAGMENT_SHADER_SOURCE.getReflection() }); const auto program = gpu::Shader::createProgram(vs, ps); - - gpu::Shader::BindingSet slotBindings; - slotBindings.insert(gpu::Shader::Binding("depthMap", 0)); - gpu::Shader::makeProgram(*program, slotBindings); - _depthPipeline = gpu::Pipeline::create(program, state); } } @@ -572,21 +551,13 @@ const render::Varying DrawHighlightTask::addSelectItemJobs(JobModel& task, const return task.addJob("TransparentSelection", selectItemInput); } -#include "model_shadow_vert.h" -#include "skin_model_shadow_vert.h" - -#include "model_shadow_frag.h" - void DrawHighlightTask::initMaskPipelines(render::ShapePlumber& shapePlumber, gpu::StatePointer state) { - auto modelVertex = model_shadow_vert::getShader(); - auto modelPixel = model_shadow_frag::getShader(); - gpu::ShaderPointer modelProgram = gpu::Shader::createProgram(modelVertex, modelPixel); + gpu::ShaderPointer modelProgram = gpu::Shader::createProgram(shader::render_utils::program::model_shadow); shapePlumber.addPipeline( ShapeKey::Filter::Builder().withoutSkinned(), modelProgram, state); - auto skinVertex = skin_model_shadow_vert::getShader(); - gpu::ShaderPointer skinProgram = gpu::Shader::createProgram(skinVertex, modelPixel); + gpu::ShaderPointer skinProgram = gpu::Shader::createProgram(shader::render_utils::program::skin_model_shadow); shapePlumber.addPipeline( ShapeKey::Filter::Builder().withSkinned(), skinProgram, state); diff --git a/libraries/render-utils/src/HighlightEffect.h b/libraries/render-utils/src/HighlightEffect.h index eee1c29cb7..64a97a549e 100644 --- a/libraries/render-utils/src/HighlightEffect.h +++ b/libraries/render-utils/src/HighlightEffect.h @@ -148,14 +148,6 @@ private: #include "Highlight_shared.slh" - enum { - SCENE_DEPTH_MAP_SLOT = 0, - HIGHLIGHTED_DEPTH_MAP_SLOT, - - HIGHLIGHT_PARAMS_SLOT = 0, - FRAME_TRANSFORM_SLOT, - }; - using HighlightConfigurationBuffer = gpu::StructBuffer; static const gpu::PipelinePointer& getPipeline(const render::HighlightStyle& style); diff --git a/libraries/render-utils/src/Highlight_aabox.slv b/libraries/render-utils/src/Highlight_aabox.slv index 2a87e00f94..5130d5e7ff 100644 --- a/libraries/render-utils/src/Highlight_aabox.slv +++ b/libraries/render-utils/src/Highlight_aabox.slv @@ -12,6 +12,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +<@include gpu/ShaderConstants.h@> <@include gpu/Transform.slh@> <$declareStandardTransform()$> @@ -22,7 +23,7 @@ struct ItemBound { }; #if defined(GPU_GL410) -uniform samplerBuffer ssbo0Buffer; +layout(binding=0) uniform samplerBuffer ssbo0Buffer; ItemBound getItemBound(int i) { int offset = 2 * i; ItemBound bound; @@ -31,7 +32,7 @@ ItemBound getItemBound(int i) { return bound; } #else -layout(std140) buffer ssbo0Buffer { +layout(std140, binding=0) buffer ssbo0Buffer { ItemBound bounds[]; }; ItemBound getItemBound(int i) { @@ -40,10 +41,14 @@ ItemBound getItemBound(int i) { } #endif -uniform parametersBuffer { +struct HighlightParameters { vec2 outlineWidth; }; +layout(binding=0) uniform parametersBuffer { + HighlightParameters _parameters; +}; + void main(void) { const vec3 UNIT_BOX_VERTICES[8] = vec3[8]( vec3(0.0, 1.0, 0.0), @@ -101,6 +106,6 @@ void main(void) { pos.xyz += UNIT_BOX_NORMALS[triangleIndex]; vec4 offsetPosition; <$transformModelToMonoClipPos(cam, obj, pos, offsetPosition)$> - gl_Position.xy += normalize(offsetPosition.xy-gl_Position.xy) * outlineWidth * gl_Position.w; + gl_Position.xy += normalize(offsetPosition.xy-gl_Position.xy) * _parameters.outlineWidth * gl_Position.w; <$transformStereoClipsSpace(cam, gl_Position)$> } diff --git a/libraries/render-utils/src/Highlight_filled.slf b/libraries/render-utils/src/Highlight_filled.slf index 53530746f0..8e4da681f9 100644 --- a/libraries/render-utils/src/Highlight_filled.slf +++ b/libraries/render-utils/src/Highlight_filled.slf @@ -1,4 +1,4 @@ -// Highlight_filled.slf +// Highlight_filled.frag // Add highlight effect based on two zbuffers : one containing the total scene z and another // with the z of only the objects to be outlined. // This is the version with the fill effect inside the silhouette. diff --git a/libraries/render-utils/src/LightAmbient.slh b/libraries/render-utils/src/LightAmbient.slh index b2377d1904..797595bf47 100644 --- a/libraries/render-utils/src/LightAmbient.slh +++ b/libraries/render-utils/src/LightAmbient.slh @@ -6,9 +6,10 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +<@include render-utils/ShaderConstants.h@> <@func declareSkyboxMap()@> // declareSkyboxMap -uniform samplerCube skyboxMap; +layout(binding=RENDER_UTILS_TEXTURE_SKYBOX) uniform samplerCube skyboxMap; vec4 evalSkyboxLight(vec3 direction, float lod) { // textureQueryLevels is not available until #430, so we require explicit lod diff --git a/libraries/render-utils/src/LightClusterGrid.slh b/libraries/render-utils/src/LightClusterGrid.slh index 709e8c0f70..8f57169ace 100644 --- a/libraries/render-utils/src/LightClusterGrid.slh +++ b/libraries/render-utils/src/LightClusterGrid.slh @@ -10,7 +10,7 @@ <@if not RENDER_LIGHT_CLUSTER_GRID_SLH@> <@def RENDER_LIGHT_CLUSTER_GRID_SLH@> - +<@include render-utils/ShaderConstants.h@> struct FrustumGrid { float frustumNear; @@ -24,7 +24,7 @@ struct FrustumGrid { mat4 eyeToWorldMat; }; -layout(std140) uniform frustumGridBuffer { +layout(std140, binding=RENDER_UTILS_BUFFER_LIGHT_CLUSTER_FRUSTUM_GRID) uniform frustumGridBuffer { FrustumGrid frustumGrid; }; @@ -60,11 +60,11 @@ float projection_getFar(mat4 projection) { #define GRID_FETCH_BUFFER(i) i!> <@endif@> -layout(std140) uniform clusterGridBuffer { +layout(std140, binding=RENDER_UTILS_BUFFER_LIGHT_CLUSTER_GRID) uniform clusterGridBuffer { GRID_INDEX_TYPE _clusterGridTable[GRID_NUM_ELEMENTS]; }; -layout(std140) uniform clusterContentBuffer { +layout(std140, binding=RENDER_UTILS_BUFFER_LIGHT_CLUSTER_CONTENT) uniform clusterContentBuffer { GRID_INDEX_TYPE _clusterGridContent[GRID_NUM_ELEMENTS]; }; diff --git a/libraries/render-utils/src/LightClusters.cpp b/libraries/render-utils/src/LightClusters.cpp index fdd9ea461f..ae484f868f 100644 --- a/libraries/render-utils/src/LightClusters.cpp +++ b/libraries/render-utils/src/LightClusters.cpp @@ -9,42 +9,25 @@ // #include "LightClusters.h" -#include "RenderUtilsLogging.h" - #include +#include +#include -#include - +#include "RenderUtilsLogging.h" +#include "render-utils/ShaderConstants.h" #include "StencilMaskPass.h" -#include "lightClusters_drawGrid_vert.h" -#include "lightClusters_drawGrid_frag.h" -//#include "lightClusters_drawClusterFromDepth_vert.h" -#include "lightClusters_drawClusterFromDepth_frag.h" +namespace ru { + using render_utils::slot::texture::Texture; + using render_utils::slot::buffer::Buffer; +} - -#include "lightClusters_drawClusterContent_vert.h" -#include "lightClusters_drawClusterContent_frag.h" - -enum LightClusterGridShader_MapSlot { - DEFERRED_BUFFER_LINEAR_DEPTH_UNIT = 0, - DEFERRED_BUFFER_COLOR_UNIT = 1, - DEFERRED_BUFFER_NORMAL_UNIT = 2, - DEFERRED_BUFFER_EMISSIVE_UNIT = 3, - DEFERRED_BUFFER_DEPTH_UNIT = 4, -}; - -enum LightClusterGridShader_BufferSlot { - DEFERRED_FRAME_TRANSFORM_BUFFER_SLOT = 0, - CAMERA_CORRECTION_BUFFER_SLOT = 1, - LIGHT_GPU_SLOT = render::ShapePipeline::Slot::LIGHT_ARRAY_BUFFER, - LIGHT_INDEX_GPU_SLOT = 7, - LIGHT_CLUSTER_GRID_FRUSTUM_GRID_SLOT = 8, - LIGHT_CLUSTER_GRID_CLUSTER_GRID_SLOT = 9, - LIGHT_CLUSTER_GRID_CLUSTER_CONTENT_SLOT = 10, -}; +namespace gr { + using graphics::slot::texture::Texture; + using graphics::slot::buffer::Buffer; +} FrustumGrid::FrustumGrid(const FrustumGrid& source) : frustumNear(source.frustumNear), @@ -605,19 +588,7 @@ void DebugLightClusters::configure(const Config& config) { const gpu::PipelinePointer DebugLightClusters::getDrawClusterGridPipeline() { if (!_drawClusterGrid) { - auto vs = lightClusters_drawGrid_vert::getShader(); - auto ps = lightClusters_drawGrid_frag::getShader(); - gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); - - gpu::Shader::BindingSet slotBindings; - slotBindings.insert(gpu::Shader::Binding(std::string("frustumGridBuffer"), LIGHT_CLUSTER_GRID_FRUSTUM_GRID_SLOT)); - slotBindings.insert(gpu::Shader::Binding(std::string("clusterGridBuffer"), LIGHT_CLUSTER_GRID_CLUSTER_GRID_SLOT)); - slotBindings.insert(gpu::Shader::Binding(std::string("clusterContentBuffer"), LIGHT_CLUSTER_GRID_CLUSTER_CONTENT_SLOT)); - - - gpu::Shader::makeProgram(*program, slotBindings); - - + gpu::ShaderPointer program = gpu::Shader::createProgram(shader::render_utils::program::lightClusters_drawGrid); auto state = std::make_shared(); state->setDepthTest(true, false, gpu::LESS_EQUAL); @@ -633,23 +604,7 @@ const gpu::PipelinePointer DebugLightClusters::getDrawClusterGridPipeline() { const gpu::PipelinePointer DebugLightClusters::getDrawClusterFromDepthPipeline() { if (!_drawClusterFromDepth) { - // auto vs = gpu::Shader::createVertex(std::string(lightClusters_drawGrid_vert)); - auto vs = gpu::StandardShaderLib::getDrawUnitQuadTexcoordVS(); - auto ps = lightClusters_drawClusterFromDepth_frag::getShader(); - gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); - - gpu::Shader::BindingSet slotBindings; - slotBindings.insert(gpu::Shader::Binding(std::string("frustumGridBuffer"), LIGHT_CLUSTER_GRID_FRUSTUM_GRID_SLOT)); - slotBindings.insert(gpu::Shader::Binding(std::string("clusterGridBuffer"), LIGHT_CLUSTER_GRID_CLUSTER_GRID_SLOT)); - slotBindings.insert(gpu::Shader::Binding(std::string("clusterContentBuffer"), LIGHT_CLUSTER_GRID_CLUSTER_CONTENT_SLOT)); - slotBindings.insert(gpu::Shader::Binding(std::string("linearZeyeMap"), DEFERRED_BUFFER_LINEAR_DEPTH_UNIT)); - - slotBindings.insert(gpu::Shader::Binding(std::string("cameraCorrectionBuffer"), CAMERA_CORRECTION_BUFFER_SLOT)); - slotBindings.insert(gpu::Shader::Binding(std::string("deferredFrameTransformBuffer"), DEFERRED_FRAME_TRANSFORM_BUFFER_SLOT)); - - gpu::Shader::makeProgram(*program, slotBindings); - - + gpu::ShaderPointer program = gpu::Shader::createProgram(shader::render_utils::program::lightClusters_drawClusterFromDepth); auto state = std::make_shared(); // Blend on transparent @@ -663,25 +618,7 @@ const gpu::PipelinePointer DebugLightClusters::getDrawClusterFromDepthPipeline() const gpu::PipelinePointer DebugLightClusters::getDrawClusterContentPipeline() { if (!_drawClusterContent) { - // auto vs = gpu::Shader::createVertex(std::string(lightClusters_drawClusterContent_vert)); - auto vs = gpu::StandardShaderLib::getDrawUnitQuadTexcoordVS(); - auto ps = lightClusters_drawClusterContent_frag::getShader(); - gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); - - gpu::Shader::BindingSet slotBindings; - slotBindings.insert(gpu::Shader::Binding(std::string("lightBuffer"), LIGHT_GPU_SLOT)); - - slotBindings.insert(gpu::Shader::Binding(std::string("frustumGridBuffer"), LIGHT_CLUSTER_GRID_FRUSTUM_GRID_SLOT)); - slotBindings.insert(gpu::Shader::Binding(std::string("clusterGridBuffer"), LIGHT_CLUSTER_GRID_CLUSTER_GRID_SLOT)); - slotBindings.insert(gpu::Shader::Binding(std::string("clusterContentBuffer"), LIGHT_CLUSTER_GRID_CLUSTER_CONTENT_SLOT)); - slotBindings.insert(gpu::Shader::Binding(std::string("linearZeyeMap"), DEFERRED_BUFFER_LINEAR_DEPTH_UNIT)); - - slotBindings.insert(gpu::Shader::Binding(std::string("cameraCorrectionBuffer"), CAMERA_CORRECTION_BUFFER_SLOT)); - slotBindings.insert(gpu::Shader::Binding(std::string("deferredFrameTransformBuffer"), DEFERRED_FRAME_TRANSFORM_BUFFER_SLOT)); - - gpu::Shader::makeProgram(*program, slotBindings); - - + gpu::ShaderPointer program = gpu::Shader::createProgram(shader::render_utils::program::lightClusters_drawClusterContent); auto state = std::make_shared(); // Blend on transparent @@ -725,41 +662,42 @@ void DebugLightClusters::run(const render::RenderContextPointer& renderContext, batch.setModelTransform(Transform()); // Bind the Light CLuster data strucutre - batch.setUniformBuffer(LIGHT_GPU_SLOT, lightClusters->_lightStage->getLightArrayBuffer()); - batch.setUniformBuffer(LIGHT_CLUSTER_GRID_FRUSTUM_GRID_SLOT, lightClusters->_frustumGridBuffer); - batch.setUniformBuffer(LIGHT_CLUSTER_GRID_CLUSTER_GRID_SLOT, lightClusters->_clusterGridBuffer); - batch.setUniformBuffer(LIGHT_CLUSTER_GRID_CLUSTER_CONTENT_SLOT, lightClusters->_clusterContentBuffer); + // FIXME consolidate code with DeferredLightingEffect logic that does the same thing + batch.setUniformBuffer(gr::Buffer::Light, lightClusters->_lightStage->getLightArrayBuffer()); + batch.setUniformBuffer(ru::Buffer::LightClusterFrustumGrid, lightClusters->_frustumGridBuffer); + batch.setUniformBuffer(ru::Buffer::LightClusterGrid, lightClusters->_clusterGridBuffer); + batch.setUniformBuffer(ru::Buffer::LightClusterContent, lightClusters->_clusterContentBuffer); if (doDrawClusterFromDepth) { batch.setPipeline(getDrawClusterFromDepthPipeline()); - batch.setUniformBuffer(DEFERRED_FRAME_TRANSFORM_BUFFER_SLOT, deferredTransform->getFrameTransformBuffer()); + batch.setUniformBuffer(ru::Buffer::DeferredFrameTransform, deferredTransform->getFrameTransformBuffer()); if (linearDepthTarget) { - batch.setResourceTexture(DEFERRED_BUFFER_LINEAR_DEPTH_UNIT, linearDepthTarget->getLinearDepthTexture()); + batch.setResourceTexture(ru::Texture::DeferredLinearZEye, linearDepthTarget->getLinearDepthTexture()); } batch.draw(gpu::TRIANGLE_STRIP, 4, 0); - batch.setResourceTexture(DEFERRED_BUFFER_LINEAR_DEPTH_UNIT, nullptr); - batch.setUniformBuffer(DEFERRED_FRAME_TRANSFORM_BUFFER_SLOT, nullptr); + batch.setResourceTexture(ru::Texture::DeferredLinearZEye, nullptr); + batch.setUniformBuffer(ru::Buffer::DeferredFrameTransform, nullptr); } if (doDrawContent) { // bind the one gpu::Pipeline we need batch.setPipeline(getDrawClusterContentPipeline()); - batch.setUniformBuffer(DEFERRED_FRAME_TRANSFORM_BUFFER_SLOT, deferredTransform->getFrameTransformBuffer()); + batch.setUniformBuffer(ru::Buffer::DeferredFrameTransform, deferredTransform->getFrameTransformBuffer()); if (linearDepthTarget) { - batch.setResourceTexture(DEFERRED_BUFFER_LINEAR_DEPTH_UNIT, linearDepthTarget->getLinearDepthTexture()); + batch.setResourceTexture(ru::Texture::DeferredLinearZEye, linearDepthTarget->getLinearDepthTexture()); } batch.draw(gpu::TRIANGLE_STRIP, 4, 0); - batch.setResourceTexture(DEFERRED_BUFFER_LINEAR_DEPTH_UNIT, nullptr); - batch.setUniformBuffer(DEFERRED_FRAME_TRANSFORM_BUFFER_SLOT, nullptr); + batch.setResourceTexture(ru::Texture::DeferredLinearZEye, nullptr); + batch.setUniformBuffer(ru::Buffer::DeferredFrameTransform, nullptr); } }); @@ -776,14 +714,14 @@ void DebugLightClusters::run(const render::RenderContextPointer& renderContext, drawGridAndCleanBatch.drawInstanced(summedDims.x, gpu::LINES, 24, 0); } - drawGridAndCleanBatch.setUniformBuffer(LIGHT_GPU_SLOT, nullptr); - drawGridAndCleanBatch.setUniformBuffer(LIGHT_CLUSTER_GRID_FRUSTUM_GRID_SLOT, nullptr); - drawGridAndCleanBatch.setUniformBuffer(LIGHT_CLUSTER_GRID_CLUSTER_GRID_SLOT, nullptr); - drawGridAndCleanBatch.setUniformBuffer(LIGHT_CLUSTER_GRID_CLUSTER_CONTENT_SLOT, nullptr); + drawGridAndCleanBatch.setUniformBuffer(gr::Buffer::Light, nullptr); + drawGridAndCleanBatch.setUniformBuffer(ru::Buffer::LightClusterFrustumGrid, nullptr); + drawGridAndCleanBatch.setUniformBuffer(ru::Buffer::LightClusterGrid, nullptr); + drawGridAndCleanBatch.setUniformBuffer(ru::Buffer::LightClusterContent, nullptr); - drawGridAndCleanBatch.setResourceTexture(DEFERRED_BUFFER_COLOR_UNIT, nullptr); - drawGridAndCleanBatch.setResourceTexture(DEFERRED_BUFFER_NORMAL_UNIT, nullptr); - drawGridAndCleanBatch.setResourceTexture(DEFERRED_BUFFER_EMISSIVE_UNIT, nullptr); - drawGridAndCleanBatch.setResourceTexture(DEFERRED_BUFFER_DEPTH_UNIT, nullptr); + drawGridAndCleanBatch.setResourceTexture(ru::Texture::DeferredColor, nullptr); + drawGridAndCleanBatch.setResourceTexture(ru::Texture::DeferredNormal, nullptr); + drawGridAndCleanBatch.setResourceTexture(ru::Texture::DeferredSpecular, nullptr); + drawGridAndCleanBatch.setResourceTexture(ru::Texture::DeferredLinearZEye, nullptr); }); -} \ No newline at end of file +} diff --git a/libraries/render-utils/src/LightDirectional.slh b/libraries/render-utils/src/LightDirectional.slh index b6e1720a2c..1490a2ff25 100644 --- a/libraries/render-utils/src/LightDirectional.slh +++ b/libraries/render-utils/src/LightDirectional.slh @@ -7,7 +7,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // - +<@include ShadingModel.slh@> <@func declareLightingDirectional(supportScattering)@> diff --git a/libraries/render-utils/src/LightLocal.slh b/libraries/render-utils/src/LightLocal.slh index 06f8871e24..91f0b4fd20 100644 --- a/libraries/render-utils/src/LightLocal.slh +++ b/libraries/render-utils/src/LightLocal.slh @@ -9,17 +9,15 @@ // Everything about light <@include graphics/Light.slh@> -<$declareLightBuffer(256)$> <@include LightingModel.slh@> - - <@include LightPoint.slh@> -<$declareLightingPoint(supportScattering)$> <@include LightSpot.slh@> -<$declareLightingSpot(supportScattering)$> - <@include LightClusterGrid.slh@> +<$declareLightBuffer(256)$> +<$declareLightingPoint(supportScattering)$> +<$declareLightingSpot(supportScattering)$> + vec4 evalLocalLighting(ivec3 cluster, int numLights, vec3 fragWorldPos, SurfaceData surface, float fragMetallic, vec3 fragFresnel, vec3 fragAlbedo, float fragScattering, vec4 midNormalCurvature, vec4 lowNormalCurvature, float opacity) { @@ -145,4 +143,5 @@ vec4 evalLocalLighting(ivec3 cluster, int numLights, vec3 fragWorldPos, SurfaceD fragColor.rgb += fragDiffuse; fragColor.rgb += evalSpecularWithOpacity(fragSpecular, opacity); return fragColor; -} \ No newline at end of file +} + diff --git a/libraries/render-utils/src/LightPoint.slh b/libraries/render-utils/src/LightPoint.slh index 91a1260fcc..1a361e3717 100644 --- a/libraries/render-utils/src/LightPoint.slh +++ b/libraries/render-utils/src/LightPoint.slh @@ -7,7 +7,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // - +<@include ShadingModel.slh@> <@func declareLightingPoint(supportScattering)@> diff --git a/libraries/render-utils/src/LightSpot.slh b/libraries/render-utils/src/LightSpot.slh index 73c5bd9559..2546c0225c 100644 --- a/libraries/render-utils/src/LightSpot.slh +++ b/libraries/render-utils/src/LightSpot.slh @@ -7,7 +7,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // - +<@include ShadingModel.slh@> <@func declareLightingSpot(supportScattering)@> diff --git a/libraries/render-utils/src/LightingModel.slh b/libraries/render-utils/src/LightingModel.slh index fe2d684c32..10a2afabef 100644 --- a/libraries/render-utils/src/LightingModel.slh +++ b/libraries/render-utils/src/LightingModel.slh @@ -11,6 +11,7 @@ <@if not LIGHTING_MODEL_SLH@> <@def LIGHTING_MODEL_SLH@> +<@include render-utils/ShaderConstants.h@> <@func declareLightingModel()@> struct LightingModel { @@ -21,7 +22,8 @@ struct LightingModel { vec4 _Haze_spareyzw; }; -uniform lightingModelBuffer{ +// See DeferredShader_BufferSlot in DeferredLightingEffect.cpp +layout(binding=RENDER_UTILS_BUFFER_LIGHT_MODEL) uniform lightingModelBuffer{ LightingModel lightingModel; }; @@ -106,225 +108,4 @@ vec3 getFresnelF0(float metallic, vec3 metalF0) { } <@endif@> -<@func declareBeckmannSpecular()@> - -uniform sampler2D scatteringSpecularBeckmann; - -float fetchSpecularBeckmann(float ndoth, float roughness) { - return pow(2.0 * texture(scatteringSpecularBeckmann, vec2(ndoth, roughness)).r, 10.0); -} - -vec2 skinSpecular(SurfaceData surface, float intensity) { - vec2 result = vec2(0.0, 1.0); - if (surface.ndotl > 0.0) { - float PH = fetchSpecularBeckmann(surface.ndoth, surface.roughness); - float F = fresnelSchlickScalar(0.028, surface); - float frSpec = max(PH * F / dot(surface.halfDir, surface.halfDir), 0.0); - result.x = surface.ndotl * intensity * frSpec; - result.y -= F; - } - - return result; -} -<@endfunc@> - -<@func declareEvalPBRShading()@> - -float evalSmithInvG1(float roughness4, float ndotd) { - return ndotd + sqrt(roughness4+ndotd*ndotd*(1.0-roughness4)); -} - -SurfaceData initSurfaceData(float roughness, vec3 normal, vec3 eyeDir) { - SurfaceData surface; - surface.eyeDir = eyeDir; - surface.normal = normal; - surface.roughness = mix(0.01, 1.0, roughness); - surface.roughness2 = surface.roughness * surface.roughness; - surface.roughness4 = surface.roughness2 * surface.roughness2; - surface.ndotv = clamp(dot(normal, eyeDir), 0.0, 1.0); - surface.smithInvG1NdotV = evalSmithInvG1(surface.roughness4, surface.ndotv); - - // These values will be set when we know the light direction, in updateSurfaceDataWithLight - surface.ndoth = 0.0; - surface.ndotl = 0.0; - surface.ldoth = 0.0; - surface.lightDir = vec3(0,0,1); - surface.halfDir = vec3(0,0,1); - - return surface; -} - -void updateSurfaceDataWithLight(inout SurfaceData surface, vec3 lightDir) { - surface.lightDir = lightDir; - surface.halfDir = normalize(surface.eyeDir + lightDir); - vec3 dots; - dots.x = dot(surface.normal, surface.halfDir); - dots.y = dot(surface.normal, surface.lightDir); - dots.z = dot(surface.halfDir, surface.lightDir); - dots = clamp(dots, vec3(0), vec3(1)); - surface.ndoth = dots.x; - surface.ndotl = dots.y; - surface.ldoth = dots.z; -} - -vec3 fresnelSchlickColor(vec3 fresnelColor, SurfaceData surface) { - float base = 1.0 - surface.ldoth; - //float exponential = pow(base, 5.0); - float base2 = base * base; - float exponential = base * base2 * base2; - return vec3(exponential) + fresnelColor * (1.0 - exponential); -} - -float fresnelSchlickScalar(float fresnelScalar, SurfaceData surface) { - float base = 1.0 - surface.ldoth; - //float exponential = pow(base, 5.0); - float base2 = base * base; - float exponential = base * base2 * base2; - return (exponential) + fresnelScalar * (1.0 - exponential); -} - -float specularDistribution(SurfaceData surface) { - // See https://www.khronos.org/assets/uploads/developers/library/2017-web3d/glTF-2.0-Launch_Jun17.pdf - // for details of equations, especially page 20 - float denom = (surface.ndoth*surface.ndoth * (surface.roughness4 - 1.0) + 1.0); - denom *= denom; - // Add geometric factors G1(n,l) and G1(n,v) - float smithInvG1NdotL = evalSmithInvG1(surface.roughness4, surface.ndotl); - denom *= surface.smithInvG1NdotV * smithInvG1NdotL; - // Don't divide by PI as this is part of the light normalization factor - float power = surface.roughness4 / denom; - return power; -} - -// Frag Shading returns the diffuse amount as W and the specular rgb as xyz -vec4 evalPBRShading(float metallic, vec3 fresnel, SurfaceData surface) { - // Incident angle attenuation - float angleAttenuation = surface.ndotl; - - // Specular Lighting - vec3 fresnelColor = fresnelSchlickColor(fresnel, surface); - float power = specularDistribution(surface); - vec3 specular = fresnelColor * power * angleAttenuation; - float diffuse = (1.0 - metallic) * angleAttenuation * (1.0 - fresnelColor.x); - - // We don't divided by PI, as the "normalized" equations state we should, because we decide, as Naty Hoffman, that - // we wish to have a similar color as raw albedo on a perfectly diffuse surface perpendicularly lit - // by a white light of intensity 1. But this is an arbitrary normalization of what light intensity "means". - // (see http://blog.selfshadow.com/publications/s2013-shading-course/hoffman/s2013_pbs_physics_math_notes.pdf - // page 23 paragraph "Punctual light sources") - return vec4(specular, diffuse); -} - -// Frag Shading returns the diffuse amount as W and the specular rgb as xyz -vec4 evalPBRShadingDielectric(SurfaceData surface, float fresnel) { - // Incident angle attenuation - float angleAttenuation = surface.ndotl; - - // Specular Lighting - float fresnelScalar = fresnelSchlickScalar(fresnel, surface); - float power = specularDistribution(surface); - vec3 specular = vec3(fresnelScalar) * power * angleAttenuation; - float diffuse = angleAttenuation * (1.0 - fresnelScalar); - - // We don't divided by PI, as the "normalized" equations state we should, because we decide, as Naty Hoffman, that - // we wish to have a similar color as raw albedo on a perfectly diffuse surface perpendicularly lit - // by a white light of intensity 1. But this is an arbitrary normalization of what light intensity "means". - // (see http://blog.selfshadow.com/publications/s2013-shading-course/hoffman/s2013_pbs_physics_math_notes.pdf - // page 23 paragraph "Punctual light sources") - return vec4(specular, diffuse); -} - -vec4 evalPBRShadingMetallic(SurfaceData surface, vec3 fresnel) { - // Incident angle attenuation - float angleAttenuation = surface.ndotl; - - // Specular Lighting - vec3 fresnelColor = fresnelSchlickColor(fresnel, surface); - float power = specularDistribution(surface); - vec3 specular = fresnelColor * power * angleAttenuation; - - // We don't divided by PI, as the "normalized" equations state we should, because we decide, as Naty Hoffman, that - // we wish to have a similar color as raw albedo on a perfectly diffuse surface perpendicularly lit - // by a white light of intensity 1. But this is an arbitrary normalization of what light intensity "means". - // (see http://blog.selfshadow.com/publications/s2013-shading-course/hoffman/s2013_pbs_physics_math_notes.pdf - // page 23 paragraph "Punctual light sources") - return vec4(specular, 0.f); -} - -<@endfunc@> - - - -<$declareEvalPBRShading()$> - -void evalFragShading(out vec3 diffuse, out vec3 specular, - float metallic, vec3 fresnel, SurfaceData surface, vec3 albedo) { - vec4 shading = evalPBRShading(metallic, fresnel, surface); - diffuse = vec3(shading.w); - diffuse *= mix(vec3(1.0), albedo, isAlbedoEnabled()); - specular = shading.xyz; -} - -<$declareBeckmannSpecular()$> -<@include SubsurfaceScattering.slh@> -<$declareSubsurfaceScatteringBRDF()$> - - -void evalFragShading(out vec3 diffuse, out vec3 specular, - float metallic, vec3 fresnel, SurfaceData surface, vec3 albedo, - float scattering, vec4 midNormalCurvature, vec4 lowNormalCurvature) { - if (scattering * isScatteringEnabled() > 0.0) { - vec3 brdf = evalSkinBRDF(surface.lightDir, surface.normal, midNormalCurvature.xyz, lowNormalCurvature.xyz, lowNormalCurvature.w); - diffuse = mix(vec3(surface.ndotl), brdf, scattering); - - // Specular Lighting - vec2 specularBrdf = skinSpecular(surface, 1.0); - - diffuse *= specularBrdf.y; - specular = vec3(specularBrdf.x); - } else { - vec4 shading = evalPBRShading(metallic, fresnel, surface); - diffuse = vec3(shading.w); - specular = shading.xyz; - } - diffuse *= mix(vec3(1.0), albedo, isAlbedoEnabled()); -} - - -void evalFragShadingScattering(out vec3 diffuse, out vec3 specular, - float metallic, vec3 fresnel, SurfaceData surface, vec3 albedo, - float scattering, vec4 midNormalCurvature, vec4 lowNormalCurvature) { - vec3 brdf = evalSkinBRDF(surface.lightDir, surface.normal, midNormalCurvature.xyz, lowNormalCurvature.xyz, lowNormalCurvature.w); - float NdotL = surface.ndotl; - diffuse = mix(vec3(NdotL), brdf, scattering); - - // Specular Lighting - vec2 specularBrdf = skinSpecular(surface, 1.0); - - diffuse *= specularBrdf.y; - specular = vec3(specularBrdf.x); - diffuse *= mix(vec3(1.0), albedo, isAlbedoEnabled()); -} - -void evalFragShadingGloss(out vec3 diffuse, out vec3 specular, - float metallic, vec3 fresnel, SurfaceData surface, vec3 albedo) { - vec4 shading = evalPBRShading(metallic, fresnel, surface); - diffuse = vec3(shading.w); - diffuse *= mix(vec3(1.0), albedo, isAlbedoEnabled()); - specular = shading.xyz; -} - -vec3 evalSpecularWithOpacity(vec3 specular, float opacity) { - return specular / opacity; -} - -<@if not GETFRESNEL0@> -<@def GETFRESNEL0@> -vec3 getFresnelF0(float metallic, vec3 metalF0) { - // Enable continuous metallness value by lerping between dielectric - // and metal fresnel F0 value based on the "metallic" parameter - return mix(vec3(0.03), metalF0, metallic); -} -<@endif@> - <@endif@> diff --git a/libraries/render-utils/src/MeshPartPayload.cpp b/libraries/render-utils/src/MeshPartPayload.cpp index 7cf8bc8297..94a5026fb4 100644 --- a/libraries/render-utils/src/MeshPartPayload.cpp +++ b/libraries/render-utils/src/MeshPartPayload.cpp @@ -13,7 +13,9 @@ #include #include +#include +#include "render-utils/ShaderConstants.h" #include "DeferredLightingEffect.h" #include "RenderPipelines.h" @@ -155,8 +157,10 @@ void MeshPartPayload::render(RenderArgs* args) { bindMesh(batch); // apply material properties - RenderPipelines::bindMaterial(!_drawMaterials.empty() ? _drawMaterials.top().material : DEFAULT_MATERIAL, batch, args->_enableTexturing); - args->_details._materialSwitches++; + if (args->_renderMode != render::Args::RenderMode::SHADOW_RENDER_MODE) { + RenderPipelines::bindMaterial(!_drawMaterials.empty() ? _drawMaterials.top().material : DEFAULT_MATERIAL, batch, args->_enableTexturing); + args->_details._materialSwitches++; + } // Draw! { @@ -204,7 +208,9 @@ ModelMeshPartPayload::ModelMeshPartPayload(ModelPointer model, int meshIndex, in bool useDualQuaternionSkinning = model->getUseDualQuaternionSkinning(); - _blendedVertexBuffer = model->_blendedVertexBuffers[_meshIndex]; + if (!model->getFBXGeometry().meshes[meshIndex].blendshapes.isEmpty()) { + _blendedVertexBuffer = model->_blendedVertexBuffers[meshIndex]; + } auto& modelMesh = model->getGeometry()->getMeshes().at(_meshIndex); const Model::MeshState& state = model->getMeshState(_meshIndex); @@ -395,7 +401,7 @@ void ModelMeshPartPayload::bindMesh(gpu::Batch& batch) { void ModelMeshPartPayload::bindTransform(gpu::Batch& batch, RenderArgs::RenderMode renderMode) const { if (_clusterBuffer) { - batch.setUniformBuffer(ShapePipeline::Slot::BUFFER::SKINNING, _clusterBuffer); + batch.setUniformBuffer(graphics::slot::buffer::Skinning, _clusterBuffer); } batch.setModelTransform(_transform); } @@ -415,8 +421,10 @@ void ModelMeshPartPayload::render(RenderArgs* args) { bindMesh(batch); // apply material properties - RenderPipelines::bindMaterial(!_drawMaterials.empty() ? _drawMaterials.top().material : DEFAULT_MATERIAL, batch, args->_enableTexturing); - args->_details._materialSwitches++; + if (args->_renderMode != render::Args::RenderMode::SHADOW_RENDER_MODE) { + RenderPipelines::bindMaterial(!_drawMaterials.empty() ? _drawMaterials.top().material : DEFAULT_MATERIAL, batch, args->_enableTexturing); + args->_details._materialSwitches++; + } // Draw! { diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index 41b95312f6..ba2bd28852 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -42,8 +42,9 @@ using namespace std; int nakedModelPointerTypeId = qRegisterMetaType(); -int weakGeometryResourceBridgePointerTypeId = qRegisterMetaType(); -int vec3VectorTypeId = qRegisterMetaType >(); +int weakGeometryResourceBridgePointerTypeId = qRegisterMetaType(); +int vec3VectorTypeId = qRegisterMetaType>(); +int normalTypeVecTypeId = qRegisterMetaType>("QVector"); float Model::FAKE_DIMENSION_PLACEHOLDER = -1.0f; #define HTTP_INVALID_COM "http://invalid.com" @@ -282,6 +283,7 @@ void Model::reset() { const FBXGeometry& geometry = getFBXGeometry(); _rig.reset(geometry); emit rigReset(); + emit rigReady(); } } @@ -305,36 +307,6 @@ bool Model::updateGeometry() { state.clusterDualQuaternions.resize(mesh.clusters.size()); state.clusterMatrices.resize(mesh.clusters.size()); _meshStates.push_back(state); - - // Note: we add empty buffers for meshes that lack blendshapes so we can access the buffers by index - // later in ModelMeshPayload, however the vast majority of meshes will not have them. - // TODO? make _blendedVertexBuffers a map instead of vector and only add for meshes with blendshapes? - auto buffer = std::make_shared(); - if (!mesh.blendshapes.isEmpty()) { - std::vector normalsAndTangents; - normalsAndTangents.reserve(mesh.normals.size() + mesh.tangents.size()); - - for (auto normalIt = mesh.normals.begin(), tangentIt = mesh.tangents.begin(); - normalIt != mesh.normals.end(); - ++normalIt, ++tangentIt) { -#if FBX_PACK_NORMALS - glm::uint32 finalNormal; - glm::uint32 finalTangent; - buffer_helpers::packNormalAndTangent(*normalIt, *tangentIt, finalNormal, finalTangent); -#else - const auto finalNormal = *normalIt; - const auto finalTangent = *tangentIt; -#endif - normalsAndTangents.push_back(finalNormal); - normalsAndTangents.push_back(finalTangent); - } - - buffer->resize(mesh.vertices.size() * (sizeof(glm::vec3) + 2 * sizeof(NormalType))); - buffer->setSubData(0, mesh.vertices.size() * sizeof(glm::vec3), (const gpu::Byte*) mesh.vertices.constData()); - buffer->setSubData(mesh.vertices.size() * sizeof(glm::vec3), - mesh.normals.size() * 2 * sizeof(NormalType), (const gpu::Byte*) normalsAndTangents.data()); - } - _blendedVertexBuffers.push_back(buffer); } needFullUpdate = true; emit rigReady(); @@ -353,89 +325,271 @@ void Model::initJointStates() { bool Model::findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool pickAgainstTriangles, bool allowBackface) { - bool intersectedSomething = false; - // if we aren't active, we can't ray pick yet... + // if we aren't active, we can't pick yet... if (!isActive()) { return intersectedSomething; } // extents is the entity relative, scaled, centered extents of the entity - glm::vec3 position = _translation; - glm::mat4 rotation = glm::mat4_cast(_rotation); - glm::mat4 translation = glm::translate(position); - glm::mat4 modelToWorldMatrix = translation * rotation; + glm::mat4 modelToWorldMatrix = createMatFromQuatAndPos(_rotation, _translation); glm::mat4 worldToModelMatrix = glm::inverse(modelToWorldMatrix); Extents modelExtents = getMeshExtents(); // NOTE: unrotated glm::vec3 dimensions = modelExtents.maximum - modelExtents.minimum; - glm::vec3 corner = -(dimensions * _registrationPoint); // since we're going to do the ray picking in the model frame of reference + glm::vec3 corner = -(dimensions * _registrationPoint); // since we're going to do the picking in the model frame of reference AABox modelFrameBox(corner, dimensions); glm::vec3 modelFrameOrigin = glm::vec3(worldToModelMatrix * glm::vec4(origin, 1.0f)); glm::vec3 modelFrameDirection = glm::vec3(worldToModelMatrix * glm::vec4(direction, 0.0f)); - // we can use the AABox's ray intersection by mapping our origin and direction into the model frame + // we can use the AABox's intersection by mapping our origin and direction into the model frame // and testing intersection there. - if (modelFrameBox.findRayIntersection(modelFrameOrigin, modelFrameDirection, distance, face, surfaceNormal)) { + if (modelFrameBox.findRayIntersection(modelFrameOrigin, modelFrameDirection, 1.0f / modelFrameDirection, distance, face, surfaceNormal)) { QMutexLocker locker(&_mutex); - float bestDistance = std::numeric_limits::max(); + float bestDistance = FLT_MAX; + BoxFace bestFace; Triangle bestModelTriangle; Triangle bestWorldTriangle; + glm::vec3 bestWorldIntersectionPoint; + glm::vec3 bestMeshIntersectionPoint; + int bestPartIndex = 0; + int bestShapeID = 0; int bestSubMeshIndex = 0; - int subMeshIndex = 0; const FBXGeometry& geometry = getFBXGeometry(); - if (!_triangleSetsValid) { calculateTriangleSets(geometry); } glm::mat4 meshToModelMatrix = glm::scale(_scale) * glm::translate(_offset); - glm::mat4 meshToWorldMatrix = createMatFromQuatAndPos(_rotation, _translation) * meshToModelMatrix; + glm::mat4 meshToWorldMatrix = modelToWorldMatrix * meshToModelMatrix; glm::mat4 worldToMeshMatrix = glm::inverse(meshToWorldMatrix); glm::vec3 meshFrameOrigin = glm::vec3(worldToMeshMatrix * glm::vec4(origin, 1.0f)); glm::vec3 meshFrameDirection = glm::vec3(worldToMeshMatrix * glm::vec4(direction, 0.0f)); + glm::vec3 meshFrameInvDirection = 1.0f / meshFrameDirection; int shapeID = 0; + int subMeshIndex = 0; + + std::vector sortedTriangleSets; for (auto& meshTriangleSets : _modelSpaceMeshTriangleSets) { int partIndex = 0; - for (auto &partTriangleSet : meshTriangleSets) { - float triangleSetDistance = 0.0f; - BoxFace triangleSetFace; - Triangle triangleSetTriangle; - if (partTriangleSet.findRayIntersection(meshFrameOrigin, meshFrameDirection, triangleSetDistance, triangleSetFace, triangleSetTriangle, pickAgainstTriangles, allowBackface)) { - - glm::vec3 meshIntersectionPoint = meshFrameOrigin + (meshFrameDirection * triangleSetDistance); - glm::vec3 worldIntersectionPoint = glm::vec3(meshToWorldMatrix * glm::vec4(meshIntersectionPoint, 1.0f)); - float worldDistance = glm::distance(origin, worldIntersectionPoint); - - if (worldDistance < bestDistance) { - bestDistance = worldDistance; - intersectedSomething = true; - face = triangleSetFace; - bestModelTriangle = triangleSetTriangle; - bestWorldTriangle = triangleSetTriangle * meshToWorldMatrix; - extraInfo["worldIntersectionPoint"] = vec3toVariant(worldIntersectionPoint); - extraInfo["meshIntersectionPoint"] = vec3toVariant(meshIntersectionPoint); - extraInfo["partIndex"] = partIndex; - extraInfo["shapeID"] = shapeID; - bestSubMeshIndex = subMeshIndex; + for (auto& partTriangleSet : meshTriangleSets) { + float priority = FLT_MAX; + if (partTriangleSet.getBounds().contains(meshFrameOrigin)) { + priority = 0.0f; + } else { + float partBoundDistance = FLT_MAX; + BoxFace partBoundFace; + glm::vec3 partBoundNormal; + if (partTriangleSet.getBounds().findRayIntersection(meshFrameOrigin, meshFrameDirection, meshFrameInvDirection, + partBoundDistance, partBoundFace, partBoundNormal)) { + priority = partBoundDistance; } } + + if (priority < FLT_MAX) { + sortedTriangleSets.emplace_back(priority, &partTriangleSet, partIndex, shapeID, subMeshIndex); + } partIndex++; shapeID++; } subMeshIndex++; } + if (sortedTriangleSets.size() > 1) { + static auto comparator = [](const SortedTriangleSet& left, const SortedTriangleSet& right) { return left.distance < right.distance; }; + std::sort(sortedTriangleSets.begin(), sortedTriangleSets.end(), comparator); + } + + for (auto it = sortedTriangleSets.begin(); it != sortedTriangleSets.end(); ++it) { + const SortedTriangleSet& sortedTriangleSet = *it; + // We can exit once triangleSetDistance > bestDistance + if (sortedTriangleSet.distance > bestDistance) { + break; + } + float triangleSetDistance = FLT_MAX; + BoxFace triangleSetFace; + Triangle triangleSetTriangle; + if (sortedTriangleSet.triangleSet->findRayIntersection(meshFrameOrigin, meshFrameDirection, meshFrameInvDirection, triangleSetDistance, triangleSetFace, + triangleSetTriangle, pickAgainstTriangles, allowBackface)) { + if (triangleSetDistance < bestDistance) { + bestDistance = triangleSetDistance; + intersectedSomething = true; + bestFace = triangleSetFace; + bestModelTriangle = triangleSetTriangle; + bestWorldTriangle = triangleSetTriangle * meshToWorldMatrix; + glm::vec3 meshIntersectionPoint = meshFrameOrigin + (meshFrameDirection * triangleSetDistance); + glm::vec3 worldIntersectionPoint = glm::vec3(meshToWorldMatrix * glm::vec4(meshIntersectionPoint, 1.0f)); + bestWorldIntersectionPoint = worldIntersectionPoint; + bestMeshIntersectionPoint = meshIntersectionPoint; + bestPartIndex = sortedTriangleSet.partIndex; + bestShapeID = sortedTriangleSet.shapeID; + bestSubMeshIndex = sortedTriangleSet.subMeshIndex; + } + } + } + if (intersectedSomething) { distance = bestDistance; + face = bestFace; surfaceNormal = bestWorldTriangle.getNormal(); + extraInfo["worldIntersectionPoint"] = vec3toVariant(bestWorldIntersectionPoint); + extraInfo["meshIntersectionPoint"] = vec3toVariant(bestMeshIntersectionPoint); + extraInfo["partIndex"] = bestPartIndex; + extraInfo["shapeID"] = bestShapeID; + if (pickAgainstTriangles) { + extraInfo["subMeshIndex"] = bestSubMeshIndex; + extraInfo["subMeshName"] = geometry.getModelNameOfMesh(bestSubMeshIndex); + extraInfo["subMeshTriangleWorld"] = QVariantMap{ + { "v0", vec3toVariant(bestWorldTriangle.v0) }, + { "v1", vec3toVariant(bestWorldTriangle.v1) }, + { "v2", vec3toVariant(bestWorldTriangle.v2) }, + }; + extraInfo["subMeshNormal"] = vec3toVariant(bestModelTriangle.getNormal()); + extraInfo["subMeshTriangle"] = QVariantMap{ + { "v0", vec3toVariant(bestModelTriangle.v0) }, + { "v1", vec3toVariant(bestModelTriangle.v1) }, + { "v2", vec3toVariant(bestModelTriangle.v2) }, + }; + } + } + } + + return intersectedSomething; +} + +bool Model::findParabolaIntersectionAgainstSubMeshes(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, + bool pickAgainstTriangles, bool allowBackface) { + bool intersectedSomething = false; + + // if we aren't active, we can't pick yet... + if (!isActive()) { + return intersectedSomething; + } + + // extents is the entity relative, scaled, centered extents of the entity + glm::mat4 modelToWorldMatrix = createMatFromQuatAndPos(_rotation, _translation); + glm::mat4 worldToModelMatrix = glm::inverse(modelToWorldMatrix); + + Extents modelExtents = getMeshExtents(); // NOTE: unrotated + + glm::vec3 dimensions = modelExtents.maximum - modelExtents.minimum; + glm::vec3 corner = -(dimensions * _registrationPoint); // since we're going to do the picking in the model frame of reference + AABox modelFrameBox(corner, dimensions); + + glm::vec3 modelFrameOrigin = glm::vec3(worldToModelMatrix * glm::vec4(origin, 1.0f)); + glm::vec3 modelFrameVelocity = glm::vec3(worldToModelMatrix * glm::vec4(velocity, 0.0f)); + glm::vec3 modelFrameAcceleration = glm::vec3(worldToModelMatrix * glm::vec4(acceleration, 0.0f)); + + // we can use the AABox's intersection by mapping our origin and direction into the model frame + // and testing intersection there. + if (modelFrameBox.findParabolaIntersection(modelFrameOrigin, modelFrameVelocity, modelFrameAcceleration, parabolicDistance, face, surfaceNormal)) { + QMutexLocker locker(&_mutex); + + float bestDistance = FLT_MAX; + BoxFace bestFace; + Triangle bestModelTriangle; + Triangle bestWorldTriangle; + glm::vec3 bestWorldIntersectionPoint; + glm::vec3 bestMeshIntersectionPoint; + int bestPartIndex = 0; + int bestShapeID = 0; + int bestSubMeshIndex = 0; + + const FBXGeometry& geometry = getFBXGeometry(); + if (!_triangleSetsValid) { + calculateTriangleSets(geometry); + } + + glm::mat4 meshToModelMatrix = glm::scale(_scale) * glm::translate(_offset); + glm::mat4 meshToWorldMatrix = modelToWorldMatrix * meshToModelMatrix; + glm::mat4 worldToMeshMatrix = glm::inverse(meshToWorldMatrix); + + glm::vec3 meshFrameOrigin = glm::vec3(worldToMeshMatrix * glm::vec4(origin, 1.0f)); + glm::vec3 meshFrameVelocity = glm::vec3(worldToMeshMatrix * glm::vec4(velocity, 0.0f)); + glm::vec3 meshFrameAcceleration = glm::vec3(worldToMeshMatrix * glm::vec4(acceleration, 0.0f)); + + int shapeID = 0; + int subMeshIndex = 0; + + std::vector sortedTriangleSets; + for (auto& meshTriangleSets : _modelSpaceMeshTriangleSets) { + int partIndex = 0; + for (auto& partTriangleSet : meshTriangleSets) { + float priority = FLT_MAX; + if (partTriangleSet.getBounds().contains(meshFrameOrigin)) { + priority = 0.0f; + } else { + float partBoundDistance = FLT_MAX; + BoxFace partBoundFace; + glm::vec3 partBoundNormal; + if (partTriangleSet.getBounds().findParabolaIntersection(meshFrameOrigin, meshFrameVelocity, meshFrameAcceleration, + partBoundDistance, partBoundFace, partBoundNormal)) { + priority = partBoundDistance; + } + } + + if (priority < FLT_MAX) { + sortedTriangleSets.emplace_back(priority, &partTriangleSet, partIndex, shapeID, subMeshIndex); + } + partIndex++; + shapeID++; + } + subMeshIndex++; + } + + if (sortedTriangleSets.size() > 1) { + static auto comparator = [](const SortedTriangleSet& left, const SortedTriangleSet& right) { return left.distance < right.distance; }; + std::sort(sortedTriangleSets.begin(), sortedTriangleSets.end(), comparator); + } + + for (auto it = sortedTriangleSets.begin(); it != sortedTriangleSets.end(); ++it) { + const SortedTriangleSet& sortedTriangleSet = *it; + // We can exit once triangleSetDistance > bestDistance + if (sortedTriangleSet.distance > bestDistance) { + break; + } + float triangleSetDistance = FLT_MAX; + BoxFace triangleSetFace; + Triangle triangleSetTriangle; + if (sortedTriangleSet.triangleSet->findParabolaIntersection(meshFrameOrigin, meshFrameVelocity, meshFrameAcceleration, + triangleSetDistance, triangleSetFace, triangleSetTriangle, + pickAgainstTriangles, allowBackface)) { + if (triangleSetDistance < bestDistance) { + bestDistance = triangleSetDistance; + intersectedSomething = true; + bestFace = triangleSetFace; + bestModelTriangle = triangleSetTriangle; + bestWorldTriangle = triangleSetTriangle * meshToWorldMatrix; + glm::vec3 meshIntersectionPoint = meshFrameOrigin + meshFrameVelocity * triangleSetDistance + + 0.5f * meshFrameAcceleration * triangleSetDistance * triangleSetDistance; + glm::vec3 worldIntersectionPoint = origin + velocity * triangleSetDistance + + 0.5f * acceleration * triangleSetDistance * triangleSetDistance; + bestWorldIntersectionPoint = worldIntersectionPoint; + bestMeshIntersectionPoint = meshIntersectionPoint; + bestPartIndex = sortedTriangleSet.partIndex; + bestShapeID = sortedTriangleSet.shapeID; + bestSubMeshIndex = sortedTriangleSet.subMeshIndex; + // These sets can overlap, so we can't exit early if we find something + } + } + } + + if (intersectedSomething) { + parabolicDistance = bestDistance; + face = bestFace; + surfaceNormal = bestWorldTriangle.getNormal(); + extraInfo["worldIntersectionPoint"] = vec3toVariant(bestWorldIntersectionPoint); + extraInfo["meshIntersectionPoint"] = vec3toVariant(bestMeshIntersectionPoint); + extraInfo["partIndex"] = bestPartIndex; + extraInfo["shapeID"] = bestShapeID; if (pickAgainstTriangles) { extraInfo["subMeshIndex"] = bestSubMeshIndex; extraInfo["subMeshName"] = geometry.getModelNameOfMesh(bestSubMeshIndex); @@ -594,7 +748,7 @@ bool Model::replaceScriptableModelMeshPart(scriptable::ScriptableModelBasePointe } scene->enqueueTransaction(transaction); } - // update triangles for ray picking + // update triangles for picking { FBXGeometry geometry; for (const auto& newMesh : meshes) { @@ -1144,20 +1298,22 @@ Blender::Blender(ModelPointer model, int blendNumber, const Geometry::WeakPointe void Blender::run() { DETAILED_PROFILE_RANGE_EX(simulation_animation, __FUNCTION__, 0xFFFF0000, 0, { { "url", _model->getURL().toString() } }); - QVector vertices, normals, tangents; + QVector vertices; + QVector normalsAndTangents; if (_model) { int offset = 0; + int normalsAndTangentsOffset = 0; foreach (const FBXMesh& mesh, _meshes) { if (mesh.blendshapes.isEmpty()) { continue; } + vertices += mesh.vertices; - normals += mesh.normals; - tangents += mesh.tangents; + normalsAndTangents += mesh.normalsAndTangents; glm::vec3* meshVertices = vertices.data() + offset; - glm::vec3* meshNormals = normals.data() + offset; - glm::vec3* meshTangents = tangents.data() + offset; + NormalType* meshNormalsAndTangents = normalsAndTangents.data() + normalsAndTangentsOffset; offset += mesh.vertices.size(); + normalsAndTangentsOffset += mesh.normalsAndTangents.size(); const float NORMAL_COEFFICIENT_SCALE = 0.01f; for (int i = 0, n = qMin(_blendshapeCoefficients.size(), mesh.blendshapes.size()); i < n; i++) { float vertexCoefficient = _blendshapeCoefficients.at(i); @@ -1167,22 +1323,39 @@ void Blender::run() { } float normalCoefficient = vertexCoefficient * NORMAL_COEFFICIENT_SCALE; const FBXBlendshape& blendshape = mesh.blendshapes.at(i); - for (int j = 0; j < blendshape.indices.size(); j++) { - int index = blendshape.indices.at(j); - meshVertices[index] += blendshape.vertices.at(j) * vertexCoefficient; - meshNormals[index] += blendshape.normals.at(j) * normalCoefficient; - if (blendshape.tangents.size() > j) { - meshTangents[index] += blendshape.tangents.at(j) * normalCoefficient; + tbb::parallel_for(tbb::blocked_range(0, blendshape.indices.size()), [&](const tbb::blocked_range& range) { + for (auto j = range.begin(); j < range.end(); j++) { + int index = blendshape.indices.at(j); + meshVertices[index] += blendshape.vertices.at(j) * vertexCoefficient; + + glm::vec3 normal = mesh.normals.at(index) + blendshape.normals.at(j) * normalCoefficient; + glm::vec3 tangent; + if (index < mesh.tangents.size()) { + tangent = mesh.tangents.at(index); + if ((int)j < blendshape.tangents.size()) { + tangent += blendshape.tangents.at(j) * normalCoefficient; + } + } +#if FBX_PACK_NORMALS + glm::uint32 finalNormal; + glm::uint32 finalTangent; + buffer_helpers::packNormalAndTangent(normal, tangent, finalNormal, finalTangent); +#else + const auto& finalNormal = normal; + const auto& finalTangent = tangent; +#endif + meshNormalsAndTangents[2 * index] = finalNormal; + meshNormalsAndTangents[2 * index + 1] = finalTangent; } - } + }); } } } - // post the result to the geometry cache, which will dispatch to the model if still alive + // post the result to the ModelBlender, which will dispatch to the model if still alive QMetaObject::invokeMethod(DependencyManager::get().data(), "setBlendedVertices", - Q_ARG(ModelPointer, _model), Q_ARG(int, _blendNumber), - Q_ARG(const Geometry::WeakPointer&, _geometry), Q_ARG(const QVector&, vertices), - Q_ARG(const QVector&, normals), Q_ARG(const QVector&, tangents)); + Q_ARG(ModelPointer, _model), Q_ARG(int, _blendNumber), + Q_ARG(const Geometry::WeakPointer&, _geometry), Q_ARG(const QVector&, vertices), + Q_ARG(const QVector&, normalsAndTangents)); } void Model::setScaleToFit(bool scaleToFit, const glm::vec3& dimensions, bool forceRescale) { @@ -1342,9 +1515,10 @@ void Model::updateClusterMatrices() { } // post the blender if we're not currently waiting for one to finish - if (geometry.hasBlendedMeshes() && _blendshapeCoefficients != _blendedBlendshapeCoefficients) { + auto modelBlender = DependencyManager::get(); + if (_blendedVertexBuffersInitialized && modelBlender->shouldComputeBlendshapes() && geometry.hasBlendedMeshes() && _blendshapeCoefficients != _blendedBlendshapeCoefficients) { _blendedBlendshapeCoefficients = _blendshapeCoefficients; - DependencyManager::get()->noteRequiresBlend(getThisPointer()); + modelBlender->noteRequiresBlend(getThisPointer()); } } @@ -1361,89 +1535,38 @@ bool Model::maybeStartBlender() { } void Model::setBlendedVertices(int blendNumber, const Geometry::WeakPointer& geometry, - const QVector& vertices, const QVector& normals, const QVector& tangents) { + const QVector& vertices, const QVector& normalsAndTangents) { auto geometryRef = geometry.lock(); - if (!geometryRef || _renderGeometry != geometryRef || _blendedVertexBuffers.empty() || blendNumber < _appliedBlendNumber) { + if (!geometryRef || _renderGeometry != geometryRef || blendNumber < _appliedBlendNumber) { return; } _appliedBlendNumber = blendNumber; const FBXGeometry& fbxGeometry = getFBXGeometry(); int index = 0; - std::vector normalsAndTangents; + int normalAndTangentIndex = 0; for (int i = 0; i < fbxGeometry.meshes.size(); i++) { const FBXMesh& mesh = fbxGeometry.meshes.at(i); if (mesh.blendshapes.isEmpty()) { continue; } - gpu::BufferPointer& buffer = _blendedVertexBuffers[i]; const auto vertexCount = mesh.vertices.size(); const auto verticesSize = vertexCount * sizeof(glm::vec3); - const auto offset = index * sizeof(glm::vec3); - - normalsAndTangents.clear(); - normalsAndTangents.resize(normals.size()+tangents.size()); -// assert(normalsAndTangents.size() == 2 * vertexCount); - - // Interleave normals and tangents -#if 0 - // Sequential version for debugging - auto normalsRange = std::make_pair(normals.begin() + index, normals.begin() + index + vertexCount); - auto tangentsRange = std::make_pair(tangents.begin() + index, tangents.begin() + index + vertexCount); - auto normalsAndTangentsIt = normalsAndTangents.begin(); - - for (auto normalIt = normalsRange.first, tangentIt = tangentsRange.first; - normalIt != normalsRange.second; - ++normalIt, ++tangentIt) { -#if FBX_PACK_NORMALS - glm::uint32 finalNormal; - glm::uint32 finalTangent; - buffer_helpers::packNormalAndTangent(*normalIt, *tangentIt, finalNormal, finalTangent); -#else - const auto finalNormal = *normalIt; - const auto finalTangent = *tangentIt; -#endif - *normalsAndTangentsIt = finalNormal; - ++normalsAndTangentsIt; - *normalsAndTangentsIt = finalTangent; - ++normalsAndTangentsIt; - } -#else - // Parallel version for performance - tbb::parallel_for(tbb::blocked_range(index, index+vertexCount), [&](const tbb::blocked_range& range) { - auto normalsRange = std::make_pair(normals.begin() + range.begin(), normals.begin() + range.end()); - auto tangentsRange = std::make_pair(tangents.begin() + range.begin(), tangents.begin() + range.end()); - auto normalsAndTangentsIt = normalsAndTangents.begin() + (range.begin()-index)*2; - - for (auto normalIt = normalsRange.first, tangentIt = tangentsRange.first; - normalIt != normalsRange.second; - ++normalIt, ++tangentIt) { -#if FBX_PACK_NORMALS - glm::uint32 finalNormal; - glm::uint32 finalTangent; - buffer_helpers::packNormalAndTangent(*normalIt, *tangentIt, finalNormal, finalTangent); -#else - const auto finalNormal = *normalIt; - const auto finalTangent = *tangentIt; -#endif - *normalsAndTangentsIt = finalNormal; - ++normalsAndTangentsIt; - *normalsAndTangentsIt = finalTangent; - ++normalsAndTangentsIt; - } - }); -#endif - - buffer->setSubData(0, verticesSize, (gpu::Byte*) vertices.constData() + offset); - buffer->setSubData(verticesSize, 2 * vertexCount * sizeof(NormalType), (const gpu::Byte*) normalsAndTangents.data()); + const auto& buffer = _blendedVertexBuffers[i]; + assert(buffer && _blendedVertexBuffersInitialized); + buffer->resize(mesh.vertices.size() * sizeof(glm::vec3) + mesh.normalsAndTangents.size() * sizeof(NormalType)); + buffer->setSubData(0, verticesSize, (gpu::Byte*) vertices.constData() + index * sizeof(glm::vec3)); + buffer->setSubData(verticesSize, mesh.normalsAndTangents.size() * sizeof(NormalType), (const gpu::Byte*) normalsAndTangents.data() + normalAndTangentIndex * sizeof(NormalType)); index += vertexCount; + normalAndTangentIndex += mesh.normalsAndTangents.size(); } } void Model::deleteGeometry() { _deleteGeometryCounter++; _blendedVertexBuffers.clear(); + _blendedVertexBuffersInitialized = false; _meshStates.clear(); _rig.destroyAnimGraph(); _blendedBlendshapeCoefficients.clear(); @@ -1475,6 +1598,43 @@ const render::ItemIDs& Model::fetchRenderItemIDs() const { return _modelMeshRenderItemIDs; } +void Model::initializeBlendshapes(const FBXMesh& mesh, int index) { + QVector normalsAndTangents; + normalsAndTangents.resize(2 * mesh.normals.size()); + + // Interleave normals and tangents + // Parallel version for performance + tbb::parallel_for(tbb::blocked_range(0, mesh.normals.size()), [&](const tbb::blocked_range& range) { + auto normalsRange = std::make_pair(mesh.normals.begin() + range.begin(), mesh.normals.begin() + range.end()); + auto tangentsRange = std::make_pair(mesh.tangents.begin() + range.begin(), mesh.tangents.begin() + range.end()); + auto normalsAndTangentsIt = normalsAndTangents.begin() + 2 * range.begin(); + + for (auto normalIt = normalsRange.first, tangentIt = tangentsRange.first; + normalIt != normalsRange.second; + ++normalIt, ++tangentIt) { +#if FBX_PACK_NORMALS + glm::uint32 finalNormal; + glm::uint32 finalTangent; + buffer_helpers::packNormalAndTangent(*normalIt, *tangentIt, finalNormal, finalTangent); +#else + const auto& finalNormal = *normalIt; + const auto& finalTangent = *tangentIt; +#endif + *normalsAndTangentsIt = finalNormal; + ++normalsAndTangentsIt; + *normalsAndTangentsIt = finalTangent; + ++normalsAndTangentsIt; + } + }); + const auto verticesSize = mesh.vertices.size() * sizeof(glm::vec3); + _blendedVertexBuffers[index] = std::make_shared(); + _blendedVertexBuffers[index]->resize(mesh.vertices.size() * sizeof(glm::vec3) + normalsAndTangents.size() * sizeof(NormalType)); + _blendedVertexBuffers[index]->setSubData(0, verticesSize, (const gpu::Byte*) mesh.vertices.constData()); + _blendedVertexBuffers[index]->setSubData(verticesSize, normalsAndTangents.size() * sizeof(NormalType), (const gpu::Byte*) normalsAndTangents.data()); + mesh.normalsAndTangents = normalsAndTangents; + _blendedVertexBuffersInitialized = true; +} + void Model::createRenderItemSet() { assert(isLoaded()); const auto& meshes = _renderGeometry->getMeshes(); @@ -1503,6 +1663,7 @@ void Model::createRenderItemSet() { // Run through all of the meshes, and place them into their segregated, but unsorted buckets int shapeID = 0; uint32_t numMeshes = (uint32_t)meshes.size(); + auto& fbxGeometry = getFBXGeometry(); for (uint32_t i = 0; i < numMeshes; i++) { const auto& mesh = meshes.at(i); if (!mesh) { @@ -1512,6 +1673,9 @@ void Model::createRenderItemSet() { // Create the render payloads int numParts = (int)mesh->getNumParts(); for (int partIndex = 0; partIndex < numParts; partIndex++) { + if (!fbxGeometry.meshes[i].blendshapes.empty()) { + initializeBlendshapes(fbxGeometry.meshes[i], i); + } _modelMeshRenderItems << std::make_shared(shared_from_this(), i, partIndex, shapeID, transform, offset); auto material = getGeometry()->getShapeMaterial(shapeID); _modelMeshMaterialNames.push_back(material ? material->getName() : ""); @@ -1624,11 +1788,10 @@ void ModelBlender::noteRequiresBlend(ModelPointer model) { } } -void ModelBlender::setBlendedVertices(ModelPointer model, int blendNumber, const Geometry::WeakPointer& geometry, - const QVector& vertices, const QVector& normals, - const QVector& tangents) { +void ModelBlender::setBlendedVertices(ModelPointer model, int blendNumber, const Geometry::WeakPointer& geometry, + const QVector& vertices, const QVector& normalsAndTangents) { if (model) { - model->setBlendedVertices(blendNumber, geometry, vertices, normals, tangents); + model->setBlendedVertices(blendNumber, geometry, vertices, normalsAndTangents); } _pendingBlenders--; { @@ -1644,4 +1807,3 @@ void ModelBlender::setBlendedVertices(ModelPointer model, int blendNumber, const } } } - diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index 0bddae6a38..e7534f5b89 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -64,6 +64,16 @@ class Model; using ModelPointer = std::shared_ptr; using ModelWeakPointer = std::weak_ptr; +struct SortedTriangleSet { + SortedTriangleSet(float distance, TriangleSet* triangleSet, int partIndex, int shapeID, int subMeshIndex) : + distance(distance), triangleSet(triangleSet), partIndex(partIndex), shapeID(shapeID), subMeshIndex(subMeshIndex) {} + + float distance; + TriangleSet* triangleSet; + int partIndex; + int shapeID; + int subMeshIndex; +}; /// A generic 3D model displaying geometry loaded from a URL. class Model : public QObject, public std::enable_shared_from_this, public scriptable::ModelProvider { @@ -135,7 +145,7 @@ public: /// Sets blended vertices computed in a separate thread. void setBlendedVertices(int blendNumber, const Geometry::WeakPointer& geometry, - const QVector& vertices, const QVector& normals, const QVector& tangents); + const QVector& vertices, const QVector& normalsAndTangents); bool isLoaded() const { return (bool)_renderGeometry && _renderGeometry->isGeometryLoaded(); } bool isAddedToScene() const { return _addedToScene; } @@ -178,6 +188,9 @@ public: bool findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool pickAgainstTriangles = false, bool allowBackface = false); + bool findParabolaIntersectionAgainstSubMeshes(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, + QVariantMap& extraInfo, bool pickAgainstTriangles = false, bool allowBackface = false); void setOffset(const glm::vec3& offset); const glm::vec3& getOffset() const { return _offset; } @@ -332,6 +345,8 @@ public: void addMaterial(graphics::MaterialLayer material, const std::string& parentMaterialName); void removeMaterial(graphics::MaterialPointer material, const std::string& parentMaterialName); + bool areBlendedVertexBuffersInitialized(int index) { return _blendedVertexBuffersInitialized; } + public slots: void loadURLFinished(bool success); @@ -410,9 +425,10 @@ protected: QUrl _url; - gpu::Buffers _blendedVertexBuffers; + std::unordered_map _blendedVertexBuffers; + bool _blendedVertexBuffersInitialized { false }; - QVector > > _dilatedTextures; + QVector>> _dilatedTextures; QVector _blendedBlendshapeCoefficients; int _blendNumber; @@ -479,6 +495,8 @@ protected: bool shouldInvalidatePayloadShapeKey(int meshIndex); + void initializeBlendshapes(const FBXMesh& mesh, int index); + private: float _loadingPriority { 0.0f }; @@ -500,9 +518,12 @@ public: /// Adds the specified model to the list requiring vertex blends. void noteRequiresBlend(ModelPointer model); + bool shouldComputeBlendshapes() { return _computeBlendshapes; } + public slots: void setBlendedVertices(ModelPointer model, int blendNumber, const Geometry::WeakPointer& geometry, - const QVector& vertices, const QVector& normals, const QVector& tangents); + const QVector& vertices, const QVector& normalsAndTangents); + void setComputeBlendshapes(bool computeBlendshapes) { _computeBlendshapes = computeBlendshapes; } private: using Mutex = std::mutex; @@ -514,6 +535,8 @@ private: std::set> _modelsRequiringBlends; int _pendingBlenders; Mutex _mutex; + + bool _computeBlendshapes { true }; }; diff --git a/libraries/render-utils/src/PickItemsJob.cpp b/libraries/render-utils/src/PickItemsJob.cpp index 860262a969..f41222d358 100644 --- a/libraries/render-utils/src/PickItemsJob.cpp +++ b/libraries/render-utils/src/PickItemsJob.cpp @@ -33,6 +33,7 @@ void PickItemsJob::run(const render::RenderContextPointer& renderContext, const render::ItemBound PickItemsJob::findNearestItem(const render::RenderContextPointer& renderContext, const render::ItemBounds& inputs, float& minIsectDistance) const { const glm::vec3 rayOrigin = renderContext->args->getViewFrustum().getPosition(); const glm::vec3 rayDirection = renderContext->args->getViewFrustum().getDirection(); + const glm::vec3 rayInvDirection = 1.0f / rayDirection; BoxFace face; glm::vec3 normal; float isectDistance; @@ -42,7 +43,7 @@ render::ItemBound PickItemsJob::findNearestItem(const render::RenderContextPoint render::ItemKey itemKey; for (const auto& itemBound : inputs) { - if (!itemBound.bound.contains(rayOrigin) && itemBound.bound.findRayIntersection(rayOrigin, rayDirection, isectDistance, face, normal)) { + if (!itemBound.bound.contains(rayOrigin) && itemBound.bound.findRayIntersection(rayOrigin, rayDirection, rayInvDirection, isectDistance, face, normal)) { auto& item = renderContext->_scene->getItem(itemBound.id); itemKey = item.getKey(); if (itemKey.isWorldSpace() && isectDistance>minDistance && isectDistance < minIsectDistance && isectDistance +#include +#include "render-utils/ShaderConstants.h" #include "DeferredLightingEffect.h" #include "RenderUtilsLogging.h" +namespace ru { + using render_utils::slot::texture::Texture; + using render_utils::slot::buffer::Buffer; +} + +namespace gr { + using graphics::slot::texture::Texture; + using graphics::slot::buffer::Buffer; +} + + using namespace render; extern void initForwardPipelines(ShapePlumber& plumber); @@ -46,7 +59,7 @@ void DrawOverlay3D::run(const RenderContextPointer& renderContext, const Inputs& const auto& inItems = inputs.get0(); const auto& lightingModel = inputs.get1(); - const auto jitter = inputs.get2(); + const auto jitter = inputs.get2(); config->setNumDrawn((int)inItems.size()); emit config->numDrawnChanged(); @@ -76,11 +89,11 @@ void DrawOverlay3D::run(const RenderContextPointer& renderContext, const Inputs& args->getViewFrustum().evalViewTransform(viewMat); batch.setProjectionTransform(projMat); - batch.setProjectionJitter(jitter.x, jitter.y); - batch.setViewTransform(viewMat); + batch.setProjectionJitter(jitter.x, jitter.y); + batch.setViewTransform(viewMat); // Setup lighting model for all items; - batch.setUniformBuffer(render::ShapePipeline::Slot::LIGHTING_MODEL, lightingModel->getParametersBuffer()); + batch.setUniformBuffer(ru::Buffer::LightModel, lightingModel->getParametersBuffer()); renderShapes(renderContext, _shapePlumber, inItems, _maxDrawn); args->_batch = nullptr; diff --git a/libraries/render-utils/src/RenderDeferredTask.cpp b/libraries/render-utils/src/RenderDeferredTask.cpp index 0b05977265..57f5c3ec34 100644 --- a/libraries/render-utils/src/RenderDeferredTask.cpp +++ b/libraries/render-utils/src/RenderDeferredTask.cpp @@ -12,12 +12,15 @@ #include "RenderDeferredTask.h" +#include + #include #include #include #include #include +#include #include #include @@ -29,6 +32,7 @@ #include #include "RenderHifi.h" +#include "render-utils/ShaderConstants.h" #include "RenderCommonTask.h" #include "LightingModel.h" #include "StencilMaskPass.h" @@ -41,6 +45,7 @@ #include "TextureCache.h" #include "ZoneRenderer.h" #include "FadeEffect.h" +#include "BloomStage.h" #include "RenderUtilsLogging.h" #include "AmbientOcclusionEffect.h" @@ -56,6 +61,17 @@ using namespace render; extern void initDeferredPipelines(render::ShapePlumber& plumber, const render::ShapePipeline::BatchSetter& batchSetter, const render::ShapePipeline::ItemSetter& itemSetter); +namespace ru { + using render_utils::slot::texture::Texture; + using render_utils::slot::buffer::Buffer; +} + +namespace gr { + using graphics::slot::texture::Texture; + using graphics::slot::buffer::Buffer; +} + + RenderDeferredTask::RenderDeferredTask() { } @@ -83,7 +99,9 @@ const render::Varying RenderDeferredTask::addSelectItemJobs(JobModel& task, cons } void RenderDeferredTask::build(JobModel& task, const render::Varying& input, render::Varying& output, bool renderShadows) { - const auto& items = input.get(); + const auto& inputs = input.get(); + const auto& items = inputs.get0(); + auto fadeEffect = DependencyManager::get(); // Prepare the ShapePipelines @@ -156,7 +174,7 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren const auto velocityBufferOutputs = task.addJob("VelocityBuffer", velocityBufferInputs); const auto velocityBuffer = velocityBufferOutputs.getN(0); - // Clear Light, Haze and Skybox Stages and render zones from the general metas bucket + // Clear Light, Haze, Bloom, and Skybox Stages and render zones from the general metas bucket const auto zones = task.addJob("ZoneRenderer", metas); // Draw Lights just add the lights to the current list of lights to deal with. NOt really gpu job for now. @@ -211,6 +229,9 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren const auto overlaysInFrontOpaque = filteredOverlaysOpaque.getN(0); const auto overlaysInFrontTransparent = filteredOverlaysTransparent.getN(0); + // We don't want the overlay to clear the deferred frame buffer depth because we would like to keep it for debugging visualisation + // task.addJob("SeparateDepthForOverlay", deferredFramebuffer); + const auto overlayInFrontOpaquesInputs = DrawOverlay3D::Inputs(overlaysInFrontOpaque, lightingModel, jitter).asVarying(); const auto overlayInFrontTransparentsInputs = DrawOverlay3D::Inputs(overlaysInFrontTransparent, lightingModel, jitter).asVarying(); task.addJob("DrawOverlayInFrontOpaque", overlayInFrontOpaquesInputs, true); @@ -225,8 +246,9 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren task.addJob("Antialiasing", antialiasingInputs); // Add bloom - const auto bloomInputs = Bloom::Inputs(deferredFrameTransform, lightingFramebuffer).asVarying(); - task.addJob("Bloom", bloomInputs); + const auto bloomModel = task.addJob("BloomModel"); + const auto bloomInputs = BloomEffect::Inputs(deferredFrameTransform, lightingFramebuffer, bloomModel).asVarying(); + task.addJob("Bloom", bloomInputs); // Lighting Buffer ready for tone mapping const auto toneMappingInputs = ToneMappingDeferred::Inputs(lightingFramebuffer, scaledPrimaryFramebuffer).asVarying(); @@ -241,13 +263,19 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren task.addJob("DrawZones", zones); const auto frustums = task.addJob("ExtractFrustums"); const auto viewFrustum = frustums.getN(ExtractFrustums::VIEW_FRUSTUM); - task.addJob("DrawViewFrustum", viewFrustum, glm::vec3(1.0f, 1.0f, 0.0f)); + task.addJob("DrawViewFrustum", viewFrustum, glm::vec3(0.0f, 1.0f, 0.0f)); for (auto i = 0; i < ExtractFrustums::SHADOW_CASCADE_FRUSTUM_COUNT; i++) { - const auto shadowFrustum = frustums.getN(ExtractFrustums::SHADOW_CASCADE0_FRUSTUM+i); + const auto shadowFrustum = frustums.getN(ExtractFrustums::SHADOW_CASCADE0_FRUSTUM + i); float tint = 1.0f - i / float(ExtractFrustums::SHADOW_CASCADE_FRUSTUM_COUNT - 1); char jobName[64]; sprintf(jobName, "DrawShadowFrustum%d", i); task.addJob(jobName, shadowFrustum, glm::vec3(0.0f, tint, 1.0f)); + if (!inputs[1].isNull()) { + const auto& shadowCascadeSceneBBoxes = inputs.get1(); + const auto shadowBBox = shadowCascadeSceneBBoxes[ExtractFrustums::SHADOW_CASCADE0_FRUSTUM + i]; + sprintf(jobName, "DrawShadowBBox%d", i); + task.addJob(jobName, shadowBBox, glm::vec3(1.0f, tint, 0.0f)); + } } // Render.getConfig("RenderMainView.DrawSelectionBounds").enabled = true @@ -347,27 +375,18 @@ void DrawDeferred::run(const RenderContextPointer& renderContext, const Inputs& batch.setViewTransform(viewMat); // Setup lighting model for all items; - batch.setUniformBuffer(render::ShapePipeline::Slot::LIGHTING_MODEL, lightingModel->getParametersBuffer()); + batch.setUniformBuffer(ru::Buffer::LightModel, lightingModel->getParametersBuffer()); // Set the light - deferredLightingEffect->setupKeyLightBatch(args, batch, - render::ShapePipeline::Slot::KEY_LIGHT, - render::ShapePipeline::Slot::LIGHT_AMBIENT_BUFFER, - render::ShapePipeline::Slot::LIGHT_AMBIENT_MAP); - - deferredLightingEffect->setupLocalLightsBatch(batch, - render::ShapePipeline::Slot::LIGHT_ARRAY_BUFFER, - render::ShapePipeline::Slot::LIGHT_CLUSTER_GRID_CLUSTER_GRID_SLOT, - render::ShapePipeline::Slot::LIGHT_CLUSTER_GRID_CLUSTER_CONTENT_SLOT, - render::ShapePipeline::Slot::LIGHT_CLUSTER_GRID_FRUSTUM_GRID_SLOT, - lightClusters); + deferredLightingEffect->setupKeyLightBatch(args, batch); + deferredLightingEffect->setupLocalLightsBatch(batch, lightClusters); // Setup haze if current zone has haze auto hazeStage = args->_scene->getStage(); if (hazeStage && hazeStage->_currentFrame._hazes.size() > 0) { graphics::HazePointer hazePointer = hazeStage->getHaze(hazeStage->_currentFrame._hazes.front()); if (hazePointer) { - batch.setUniformBuffer(render::ShapePipeline::Slot::HAZE_MODEL, hazePointer->getHazeParametersBuffer()); + batch.setUniformBuffer(ru::Buffer::HazeParams, hazePointer->getHazeParametersBuffer()); } } @@ -385,16 +404,8 @@ void DrawDeferred::run(const RenderContextPointer& renderContext, const Inputs& args->_batch = nullptr; args->_globalShapeKey = 0; - deferredLightingEffect->unsetLocalLightsBatch(batch, - render::ShapePipeline::Slot::LIGHT_ARRAY_BUFFER, - render::ShapePipeline::Slot::LIGHT_CLUSTER_GRID_CLUSTER_GRID_SLOT, - render::ShapePipeline::Slot::LIGHT_CLUSTER_GRID_CLUSTER_CONTENT_SLOT, - render::ShapePipeline::Slot::LIGHT_CLUSTER_GRID_FRUSTUM_GRID_SLOT); - - deferredLightingEffect->unsetKeyLightBatch(batch, - render::ShapePipeline::Slot::KEY_LIGHT, - render::ShapePipeline::Slot::LIGHT_AMBIENT_BUFFER, - render::ShapePipeline::Slot::LIGHT_AMBIENT_MAP); + deferredLightingEffect->unsetLocalLightsBatch(batch); + deferredLightingEffect->unsetKeyLightBatch(batch); }); config->setNumDrawn((int)inItems.size()); @@ -429,7 +440,7 @@ void DrawStateSortDeferred::run(const RenderContextPointer& renderContext, const batch.setViewTransform(viewMat); // Setup lighting model for all items; - batch.setUniformBuffer(render::ShapePipeline::Slot::LIGHTING_MODEL, lightingModel->getParametersBuffer()); + batch.setUniformBuffer(ru::Buffer::LightModel, lightingModel->getParametersBuffer()); // From the lighting model define a global shapeKey ORED with individiual keys ShapeKey::Builder keyBuilder; @@ -451,3 +462,26 @@ void DrawStateSortDeferred::run(const RenderContextPointer& renderContext, const config->setNumDrawn((int)inItems.size()); } + +void SetSeparateDeferredDepthBuffer::run(const render::RenderContextPointer& renderContext, const Inputs& inputs) { + assert(renderContext->args); + + const auto deferredFramebuffer = inputs->getDeferredFramebuffer(); + const auto frameSize = deferredFramebuffer->getSize(); + const auto renderbufferCount = deferredFramebuffer->getNumRenderBuffers(); + + if (!_framebuffer || _framebuffer->getSize() != frameSize || _framebuffer->getNumRenderBuffers() != renderbufferCount) { + auto depthFormat = deferredFramebuffer->getDepthStencilBufferFormat(); + auto depthStencilTexture = gpu::TexturePointer(gpu::Texture::createRenderBuffer(depthFormat, frameSize.x, frameSize.y)); + _framebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("deferredFramebufferSeparateDepth")); + _framebuffer->setDepthStencilBuffer(depthStencilTexture, depthFormat); + for (decltype(deferredFramebuffer->getNumRenderBuffers()) i = 0; i < renderbufferCount; i++) { + _framebuffer->setRenderBuffer(i, deferredFramebuffer->getRenderBuffer(i)); + } + } + + RenderArgs* args = renderContext->args; + gpu::doInBatch("SetSeparateDeferredDepthBuffer::run", args->_context, [this](gpu::Batch& batch) { + batch.setFramebuffer(_framebuffer); + }); +} diff --git a/libraries/render-utils/src/RenderDeferredTask.h b/libraries/render-utils/src/RenderDeferredTask.h index 1ce1682cf1..161a14c943 100644 --- a/libraries/render-utils/src/RenderDeferredTask.h +++ b/libraries/render-utils/src/RenderDeferredTask.h @@ -16,6 +16,7 @@ #include #include "LightingModel.h" #include "LightClusters.h" +#include "RenderShadowTask.h" class DrawDeferredConfig : public render::Job::Config { Q_OBJECT @@ -87,7 +88,8 @@ public: using JobModel = render::Job::ModelI; DrawStateSortDeferred(render::ShapePlumberPointer shapePlumber) - : _shapePlumber{ shapePlumber } {} + : _shapePlumber{ shapePlumber } { + } void configure(const Config& config) { _maxDrawn = config.maxDrawn; @@ -101,6 +103,19 @@ protected: bool _stateSort; }; +class SetSeparateDeferredDepthBuffer { +public: + using Inputs = DeferredFramebufferPointer; + using JobModel = render::Job::ModelI; + + SetSeparateDeferredDepthBuffer() = default; + + void run(const render::RenderContextPointer& renderContext, const Inputs& inputs); + +protected: + gpu::FramebufferPointer _framebuffer; +}; + class RenderDeferredTaskConfig : public render::Task::Config { Q_OBJECT Q_PROPERTY(float fadeScale MEMBER fadeScale NOTIFY dirty) @@ -121,7 +136,7 @@ signals: class RenderDeferredTask { public: - using Input = RenderFetchCullSortTask::Output; + using Input = render::VaryingSet2; using Config = RenderDeferredTaskConfig; using JobModel = render::Task::ModelI; diff --git a/libraries/render-utils/src/RenderForwardTask.cpp b/libraries/render-utils/src/RenderForwardTask.cpp index d2933627f4..9ab60786b5 100755 --- a/libraries/render-utils/src/RenderForwardTask.cpp +++ b/libraries/render-utils/src/RenderForwardTask.cpp @@ -17,13 +17,13 @@ #include #include #include -#include - +#include #include #include #include "RenderHifi.h" +#include "render-utils/ShaderConstants.h" #include "StencilMaskPass.h" #include "ZoneRenderer.h" #include "FadeEffect.h" @@ -34,9 +34,18 @@ #include "RenderCommonTask.h" #include "LightStage.h" -#include "nop_frag.h" +namespace ru { + using render_utils::slot::texture::Texture; + using render_utils::slot::buffer::Buffer; +} + +namespace gr { + using graphics::slot::texture::Texture; + using graphics::slot::buffer::Buffer; +} using namespace render; + extern void initForwardPipelines(ShapePlumber& plumber); void RenderForwardTask::build(JobModel& task, const render::Varying& input, render::Varying& output) { @@ -80,9 +89,10 @@ void RenderForwardTask::build(JobModel& task, const render::Varying& input, rend const auto filteredOverlaysTransparent = task.addJob("FilterOverlaysLayeredTransparent", overlayTransparents, render::hifi::LAYER_3D_FRONT); const auto overlaysInFrontOpaque = filteredOverlaysOpaque.getN(0); const auto overlaysInFrontTransparent = filteredOverlaysTransparent.getN(0); + const auto nullJitter = Varying(glm::vec2(0.0f, 0.0f)); - const auto overlayInFrontOpaquesInputs = DrawOverlay3D::Inputs(overlaysInFrontOpaque, lightingModel, nullptr).asVarying(); - const auto overlayInFrontTransparentsInputs = DrawOverlay3D::Inputs(overlaysInFrontTransparent, lightingModel, nullptr).asVarying(); + const auto overlayInFrontOpaquesInputs = DrawOverlay3D::Inputs(overlaysInFrontOpaque, lightingModel, nullJitter).asVarying(); + const auto overlayInFrontTransparentsInputs = DrawOverlay3D::Inputs(overlaysInFrontTransparent, lightingModel, nullJitter).asVarying(); task.addJob("DrawOverlayInFrontOpaque", overlayInFrontOpaquesInputs, true); task.addJob("DrawOverlayInFrontTransparent", overlayInFrontTransparentsInputs, false); @@ -179,14 +189,14 @@ void PrepareForward::run(const RenderContextPointer& renderContext, const Inputs } if (keySunLight) { - batch.setUniformBuffer(render::ShapePipeline::Slot::KEY_LIGHT, keySunLight->getLightSchemaBuffer()); + batch.setUniformBuffer(gr::Buffer::KeyLight, keySunLight->getLightSchemaBuffer()); } if (keyAmbiLight) { - batch.setUniformBuffer(render::ShapePipeline::Slot::LIGHT_AMBIENT_BUFFER, keyAmbiLight->getAmbientSchemaBuffer()); + batch.setUniformBuffer(gr::Buffer::AmbientLight, keyAmbiLight->getAmbientSchemaBuffer()); if (keyAmbiLight->getAmbientMap()) { - batch.setResourceTexture(render::ShapePipeline::Slot::LIGHT_AMBIENT_MAP, keyAmbiLight->getAmbientMap()); + batch.setResourceTexture(ru::Texture::Skybox, keyAmbiLight->getAmbientMap()); } } }); @@ -212,7 +222,7 @@ void DrawForward::run(const RenderContextPointer& renderContext, const Inputs& i batch.setModelTransform(Transform()); // Setup lighting model for all items; - batch.setUniformBuffer(render::ShapePipeline::Slot::LIGHTING_MODEL, lightingModel->getParametersBuffer()); + batch.setUniformBuffer(ru::Buffer::LightModel, lightingModel->getParametersBuffer()); // From the lighting model define a global shapeKey ORED with individiual keys ShapeKey::Builder keyBuilder; diff --git a/libraries/render-utils/src/RenderPipelines.cpp b/libraries/render-utils/src/RenderPipelines.cpp index 6a653bb192..704b1d7663 100644 --- a/libraries/render-utils/src/RenderPipelines.cpp +++ b/libraries/render-utils/src/RenderPipelines.cpp @@ -15,99 +15,35 @@ #include #include -#include #include +#include +#include +#include +#include "render-utils/ShaderConstants.h" #include "StencilMaskPass.h" #include "DeferredLightingEffect.h" #include "TextureCache.h" -#include "render/DrawTask.h" - -#include "model_vert.h" -#include "model_normal_map_vert.h" -#include "model_lightmap_vert.h" -#include "model_lightmap_normal_map_vert.h" -#include "skin_model_vert.h" -#include "skin_model_normal_map_vert.h" -#include "skin_model_dq_vert.h" -#include "skin_model_normal_map_dq_vert.h" - -#include "model_lightmap_fade_vert.h" -#include "model_lightmap_normal_map_fade_vert.h" -#include "model_translucent_vert.h" -#include "model_translucent_normal_map_vert.h" -#include "skin_model_fade_vert.h" -#include "skin_model_normal_map_fade_vert.h" -#include "skin_model_fade_dq_vert.h" -#include "skin_model_normal_map_fade_dq_vert.h" - -#include "simple_vert.h" -#include "simple_textured_frag.h" -#include "simple_textured_unlit_frag.h" -#include "simple_transparent_textured_frag.h" -#include "simple_transparent_textured_unlit_frag.h" - -#include "simple_fade_vert.h" -#include "simple_textured_fade_frag.h" -#include "simple_textured_unlit_fade_frag.h" -#include "simple_transparent_textured_fade_frag.h" -#include "simple_transparent_textured_unlit_fade_frag.h" - -#include "model_frag.h" -#include "model_unlit_frag.h" -#include "model_normal_map_frag.h" -#include "model_fade_vert.h" -#include "model_normal_map_fade_vert.h" - -#include "model_fade_frag.h" -#include "model_unlit_fade_frag.h" -#include "model_normal_map_fade_frag.h" - -#include "forward_model_frag.h" -#include "forward_model_unlit_frag.h" -#include "forward_model_normal_map_frag.h" -#include "forward_model_translucent_frag.h" - -#include "model_lightmap_frag.h" -#include "model_lightmap_normal_map_frag.h" -#include "model_translucent_frag.h" -#include "model_translucent_unlit_frag.h" -#include "model_translucent_normal_map_frag.h" - -#include "model_lightmap_fade_frag.h" -#include "model_lightmap_normal_map_fade_frag.h" -#include "model_translucent_fade_frag.h" -#include "model_translucent_unlit_fade_frag.h" -#include "model_translucent_normal_map_fade_frag.h" - -#include "model_shadow_vert.h" -#include "skin_model_shadow_vert.h" -#include "skin_model_shadow_dq_vert.h" -#include "skin_model_shadow_fade_dq_vert.h" - -#include "model_shadow_frag.h" -#include "skin_model_shadow_frag.h" - -#include "model_shadow_fade_vert.h" -#include "skin_model_shadow_fade_vert.h" - -#include "model_shadow_fade_frag.h" -#include "skin_model_shadow_fade_frag.h" - -#include "simple_vert.h" -#include "forward_simple_textured_frag.h" -#include "forward_simple_textured_transparent_frag.h" -#include "forward_simple_textured_unlit_frag.h" using namespace render; using namespace std::placeholders; +namespace ru { + using render_utils::slot::texture::Texture; + using render_utils::slot::buffer::Buffer; +} + +namespace gr { + using graphics::slot::texture::Texture; + using graphics::slot::buffer::Buffer; +} + void initDeferredPipelines(ShapePlumber& plumber, const render::ShapePipeline::BatchSetter& batchSetter, const render::ShapePipeline::ItemSetter& itemSetter); void initForwardPipelines(ShapePlumber& plumber); void initZPassPipelines(ShapePlumber& plumber, gpu::StatePointer state); void addPlumberPipeline(ShapePlumber& plumber, - const ShapeKey& key, const gpu::ShaderPointer& vertex, const gpu::ShaderPointer& pixel, + const ShapeKey& key, int programId, const render::ShapePipeline::BatchSetter& batchSetter, const render::ShapePipeline::ItemSetter& itemSetter); void batchSetter(const ShapePipeline& pipeline, gpu::Batch& batch, RenderArgs* args); @@ -115,326 +51,239 @@ void lightBatchSetter(const ShapePipeline& pipeline, gpu::Batch& batch, RenderAr static bool forceLightBatchSetter{ false }; void initDeferredPipelines(render::ShapePlumber& plumber, const render::ShapePipeline::BatchSetter& batchSetter, const render::ShapePipeline::ItemSetter& itemSetter) { - // Vertex shaders - auto simpleVertex = simple_vert::getShader(); - auto modelVertex = model_vert::getShader(); - auto modelNormalMapVertex = model_normal_map_vert::getShader(); - auto modelLightmapVertex = model_lightmap_vert::getShader(); - auto modelLightmapNormalMapVertex = model_lightmap_normal_map_vert::getShader(); - auto modelTranslucentVertex = model_translucent_vert::getShader(); - auto modelTranslucentNormalMapVertex = model_translucent_normal_map_vert::getShader(); - auto modelShadowVertex = model_shadow_vert::getShader(); - - auto modelLightmapFadeVertex = model_lightmap_fade_vert::getShader(); - auto modelLightmapNormalMapFadeVertex = model_lightmap_normal_map_fade_vert::getShader(); - - // matrix palette skinned - auto skinModelVertex = skin_model_vert::getShader(); - auto skinModelNormalMapVertex = skin_model_normal_map_vert::getShader(); - auto skinModelShadowVertex = skin_model_shadow_vert::getShader(); - auto skinModelFadeVertex = skin_model_fade_vert::getShader(); - auto skinModelNormalMapFadeVertex = skin_model_normal_map_fade_vert::getShader(); - auto skinModelTranslucentVertex = skinModelFadeVertex; // We use the same because it ouputs world position per vertex - auto skinModelNormalMapTranslucentVertex = skinModelNormalMapFadeVertex; // We use the same because it ouputs world position per vertex - - // dual quaternion skinned - auto skinModelDualQuatVertex = skin_model_dq_vert::getShader(); - auto skinModelNormalMapDualQuatVertex = skin_model_normal_map_dq_vert::getShader(); - auto skinModelShadowDualQuatVertex = skin_model_shadow_dq_vert::getShader(); - auto skinModelShadowFadeDualQuatVertex = skin_model_shadow_fade_dq_vert::getShader(); - auto skinModelFadeDualQuatVertex = skin_model_fade_dq_vert::getShader(); - auto skinModelNormalMapFadeDualQuatVertex = skin_model_normal_map_fade_dq_vert::getShader(); - auto skinModelTranslucentDualQuatVertex = skinModelFadeDualQuatVertex; // We use the same because it ouputs world position per vertex - auto skinModelNormalMapTranslucentDualQuatVertex = skinModelNormalMapFadeDualQuatVertex; // We use the same because it ouputs world position per vertex - - auto modelFadeVertex = model_fade_vert::getShader(); - auto modelNormalMapFadeVertex = model_normal_map_fade_vert::getShader(); - auto simpleFadeVertex = simple_fade_vert::getShader(); - auto modelShadowFadeVertex = model_shadow_fade_vert::getShader(); - auto skinModelShadowFadeVertex = skin_model_shadow_fade_vert::getShader(); - - // Pixel shaders - auto simplePixel = simple_textured_frag::getShader(); - auto simpleUnlitPixel = simple_textured_unlit_frag::getShader(); - auto simpleTranslucentPixel = simple_transparent_textured_frag::getShader(); - auto simpleTranslucentUnlitPixel = simple_transparent_textured_unlit_frag::getShader(); - auto modelPixel = model_frag::getShader(); - auto modelUnlitPixel = model_unlit_frag::getShader(); - auto modelNormalMapPixel = model_normal_map_frag::getShader(); - auto modelTranslucentPixel = model_translucent_frag::getShader(); - auto modelTranslucentUnlitPixel = model_translucent_unlit_frag::getShader(); - auto modelTranslucentNormalMapPixel = model_translucent_normal_map_frag::getShader(); - auto modelShadowPixel = model_shadow_frag::getShader(); - auto modelLightmapPixel = model_lightmap_frag::getShader(); - auto modelLightmapNormalMapPixel = model_lightmap_normal_map_frag::getShader(); - auto modelLightmapFadePixel = model_lightmap_fade_frag::getShader(); - auto modelLightmapNormalMapFadePixel = model_lightmap_normal_map_fade_frag::getShader(); - - auto modelFadePixel = model_fade_frag::getShader(); - auto modelUnlitFadePixel = model_unlit_fade_frag::getShader(); - auto modelNormalMapFadePixel = model_normal_map_fade_frag::getShader(); - auto modelShadowFadePixel = model_shadow_fade_frag::getShader(); - auto modelTranslucentFadePixel = model_translucent_fade_frag::getShader(); - auto modelTranslucentUnlitFadePixel = model_translucent_unlit_fade_frag::getShader(); - auto modelTranslucentNormalMapFadePixel = model_translucent_normal_map_fade_frag::getShader(); - auto simpleFadePixel = simple_textured_fade_frag::getShader(); - auto simpleUnlitFadePixel = simple_textured_unlit_fade_frag::getShader(); - auto simpleTranslucentFadePixel = simple_transparent_textured_fade_frag::getShader(); - auto simpleTranslucentUnlitFadePixel = simple_transparent_textured_unlit_fade_frag::getShader(); - + using namespace shader::render_utils::program; using Key = render::ShapeKey; - auto addPipeline = std::bind(&addPlumberPipeline, std::ref(plumber), _1, _2, _3, _4, _5); + auto addPipeline = std::bind(&addPlumberPipeline, std::ref(plumber), _1, _2, _3, _4); // TODO: Refactor this to use a filter // Opaques addPipeline( Key::Builder().withMaterial(), - modelVertex, modelPixel, nullptr, nullptr); + model, nullptr, nullptr); addPipeline( Key::Builder(), - simpleVertex, simplePixel, nullptr, nullptr); + simple_textured, nullptr, nullptr); addPipeline( Key::Builder().withMaterial().withUnlit(), - modelVertex, modelUnlitPixel, nullptr, nullptr); + model_unlit, nullptr, nullptr); addPipeline( Key::Builder().withUnlit(), - simpleVertex, simpleUnlitPixel, nullptr, nullptr); + simple_textured_unlit, nullptr, nullptr); addPipeline( Key::Builder().withMaterial().withTangents(), - modelNormalMapVertex, modelNormalMapPixel, nullptr, nullptr); + model_normal_map, nullptr, nullptr); // Same thing but with Fade on addPipeline( Key::Builder().withMaterial().withFade(), - modelFadeVertex, modelFadePixel, batchSetter, itemSetter); + model_fade, batchSetter, itemSetter); addPipeline( Key::Builder().withFade(), - simpleFadeVertex, simpleFadePixel, batchSetter, itemSetter); + simple_textured_fade, batchSetter, itemSetter); addPipeline( Key::Builder().withMaterial().withUnlit().withFade(), - modelFadeVertex, modelUnlitFadePixel, batchSetter, itemSetter); + model_unlit_fade, batchSetter, itemSetter); addPipeline( Key::Builder().withUnlit().withFade(), - simpleFadeVertex, simpleUnlitFadePixel, batchSetter, itemSetter); + simple_textured_unlit_fade, batchSetter, itemSetter); addPipeline( Key::Builder().withMaterial().withTangents().withFade(), - modelNormalMapFadeVertex, modelNormalMapFadePixel, batchSetter, itemSetter); + model_normal_map_fade, batchSetter, itemSetter); // Translucents addPipeline( Key::Builder().withMaterial().withTranslucent(), - modelTranslucentVertex, modelTranslucentPixel, nullptr, nullptr); + model_translucent, nullptr, nullptr); addPipeline( Key::Builder().withTranslucent(), - simpleVertex, simpleTranslucentPixel, nullptr, nullptr); + simple_transparent_textured, nullptr, nullptr); addPipeline( Key::Builder().withMaterial().withTranslucent().withUnlit(), - modelVertex, modelTranslucentUnlitPixel, nullptr, nullptr); + model_translucent_unlit, nullptr, nullptr); addPipeline( Key::Builder().withTranslucent().withUnlit(), - simpleVertex, simpleTranslucentUnlitPixel, nullptr, nullptr); + simple_transparent_textured_unlit, nullptr, nullptr); addPipeline( Key::Builder().withMaterial().withTranslucent().withTangents(), - modelTranslucentNormalMapVertex, modelTranslucentNormalMapPixel, nullptr, nullptr); + model_translucent_normal_map, nullptr, nullptr); addPipeline( // FIXME: Ignore lightmap for translucents meshpart Key::Builder().withMaterial().withTranslucent().withLightmap(), - modelTranslucentVertex, modelTranslucentPixel, nullptr, nullptr); + model_translucent, nullptr, nullptr); // Same thing but with Fade on addPipeline( Key::Builder().withMaterial().withTranslucent().withFade(), - modelTranslucentVertex, modelTranslucentFadePixel, batchSetter, itemSetter); + model_translucent_fade, batchSetter, itemSetter); addPipeline( Key::Builder().withTranslucent().withFade(), - simpleFadeVertex, simpleTranslucentFadePixel, batchSetter, itemSetter); + simple_transparent_textured_fade, batchSetter, itemSetter); addPipeline( Key::Builder().withMaterial().withTranslucent().withUnlit().withFade(), - modelFadeVertex, modelTranslucentUnlitFadePixel, batchSetter, itemSetter); + model_translucent_unlit_fade, batchSetter, itemSetter); addPipeline( Key::Builder().withTranslucent().withUnlit().withFade(), - simpleFadeVertex, simpleTranslucentUnlitFadePixel, batchSetter, itemSetter); + simple_transparent_textured_unlit_fade, batchSetter, itemSetter); addPipeline( Key::Builder().withMaterial().withTranslucent().withTangents().withFade(), - modelTranslucentNormalMapVertex, modelTranslucentNormalMapFadePixel, batchSetter, itemSetter); + model_translucent_normal_map_fade, batchSetter, itemSetter); addPipeline( // FIXME: Ignore lightmap for translucents meshpart Key::Builder().withMaterial().withTranslucent().withLightmap().withFade(), - modelFadeVertex, modelTranslucentFadePixel, batchSetter, itemSetter); - + model_translucent_fade, batchSetter, itemSetter); // Lightmapped addPipeline( Key::Builder().withMaterial().withLightmap(), - modelLightmapVertex, modelLightmapPixel, nullptr, nullptr); + model_lightmap, nullptr, nullptr); addPipeline( Key::Builder().withMaterial().withLightmap().withTangents(), - modelLightmapNormalMapVertex, modelLightmapNormalMapPixel, nullptr, nullptr); + model_lightmap_normal_map, nullptr, nullptr); // Same thing but with Fade on addPipeline( Key::Builder().withMaterial().withLightmap().withFade(), - modelLightmapFadeVertex, modelLightmapFadePixel, batchSetter, itemSetter); + model_lightmap_fade, batchSetter, itemSetter); addPipeline( Key::Builder().withMaterial().withLightmap().withTangents().withFade(), - modelLightmapNormalMapFadeVertex, modelLightmapNormalMapFadePixel, batchSetter, itemSetter); + model_lightmap_normal_map_fade, batchSetter, itemSetter); // matrix palette skinned addPipeline( Key::Builder().withMaterial().withSkinned(), - skinModelVertex, modelPixel, nullptr, nullptr); + skin_model, nullptr, nullptr); addPipeline( Key::Builder().withMaterial().withSkinned().withTangents(), - skinModelNormalMapVertex, modelNormalMapPixel, nullptr, nullptr); + skin_model_normal_map, nullptr, nullptr); // Same thing but with Fade on addPipeline( Key::Builder().withMaterial().withSkinned().withFade(), - skinModelFadeVertex, modelFadePixel, batchSetter, itemSetter); + skin_model_fade, batchSetter, itemSetter); addPipeline( Key::Builder().withMaterial().withSkinned().withTangents().withFade(), - skinModelNormalMapFadeVertex, modelNormalMapFadePixel, batchSetter, itemSetter); - + skin_model_normal_map_fade, batchSetter, itemSetter); // matrix palette skinned and translucent addPipeline( Key::Builder().withMaterial().withSkinned().withTranslucent(), - skinModelTranslucentVertex, modelTranslucentPixel, nullptr, nullptr); + skin_model_translucent, nullptr, nullptr); addPipeline( Key::Builder().withMaterial().withSkinned().withTranslucent().withTangents(), - skinModelNormalMapTranslucentVertex, modelTranslucentNormalMapPixel, nullptr, nullptr); + skin_model_normal_map_translucent, nullptr, nullptr); // Same thing but with Fade on addPipeline( Key::Builder().withMaterial().withSkinned().withTranslucent().withFade(), - skinModelFadeVertex, modelTranslucentFadePixel, batchSetter, itemSetter); + skin_model_translucent_fade, batchSetter, itemSetter); addPipeline( Key::Builder().withMaterial().withSkinned().withTranslucent().withTangents().withFade(), - skinModelNormalMapFadeVertex, modelTranslucentNormalMapFadePixel, batchSetter, itemSetter); + skin_model_normal_map_translucent_fade, batchSetter, itemSetter); // dual quaternion skinned addPipeline( Key::Builder().withMaterial().withSkinned().withDualQuatSkinned(), - skinModelDualQuatVertex, modelPixel, nullptr, nullptr); + skin_model_dq, nullptr, nullptr); addPipeline( Key::Builder().withMaterial().withSkinned().withDualQuatSkinned().withTangents(), - skinModelNormalMapDualQuatVertex, modelNormalMapPixel, nullptr, nullptr); + skin_model_normal_map_dq, nullptr, nullptr); // Same thing but with Fade on addPipeline( Key::Builder().withMaterial().withSkinned().withDualQuatSkinned().withFade(), - skinModelFadeDualQuatVertex, modelFadePixel, batchSetter, itemSetter); + skin_model_fade_dq, batchSetter, itemSetter); addPipeline( Key::Builder().withMaterial().withSkinned().withDualQuatSkinned().withTangents().withFade(), - skinModelNormalMapFadeDualQuatVertex, modelNormalMapFadePixel, batchSetter, itemSetter); - + skin_model_normal_map_fade_dq, batchSetter, itemSetter); // dual quaternion skinned and translucent addPipeline( Key::Builder().withMaterial().withSkinned().withDualQuatSkinned().withTranslucent(), - skinModelTranslucentDualQuatVertex, modelTranslucentPixel, nullptr, nullptr); + skin_model_translucent_dq, nullptr, nullptr); addPipeline( Key::Builder().withMaterial().withSkinned().withDualQuatSkinned().withTranslucent().withTangents(), - skinModelNormalMapTranslucentDualQuatVertex, modelTranslucentNormalMapPixel, nullptr, nullptr); + skin_model_normal_map_translucent_dq, nullptr, nullptr); // Same thing but with Fade on addPipeline( Key::Builder().withMaterial().withSkinned().withDualQuatSkinned().withTranslucent().withFade(), - skinModelFadeDualQuatVertex, modelTranslucentFadePixel, batchSetter, itemSetter); + skin_model_translucent_fade_dq, batchSetter, itemSetter); addPipeline( Key::Builder().withMaterial().withSkinned().withDualQuatSkinned().withTranslucent().withTangents().withFade(), - skinModelNormalMapFadeDualQuatVertex, modelTranslucentNormalMapFadePixel, batchSetter, itemSetter); + skin_model_normal_map_translucent_fade_dq, batchSetter, itemSetter); // Depth-only addPipeline( Key::Builder().withDepthOnly(), - modelShadowVertex, modelShadowPixel, nullptr, nullptr); + model_shadow, nullptr, nullptr); addPipeline( Key::Builder().withSkinned().withDepthOnly(), - skinModelShadowVertex, modelShadowPixel, nullptr, nullptr); + skin_model_shadow, nullptr, nullptr); // Same thing but with Fade on addPipeline( Key::Builder().withDepthOnly().withFade(), - modelShadowFadeVertex, modelShadowFadePixel, batchSetter, itemSetter); + model_shadow_fade, batchSetter, itemSetter); addPipeline( Key::Builder().withSkinned().withDepthOnly().withFade(), - skinModelShadowFadeVertex, modelShadowFadePixel, batchSetter, itemSetter); + skin_model_shadow_fade, batchSetter, itemSetter); // Now repeat for dual quaternion // Depth-only addPipeline( Key::Builder().withSkinned().withDualQuatSkinned().withDepthOnly(), - skinModelShadowDualQuatVertex, modelShadowPixel, nullptr, nullptr); + skin_model_shadow_dq, nullptr, nullptr); // Same thing but with Fade on addPipeline( Key::Builder().withSkinned().withDualQuatSkinned().withDepthOnly().withFade(), - skinModelShadowFadeDualQuatVertex, modelShadowFadePixel, batchSetter, itemSetter); + skin_model_shadow_fade_dq, batchSetter, itemSetter); } void initForwardPipelines(ShapePlumber& plumber) { - // Vertex shaders - auto simpleVertex = simple_vert::getShader(); - auto modelVertex = model_vert::getShader(); - auto modelNormalMapVertex = model_normal_map_vert::getShader(); - auto skinModelVertex = skin_model_vert::getShader(); - auto skinModelNormalMapVertex = skin_model_normal_map_vert::getShader(); - - auto skinModelDualQuatVertex = skin_model_dq_vert::getShader(); - auto skinModelNormalMapDualQuatVertex = skin_model_normal_map_dq_vert::getShader(); - - // Pixel shaders - auto simplePixel = forward_simple_textured_frag::getShader(); - auto simpleTranslucentPixel = forward_simple_textured_transparent_frag::getShader(); - auto simpleUnlitPixel = forward_simple_textured_unlit_frag::getShader(); - auto simpleTranslucentUnlitPixel = simple_transparent_textured_unlit_frag::getShader(); - auto modelPixel = forward_model_frag::getShader(); - auto modelUnlitPixel = forward_model_unlit_frag::getShader(); - auto modelNormalMapPixel = forward_model_normal_map_frag::getShader(); - auto modelTranslucentPixel = forward_model_translucent_frag::getShader(); + using namespace shader::render_utils::program; using Key = render::ShapeKey; - auto addPipelineBind = std::bind(&addPlumberPipeline, std::ref(plumber), _1, _2, _3, _4, _5); + auto addPipelineBind = std::bind(&addPlumberPipeline, std::ref(plumber), _1, _2, _3, _4); // Disable fade on the forward pipeline, all shaders get added twice, once with the fade key and once without - auto addPipeline = [&](const ShapeKey& key, const gpu::ShaderPointer& vertex, const gpu::ShaderPointer& pixel) { - addPipelineBind(key, vertex, pixel, nullptr, nullptr); - addPipelineBind(Key::Builder(key).withFade(), vertex, pixel, nullptr, nullptr); + auto addPipeline = [&](const ShapeKey& key, int programId) { + addPipelineBind(key, programId, nullptr, nullptr); + addPipelineBind(Key::Builder(key).withFade(), programId, nullptr, nullptr); }; // Forward pipelines need the lightBatchSetter for opaques and transparents forceLightBatchSetter = true; // Simple Opaques - addPipeline(Key::Builder(), simpleVertex, simplePixel); - addPipeline(Key::Builder().withUnlit(), simpleVertex, simpleUnlitPixel); + addPipeline(Key::Builder(), simple); + addPipeline(Key::Builder().withUnlit(), simpleUnlit); // Simple Translucents - addPipeline(Key::Builder().withTranslucent(), simpleVertex, simpleTranslucentPixel); - addPipeline(Key::Builder().withTranslucent().withUnlit(), simpleVertex, simpleTranslucentUnlitPixel); + addPipeline(Key::Builder().withTranslucent(), simpleTranslucent); + addPipeline(Key::Builder().withTranslucent().withUnlit(), simpleTranslucentUnlit); // Opaques - addPipeline(Key::Builder().withMaterial(), modelVertex, modelPixel); - addPipeline(Key::Builder().withMaterial().withUnlit(), modelVertex, modelUnlitPixel); - addPipeline(Key::Builder().withMaterial().withTangents(), modelNormalMapVertex, modelNormalMapPixel); + addPipeline(Key::Builder().withMaterial(), forward_model); + addPipeline(Key::Builder().withMaterial().withUnlit(), forward_model_unlit); + addPipeline(Key::Builder().withMaterial().withTangents(), forward_model_translucent); // Skinned Opaques - addPipeline(Key::Builder().withMaterial().withSkinned(), skinModelVertex, modelPixel); - addPipeline(Key::Builder().withMaterial().withSkinned().withTangents(), skinModelNormalMapVertex, modelNormalMapPixel); - addPipeline(Key::Builder().withMaterial().withSkinned().withDualQuatSkinned(), skinModelDualQuatVertex, modelPixel); - addPipeline(Key::Builder().withMaterial().withSkinned().withTangents().withDualQuatSkinned(), skinModelNormalMapDualQuatVertex, modelNormalMapPixel); + addPipeline(Key::Builder().withMaterial().withSkinned(), forward_skin_model); + addPipeline(Key::Builder().withMaterial().withSkinned().withTangents(), forward_skin_model_normal_map); + addPipeline(Key::Builder().withMaterial().withSkinned().withDualQuatSkinned(), forward_skin_model_dq); + addPipeline(Key::Builder().withMaterial().withSkinned().withTangents().withDualQuatSkinned(), forward_skin_model_normal_map_dq); // Translucents - addPipeline(Key::Builder().withMaterial().withTranslucent(), modelVertex, modelTranslucentPixel); - addPipeline(Key::Builder().withMaterial().withTranslucent().withTangents(), modelNormalMapVertex, modelTranslucentPixel); + addPipeline(Key::Builder().withMaterial().withTranslucent(), forward_model_translucent); + addPipeline(Key::Builder().withMaterial().withTranslucent().withTangents(), forward_model_normal_map_translucent); // Skinned Translucents - addPipeline(Key::Builder().withMaterial().withSkinned().withTranslucent(), skinModelVertex, modelTranslucentPixel); - addPipeline(Key::Builder().withMaterial().withSkinned().withTranslucent().withTangents(), skinModelNormalMapVertex, modelTranslucentPixel); - addPipeline(Key::Builder().withMaterial().withSkinned().withTranslucent().withDualQuatSkinned(), skinModelDualQuatVertex, modelTranslucentPixel); - addPipeline(Key::Builder().withMaterial().withSkinned().withTranslucent().withTangents().withDualQuatSkinned(), skinModelNormalMapDualQuatVertex, modelTranslucentPixel); + addPipeline(Key::Builder().withMaterial().withSkinned().withTranslucent(), forward_skin_translucent); + addPipeline(Key::Builder().withMaterial().withSkinned().withTranslucent().withTangents(), forward_skin_translucent_normal_map); + addPipeline(Key::Builder().withMaterial().withSkinned().withTranslucent().withDualQuatSkinned(), forward_skin_translucent_dq); + addPipeline(Key::Builder().withMaterial().withSkinned().withTranslucent().withTangents().withDualQuatSkinned(), forward_skin_translucent_normal_map_dq); forceLightBatchSetter = false; } void addPlumberPipeline(ShapePlumber& plumber, - const ShapeKey& key, const gpu::ShaderPointer& vertex, const gpu::ShaderPointer& pixel, + const ShapeKey& key, int programId, const render::ShapePipeline::BatchSetter& extraBatchSetter, const render::ShapePipeline::ItemSetter& itemSetter) { // These key-values' pipelines are added by this functor in addition to the key passed assert(!key.isWireframe()); assert(!key.isDepthBiased()); assert(key.isCullFace()); - gpu::ShaderPointer program = gpu::Shader::createProgram(vertex, pixel); + gpu::ShaderPointer program = gpu::Shader::createProgram(programId); for (int i = 0; i < 8; i++) { bool isCulled = (i & 1); @@ -482,11 +331,11 @@ void addPlumberPipeline(ShapePlumber& plumber, void batchSetter(const ShapePipeline& pipeline, gpu::Batch& batch, RenderArgs* args) { // Set a default albedo map - batch.setResourceTexture(render::ShapePipeline::Slot::MAP::ALBEDO, + batch.setResourceTexture(gr::Texture::MaterialAlbedo, DependencyManager::get()->getWhiteTexture()); // Set a default material - if (pipeline.locations->materialBufferUnit >= 0) { + if (pipeline.locations->materialBufferUnit) { // Create a default schema static bool isMaterialSet = false; static graphics::Material material; @@ -499,7 +348,7 @@ void batchSetter(const ShapePipeline& pipeline, gpu::Batch& batch, RenderArgs* a } // Set a default schema - batch.setUniformBuffer(ShapePipeline::Slot::BUFFER::MATERIAL, material.getSchemaBuffer()); + batch.setUniformBuffer(gr::Buffer::Material, material.getSchemaBuffer()); } } @@ -508,52 +357,39 @@ void lightBatchSetter(const ShapePipeline& pipeline, gpu::Batch& batch, RenderAr batchSetter(pipeline, batch, args); // Set the light - if (pipeline.locations->keyLightBufferUnit >= 0) { - DependencyManager::get()->setupKeyLightBatch(args, batch, - pipeline.locations->keyLightBufferUnit, - pipeline.locations->lightAmbientBufferUnit, - pipeline.locations->lightAmbientMapUnit); + if (pipeline.locations->keyLightBufferUnit) { + DependencyManager::get()->setupKeyLightBatch(args, batch); } } void initZPassPipelines(ShapePlumber& shapePlumber, gpu::StatePointer state) { - auto modelVertex = model_shadow_vert::getShader(); - auto modelPixel = model_shadow_frag::getShader(); - gpu::ShaderPointer modelProgram = gpu::Shader::createProgram(modelVertex, modelPixel); + using namespace shader::render_utils::program; + gpu::ShaderPointer modelProgram = gpu::Shader::createProgram(model_shadow); shapePlumber.addPipeline( ShapeKey::Filter::Builder().withoutSkinned().withoutFade(), modelProgram, state); - auto skinVertex = skin_model_shadow_vert::getShader(); - auto skinPixel = skin_model_shadow_frag::getShader(); - gpu::ShaderPointer skinProgram = gpu::Shader::createProgram(skinVertex, skinPixel); + gpu::ShaderPointer skinProgram = gpu::Shader::createProgram(skin_model_shadow); shapePlumber.addPipeline( ShapeKey::Filter::Builder().withSkinned().withoutDualQuatSkinned().withoutFade(), skinProgram, state); - auto modelFadeVertex = model_shadow_fade_vert::getShader(); - auto modelFadePixel = model_shadow_fade_frag::getShader(); - gpu::ShaderPointer modelFadeProgram = gpu::Shader::createProgram(modelFadeVertex, modelFadePixel); + gpu::ShaderPointer modelFadeProgram = gpu::Shader::createProgram(model_shadow_fade); shapePlumber.addPipeline( ShapeKey::Filter::Builder().withoutSkinned().withFade(), modelFadeProgram, state); - auto skinFadeVertex = skin_model_shadow_fade_vert::getShader(); - auto skinFadePixel = skin_model_shadow_fade_frag::getShader(); - gpu::ShaderPointer skinFadeProgram = gpu::Shader::createProgram(skinFadeVertex, skinFadePixel); + gpu::ShaderPointer skinFadeProgram = gpu::Shader::createProgram(skin_model_shadow_fade); shapePlumber.addPipeline( ShapeKey::Filter::Builder().withSkinned().withoutDualQuatSkinned().withFade(), skinFadeProgram, state); - //Added for dual quaternions - auto skinModelShadowDualQuatVertex = skin_model_shadow_dq_vert::getShader(); - gpu::ShaderPointer skinModelShadowDualQuatProgram = gpu::Shader::createProgram(skinModelShadowDualQuatVertex, skinPixel); + gpu::ShaderPointer skinModelShadowDualQuatProgram = gpu::Shader::createProgram(skin_model_shadow_dq); shapePlumber.addPipeline( ShapeKey::Filter::Builder().withSkinned().withDualQuatSkinned().withoutFade(), skinModelShadowDualQuatProgram, state); - auto skinModelShadowFadeDualQuatVertex = skin_model_shadow_fade_dq_vert::getShader(); - gpu::ShaderPointer skinModelShadowFadeDualQuatProgram = gpu::Shader::createProgram(skinModelShadowFadeDualQuatVertex, skinFadePixel); + gpu::ShaderPointer skinModelShadowFadeDualQuatProgram = gpu::Shader::createProgram(skin_model_shadow_fade_dq); shapePlumber.addPipeline( ShapeKey::Filter::Builder().withSkinned().withDualQuatSkinned().withFade(), skinModelShadowFadeDualQuatProgram, state); @@ -567,8 +403,8 @@ void RenderPipelines::bindMaterial(const graphics::MaterialPointer& material, gp auto textureCache = DependencyManager::get(); - batch.setUniformBuffer(ShapePipeline::Slot::BUFFER::MATERIAL, material->getSchemaBuffer()); - batch.setUniformBuffer(ShapePipeline::Slot::BUFFER::TEXMAPARRAY, material->getTexMapArrayBuffer()); + batch.setUniformBuffer(gr::Buffer::Material, material->getSchemaBuffer()); + batch.setUniformBuffer(gr::Buffer::TexMapArray, material->getTexMapArrayBuffer()); const auto& materialKey = material->getKey(); const auto& textureMaps = material->getTextureMaps(); @@ -584,9 +420,9 @@ void RenderPipelines::bindMaterial(const graphics::MaterialPointer& material, gp if (materialKey.isAlbedoMap()) { auto itr = textureMaps.find(graphics::MaterialKey::ALBEDO_MAP); if (enableTextures && itr != textureMaps.end() && itr->second->isDefined()) { - drawMaterialTextures->setTexture(ShapePipeline::Slot::ALBEDO, itr->second->getTextureView()); + drawMaterialTextures->setTexture(gr::Texture::MaterialAlbedo, itr->second->getTextureView()); } else { - drawMaterialTextures->setTexture(ShapePipeline::Slot::ALBEDO, textureCache->getWhiteTexture()); + drawMaterialTextures->setTexture(gr::Texture::MaterialAlbedo, textureCache->getWhiteTexture()); } } @@ -594,9 +430,9 @@ void RenderPipelines::bindMaterial(const graphics::MaterialPointer& material, gp if (materialKey.isRoughnessMap()) { auto itr = textureMaps.find(graphics::MaterialKey::ROUGHNESS_MAP); if (enableTextures && itr != textureMaps.end() && itr->second->isDefined()) { - drawMaterialTextures->setTexture(ShapePipeline::Slot::ROUGHNESS, itr->second->getTextureView()); + drawMaterialTextures->setTexture(gr::Texture::MaterialRoughness, itr->second->getTextureView()); } else { - drawMaterialTextures->setTexture(ShapePipeline::Slot::ROUGHNESS, textureCache->getWhiteTexture()); + drawMaterialTextures->setTexture(gr::Texture::MaterialRoughness, textureCache->getWhiteTexture()); } } @@ -604,9 +440,9 @@ void RenderPipelines::bindMaterial(const graphics::MaterialPointer& material, gp if (materialKey.isNormalMap()) { auto itr = textureMaps.find(graphics::MaterialKey::NORMAL_MAP); if (enableTextures && itr != textureMaps.end() && itr->second->isDefined()) { - drawMaterialTextures->setTexture(ShapePipeline::Slot::NORMAL, itr->second->getTextureView()); + drawMaterialTextures->setTexture(gr::Texture::MaterialNormal, itr->second->getTextureView()); } else { - drawMaterialTextures->setTexture(ShapePipeline::Slot::NORMAL, textureCache->getBlueTexture()); + drawMaterialTextures->setTexture(gr::Texture::MaterialNormal, textureCache->getBlueTexture()); } } @@ -614,9 +450,9 @@ void RenderPipelines::bindMaterial(const graphics::MaterialPointer& material, gp if (materialKey.isMetallicMap()) { auto itr = textureMaps.find(graphics::MaterialKey::METALLIC_MAP); if (enableTextures && itr != textureMaps.end() && itr->second->isDefined()) { - drawMaterialTextures->setTexture(ShapePipeline::Slot::METALLIC, itr->second->getTextureView()); + drawMaterialTextures->setTexture(gr::Texture::MaterialMetallic, itr->second->getTextureView()); } else { - drawMaterialTextures->setTexture(ShapePipeline::Slot::METALLIC, textureCache->getBlackTexture()); + drawMaterialTextures->setTexture(gr::Texture::MaterialMetallic, textureCache->getBlackTexture()); } } @@ -624,9 +460,9 @@ void RenderPipelines::bindMaterial(const graphics::MaterialPointer& material, gp if (materialKey.isOcclusionMap()) { auto itr = textureMaps.find(graphics::MaterialKey::OCCLUSION_MAP); if (enableTextures && itr != textureMaps.end() && itr->second->isDefined()) { - drawMaterialTextures->setTexture(ShapePipeline::Slot::OCCLUSION, itr->second->getTextureView()); + drawMaterialTextures->setTexture(gr::Texture::MaterialOcclusion, itr->second->getTextureView()); } else { - drawMaterialTextures->setTexture(ShapePipeline::Slot::OCCLUSION, textureCache->getWhiteTexture()); + drawMaterialTextures->setTexture(gr::Texture::MaterialOcclusion, textureCache->getWhiteTexture()); } } @@ -634,9 +470,9 @@ void RenderPipelines::bindMaterial(const graphics::MaterialPointer& material, gp if (materialKey.isScatteringMap()) { auto itr = textureMaps.find(graphics::MaterialKey::SCATTERING_MAP); if (enableTextures && itr != textureMaps.end() && itr->second->isDefined()) { - drawMaterialTextures->setTexture(ShapePipeline::Slot::SCATTERING, itr->second->getTextureView()); + drawMaterialTextures->setTexture(gr::Texture::MaterialScattering, itr->second->getTextureView()); } else { - drawMaterialTextures->setTexture(ShapePipeline::Slot::SCATTERING, textureCache->getWhiteTexture()); + drawMaterialTextures->setTexture(gr::Texture::MaterialScattering, textureCache->getWhiteTexture()); } } @@ -645,16 +481,16 @@ void RenderPipelines::bindMaterial(const graphics::MaterialPointer& material, gp auto itr = textureMaps.find(graphics::MaterialKey::LIGHTMAP_MAP); if (enableTextures && itr != textureMaps.end() && itr->second->isDefined()) { - drawMaterialTextures->setTexture(ShapePipeline::Slot::EMISSIVE_LIGHTMAP, itr->second->getTextureView()); + drawMaterialTextures->setTexture(gr::Texture::MaterialEmissiveLightmap, itr->second->getTextureView()); } else { - drawMaterialTextures->setTexture(ShapePipeline::Slot::EMISSIVE_LIGHTMAP, textureCache->getGrayTexture()); + drawMaterialTextures->setTexture(gr::Texture::MaterialEmissiveLightmap, textureCache->getGrayTexture()); } } else if (materialKey.isEmissiveMap()) { auto itr = textureMaps.find(graphics::MaterialKey::EMISSIVE_MAP); if (enableTextures && itr != textureMaps.end() && itr->second->isDefined()) { - drawMaterialTextures->setTexture(ShapePipeline::Slot::EMISSIVE_LIGHTMAP, itr->second->getTextureView()); + drawMaterialTextures->setTexture(gr::Texture::MaterialEmissiveLightmap, itr->second->getTextureView()); } else { - drawMaterialTextures->setTexture(ShapePipeline::Slot::EMISSIVE_LIGHTMAP, textureCache->getBlackTexture()); + drawMaterialTextures->setTexture(gr::Texture::MaterialEmissiveLightmap, textureCache->getBlackTexture()); } } diff --git a/libraries/render-utils/src/RenderShadowTask.cpp b/libraries/render-utils/src/RenderShadowTask.cpp index 91eb777199..137ee07c96 100644 --- a/libraries/render-utils/src/RenderShadowTask.cpp +++ b/libraries/render-utils/src/RenderShadowTask.cpp @@ -33,6 +33,89 @@ using namespace render; extern void initZPassPipelines(ShapePlumber& plumber, gpu::StatePointer state); +void RenderShadowTask::configure(const Config& configuration) { + DependencyManager::get()->setShadowMapEnabled(configuration.enabled); + // This is a task, so must still propogate configure() to its Jobs + // Task::configure(configuration); +} + +void RenderShadowTask::build(JobModel& task, const render::Varying& input, render::Varying& output, render::CullFunctor cameraCullFunctor, uint8_t tagBits, uint8_t tagMask) { + ::CullFunctor shadowCullFunctor = [this](const RenderArgs* args, const AABox& bounds) { + return _cullFunctor(args, bounds); + }; + + // Prepare the ShapePipeline + ShapePlumberPointer shapePlumber = std::make_shared(); + { + auto state = std::make_shared(); + state->setCullMode(gpu::State::CULL_BACK); + state->setDepthTest(true, true, gpu::LESS_EQUAL); + + initZPassPipelines(*shapePlumber, state); + } + + const auto setupOutput = task.addJob("ShadowSetup"); + const auto queryResolution = setupOutput.getN(1); + // Fetch and cull the items from the scene + + static const auto shadowCasterReceiverFilter = ItemFilter::Builder::visibleWorldItems().withTypeShape().withOpaque().withoutLayered().withTagBits(tagBits, tagMask); + + const auto fetchInput = FetchSpatialTree::Inputs(shadowCasterReceiverFilter, queryResolution).asVarying(); + const auto shadowSelection = task.addJob("FetchShadowTree", fetchInput); + const auto selectionInputs = FilterSpatialSelection::Inputs(shadowSelection, shadowCasterReceiverFilter).asVarying(); + const auto shadowItems = task.addJob("FilterShadowSelection", selectionInputs); + + // Cull objects that are not visible in camera view. Hopefully the cull functor only performs LOD culling, not + // frustum culling or this will make shadow casters out of the camera frustum disappear. + const auto cameraFrustum = setupOutput.getN(2); + const auto applyFunctorInputs = ApplyCullFunctorOnItemBounds::Inputs(shadowItems, cameraFrustum).asVarying(); + const auto culledShadowItems = task.addJob("ShadowCullCamera", applyFunctorInputs, cameraCullFunctor); + + // Sort + const auto sortedPipelines = task.addJob("PipelineSortShadow", culledShadowItems); + const auto sortedShapes = task.addJob("DepthSortShadow", sortedPipelines, true); + + render::Varying cascadeFrustums[SHADOW_CASCADE_MAX_COUNT] = { + ViewFrustumPointer() +#if SHADOW_CASCADE_MAX_COUNT>1 + ,ViewFrustumPointer(), + ViewFrustumPointer(), + ViewFrustumPointer() +#endif + }; + + Output cascadeSceneBBoxes; + + for (auto i = 0; i < SHADOW_CASCADE_MAX_COUNT; i++) { + char jobName[64]; + sprintf(jobName, "ShadowCascadeSetup%d", i); + const auto cascadeSetupOutput = task.addJob(jobName, i, _cullFunctor, tagBits, tagMask); + const auto shadowFilter = cascadeSetupOutput.getN(0); + auto antiFrustum = render::Varying(ViewFrustumPointer()); + cascadeFrustums[i] = cascadeSetupOutput.getN(1); + if (i > 1) { + antiFrustum = cascadeFrustums[i - 2]; + } + + // CPU jobs: finer grained culling + const auto cullInputs = CullShadowBounds::Inputs(sortedShapes, shadowFilter, antiFrustum).asVarying(); + sprintf(jobName, "CullShadowCascade%d", i); + const auto culledShadowItemsAndBounds = task.addJob(jobName, cullInputs, shadowCullFunctor); + + // GPU jobs: Render to shadow map + sprintf(jobName, "RenderShadowMap%d", i); + task.addJob(jobName, culledShadowItemsAndBounds, shapePlumber, i); + sprintf(jobName, "ShadowCascadeTeardown%d", i); + task.addJob(jobName, shadowFilter); + + cascadeSceneBBoxes[i] = culledShadowItemsAndBounds.getN(1); + } + + output = render::Varying(cascadeSceneBBoxes); + + task.addJob("ShadowTeardown", setupOutput); +} + static void computeNearFar(const Triangle& triangle, const Plane shadowClipPlanes[4], float& near, float& far) { static const int MAX_TRIANGLE_COUNT = 16; Triangle clippedTriangles[MAX_TRIANGLE_COUNT]; @@ -78,36 +161,41 @@ static void computeNearFar(const glm::vec3 sceneBoundVertices[8], const Plane sh } static void adjustNearFar(const AABox& inShapeBounds, ViewFrustum& shadowFrustum) { - const Transform shadowView{ shadowFrustum.getView() }; - const Transform shadowViewInverse{ shadowView.getInverseMatrix() }; + if (!inShapeBounds.isNull()) { + const Transform shadowView{ shadowFrustum.getView() }; + const Transform shadowViewInverse{ shadowView.getInverseMatrix() }; - glm::vec3 sceneBoundVertices[8]; - // Keep only the left, right, top and bottom shadow frustum planes as we wish to determine - // the near and far - Plane shadowClipPlanes[4]; - int i; + glm::vec3 sceneBoundVertices[8]; + // Keep only the left, right, top and bottom shadow frustum planes as we wish to determine + // the near and far + Plane shadowClipPlanes[4]; + int i; - // The vertices of the scene bounding box are expressed in the shadow frustum's local space - for (i = 0; i < 8; i++) { - sceneBoundVertices[i] = shadowViewInverse.transform(inShapeBounds.getVertex(static_cast(i))); + // The vertices of the scene bounding box are expressed in the shadow frustum's local space + for (i = 0; i < 8; i++) { + sceneBoundVertices[i] = shadowViewInverse.transform(inShapeBounds.getVertex(static_cast(i))); + } + shadowFrustum.getUniformlyTransformedSidePlanes(shadowViewInverse, shadowClipPlanes); + + float near = std::numeric_limits::max(); + float far = 0.0f; + + computeNearFar(sceneBoundVertices, shadowClipPlanes, near, far); + // Limit the far range to the one used originally. + far = glm::min(far, shadowFrustum.getFarClip()); + if (near > far) { + near = far; + } + + const auto depthEpsilon = 0.1f; + auto projMatrix = glm::ortho(-1.0f, 1.0f, -1.0f, 1.0f, near - depthEpsilon, far + depthEpsilon); + auto shadowProjection = shadowFrustum.getProjection(); + + shadowProjection[2][2] = projMatrix[2][2]; + shadowProjection[3][2] = projMatrix[3][2]; + shadowFrustum.setProjection(shadowProjection); + shadowFrustum.calculate(); } - shadowFrustum.getUniformlyTransformedSidePlanes(shadowViewInverse, shadowClipPlanes); - - float near = std::numeric_limits::max(); - float far = 0.0f; - - computeNearFar(sceneBoundVertices, shadowClipPlanes, near, far); - // Limit the far range to the one used originally. - far = glm::min(far, shadowFrustum.getFarClip()); - - const auto depthEpsilon = 0.1f; - auto projMatrix = glm::ortho(-1.0f, 1.0f, -1.0f, 1.0f, near - depthEpsilon, far + depthEpsilon); - auto shadowProjection = shadowFrustum.getProjection(); - - shadowProjection[2][2] = projMatrix[2][2]; - shadowProjection[3][2] = projMatrix[3][2]; - shadowFrustum.setProjection(shadowProjection); - shadowFrustum.calculate(); } void RenderShadowMap::run(const render::RenderContextPointer& renderContext, const Inputs& inputs) { @@ -151,142 +239,68 @@ void RenderShadowMap::run(const render::RenderContextPointer& renderContext, con batch.setFramebuffer(fbo); batch.clearDepthFramebuffer(1.0, false); - glm::mat4 projMat; - Transform viewMat; - args->getViewFrustum().evalProjectionMatrix(projMat); - args->getViewFrustum().evalViewTransform(viewMat); + if (!inShapeBounds.isNull()) { + glm::mat4 projMat; + Transform viewMat; + args->getViewFrustum().evalProjectionMatrix(projMat); + args->getViewFrustum().evalViewTransform(viewMat); - batch.setProjectionTransform(projMat); - batch.setViewTransform(viewMat, false); + batch.setProjectionTransform(projMat); + batch.setViewTransform(viewMat, false); - auto shadowPipeline = _shapePlumber->pickPipeline(args, defaultKeyBuilder); - auto shadowSkinnedPipeline = _shapePlumber->pickPipeline(args, defaultKeyBuilder.withSkinned()); - auto shadowSkinnedDQPipeline = _shapePlumber->pickPipeline(args, defaultKeyBuilder.withSkinned().withDualQuatSkinned()); + auto shadowPipeline = _shapePlumber->pickPipeline(args, defaultKeyBuilder); + auto shadowSkinnedPipeline = _shapePlumber->pickPipeline(args, defaultKeyBuilder.withSkinned()); + auto shadowSkinnedDQPipeline = _shapePlumber->pickPipeline(args, defaultKeyBuilder.withSkinned().withDualQuatSkinned()); - std::vector skinnedShapeKeys{}; - std::vector skinnedDQShapeKeys{}; - std::vector ownPipelineShapeKeys{}; + std::vector skinnedShapeKeys{}; + std::vector skinnedDQShapeKeys{}; + std::vector ownPipelineShapeKeys{}; - // Iterate through all inShapes and render the unskinned - args->_shapePipeline = shadowPipeline; - batch.setPipeline(shadowPipeline->pipeline); - for (auto items : inShapes) { - if (items.first.isSkinned()) { - if (items.first.isDualQuatSkinned()) { - skinnedDQShapeKeys.push_back(items.first); + // Iterate through all inShapes and render the unskinned + args->_shapePipeline = shadowPipeline; + batch.setPipeline(shadowPipeline->pipeline); + for (auto items : inShapes) { + if (items.first.isSkinned()) { + if (items.first.isDualQuatSkinned()) { + skinnedDQShapeKeys.push_back(items.first); + } else { + skinnedShapeKeys.push_back(items.first); + } + } else if (!items.first.hasOwnPipeline()) { + renderItems(renderContext, items.second); } else { - skinnedShapeKeys.push_back(items.first); + ownPipelineShapeKeys.push_back(items.first); } - } else if (!items.first.hasOwnPipeline()) { - renderItems(renderContext, items.second); - } else { - ownPipelineShapeKeys.push_back(items.first); } - } - // Reiterate to render the skinned - args->_shapePipeline = shadowSkinnedPipeline; - batch.setPipeline(shadowSkinnedPipeline->pipeline); - for (const auto& key : skinnedShapeKeys) { - renderItems(renderContext, inShapes.at(key)); - } + // Reiterate to render the skinned + args->_shapePipeline = shadowSkinnedPipeline; + batch.setPipeline(shadowSkinnedPipeline->pipeline); + for (const auto& key : skinnedShapeKeys) { + renderItems(renderContext, inShapes.at(key)); + } - // Reiterate to render the DQ skinned - args->_shapePipeline = shadowSkinnedDQPipeline; - batch.setPipeline(shadowSkinnedDQPipeline->pipeline); - for (const auto& key : skinnedDQShapeKeys) { - renderItems(renderContext, inShapes.at(key)); - } + // Reiterate to render the DQ skinned + args->_shapePipeline = shadowSkinnedDQPipeline; + batch.setPipeline(shadowSkinnedDQPipeline->pipeline); + for (const auto& key : skinnedDQShapeKeys) { + renderItems(renderContext, inShapes.at(key)); + } - // Finally render the items with their own pipeline last to prevent them from breaking the - // render state. This is probably a temporary code as there is probably something better - // to do in the render call of objects that have their own pipeline. - args->_shapePipeline = nullptr; - for (const auto& key : ownPipelineShapeKeys) { - args->_itemShapeKey = key._flags.to_ulong(); - renderItems(renderContext, inShapes.at(key)); + // Finally render the items with their own pipeline last to prevent them from breaking the + // render state. This is probably a temporary code as there is probably something better + // to do in the render call of objects that have their own pipeline. + args->_shapePipeline = nullptr; + for (const auto& key : ownPipelineShapeKeys) { + args->_itemShapeKey = key._flags.to_ulong(); + renderItems(renderContext, inShapes.at(key)); + } } args->_batch = nullptr; }); } -void RenderShadowTask::build(JobModel& task, const render::Varying& input, render::Varying& output, render::CullFunctor cameraCullFunctor, uint8_t tagBits, uint8_t tagMask) { - ::CullFunctor shadowCullFunctor = [this](const RenderArgs* args, const AABox& bounds) { - return _cullFunctor(args, bounds); - }; - - // Prepare the ShapePipeline - ShapePlumberPointer shapePlumber = std::make_shared(); - { - auto state = std::make_shared(); - state->setCullMode(gpu::State::CULL_BACK); - state->setDepthTest(true, true, gpu::LESS_EQUAL); - - initZPassPipelines(*shapePlumber, state); - } - - const auto setupOutput = task.addJob("ShadowSetup"); - const auto queryResolution = setupOutput.getN(1); - // Fetch and cull the items from the scene - - static const auto shadowCasterReceiverFilter = ItemFilter::Builder::visibleWorldItems().withTypeShape().withOpaque().withoutLayered().withTagBits(tagBits, tagMask); - - const auto fetchInput = FetchSpatialTree::Inputs(shadowCasterReceiverFilter, queryResolution).asVarying(); - const auto shadowSelection = task.addJob("FetchShadowTree", fetchInput); - const auto selectionInputs = FetchSpatialSelection::Inputs(shadowSelection, shadowCasterReceiverFilter).asVarying(); - const auto shadowItems = task.addJob("FetchShadowSelection", selectionInputs); - - // Cull objects that are not visible in camera view. Hopefully the cull functor only performs LOD culling, not - // frustum culling or this will make shadow casters out of the camera frustum disappear. - const auto cameraFrustum = setupOutput.getN(2); - const auto applyFunctorInputs = ApplyCullFunctorOnItemBounds::Inputs(shadowItems, cameraFrustum).asVarying(); - const auto culledShadowItems = task.addJob("ShadowCullCamera", applyFunctorInputs, cameraCullFunctor); - - // Sort - const auto sortedPipelines = task.addJob("PipelineSortShadow", culledShadowItems); - const auto sortedShapes = task.addJob("DepthSortShadow", sortedPipelines, true); - - render::Varying cascadeFrustums[SHADOW_CASCADE_MAX_COUNT] = { - ViewFrustumPointer() -#if SHADOW_CASCADE_MAX_COUNT>1 - ,ViewFrustumPointer(), - ViewFrustumPointer(), - ViewFrustumPointer() -#endif - }; - - for (auto i = 0; i < SHADOW_CASCADE_MAX_COUNT; i++) { - char jobName[64]; - sprintf(jobName, "ShadowCascadeSetup%d", i); - const auto cascadeSetupOutput = task.addJob(jobName, i, _cullFunctor, tagBits, tagMask); - const auto shadowRenderFilter = cascadeSetupOutput.getN(0); - const auto shadowBoundsFilter = cascadeSetupOutput.getN(1); - auto antiFrustum = render::Varying(ViewFrustumPointer()); - cascadeFrustums[i] = cascadeSetupOutput.getN(2); - if (i > 1) { - antiFrustum = cascadeFrustums[i - 2]; - } - - // CPU jobs: finer grained culling - const auto cullInputs = CullShapeBounds::Inputs(sortedShapes, shadowRenderFilter, shadowBoundsFilter, antiFrustum).asVarying(); - const auto culledShadowItemsAndBounds = task.addJob("CullShadowCascade", cullInputs, shadowCullFunctor, RenderDetails::SHADOW); - - // GPU jobs: Render to shadow map - sprintf(jobName, "RenderShadowMap%d", i); - task.addJob(jobName, culledShadowItemsAndBounds, shapePlumber, i); - task.addJob("ShadowCascadeTeardown", shadowRenderFilter); - } - - task.addJob("ShadowTeardown", setupOutput); -} - -void RenderShadowTask::configure(const Config& configuration) { - DependencyManager::get()->setShadowMapEnabled(configuration.enabled); - // This is a task, so must still propogate configure() to its Jobs -// Task::configure(configuration); -} - RenderShadowSetup::RenderShadowSetup() : _cameraFrustum{ std::make_shared() }, _coarseShadowFrustum{ std::make_shared() } { @@ -408,11 +422,8 @@ void RenderShadowCascadeSetup::run(const render::RenderContextPointer& renderCon const auto globalShadow = lightStage->getCurrentKeyShadow(); if (globalShadow && _cascadeIndexgetCascadeCount()) { - auto baseFilter = ItemFilter::Builder::visibleWorldItems().withTypeShape().withOpaque().withoutLayered().withTagBits(_tagBits, _tagMask); // Second item filter is to filter items to keep in shadow frustum computation (here we need to keep shadow receivers) - output.edit1() = baseFilter; - // First item filter is to filter items to render in shadow map (so only keep casters) - output.edit0() = baseFilter.withShadowCaster(); + output.edit0() = ItemFilter::Builder::visibleWorldItems().withTypeShape().withOpaque().withoutLayered().withTagBits(_tagBits, _tagMask); // Set the keylight render args auto& cascade = globalShadow->getCascade(_cascadeIndex); @@ -425,11 +436,10 @@ void RenderShadowCascadeSetup::run(const render::RenderContextPointer& renderCon texelSize *= minTexelCount; _cullFunctor._minSquareSize = texelSize * texelSize; - output.edit2() = cascadeFrustum; + output.edit1() = cascadeFrustum; } else { output.edit0() = ItemFilter::Builder::nothing(); - output.edit1() = ItemFilter::Builder::nothing(); - output.edit2() = ViewFrustumPointer(); + output.edit1() = ViewFrustumPointer(); } } @@ -452,3 +462,98 @@ void RenderShadowTeardown::run(const render::RenderContextPointer& renderContext // Reset the render args args->_renderMode = input.get0(); } + +static AABox& merge(AABox& box, const AABox& otherBox, const glm::vec3& dir) { + if (!otherBox.isInvalid()) { + int vertexIndex = 0; + vertexIndex |= ((dir.z > 0.0f) & 1) << 2; + vertexIndex |= ((dir.y > 0.0f) & 1) << 1; + vertexIndex |= ((dir.x < 0.0f) & 1); + auto vertex = otherBox.getVertex((BoxVertex)vertexIndex); + if (!box.isInvalid()) { + const auto boxCenter = box.calcCenter(); + vertex -= boxCenter; + vertex = dir * glm::max(0.0f, glm::dot(vertex, dir)); + vertex += boxCenter; + } + box += vertex; + } + return box; +} + +void CullShadowBounds::run(const render::RenderContextPointer& renderContext, const Inputs& inputs, Outputs& outputs) { + assert(renderContext->args); + assert(renderContext->args->hasViewFrustum()); + RenderArgs* args = renderContext->args; + + const auto& inShapes = inputs.get0(); + const auto& filter = inputs.get1(); + ViewFrustumPointer antiFrustum; + auto& outShapes = outputs.edit0(); + auto& outBounds = outputs.edit1(); + + if (!inputs[3].isNull()) { + antiFrustum = inputs.get2(); + } + outShapes.clear(); + outBounds = AABox(); + + if (!filter.selectsNothing()) { + auto& details = args->_details.edit(RenderDetails::SHADOW); + render::CullTest test(_cullFunctor, args, details, antiFrustum); + auto scene = args->_scene; + auto lightStage = renderContext->_scene->getStage(); + assert(lightStage); + const auto globalLightDir = lightStage->getCurrentKeyLight()->getDirection(); + auto castersFilter = render::ItemFilter::Builder(filter).withShadowCaster().build(); + const auto& receiversFilter = filter; + + for (auto& inItems : inShapes) { + auto key = inItems.first; + auto outItems = outShapes.find(key); + if (outItems == outShapes.end()) { + outItems = outShapes.insert(std::make_pair(key, ItemBounds{})).first; + outItems->second.reserve(inItems.second.size()); + } + + details._considered += (int)inItems.second.size(); + + if (antiFrustum == nullptr) { + for (auto& item : inItems.second) { + if (test.solidAngleTest(item.bound) && test.frustumTest(item.bound)) { + const auto shapeKey = scene->getItem(item.id).getKey(); + if (castersFilter.test(shapeKey)) { + outItems->second.emplace_back(item); + outBounds += item.bound; + } else if (receiversFilter.test(shapeKey)) { + // Receivers are not rendered but they still increase the bounds of the shadow scene + // although only in the direction of the light direction so as to have a correct far + // distance without decreasing the near distance. + merge(outBounds, item.bound, globalLightDir); + } + } + } + } else { + for (auto& item : inItems.second) { + if (test.solidAngleTest(item.bound) && test.frustumTest(item.bound) && test.antiFrustumTest(item.bound)) { + const auto shapeKey = scene->getItem(item.id).getKey(); + if (castersFilter.test(shapeKey)) { + outItems->second.emplace_back(item); + outBounds += item.bound; + } else if (receiversFilter.test(shapeKey)) { + // Receivers are not rendered but they still increase the bounds of the shadow scene + // although only in the direction of the light direction so as to have a correct far + // distance without decreasing the near distance. + merge(outBounds, item.bound, globalLightDir); + } + } + } + } + details._rendered += (int)outItems->second.size(); + } + + for (auto& items : outShapes) { + items.second.shrink_to_fit(); + } + } +} diff --git a/libraries/render-utils/src/RenderShadowTask.h b/libraries/render-utils/src/RenderShadowTask.h index 19ffcb4234..77892305fb 100644 --- a/libraries/render-utils/src/RenderShadowTask.h +++ b/libraries/render-utils/src/RenderShadowTask.h @@ -46,8 +46,11 @@ signals: class RenderShadowTask { public: + + // There is one AABox per shadow cascade + using Output = render::VaryingArray; using Config = RenderShadowTaskConfig; - using JobModel = render::Task::Model; + using JobModel = render::Task::ModelO; RenderShadowTask() {} void build(JobModel& task, const render::Varying& inputs, render::Varying& outputs, render::CullFunctor cameraCullFunctor, uint8_t tagBits = 0x00, uint8_t tagMask = 0x00); @@ -118,7 +121,7 @@ private: class RenderShadowCascadeSetup { public: - using Outputs = render::VaryingSet3; + using Outputs = render::VaryingSet2; using JobModel = render::Job::ModelO; RenderShadowCascadeSetup(unsigned int cascadeIndex, RenderShadowTask::CullFunctor& cullFunctor, uint8_t tagBits = 0x00, uint8_t tagMask = 0x00) : @@ -147,4 +150,22 @@ public: void run(const render::RenderContextPointer& renderContext, const Input& input); }; +class CullShadowBounds { +public: + using Inputs = render::VaryingSet3; + using Outputs = render::VaryingSet2; + using JobModel = render::Job::ModelIO; + + CullShadowBounds(render::CullFunctor cullFunctor) : + _cullFunctor{ cullFunctor } { + } + + void run(const render::RenderContextPointer& renderContext, const Inputs& inputs, Outputs& outputs); + +private: + + render::CullFunctor _cullFunctor; + +}; + #endif // hifi_RenderShadowTask_h diff --git a/libraries/render-utils/src/RenderViewTask.cpp b/libraries/render-utils/src/RenderViewTask.cpp index 6f6a87c222..2e03082ff4 100644 --- a/libraries/render-utils/src/RenderViewTask.cpp +++ b/libraries/render-utils/src/RenderViewTask.cpp @@ -17,17 +17,15 @@ void RenderViewTask::build(JobModel& task, const render::Varying& input, render::Varying& output, render::CullFunctor cullFunctor, bool isDeferred, uint8_t tagBits, uint8_t tagMask) { // auto items = input.get(); - // Warning : the cull functor passed to the shadow pass should only be testing for LOD culling. If frustum culling - // is performed, then casters not in the view frustum will be removed, which is not what we wish. - if (isDeferred) { - task.addJob("RenderShadowTask", cullFunctor, tagBits, tagMask); - } - const auto items = task.addJob("FetchCullSort", cullFunctor, tagBits, tagMask); assert(items.canCast()); if (isDeferred) { - task.addJob("RenderDeferredTask", items, true); + // Warning : the cull functor passed to the shadow pass should only be testing for LOD culling. If frustum culling + // is performed, then casters not in the view frustum will be removed, which is not what we wish. + const auto cascadeSceneBBoxes = task.addJob("RenderShadowTask", cullFunctor, tagBits, tagMask); + const auto renderInput = RenderDeferredTask::Input(items, cascadeSceneBBoxes).asVarying(); + task.addJob("RenderDeferredTask", renderInput, true); } else { task.addJob("Forward", items); } diff --git a/libraries/render-utils/src/ShadingModel.slh b/libraries/render-utils/src/ShadingModel.slh new file mode 100644 index 0000000000..6b0b7bca18 --- /dev/null +++ b/libraries/render-utils/src/ShadingModel.slh @@ -0,0 +1,237 @@ + +<@if not SHADING_MODEL_SLH@> +<@def SHADING_MODEL_SLH@> + +<@include LightingModel.slh@> + +<@func declareBeckmannSpecular()@> + +layout(binding=RENDER_UTILS_TEXTURE_SSSC_SPECULAR_BECKMANN) uniform sampler2D scatteringSpecularBeckmann; + +float fetchSpecularBeckmann(float ndoth, float roughness) { + return pow(2.0 * texture(scatteringSpecularBeckmann, vec2(ndoth, roughness)).r, 10.0); +} + +vec2 skinSpecular(SurfaceData surface, float intensity) { + vec2 result = vec2(0.0, 1.0); + if (surface.ndotl > 0.0) { + float PH = fetchSpecularBeckmann(surface.ndoth, surface.roughness); + float F = fresnelSchlickScalar(0.028, surface); + float frSpec = max(PH * F / dot(surface.halfDir, surface.halfDir), 0.0); + result.x = surface.ndotl * intensity * frSpec; + result.y -= F; + } + + return result; +} +<@endfunc@> + +<@func declareEvalPBRShading()@> + +float evalSmithInvG1(float roughness4, float ndotd) { + return ndotd + sqrt(roughness4+ndotd*ndotd*(1.0-roughness4)); +} + +SurfaceData initSurfaceData(float roughness, vec3 normal, vec3 eyeDir) { + SurfaceData surface; + surface.eyeDir = eyeDir; + surface.normal = normal; + surface.roughness = mix(0.01, 1.0, roughness); + surface.roughness2 = surface.roughness * surface.roughness; + surface.roughness4 = surface.roughness2 * surface.roughness2; + surface.ndotv = clamp(dot(normal, eyeDir), 0.0, 1.0); + surface.smithInvG1NdotV = evalSmithInvG1(surface.roughness4, surface.ndotv); + + // These values will be set when we know the light direction, in updateSurfaceDataWithLight + surface.ndoth = 0.0; + surface.ndotl = 0.0; + surface.ldoth = 0.0; + surface.lightDir = vec3(0,0,1); + surface.halfDir = vec3(0,0,1); + + return surface; +} + +void updateSurfaceDataWithLight(inout SurfaceData surface, vec3 lightDir) { + surface.lightDir = lightDir; + surface.halfDir = normalize(surface.eyeDir + lightDir); + vec3 dots; + dots.x = dot(surface.normal, surface.halfDir); + dots.y = dot(surface.normal, surface.lightDir); + dots.z = dot(surface.halfDir, surface.lightDir); + dots = clamp(dots, vec3(0), vec3(1)); + surface.ndoth = dots.x; + surface.ndotl = dots.y; + surface.ldoth = dots.z; +} + +vec3 fresnelSchlickColor(vec3 fresnelColor, SurfaceData surface) { + float base = 1.0 - surface.ldoth; + //float exponential = pow(base, 5.0); + float base2 = base * base; + float exponential = base * base2 * base2; + return vec3(exponential) + fresnelColor * (1.0 - exponential); +} + +float fresnelSchlickScalar(float fresnelScalar, SurfaceData surface) { + float base = 1.0 - surface.ldoth; + //float exponential = pow(base, 5.0); + float base2 = base * base; + float exponential = base * base2 * base2; + return (exponential) + fresnelScalar * (1.0 - exponential); +} + +float specularDistribution(SurfaceData surface) { + // See https://www.khronos.org/assets/uploads/developers/library/2017-web3d/glTF-2.0-Launch_Jun17.pdf + // for details of equations, especially page 20 + float denom = (surface.ndoth*surface.ndoth * (surface.roughness4 - 1.0) + 1.0); + denom *= denom; + // Add geometric factors G1(n,l) and G1(n,v) + float smithInvG1NdotL = evalSmithInvG1(surface.roughness4, surface.ndotl); + denom *= surface.smithInvG1NdotV * smithInvG1NdotL; + // Don't divide by PI as this is part of the light normalization factor + float power = surface.roughness4 / denom; + return power; +} + +// Frag Shading returns the diffuse amount as W and the specular rgb as xyz +vec4 evalPBRShading(float metallic, vec3 fresnel, SurfaceData surface) { + // Incident angle attenuation + float angleAttenuation = surface.ndotl; + + // Specular Lighting + vec3 fresnelColor = fresnelSchlickColor(fresnel, surface); + float power = specularDistribution(surface); + vec3 specular = fresnelColor * power * angleAttenuation; + float diffuse = (1.0 - metallic) * angleAttenuation * (1.0 - fresnelColor.x); + + // We don't divided by PI, as the "normalized" equations state we should, because we decide, as Naty Hoffman, that + // we wish to have a similar color as raw albedo on a perfectly diffuse surface perpendicularly lit + // by a white light of intensity 1. But this is an arbitrary normalization of what light intensity "means". + // (see http://blog.selfshadow.com/publications/s2013-shading-course/hoffman/s2013_pbs_physics_math_notes.pdf + // page 23 paragraph "Punctual light sources") + return vec4(specular, diffuse); +} + +// Frag Shading returns the diffuse amount as W and the specular rgb as xyz +vec4 evalPBRShadingDielectric(SurfaceData surface, float fresnel) { + // Incident angle attenuation + float angleAttenuation = surface.ndotl; + + // Specular Lighting + float fresnelScalar = fresnelSchlickScalar(fresnel, surface); + float power = specularDistribution(surface); + vec3 specular = vec3(fresnelScalar) * power * angleAttenuation; + float diffuse = angleAttenuation * (1.0 - fresnelScalar); + + // We don't divided by PI, as the "normalized" equations state we should, because we decide, as Naty Hoffman, that + // we wish to have a similar color as raw albedo on a perfectly diffuse surface perpendicularly lit + // by a white light of intensity 1. But this is an arbitrary normalization of what light intensity "means". + // (see http://blog.selfshadow.com/publications/s2013-shading-course/hoffman/s2013_pbs_physics_math_notes.pdf + // page 23 paragraph "Punctual light sources") + return vec4(specular, diffuse); +} + +vec4 evalPBRShadingMetallic(SurfaceData surface, vec3 fresnel) { + // Incident angle attenuation + float angleAttenuation = surface.ndotl; + + // Specular Lighting + vec3 fresnelColor = fresnelSchlickColor(fresnel, surface); + float power = specularDistribution(surface); + vec3 specular = fresnelColor * power * angleAttenuation; + + // We don't divided by PI, as the "normalized" equations state we should, because we decide, as Naty Hoffman, that + // we wish to have a similar color as raw albedo on a perfectly diffuse surface perpendicularly lit + // by a white light of intensity 1. But this is an arbitrary normalization of what light intensity "means". + // (see http://blog.selfshadow.com/publications/s2013-shading-course/hoffman/s2013_pbs_physics_math_notes.pdf + // page 23 paragraph "Punctual light sources") + return vec4(specular, 0.f); +} + +<@endfunc@> + + + +<$declareEvalPBRShading()$> + +void evalFragShading(out vec3 diffuse, out vec3 specular, + float metallic, vec3 fresnel, SurfaceData surface, vec3 albedo) { + vec4 shading = evalPBRShading(metallic, fresnel, surface); + diffuse = vec3(shading.w); + diffuse *= mix(vec3(1.0), albedo, isAlbedoEnabled()); + specular = shading.xyz; +} + +<$declareBeckmannSpecular()$> +<@include SubsurfaceScattering.slh@> +<$declareSubsurfaceScatteringBRDF()$> + + +void evalFragShading(out vec3 diffuse, out vec3 specular, + float metallic, vec3 fresnel, SurfaceData surface, vec3 albedo, + float scattering, vec4 midNormalCurvature, vec4 lowNormalCurvature) { + if (scattering * isScatteringEnabled() > 0.0) { + vec3 brdf = evalSkinBRDF(surface.lightDir, surface.normal, midNormalCurvature.xyz, lowNormalCurvature.xyz, lowNormalCurvature.w); + diffuse = mix(vec3(surface.ndotl), brdf, scattering); + + // Specular Lighting + vec2 specularBrdf = skinSpecular(surface, 1.0); + + diffuse *= specularBrdf.y; + specular = vec3(specularBrdf.x); + } else { + vec4 shading = evalPBRShading(metallic, fresnel, surface); + diffuse = vec3(shading.w); + specular = shading.xyz; + } + diffuse *= mix(vec3(1.0), albedo, isAlbedoEnabled()); +} + + +void evalFragShadingScattering(out vec3 diffuse, out vec3 specular, + float metallic, vec3 fresnel, SurfaceData surface, vec3 albedo, + float scattering, vec4 midNormalCurvature, vec4 lowNormalCurvature) { + vec3 brdf = evalSkinBRDF(surface.lightDir, surface.normal, midNormalCurvature.xyz, lowNormalCurvature.xyz, lowNormalCurvature.w); + float NdotL = surface.ndotl; + diffuse = mix(vec3(NdotL), brdf, scattering); + + // Specular Lighting + vec2 specularBrdf = skinSpecular(surface, 1.0); + + diffuse *= specularBrdf.y; + specular = vec3(specularBrdf.x); + diffuse *= mix(vec3(1.0), albedo, isAlbedoEnabled()); +} + +void evalFragShadingGloss(out vec3 diffuse, out vec3 specular, + float metallic, vec3 fresnel, SurfaceData surface, vec3 albedo) { + vec4 shading = evalPBRShading(metallic, fresnel, surface); + diffuse = vec3(shading.w); + diffuse *= mix(vec3(1.0), albedo, isAlbedoEnabled()); + specular = shading.xyz; +} + +vec3 evalSpecularWithOpacity(vec3 specular, float opacity) { + return specular / opacity; +} + +<@if not GETFRESNEL0@> +<@def GETFRESNEL0@> +vec3 getFresnelF0(float metallic, vec3 metalF0) { + // Enable continuous metallness value by lerping between dielectric + // and metal fresnel F0 value based on the "metallic" parameter + return mix(vec3(0.03), metalF0, metallic); +} +<@endif@> + +<@endif@> diff --git a/libraries/render-utils/src/Shadow.slh b/libraries/render-utils/src/Shadow.slh index 235ea519ab..85a30d5889 100644 --- a/libraries/render-utils/src/Shadow.slh +++ b/libraries/render-utils/src/Shadow.slh @@ -11,13 +11,15 @@ <@if not SHADOW_SLH@> <@def SHADOW_SLH@> +<@include render-utils/ShaderConstants.h@> <@include ShadowCore.slh@> +#define SHADOW_DITHER 1 #define SHADOW_NOISE_ENABLED 0 #define SHADOW_SCREEN_SPACE_DITHER 1 // the shadow texture -uniform sampler2DArrayShadow shadowMaps; +layout(binding=RENDER_UTILS_TEXTURE_SHADOW) uniform sampler2DArrayShadow shadowMaps; // Sample the shadowMap with PCF (built-in) float fetchShadow(int cascadeIndex, vec3 shadowTexcoord) { @@ -31,10 +33,12 @@ vec2 PCFkernel[4] = vec2[4]( vec2(0.5, -1.5) ); +#if SHADOW_NOISE_ENABLED float evalShadowNoise(vec4 seed) { float dot_product = dot(seed, vec4(12.9898,78.233,45.164,94.673)); return fract(sin(dot_product) * 43758.5453); } +#endif struct ShadowSampleOffsets { vec3 points[4]; @@ -73,12 +77,16 @@ ShadowSampleOffsets evalShadowFilterOffsets(vec4 position) { float evalShadowAttenuationPCF(int cascadeIndex, ShadowSampleOffsets offsets, vec4 shadowTexcoord, float bias) { shadowTexcoord.z -= bias; +#if SHADOW_DITHER float shadowAttenuation = 0.25 * ( fetchShadow(cascadeIndex, shadowTexcoord.xyz + offsets.points[0]) + fetchShadow(cascadeIndex, shadowTexcoord.xyz + offsets.points[1]) + fetchShadow(cascadeIndex, shadowTexcoord.xyz + offsets.points[2]) + fetchShadow(cascadeIndex, shadowTexcoord.xyz + offsets.points[3]) ); +#else + float shadowAttenuation = fetchShadow(cascadeIndex, shadowTexcoord.xyz); +#endif return shadowAttenuation; } @@ -89,20 +97,55 @@ float evalShadowCascadeAttenuation(int cascadeIndex, ShadowSampleOffsets offsets float evalShadowAttenuation(vec3 worldLightDir, vec4 worldPosition, float viewDepth, vec3 worldNormal) { ShadowSampleOffsets offsets = evalShadowFilterOffsets(worldPosition); - vec4 cascadeShadowCoords[2]; - cascadeShadowCoords[0] = vec4(0); - cascadeShadowCoords[1] = vec4(0); - ivec2 cascadeIndices; - float cascadeMix = determineShadowCascadesOnPixel(worldPosition, viewDepth, cascadeShadowCoords, cascadeIndices); - - // Adjust bias if we are at a grazing angle with light + + vec4 cascadeShadowCoords[4]; + vec4 cascadeWeights; + vec4 cascadeAttenuations = vec4(1.0); + vec3 cascadeMix; + bvec4 isPixelOnCascade; + int cascadeIndex; float oneMinusNdotL = 1.0 - clamp(dot(worldLightDir, worldNormal), 0, 1); - vec2 cascadeAttenuations = vec2(1.0, 1.0); - cascadeAttenuations.x = evalShadowCascadeAttenuation(cascadeIndices.x, offsets, cascadeShadowCoords[0], oneMinusNdotL); - if (cascadeMix > 0.0 && cascadeIndices.y < getShadowCascadeCount()) { - cascadeAttenuations.y = evalShadowCascadeAttenuation(cascadeIndices.y, offsets, cascadeShadowCoords[1], oneMinusNdotL); + + for (cascadeIndex=0 ; cascadeIndex -layout(std140) uniform shadowTransformBuffer { +layout(std140, binding=RENDER_UTILS_BUFFER_SHADOW_PARAMS) uniform shadowTransformBuffer { ShadowParameters shadow; }; @@ -79,6 +79,10 @@ float evalShadowCascadeWeight(vec4 cascadeTexCoords) { return clamp(blend * getShadowCascadeInvBlendWidth(), 0.0, 1.0); } +float evalCascadeMix(float firstCascadeWeight, float secondCascadeWeight) { + return ((1.0-firstCascadeWeight) * secondCascadeWeight) / (firstCascadeWeight + secondCascadeWeight); +} + float determineShadowCascadesOnPixel(vec4 worldPosition, float viewDepth, out vec4 cascadeShadowCoords[2], out ivec2 cascadeIndices) { cascadeIndices.x = getFirstShadowCascadeOnPixel(0, worldPosition, cascadeShadowCoords[0]); cascadeIndices.y = cascadeIndices.x+1; @@ -88,7 +92,7 @@ float determineShadowCascadesOnPixel(vec4 worldPosition, float viewDepth, out ve float secondCascadeWeight = evalShadowCascadeWeight(cascadeShadowCoords[1]); // Returns the mix amount between first and second cascade. - return ((1.0-firstCascadeWeight) * secondCascadeWeight) / (firstCascadeWeight + secondCascadeWeight); + return evalCascadeMix(firstCascadeWeight, secondCascadeWeight); } else { return 0.0; } diff --git a/libraries/render-utils/src/Skinning.slh b/libraries/render-utils/src/Skinning.slh index fbfe6b7185..a35948f026 100644 --- a/libraries/render-utils/src/Skinning.slh +++ b/libraries/render-utils/src/Skinning.slh @@ -11,12 +11,14 @@ <@if not SKINNING_SLH@> <@def SKINNING_SLH@> +<@include graphics/ShaderConstants.h@> + const int MAX_CLUSTERS = 128; const int INDICES_PER_VERTEX = 4; <@func declareUseDualQuaternionSkinning(USE_DUAL_QUATERNION_SKINNING)@> -layout(std140) uniform skinClusterBuffer { +layout(std140, binding=GRAPHICS_BUFFER_SKINNING) uniform skinClusterBuffer { mat4 clusterMatrices[MAX_CLUSTERS]; }; diff --git a/libraries/render-utils/src/SoftAttachmentModel.cpp b/libraries/render-utils/src/SoftAttachmentModel.cpp index 079e6f75ef..b9a6581f1d 100644 --- a/libraries/render-utils/src/SoftAttachmentModel.cpp +++ b/libraries/render-utils/src/SoftAttachmentModel.cpp @@ -77,8 +77,9 @@ void SoftAttachmentModel::updateClusterMatrices() { } // post the blender if we're not currently waiting for one to finish - if (geometry.hasBlendedMeshes() && _blendshapeCoefficients != _blendedBlendshapeCoefficients) { + auto modelBlender = DependencyManager::get(); + if (_blendedVertexBuffersInitialized && modelBlender->shouldComputeBlendshapes() && geometry.hasBlendedMeshes() && _blendshapeCoefficients != _blendedBlendshapeCoefficients) { _blendedBlendshapeCoefficients = _blendshapeCoefficients; - DependencyManager::get()->noteRequiresBlend(getThisPointer()); + modelBlender->noteRequiresBlend(getThisPointer()); } } diff --git a/libraries/render-utils/src/StencilMaskPass.cpp b/libraries/render-utils/src/StencilMaskPass.cpp index b9b8274039..556e305fac 100644 --- a/libraries/render-utils/src/StencilMaskPass.cpp +++ b/libraries/render-utils/src/StencilMaskPass.cpp @@ -13,11 +13,7 @@ #include #include - - -#include - -#include "stencil_drawMask_frag.h" +#include using namespace render; @@ -43,11 +39,7 @@ graphics::MeshPointer PrepareStencil::getMesh() { gpu::PipelinePointer PrepareStencil::getMeshStencilPipeline() { if (!_meshStencilPipeline) { - auto vs = gpu::StandardShaderLib::getDrawVertexPositionVS(); - auto ps = gpu::StandardShaderLib::getDrawNadaPS(); - auto program = gpu::Shader::createProgram(vs, ps); - gpu::Shader::makeProgram((*program)); - + auto program = gpu::Shader::createProgram(shader::gpu::program::drawNothing); auto state = std::make_shared(); drawMask(*state); state->setColorWriteMask(0); @@ -59,11 +51,7 @@ gpu::PipelinePointer PrepareStencil::getMeshStencilPipeline() { gpu::PipelinePointer PrepareStencil::getPaintStencilPipeline() { if (!_paintStencilPipeline) { - auto vs = gpu::StandardShaderLib::getDrawUnitQuadTexcoordVS(); - auto ps = stencil_drawMask_frag::getShader(); - auto program = gpu::Shader::createProgram(vs, ps); - gpu::Shader::makeProgram((*program)); - + auto program = gpu::Shader::createProgram(shader::render_utils::program::stencil_drawMask); auto state = std::make_shared(); drawMask(*state); state->setColorWriteMask(0); @@ -151,4 +139,4 @@ void PrepareStencil::testMaskDrawShape(gpu::State& state) { void PrepareStencil::testMaskDrawShapeNoAA(gpu::State& state) { state.setStencilTest(true, STENCIL_SHAPE | STENCIL_NO_AA, gpu::State::StencilTest(STENCIL_MASK | STENCIL_SHAPE | STENCIL_NO_AA, STENCIL_MASK, gpu::NOT_EQUAL, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_REPLACE)); -} \ No newline at end of file +} diff --git a/libraries/render-utils/src/SubsurfaceScattering.cpp b/libraries/render-utils/src/SubsurfaceScattering.cpp index 1ffa0d2e5c..84b51d626a 100644 --- a/libraries/render-utils/src/SubsurfaceScattering.cpp +++ b/libraries/render-utils/src/SubsurfaceScattering.cpp @@ -11,35 +11,27 @@ #include "SubsurfaceScattering.h" #include -#include +#include +#include +#include + +#include "render-utils/ShaderConstants.h" #include "FramebufferCache.h" #include "DeferredLightingEffect.h" -#include "subsurfaceScattering_makeProfile_frag.h" -#include "subsurfaceScattering_makeLUT_frag.h" -#include "subsurfaceScattering_makeSpecularBeckmann_frag.h" -#include "subsurfaceScattering_drawScattering_frag.h" +namespace ru { + using render_utils::slot::texture::Texture; + using render_utils::slot::buffer::Buffer; +} -enum ScatteringShaderBufferSlots { - ScatteringTask_FrameTransformSlot = 0, - ScatteringTask_ParamSlot, - ScatteringTask_LightSlot, -}; -enum ScatteringShaderMapSlots { - ScatteringTask_ScatteringTableSlot = 0, - ScatteringTask_CurvatureMapSlot, - ScatteringTask_DiffusedCurvatureMapSlot, - ScatteringTask_NormalMapSlot, +namespace gr { + using graphics::slot::texture::Texture; + using graphics::slot::buffer::Buffer; +} - ScatteringTask_AlbedoMapSlot, - ScatteringTask_LinearMapSlot, - - ScatteringTask_IBLMapSlot, - -}; SubsurfaceScatteringResource::SubsurfaceScatteringResource() { Parameters parameters; @@ -307,9 +299,7 @@ void diffuseProfileGPU(gpu::TexturePointer& profileMap, RenderArgs* args) { gpu::PipelinePointer makePipeline; { - auto vs = gpu::StandardShaderLib::getDrawUnitQuadTexcoordVS(); - auto ps = subsurfaceScattering_makeProfile_frag::getShader(); - gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); + gpu::ShaderPointer program = gpu::Shader::createProgram(shader::render_utils::program::subsurfaceScattering_makeProfile); gpu::StatePointer state = gpu::StatePointer(new gpu::State()); @@ -338,30 +328,18 @@ void diffuseScatterGPU(const gpu::TexturePointer& profileMap, gpu::TexturePointe int width = lut->getWidth(); int height = lut->getHeight(); - gpu::PipelinePointer makePipeline; - - auto vs = gpu::StandardShaderLib::getDrawUnitQuadTexcoordVS(); - auto ps = subsurfaceScattering_makeLUT_frag::getShader(); - gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); + gpu::ShaderPointer program = gpu::Shader::createProgram(shader::render_utils::program::subsurfaceScattering_makeLUT); gpu::StatePointer state = gpu::StatePointer(new gpu::State()); - makePipeline = gpu::Pipeline::create(program, state); + gpu::PipelinePointer makePipeline = gpu::Pipeline::create(program, state); auto makeFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("diffuseScatter")); makeFramebuffer->setRenderBuffer(0, lut); gpu::doInBatch("SubsurfaceScattering::diffuseScatterGPU", args->_context, [=](gpu::Batch& batch) { batch.enableStereo(false); - - batch.runLambda([program] (){ - gpu::Shader::BindingSet slotBindings; - slotBindings.insert(gpu::Shader::Binding(std::string("scatteringProfile"), 0)); - gpu::Shader::makeProgram(*program, slotBindings); - }); - batch.setViewportTransform(glm::ivec4(0, 0, width, height)); - batch.setFramebuffer(makeFramebuffer); batch.setPipeline(makePipeline); batch.setResourceTexture(0, profileMap); @@ -379,9 +357,7 @@ void computeSpecularBeckmannGPU(gpu::TexturePointer& beckmannMap, RenderArgs* ar gpu::PipelinePointer makePipeline; { - auto vs = gpu::StandardShaderLib::getDrawUnitQuadTexcoordVS(); - auto ps = subsurfaceScattering_makeSpecularBeckmann_frag::getShader(); - gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); + gpu::ShaderPointer program = gpu::Shader::createProgram(shader::render_utils::program::subsurfaceScattering_makeSpecularBeckmann); gpu::StatePointer state = gpu::StatePointer(new gpu::State()); @@ -451,27 +427,7 @@ void DebugSubsurfaceScattering::configure(const Config& config) { gpu::PipelinePointer DebugSubsurfaceScattering::getScatteringPipeline() { if (!_scatteringPipeline) { - auto vs = gpu::StandardShaderLib::getDrawUnitQuadTexcoordVS(); - auto ps = subsurfaceScattering_drawScattering_frag::getShader(); - gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); - - gpu::Shader::BindingSet slotBindings; - slotBindings.insert(gpu::Shader::Binding(std::string("deferredFrameTransformBuffer"), ScatteringTask_FrameTransformSlot)); - slotBindings.insert(gpu::Shader::Binding(std::string("scatteringParamsBuffer"), ScatteringTask_ParamSlot)); - slotBindings.insert(gpu::Shader::Binding(std::string("keyLightBuffer"), ScatteringTask_LightSlot)); - - slotBindings.insert(gpu::Shader::Binding(std::string("scatteringLUT"), ScatteringTask_ScatteringTableSlot)); - slotBindings.insert(gpu::Shader::Binding(std::string("curvatureMap"), ScatteringTask_CurvatureMapSlot)); - slotBindings.insert(gpu::Shader::Binding(std::string("diffusedCurvatureMap"), ScatteringTask_DiffusedCurvatureMapSlot)); - slotBindings.insert(gpu::Shader::Binding(std::string("normalMap"), ScatteringTask_NormalMapSlot)); - - slotBindings.insert(gpu::Shader::Binding(std::string("albedoMap"), ScatteringTask_AlbedoMapSlot)); - slotBindings.insert(gpu::Shader::Binding(std::string("linearDepthMap"), ScatteringTask_LinearMapSlot)); - - slotBindings.insert(gpu::Shader::Binding(std::string("skyboxMap"), ScatteringTask_IBLMapSlot)); - - gpu::Shader::makeProgram(*program, slotBindings); - + gpu::ShaderPointer program = gpu::Shader::createProgram(shader::render_utils::program::subsurfaceScattering_drawScattering); gpu::StatePointer state = gpu::StatePointer(new gpu::State()); _scatteringPipeline = gpu::Pipeline::create(program, state); @@ -480,19 +436,12 @@ gpu::PipelinePointer DebugSubsurfaceScattering::getScatteringPipeline() { return _scatteringPipeline; } - gpu::PipelinePointer _showLUTPipeline; -gpu::PipelinePointer getShowLUTPipeline(); + gpu::PipelinePointer DebugSubsurfaceScattering::getShowLUTPipeline() { if (!_showLUTPipeline) { - auto vs = gpu::StandardShaderLib::getDrawUnitQuadTexcoordVS(); - auto ps = gpu::StandardShaderLib::getDrawTextureOpaquePS(); - gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); - - gpu::Shader::BindingSet slotBindings; - + gpu::ShaderPointer program = gpu::Shader::createProgram(shader::gpu::program::drawUnitQuatTextureOpaque); gpu::StatePointer state = gpu::StatePointer(new gpu::State()); - _showLUTPipeline = gpu::Pipeline::create(program, state); } @@ -561,20 +510,20 @@ void DebugSubsurfaceScattering::run(const render::RenderContextPointer& renderCo model.setScale(glm::vec3(viewportSize / (float)args->_viewport.z, viewportSize / (float)args->_viewport.w, 1.0)); batch.setModelTransform(model); - batch.setUniformBuffer(ScatteringTask_FrameTransformSlot, frameTransform->getFrameTransformBuffer()); - batch.setUniformBuffer(ScatteringTask_ParamSlot, scatteringResource->getParametersBuffer()); + batch.setUniformBuffer(ru::Buffer::DeferredFrameTransform, frameTransform->getFrameTransformBuffer()); + batch.setUniformBuffer(ru::Buffer::SsscParams, scatteringResource->getParametersBuffer()); if (light) { - batch.setUniformBuffer(ScatteringTask_LightSlot, light->getLightSchemaBuffer()); + batch.setUniformBuffer(gr::Buffer::Light, light->getLightSchemaBuffer()); } - batch.setResourceTexture(ScatteringTask_ScatteringTableSlot, scatteringTable); - batch.setResourceTexture(ScatteringTask_CurvatureMapSlot, curvatureFramebuffer->getRenderBuffer(0)); - batch.setResourceTexture(ScatteringTask_DiffusedCurvatureMapSlot, diffusedFramebuffer->getRenderBuffer(0)); - batch.setResourceTexture(ScatteringTask_NormalMapSlot, deferredFramebuffer->getDeferredNormalTexture()); - batch.setResourceTexture(ScatteringTask_AlbedoMapSlot, deferredFramebuffer->getDeferredColorTexture()); - batch.setResourceTexture(ScatteringTask_LinearMapSlot, linearDepthTexture); + batch.setResourceTexture(ru::Texture::SsscLut, scatteringTable); + batch.setResourceTexture(ru::Texture::DeferredCurvature, curvatureFramebuffer->getRenderBuffer(0)); + batch.setResourceTexture(ru::Texture::DeferredDiffusedCurvature, diffusedFramebuffer->getRenderBuffer(0)); + batch.setResourceTexture(ru::Texture::DeferredNormal, deferredFramebuffer->getDeferredNormalTexture()); + batch.setResourceTexture(ru::Texture::DeferredColor, deferredFramebuffer->getDeferredColorTexture()); + batch.setResourceTexture(ru::Texture::DeferredDepth, linearDepthTexture); - batch._glUniform2f(debugScatteringPipeline->getProgram()->getUniforms().findLocation("uniformCursorTexcoord"), _debugCursorTexcoord.x, _debugCursorTexcoord.y); + batch._glUniform2f(gpu::slot::uniform::Extra0, _debugCursorTexcoord.x, _debugCursorTexcoord.y); batch.draw(gpu::TRIANGLE_STRIP, 4); } } diff --git a/libraries/render-utils/src/SubsurfaceScattering.slh b/libraries/render-utils/src/SubsurfaceScattering.slh index 233dfd7a0c..3d37f52e4d 100644 --- a/libraries/render-utils/src/SubsurfaceScattering.slh +++ b/libraries/render-utils/src/SubsurfaceScattering.slh @@ -9,6 +9,8 @@ <@if not SUBSURFACE_SCATTERING_SLH@> <@def SUBSURFACE_SCATTERING_SLH@> +<@include render-utils/ShaderConstants.h@> + <@func declareSubsurfaceScatteringProfileSource()@> float gaussian(float v, float r) { @@ -54,7 +56,7 @@ vec3 generateProfile(vec2 uv) { <@func declareSubsurfaceScatteringProfileMap()@> -uniform sampler2D scatteringProfile; +layout(binding=RENDER_UTILS_TEXTURE_SSSC_PROFILE) uniform sampler2D scatteringProfile; vec3 scatter(float r) { return texture(scatteringProfile, vec2(r * 0.5, 0.5)).rgb; @@ -102,7 +104,7 @@ vec3 integrate(float cosTheta, float skinRadius) { <@func declareSubsurfaceScatteringResource()@> -uniform sampler2D scatteringLUT; +layout(binding=RENDER_UTILS_TEXTURE_SSSC_LUT) uniform sampler2D scatteringLUT; vec3 fetchBRDF(float LdotN, float curvature) { return texture(scatteringLUT, vec2( clamp(LdotN * 0.5 + 0.5, 0.0, 1.0), clamp(2.0 * curvature, 0.0, 1.0))).xyz; @@ -122,7 +124,7 @@ struct ScatteringParameters { vec4 debugFlags; }; -uniform subsurfaceScatteringParametersBuffer { +layout(binding=RENDER_UTILS_BUFFER_SSSC_PARAMS) uniform subsurfaceScatteringParametersBuffer { ScatteringParameters parameters; }; diff --git a/libraries/render-utils/src/SurfaceGeometryPass.cpp b/libraries/render-utils/src/SurfaceGeometryPass.cpp index 51046f10b3..d32cba43db 100644 --- a/libraries/render-utils/src/SurfaceGeometryPass.cpp +++ b/libraries/render-utils/src/SurfaceGeometryPass.cpp @@ -13,29 +13,21 @@ #include #include -#include +#include + #include "StencilMaskPass.h" -const int DepthLinearPass_FrameTransformSlot = 0; -const int DepthLinearPass_DepthMapSlot = 0; -const int DepthLinearPass_NormalMapSlot = 1; - -const int SurfaceGeometryPass_FrameTransformSlot = 0; -const int SurfaceGeometryPass_ParamsSlot = 1; -const int SurfaceGeometryPass_DepthMapSlot = 0; -const int SurfaceGeometryPass_NormalMapSlot = 1; - -#include "surfaceGeometry_makeLinearDepth_frag.h" -#include "surfaceGeometry_downsampleDepthNormal_frag.h" - -#include "surfaceGeometry_makeCurvature_frag.h" +#include "render-utils/ShaderConstants.h" +namespace ru { + using render_utils::slot::texture::Texture; + using render_utils::slot::buffer::Buffer; +} LinearDepthFramebuffer::LinearDepthFramebuffer() { } - void LinearDepthFramebuffer::updatePrimaryDepth(const gpu::TexturePointer& depthBuffer) { //If the depth buffer or size changed, we need to delete our FBOs bool reset = false; @@ -183,21 +175,21 @@ void LinearDepthPass::run(const render::RenderContextPointer& renderContext, con batch.resetViewTransform(); batch.setModelTransform(gpu::Framebuffer::evalSubregionTexcoordTransform(_linearDepthFramebuffer->getDepthFrameSize(), depthViewport)); - batch.setUniformBuffer(DepthLinearPass_FrameTransformSlot, frameTransform->getFrameTransformBuffer()); + batch.setUniformBuffer(ru::Buffer::DeferredFrameTransform, frameTransform->getFrameTransformBuffer()); // LinearDepth batch.setFramebuffer(linearDepthFBO); batch.clearColorFramebuffer(gpu::Framebuffer::BUFFER_COLOR0, glm::vec4(clearLinearDepth, 0.0f, 0.0f, 0.0f)); batch.setPipeline(linearDepthPipeline); - batch.setResourceTexture(DepthLinearPass_DepthMapSlot, depthBuffer); + batch.setResourceTexture(ru::Texture::SurfaceGeometryDepth, depthBuffer); batch.draw(gpu::TRIANGLE_STRIP, 4); // Downsample batch.setViewportTransform(halfViewport); batch.setFramebuffer(downsampleFBO); - batch.setResourceTexture(DepthLinearPass_DepthMapSlot, linearDepthTexture); - batch.setResourceTexture(DepthLinearPass_NormalMapSlot, normalTexture); + batch.setResourceTexture(ru::Texture::SurfaceGeometryDepth, linearDepthTexture); + batch.setResourceTexture(ru::Texture::SurfaceGeometryNormal, normalTexture); batch.setPipeline(downsamplePipeline); batch.draw(gpu::TRIANGLE_STRIP, 4); @@ -212,9 +204,7 @@ void LinearDepthPass::run(const render::RenderContextPointer& renderContext, con const gpu::PipelinePointer& LinearDepthPass::getLinearDepthPipeline(const render::RenderContextPointer& renderContext) { gpu::ShaderPointer program; if (!_linearDepthPipeline) { - auto vs = gpu::StandardShaderLib::getDrawViewportQuadTransformTexcoordVS(); - auto ps = surfaceGeometry_makeLinearDepth_frag::getShader(); - program = gpu::Shader::createProgram(vs, ps); + program = gpu::Shader::createProgram(shader::render_utils::program::surfaceGeometry_makeLinearDepth); gpu::StatePointer state = gpu::StatePointer(new gpu::State()); @@ -225,15 +215,6 @@ const gpu::PipelinePointer& LinearDepthPass::getLinearDepthPipeline(const render // Good to go add the brand new pipeline _linearDepthPipeline = gpu::Pipeline::create(program, state); - - gpu::doInBatch("LinearDepthPass::run", renderContext->args->_context, [program](gpu::Batch& batch) { - batch.runLambda([program]() { - gpu::Shader::BindingSet slotBindings; - slotBindings.insert(gpu::Shader::Binding(std::string("deferredFrameTransformBuffer"), DepthLinearPass_FrameTransformSlot)); - slotBindings.insert(gpu::Shader::Binding(std::string("depthMap"), DepthLinearPass_DepthMapSlot)); - gpu::Shader::makeProgram(*program, slotBindings); - }); - }); } @@ -243,9 +224,7 @@ const gpu::PipelinePointer& LinearDepthPass::getLinearDepthPipeline(const render const gpu::PipelinePointer& LinearDepthPass::getDownsamplePipeline(const render::RenderContextPointer& renderContext) { if (!_downsamplePipeline) { - auto vs = gpu::StandardShaderLib::getDrawViewportQuadTransformTexcoordVS(); - auto ps = surfaceGeometry_downsampleDepthNormal_frag::getShader(); - gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); + gpu::ShaderPointer program = gpu::Shader::createProgram(shader::render_utils::program::surfaceGeometry_downsampleDepthNormal); gpu::StatePointer state = gpu::StatePointer(new gpu::State()); PrepareStencil::testShape(*state); @@ -254,16 +233,6 @@ const gpu::PipelinePointer& LinearDepthPass::getDownsamplePipeline(const render: // Good to go add the brand new pipeline _downsamplePipeline = gpu::Pipeline::create(program, state); - - gpu::doInBatch("LinearDepthPass::run", renderContext->args->_context, [program](gpu::Batch& batch) { - batch.runLambda([program]() { - gpu::Shader::BindingSet slotBindings; - slotBindings.insert(gpu::Shader::Binding("deferredFrameTransformBuffer", DepthLinearPass_FrameTransformSlot)); - slotBindings.insert(gpu::Shader::Binding("linearDepthMap", DepthLinearPass_DepthMapSlot)); - slotBindings.insert(gpu::Shader::Binding("normalMap", DepthLinearPass_NormalMapSlot)); - gpu::Shader::makeProgram(*program, slotBindings); - }); - }); } return _downsamplePipeline; @@ -485,8 +454,8 @@ void SurfaceGeometryPass::run(const render::RenderContextPointer& renderContext, batch.setModelTransform(gpu::Framebuffer::evalSubregionTexcoordTransform(_surfaceGeometryFramebuffer->getSourceFrameSize(), curvatureViewport)); // Curvature pass - batch.setUniformBuffer(SurfaceGeometryPass_FrameTransformSlot, frameTransform->getFrameTransformBuffer()); - batch.setUniformBuffer(SurfaceGeometryPass_ParamsSlot, _parametersBuffer); + batch.setUniformBuffer(ru::Buffer::DeferredFrameTransform, frameTransform->getFrameTransformBuffer()); + batch.setUniformBuffer(ru::Buffer::SurfaceGeometryParams, _parametersBuffer); batch.setFramebuffer(curvatureFramebuffer); // We can avoid the clear by drawing the same clear vallue from the makeCurvature shader. same performances or no worse #ifdef USE_STENCIL_TEST @@ -494,47 +463,44 @@ void SurfaceGeometryPass::run(const render::RenderContextPointer& renderContext, batch.clearColorFramebuffer(gpu::Framebuffer::BUFFER_COLOR0, glm::vec4(0.0)); #endif batch.setPipeline(curvaturePipeline); - batch.setResourceTexture(SurfaceGeometryPass_DepthMapSlot, linearDepthTexture); - batch.setResourceTexture(SurfaceGeometryPass_NormalMapSlot, normalTexture); + batch.setResourceTexture(ru::Texture::SurfaceGeometryDepth, linearDepthTexture); + batch.setResourceTexture(ru::Texture::SurfaceGeometryNormal, normalTexture); batch.draw(gpu::TRIANGLE_STRIP, 4); - batch.setResourceTexture(SurfaceGeometryPass_DepthMapSlot, nullptr); - batch.setResourceTexture(SurfaceGeometryPass_NormalMapSlot, nullptr); - batch.setUniformBuffer(SurfaceGeometryPass_ParamsSlot, nullptr); - batch.setUniformBuffer(SurfaceGeometryPass_FrameTransformSlot, nullptr); + batch.setResourceTexture(ru::Texture::SurfaceGeometryDepth, nullptr); + batch.setResourceTexture(ru::Texture::SurfaceGeometryNormal, nullptr); + batch.setUniformBuffer(ru::Buffer::SurfaceGeometryParams, nullptr); + batch.setUniformBuffer(ru::Buffer::DeferredFrameTransform, nullptr); // Diffusion pass - const int BlurTask_ParamsSlot = 0; - const int BlurTask_SourceSlot = 0; - const int BlurTask_DepthSlot = 1; - batch.setUniformBuffer(BlurTask_ParamsSlot, _diffusePass.getParameters()->_parametersBuffer); + batch.setUniformBuffer(ru::Buffer::BlurParams, _diffusePass.getParameters()->_parametersBuffer); - batch.setResourceTexture(BlurTask_DepthSlot, linearDepthTexture); + batch.setResourceTexture(ru::Texture::BlurDepth, linearDepthTexture); batch.setFramebuffer(blurringFramebuffer); batch.setPipeline(diffuseVPipeline); - batch.setResourceTexture(BlurTask_SourceSlot, curvatureTexture); + batch.setResourceTexture(ru::Texture::BlurSource, curvatureTexture); batch.draw(gpu::TRIANGLE_STRIP, 4); batch.setFramebuffer(curvatureFramebuffer); batch.setPipeline(diffuseHPipeline); - batch.setResourceTexture(BlurTask_SourceSlot, blurringTexture); + batch.setResourceTexture(ru::Texture::BlurSource, blurringTexture); batch.draw(gpu::TRIANGLE_STRIP, 4); batch.setFramebuffer(blurringFramebuffer); batch.setPipeline(diffuseVPipeline); - batch.setResourceTexture(BlurTask_SourceSlot, curvatureTexture); + batch.setResourceTexture(ru::Texture::BlurSource, curvatureTexture); batch.draw(gpu::TRIANGLE_STRIP, 4); batch.setFramebuffer(lowCurvatureFramebuffer); batch.setPipeline(diffuseHPipeline); - batch.setResourceTexture(BlurTask_SourceSlot, blurringTexture); + batch.setResourceTexture(ru::Texture::BlurSource, blurringTexture); batch.draw(gpu::TRIANGLE_STRIP, 4); - batch.setResourceTexture(BlurTask_SourceSlot, nullptr); - batch.setResourceTexture(BlurTask_DepthSlot, nullptr); - batch.setUniformBuffer(BlurTask_ParamsSlot, nullptr); + batch.setResourceTexture(ru::Texture::BlurSource, nullptr); + batch.setResourceTexture(ru::Texture::BlurDepth, nullptr); + batch.setUniformBuffer(ru::Buffer::BlurParams, nullptr); _gpuTimer->end(batch); }); @@ -546,9 +512,7 @@ void SurfaceGeometryPass::run(const render::RenderContextPointer& renderContext, const gpu::PipelinePointer& SurfaceGeometryPass::getCurvaturePipeline(const render::RenderContextPointer& renderContext) { if (!_curvaturePipeline) { - auto vs = gpu::StandardShaderLib::getDrawViewportQuadTransformTexcoordVS(); - auto ps = surfaceGeometry_makeCurvature_frag::getShader(); - gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); + gpu::ShaderPointer program = gpu::Shader::createProgram(shader::render_utils::program::surfaceGeometry_makeCurvature); gpu::StatePointer state = gpu::StatePointer(new gpu::State()); @@ -558,17 +522,6 @@ const gpu::PipelinePointer& SurfaceGeometryPass::getCurvaturePipeline(const rend #endif // Good to go add the brand new pipeline _curvaturePipeline = gpu::Pipeline::create(program, state); - - gpu::doInBatch("SurfaceGeometryPass::CurvaturePipeline", renderContext->args->_context, [program](gpu::Batch& batch) { - batch.runLambda([program]() { - gpu::Shader::BindingSet slotBindings; - slotBindings.insert(gpu::Shader::Binding(std::string("deferredFrameTransformBuffer"), SurfaceGeometryPass_FrameTransformSlot)); - slotBindings.insert(gpu::Shader::Binding(std::string("surfaceGeometryParamsBuffer"), SurfaceGeometryPass_ParamsSlot)); - slotBindings.insert(gpu::Shader::Binding(std::string("depthMap"), SurfaceGeometryPass_DepthMapSlot)); - slotBindings.insert(gpu::Shader::Binding(std::string("normalMap"), SurfaceGeometryPass_NormalMapSlot)); - gpu::Shader::makeProgram(*program, slotBindings); - }); - }); } return _curvaturePipeline; diff --git a/libraries/render-utils/src/TextRenderer3D.cpp b/libraries/render-utils/src/TextRenderer3D.cpp index 9c85952107..dd9167d248 100644 --- a/libraries/render-utils/src/TextRenderer3D.cpp +++ b/libraries/render-utils/src/TextRenderer3D.cpp @@ -19,15 +19,13 @@ #include #include +#include #include "text/Font.h" #include "GLMHelpers.h" #include "MatrixStack.h" #include "RenderUtilsLogging.h" -#include "sdf_text3D_vert.h" -#include "sdf_text3D_frag.h" - #include "GeometryCache.h" const float TextRenderer3D::DEFAULT_POINT_SIZE = 12; diff --git a/libraries/render-utils/src/ToneMappingEffect.cpp b/libraries/render-utils/src/ToneMappingEffect.cpp index 12152fd6ba..d192266d7e 100644 --- a/libraries/render-utils/src/ToneMappingEffect.cpp +++ b/libraries/render-utils/src/ToneMappingEffect.cpp @@ -12,15 +12,12 @@ #include "ToneMappingEffect.h" #include -#include +#include +#include "render-utils/ShaderConstants.h" #include "StencilMaskPass.h" #include "FramebufferCache.h" -#include "toneMapping_frag.h" - -const int ToneMappingEffect_ParamsSlot = 0; -const int ToneMappingEffect_LightingMapSlot = 0; ToneMappingEffect::ToneMappingEffect() { Parameters parameters; @@ -28,23 +25,11 @@ ToneMappingEffect::ToneMappingEffect() { } void ToneMappingEffect::init(RenderArgs* args) { - auto blitPS = toneMapping_frag::getShader(); - - auto blitVS = gpu::StandardShaderLib::getDrawViewportQuadTransformTexcoordVS(); - auto blitProgram = gpu::ShaderPointer(gpu::Shader::createProgram(blitVS, blitPS)); + auto blitProgram = gpu::Shader::createProgram(shader::render_utils::program::toneMapping); auto blitState = std::make_shared(); blitState->setColorWriteMask(true, true, true, true); _blitLightBuffer = gpu::PipelinePointer(gpu::Pipeline::create(blitProgram, blitState)); - - gpu::doInBatch("ToneMappingEffect::toneMapping", args->_context, [blitProgram](gpu::Batch& batch) { - batch.runLambda([blitProgram]() { - gpu::Shader::BindingSet slotBindings; - slotBindings.insert(gpu::Shader::Binding(std::string("toneMappingParamsBuffer"), ToneMappingEffect_ParamsSlot)); - slotBindings.insert(gpu::Shader::Binding(std::string("colorMap"), ToneMappingEffect_LightingMapSlot)); - gpu::Shader::makeProgram(*blitProgram, slotBindings); - }); - }); } void ToneMappingEffect::setExposure(float exposure) { @@ -86,8 +71,8 @@ void ToneMappingEffect::render(RenderArgs* args, const gpu::TexturePointer& ligh batch.setModelTransform(gpu::Framebuffer::evalSubregionTexcoordTransform(framebufferSize, args->_viewport)); batch.setPipeline(_blitLightBuffer); - batch.setUniformBuffer(ToneMappingEffect_ParamsSlot, _parametersBuffer); - batch.setResourceTexture(ToneMappingEffect_LightingMapSlot, lightingBuffer); + batch.setUniformBuffer(render_utils::slot::buffer::ToneMappingParams, _parametersBuffer); + batch.setResourceTexture(render_utils::slot::texture::ToneMappingColor, lightingBuffer); batch.draw(gpu::TRIANGLE_STRIP, 4); }); } diff --git a/libraries/render-utils/src/UpdateSceneTask.cpp b/libraries/render-utils/src/UpdateSceneTask.cpp index e05f28ef0d..be61953073 100644 --- a/libraries/render-utils/src/UpdateSceneTask.cpp +++ b/libraries/render-utils/src/UpdateSceneTask.cpp @@ -14,6 +14,7 @@ #include "LightStage.h" #include "BackgroundStage.h" #include "HazeStage.h" +#include "BloomStage.h" #include #include #include "DeferredLightingEffect.h" @@ -22,6 +23,7 @@ void UpdateSceneTask::build(JobModel& task, const render::Varying& input, render task.addJob("LightStageSetup"); task.addJob("BackgroundStageSetup"); task.addJob("HazeStageSetup"); + task.addJob("BloomStageSetup"); task.addJob("TransitionStageSetup"); task.addJob("HighlightStageSetup"); diff --git a/libraries/render-utils/src/VelocityBufferPass.cpp b/libraries/render-utils/src/VelocityBufferPass.cpp index 3f7da4cdcd..5833089967 100644 --- a/libraries/render-utils/src/VelocityBufferPass.cpp +++ b/libraries/render-utils/src/VelocityBufferPass.cpp @@ -13,14 +13,15 @@ #include #include -#include +#include + #include "StencilMaskPass.h" +#include "render-utils/ShaderConstants.h" -const int VelocityBufferPass_FrameTransformSlot = 0; -const int VelocityBufferPass_DepthMapSlot = 0; - - -#include "velocityBuffer_cameraMotion_frag.h" +namespace ru { + using render_utils::slot::texture::Texture; + using render_utils::slot::buffer::Buffer; +} VelocityFramebuffer::VelocityFramebuffer() { } @@ -126,13 +127,13 @@ void VelocityBufferPass::run(const render::RenderContextPointer& renderContext, batch.resetViewTransform(); batch.setModelTransform(gpu::Framebuffer::evalSubregionTexcoordTransform(_velocityFramebuffer->getDepthFrameSize(), fullViewport)); - batch.setUniformBuffer(VelocityBufferPass_FrameTransformSlot, frameTransform->getFrameTransformBuffer()); + batch.setUniformBuffer(ru::Buffer::DeferredFrameTransform, frameTransform->getFrameTransformBuffer()); // Velocity buffer camera motion batch.setFramebuffer(velocityFBO); batch.clearColorFramebuffer(gpu::Framebuffer::BUFFER_COLOR0, glm::vec4(0.0f, 0.0f, 0.0f, 0.0f)); batch.setPipeline(cameraMotionPipeline); - batch.setResourceTexture(VelocityBufferPass_DepthMapSlot, depthBuffer); + batch.setResourceTexture(ru::Texture::TaaDepth, depthBuffer); batch.draw(gpu::TRIANGLE_STRIP, 4); _gpuTimer->end(batch); @@ -145,10 +146,7 @@ void VelocityBufferPass::run(const render::RenderContextPointer& renderContext, const gpu::PipelinePointer& VelocityBufferPass::getCameraMotionPipeline(const render::RenderContextPointer& renderContext) { if (!_cameraMotionPipeline) { - auto vs = gpu::StandardShaderLib::getDrawViewportQuadTransformTexcoordVS(); - auto ps = velocityBuffer_cameraMotion_frag::getShader(); - gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); - + gpu::ShaderPointer program = gpu::Shader::createProgram(shader::render_utils::program::velocityBuffer_cameraMotion); gpu::StatePointer state = gpu::StatePointer(new gpu::State()); // Stencil test the curvature pass for objects pixels only, not the background @@ -158,16 +156,6 @@ const gpu::PipelinePointer& VelocityBufferPass::getCameraMotionPipeline(const re // Good to go add the brand new pipeline _cameraMotionPipeline = gpu::Pipeline::create(program, state); - - gpu::doInBatch("VelocityBufferPass::CameraMotionPipeline", renderContext->args->_context, - [program](gpu::Batch& batch) { - batch.runLambda([program]() { - gpu::Shader::BindingSet slotBindings; - slotBindings.insert(gpu::Shader::Binding(std::string("deferredFrameTransformBuffer"), VelocityBufferPass_FrameTransformSlot)); - slotBindings.insert(gpu::Shader::Binding(std::string("depthMap"), VelocityBufferPass_DepthMapSlot)); - gpu::Shader::makeProgram(*program, slotBindings); - }); - }); } return _cameraMotionPipeline; diff --git a/libraries/render-utils/src/WorkloadResource.slh b/libraries/render-utils/src/WorkloadResource.slh new file mode 100644 index 0000000000..81b6ed78ce --- /dev/null +++ b/libraries/render-utils/src/WorkloadResource.slh @@ -0,0 +1,82 @@ +// +// Created by Sam Gateau on 7/31/2018. +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +<@if not WORKLOAD_RESOURCE_SLH@> +<@def WORKLOAD_RESOURCE_SLH@> + +<@include gpu/Color.slh@> +<$declareColorWheel()$> + +const vec4 REGION_COLOR[4] = vec4[4]( + vec4(0.0, 1.0, 0.0, 1.0), + vec4(1.0, 0.6, 0.0, 1.0), + vec4(1.0, 0.0, 0.0, 1.0), + vec4(0.3, 0.0, 0.8, 1.0) +); + +<@func declareWorkloadProxies() @> + +struct WorkloadProxy { + vec4 sphere; + vec4 region; +}; + +#if defined(GPU_GL410) +layout(binding=0) uniform samplerBuffer workloadProxiesBuffer; +WorkloadProxy getWorkloadProxy(int i) { + int offset = 2 * i; + WorkloadProxy proxy; + proxy.sphere = texelFetch(workloadProxiesBuffer, offset); + proxy.region = texelFetch(workloadProxiesBuffer, offset + 1); + return proxy; +} +#else +layout(std140, binding=0) buffer workloadProxiesBuffer { + WorkloadProxy _proxies[]; +}; +WorkloadProxy getWorkloadProxy(int i) { + WorkloadProxy proxy = _proxies[i]; + return proxy; +} +#endif + +<@endfunc@> + + +<@func declareWorkloadViews() @> + +struct WorkloadView { + vec4 direction_far; + vec4 fov; + vec4 origin; + vec4 backFront[2]; + vec4 regions[3]; +}; + +#if defined(GPU_GL410) +layout(binding=1) uniform samplerBuffer workloadViewsBuffer; +WorkloadView getWorkloadView(int i) { + int offset = 2 * i; + WorkloadView view; + view.origin = texelFetch(workloadViewsBuffer, offset); + view.radiuses = texelFetch(workloadViewsBuffer, offset + 1); + return view; +} +#else +layout(std140, binding=1) buffer workloadViewsBuffer { + WorkloadView _views[]; +}; +WorkloadView getWorkloadView(int i) { + WorkloadView view = _views[i]; + return view; +} +#endif + +<@endfunc@> + +<@endif@> + diff --git a/libraries/render-utils/src/ZoneRenderer.cpp b/libraries/render-utils/src/ZoneRenderer.cpp index 51939efd4f..1a1b3706f9 100644 --- a/libraries/render-utils/src/ZoneRenderer.cpp +++ b/libraries/render-utils/src/ZoneRenderer.cpp @@ -10,20 +10,32 @@ // #include "ZoneRenderer.h" - #include -#include #include #include +#include +#include +#include #include "StencilMaskPass.h" #include "DeferredLightingEffect.h" -#include "zone_drawKeyLight_frag.h" -#include "zone_drawAmbient_frag.h" -#include "zone_drawSkybox_frag.h" +#include "render-utils/ShaderConstants.h" +#include "StencilMaskPass.h" +#include "DeferredLightingEffect.h" +#include "BloomStage.h" + +namespace ru { + using render_utils::slot::texture::Texture; + using render_utils::slot::buffer::Buffer; +} + +namespace gr { + using graphics::slot::texture::Texture; + using graphics::slot::buffer::Buffer; +} using namespace render; @@ -52,7 +64,7 @@ void ZoneRendererTask::build(JobModel& task, const Varying& input, Varying& oupu } void SetupZones::run(const RenderContextPointer& context, const Inputs& inputs) { - // Grab light, background and haze stages and clear them + // Grab light, background, haze, and bloom stages and clear them auto lightStage = context->_scene->getStage(); assert(lightStage); lightStage->_currentFrame.clear(); @@ -65,6 +77,10 @@ void SetupZones::run(const RenderContextPointer& context, const Inputs& inputs) assert(hazeStage); hazeStage->_currentFrame.clear(); + auto bloomStage = context->_scene->getStage(); + assert(bloomStage); + bloomStage->_currentFrame.clear(); + // call render over the zones to grab their components in the correct order first... render::renderItems(context, inputs); @@ -73,20 +89,12 @@ void SetupZones::run(const RenderContextPointer& context, const Inputs& inputs) lightStage->_currentFrame.pushAmbientLight(lightStage->getDefaultLight()); backgroundStage->_currentFrame.pushBackground(0); hazeStage->_currentFrame.pushHaze(0); + bloomStage->_currentFrame.pushBloom(INVALID_INDEX); } const gpu::PipelinePointer& DebugZoneLighting::getKeyLightPipeline() { if (!_keyLightPipeline) { - auto vs = gpu::StandardShaderLib::getDrawTransformUnitQuadVS(); - auto ps = zone_drawKeyLight_frag::getShader(); - gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); - - gpu::Shader::BindingSet slotBindings; - slotBindings.insert(gpu::Shader::Binding(std::string("deferredFrameTransformBuffer"), ZONE_DEFERRED_TRANSFORM_BUFFER)); - slotBindings.insert(gpu::Shader::Binding(std::string("keyLightBuffer"), ZONE_KEYLIGHT_BUFFER)); - - gpu::Shader::makeProgram(*program, slotBindings); - + gpu::ShaderPointer program = gpu::Shader::createProgram(shader::render_utils::program::zone_drawKeyLight); gpu::StatePointer state = gpu::StatePointer(new gpu::State()); PrepareStencil::testMask(*state); @@ -98,17 +106,7 @@ const gpu::PipelinePointer& DebugZoneLighting::getKeyLightPipeline() { const gpu::PipelinePointer& DebugZoneLighting::getAmbientPipeline() { if (!_ambientPipeline) { - auto vs = gpu::StandardShaderLib::getDrawTransformUnitQuadVS(); - auto ps = zone_drawAmbient_frag::getShader(); - gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); - - gpu::Shader::BindingSet slotBindings; - slotBindings.insert(gpu::Shader::Binding(std::string("deferredFrameTransformBuffer"), ZONE_DEFERRED_TRANSFORM_BUFFER)); - slotBindings.insert(gpu::Shader::Binding(std::string("lightAmbientBuffer"), ZONE_AMBIENT_BUFFER)); - slotBindings.insert(gpu::Shader::Binding(std::string("skyboxMap"), ZONE_AMBIENT_MAP)); - - gpu::Shader::makeProgram(*program, slotBindings); - + gpu::ShaderPointer program = gpu::Shader::createProgram(shader::render_utils::program::zone_drawAmbient); gpu::StatePointer state = gpu::StatePointer(new gpu::State()); PrepareStencil::testMask(*state); @@ -119,17 +117,7 @@ const gpu::PipelinePointer& DebugZoneLighting::getAmbientPipeline() { } const gpu::PipelinePointer& DebugZoneLighting::getBackgroundPipeline() { if (!_backgroundPipeline) { - auto vs = gpu::StandardShaderLib::getDrawTransformUnitQuadVS(); - auto ps = zone_drawSkybox_frag::getShader(); - gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); - - gpu::Shader::BindingSet slotBindings; - slotBindings.insert(gpu::Shader::Binding(std::string("deferredFrameTransformBuffer"), ZONE_DEFERRED_TRANSFORM_BUFFER)); - slotBindings.insert(gpu::Shader::Binding(std::string("skyboxMap"), ZONE_SKYBOX_MAP)); - slotBindings.insert(gpu::Shader::Binding(std::string("skyboxBuffer"), ZONE_SKYBOX_BUFFER)); - - gpu::Shader::makeProgram(*program, slotBindings); - + gpu::ShaderPointer program = gpu::Shader::createProgram(shader::render_utils::program::zone_drawSkybox); gpu::StatePointer state = gpu::StatePointer(new gpu::State()); PrepareStencil::testMask(*state); @@ -180,7 +168,7 @@ void DebugZoneLighting::run(const render::RenderContextPointer& context, const I Transform model; - batch.setUniformBuffer(ZONE_DEFERRED_TRANSFORM_BUFFER, deferredTransform->getFrameTransformBuffer()); + batch.setUniformBuffer(ru::Buffer::DeferredFrameTransform, deferredTransform->getFrameTransformBuffer()); batch.setPipeline(getKeyLightPipeline()); auto numKeys = (int) keyLightStack.size(); @@ -188,7 +176,7 @@ void DebugZoneLighting::run(const render::RenderContextPointer& context, const I model.setTranslation(glm::vec3(-4.0, -3.0 + (i * 1.0), -10.0 - (i * 3.0))); batch.setModelTransform(model); if (keyLightStack[i]) { - batch.setUniformBuffer(ZONE_KEYLIGHT_BUFFER, keyLightStack[i]->getLightSchemaBuffer()); + batch.setUniformBuffer(gr::Buffer::KeyLight, keyLightStack[i]->getLightSchemaBuffer()); batch.draw(gpu::TRIANGLE_STRIP, 4); } } @@ -199,9 +187,9 @@ void DebugZoneLighting::run(const render::RenderContextPointer& context, const I model.setTranslation(glm::vec3(0.0, -3.0 + (i * 1.0), -10.0 - (i * 3.0))); batch.setModelTransform(model); if (ambientLightStack[i]) { - batch.setUniformBuffer(ZONE_AMBIENT_BUFFER, ambientLightStack[i]->getAmbientSchemaBuffer()); + batch.setUniformBuffer(gr::Buffer::AmbientLight, ambientLightStack[i]->getAmbientSchemaBuffer()); if (ambientLightStack[i]->getAmbientMap()) { - batch.setResourceTexture(ZONE_AMBIENT_MAP, ambientLightStack[i]->getAmbientMap()); + batch.setResourceTexture(ru::Texture::Skybox, ambientLightStack[i]->getAmbientMap()); } batch.draw(gpu::TRIANGLE_STRIP, 4); } @@ -213,8 +201,8 @@ void DebugZoneLighting::run(const render::RenderContextPointer& context, const I model.setTranslation(glm::vec3(4.0, -3.0 + (i * 1.0), -10.0 - (i * 3.0))); batch.setModelTransform(model); if (skyboxStack[i]) { - batch.setResourceTexture(ZONE_SKYBOX_MAP, skyboxStack[i]->getCubemap()); - batch.setUniformBuffer(ZONE_SKYBOX_BUFFER, skyboxStack[i]->getSchemaBuffer()); + batch.setResourceTexture(ru::Texture::Skybox, skyboxStack[i]->getCubemap()); + batch.setUniformBuffer(ru::Buffer::DebugSkyboxParams, skyboxStack[i]->getSchemaBuffer()); batch.draw(gpu::TRIANGLE_STRIP, 4); } } diff --git a/libraries/render-utils/src/ZoneRenderer.h b/libraries/render-utils/src/ZoneRenderer.h index 419db4ebe2..6e85243d1a 100644 --- a/libraries/render-utils/src/ZoneRenderer.h +++ b/libraries/render-utils/src/ZoneRenderer.h @@ -69,15 +69,6 @@ public: protected: - enum Slots { - ZONE_DEFERRED_TRANSFORM_BUFFER = 0, - ZONE_KEYLIGHT_BUFFER, - ZONE_AMBIENT_BUFFER, - ZONE_AMBIENT_MAP, - ZONE_SKYBOX_BUFFER, - ZONE_SKYBOX_MAP, - }; - gpu::PipelinePointer _keyLightPipeline; gpu::PipelinePointer _ambientPipeline; gpu::PipelinePointer _backgroundPipeline; diff --git a/libraries/render-utils/src/animdebugdraw.slf b/libraries/render-utils/src/animdebugdraw.slf index 8a3aca055e..1a4b8754b3 100644 --- a/libraries/render-utils/src/animdebugdraw.slf +++ b/libraries/render-utils/src/animdebugdraw.slf @@ -10,9 +10,11 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -in vec4 _color; +<@include render-utils/ShaderConstants.h@> -out vec4 _fragColor; +layout(location=RENDER_UTILS_ATTR_COLOR) in vec4 _color; + +layout(location=0) out vec4 _fragColor; void main(void) { _fragColor = _color; diff --git a/libraries/render-utils/src/animdebugdraw.slv b/libraries/render-utils/src/animdebugdraw.slv index 3255c6783c..ec4bdc9f52 100644 --- a/libraries/render-utils/src/animdebugdraw.slv +++ b/libraries/render-utils/src/animdebugdraw.slv @@ -13,7 +13,9 @@ <@include gpu/Transform.slh@> <$declareStandardTransform()$> -out vec4 _color; +<@include render-utils/ShaderConstants.h@> + +layout(location=RENDER_UTILS_ATTR_COLOR) out vec4 _color; void main(void) { // pass along the color diff --git a/libraries/render-utils/src/debug_deferred_buffer.slf b/libraries/render-utils/src/debug_deferred_buffer.slf index 5f974acfeb..013640d910 100644 --- a/libraries/render-utils/src/debug_deferred_buffer.slf +++ b/libraries/render-utils/src/debug_deferred_buffer.slf @@ -2,7 +2,7 @@ <$VERSION_HEADER$> // Generated on <$_SCRIBE_DATE$> // -// debug_deferred_buffer.slf +// debug_deferred_buffer.frag // fragment shader // // Created by Clement on 12/3 @@ -16,14 +16,8 @@ <@include gpu/Color.slh@> <$declareColorWheel()$> -uniform sampler2D linearDepthMap; -uniform sampler2D halfLinearDepthMap; -uniform sampler2D halfNormalMap; -uniform sampler2D occlusionMap; -uniform sampler2D occlusionBlurredMap; -uniform sampler2D scatteringMap; -uniform sampler2D velocityMap; -uniform sampler2DArrayShadow shadowMaps; +layout(binding=RENDER_UTILS_DEBUG_TEXTURE0) uniform sampler2D debugTexture0; +layout(binding=RENDER_UTILS_TEXTURE_SHADOW) uniform sampler2DArrayShadow shadowMaps; <@include ShadowCore.slh@> @@ -31,7 +25,7 @@ uniform sampler2DArrayShadow shadowMaps; <@include debug_deferred_buffer_shared.slh@> -layout(std140) uniform parametersBuffer { +layout(std140, binding=RENDER_UTILS_BUFFER_DEBUG_DEFERRED_PARAMS) uniform parametersBuffer { DebugParameters parameters; }; @@ -39,8 +33,8 @@ float curvatureAO(float k) { return 1.0f - (0.0022f * k * k) + (0.0776f * k) + 0.7369f; } -in vec2 uv; -out vec4 outFragColor; +layout(location=0) in vec2 uv; +layout(location=0) out vec4 outFragColor; //SOURCE_PLACEHOLDER diff --git a/libraries/render-utils/src/debug_deferred_buffer.slv b/libraries/render-utils/src/debug_deferred_buffer.slv index 85ff2b651d..c68f986905 100644 --- a/libraries/render-utils/src/debug_deferred_buffer.slv +++ b/libraries/render-utils/src/debug_deferred_buffer.slv @@ -2,7 +2,7 @@ <$VERSION_HEADER$> // Generated on <$_SCRIBE_DATE$> // -// debug_deferred_buffer.slv +// debug_deferred_buffer.vert // vertex shader // // Created by Clement on 12/3 @@ -14,7 +14,7 @@ <@include gpu/Inputs.slh@> -out vec2 uv; +layout(location=0) out vec2 uv; void main(void) { uv = (inPosition.xy + 1.0) * 0.5; diff --git a/libraries/render-utils/src/deferred_light.slv b/libraries/render-utils/src/deferred_light.slv index 74b3749181..164fd9fb3b 100644 --- a/libraries/render-utils/src/deferred_light.slv +++ b/libraries/render-utils/src/deferred_light.slv @@ -12,9 +12,9 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -out vec2 _texCoord0; +<@include render-utils/ShaderConstants.h@> -uniform vec4 texcoordFrameTransform; +layout(location=RENDER_UTILS_ATTR_TEXCOORD01) out vec4 _texCoord01; void main(void) { const float depth = 1.0; @@ -26,10 +26,7 @@ void main(void) { ); vec4 pos = UNIT_QUAD[gl_VertexID]; - _texCoord0 = (pos.xy + 1.0) * 0.5; - - _texCoord0 *= texcoordFrameTransform.zw; - _texCoord0 += texcoordFrameTransform.xy; + _texCoord01.xy = (pos.xy + 1.0) * 0.5; gl_Position = pos; } diff --git a/libraries/render-utils/src/deferred_light_limited.slv b/libraries/render-utils/src/deferred_light_limited.slv index 36e281ab5b..fb59b8e78f 100644 --- a/libraries/render-utils/src/deferred_light_limited.slv +++ b/libraries/render-utils/src/deferred_light_limited.slv @@ -13,14 +13,14 @@ // <@include gpu/Transform.slh@> - <@include gpu/Inputs.slh@> +<@include render-utils/ShaderConstants.h@> <$declareStandardTransform()$> uniform vec4 sphereParam; -out vec4 _texCoord0; +layout(location=RENDER_UTILS_ATTR_TEXCOORD01) out vec4 _texCoord01; void main(void) { if (sphereParam.w != 0.0) { @@ -41,7 +41,7 @@ void main(void) { } #endif #endif - _texCoord0 = vec4(projected.xy, 0.0, 1.0) * gl_Position.w; + _texCoord01.xy = vec4(projected.xy, 0.0, 1.0) * gl_Position.w; } else { const float depth = -1.0; //Draw at near plane const vec4 UNIT_QUAD[4] = vec4[4]( @@ -60,13 +60,13 @@ void main(void) { #endif #endif - _texCoord0 = vec4((pos.xy + 1.0) * 0.5, 0.0, 1.0); + _texCoord01.xy = vec4((pos.xy + 1.0) * 0.5, 0.0, 1.0); #ifdef GPU_TRANSFORM_IS_STEREO #ifdef GPU_TRANSFORM_STEREO_SPLIT_SCREEN #else if (cam_isStereo()) { - _texCoord0.x = 0.5 * (_texCoord0.x + cam_getStereoSide()); + _texCoord01.x = 0.5 * (_texCoord01.x + cam_getStereoSide()); } #endif #endif diff --git a/libraries/render-utils/src/deferred_light_point.slv b/libraries/render-utils/src/deferred_light_point.slv index 2b75ee3915..1f4c66b6e5 100644 --- a/libraries/render-utils/src/deferred_light_point.slv +++ b/libraries/render-utils/src/deferred_light_point.slv @@ -12,21 +12,21 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -<@include gpu/Transform.slh@> +<@include gpu/Transform.slh@> <@include gpu/Inputs.slh@> +<@include graphics/Light.slh@> +<@include render-utils/ShaderConstants.h@> <$declareStandardTransform()$> -<@include graphics/Light.slh@> - <$declareLightBuffer(256)$> -uniform lightIndexBuffer { +layout(binding=RENDER_UTILS_BUFFER_LIGHT_INDEX) uniform lightIndexBuffer { int lightIndex[256]; }; -out vec4 _texCoord0; +layout(location=RENDER_UTILS_ATTR_TEXCOORD01) out vec4 _texCoord0; void main(void) { diff --git a/libraries/render-utils/src/deferred_light_spot.slv b/libraries/render-utils/src/deferred_light_spot.slv index 7e3e45b3b6..c86551936b 100644 --- a/libraries/render-utils/src/deferred_light_spot.slv +++ b/libraries/render-utils/src/deferred_light_spot.slv @@ -13,18 +13,18 @@ // <@include gpu/Inputs.slh@> - <@include gpu/Transform.slh@> +<@include graphics/Light.slh@> +<@include render-utils/ShaderConstants.h@> <$declareStandardTransform()$> -<@include graphics/Light.slh@> <$declareLightBuffer(256)$> -uniform lightIndexBuffer { +layout(binding=RENDER_UTILS_BUFFER_LIGHT_INDEX) uniform lightIndexBuffer { int lightIndex[256]; }; -out vec4 _texCoord0; +layout(location=RENDER_UTILS_ATTR_TEXCOORD01) out vec4 _texCoord0; void main(void) { vec4 coneVertex = inPosition; diff --git a/libraries/render-utils/src/directional_ambient_light.slf b/libraries/render-utils/src/directional_ambient_light.slf index d7e0dcd08c..15d00f713e 100644 --- a/libraries/render-utils/src/directional_ambient_light.slf +++ b/libraries/render-utils/src/directional_ambient_light.slf @@ -16,14 +16,16 @@ <@include DeferredBufferRead.slh@> <@include DeferredGlobalLight.slh@> +<@include render-utils/ShaderConstants.h@> <$declareEvalLightmappedColor()$> <$declareEvalAmbientSphereGlobalColor(supportScattering)$> - -in vec2 _texCoord0; -out vec4 _fragColor; +layout(location=RENDER_UTILS_ATTR_TEXCOORD01) in vec4 _texCoord01; +#define _texCoord0 _texCoord01.xy +#define _texCoord1 _texCoord01.zw +layout(location=0) out vec4 _fragColor; void main(void) { DeferredFrameTransform deferredTransform = getDeferredFrameTransform(); diff --git a/libraries/render-utils/src/directional_ambient_light_shadow.slf b/libraries/render-utils/src/directional_ambient_light_shadow.slf index fda0164b4d..d6cdf78f19 100644 --- a/libraries/render-utils/src/directional_ambient_light_shadow.slf +++ b/libraries/render-utils/src/directional_ambient_light_shadow.slf @@ -2,7 +2,7 @@ <$VERSION_HEADER$> // Generated on <$_SCRIBE_DATE$> // -// directional_light_shadow.frag +// directional_ambient_light_shadow.frag // fragment shader // // Created by Zach Pomerantz on 1/18/2016. @@ -15,12 +15,15 @@ <@include Shadow.slh@> <@include DeferredBufferRead.slh@> <@include DeferredGlobalLight.slh@> +<@include render-utils/ShaderConstants.h@> <$declareEvalLightmappedColor()$> <$declareEvalAmbientSphereGlobalColor(isScattering)$> -in vec2 _texCoord0; -out vec4 _fragColor; +layout(location=RENDER_UTILS_ATTR_TEXCOORD01) in vec4 _texCoord01; +#define _texCoord0 _texCoord01.xy +#define _texCoord1 _texCoord01.zw +layout(location=0) out vec4 _fragColor; void main(void) { DeferredFrameTransform deferredTransform = getDeferredFrameTransform(); diff --git a/libraries/render-utils/src/directional_skybox_light.slf b/libraries/render-utils/src/directional_skybox_light.slf index 65b4b086e9..b27d759dd4 100644 --- a/libraries/render-utils/src/directional_skybox_light.slf +++ b/libraries/render-utils/src/directional_skybox_light.slf @@ -14,12 +14,15 @@ <@include DeferredBufferRead.slh@> <@include DeferredGlobalLight.slh@> +<@include render-utils/ShaderConstants.h@> <$declareEvalLightmappedColor()$> <$declareEvalSkyboxGlobalColor(isScattering)$> -in vec2 _texCoord0; -out vec4 _fragColor; +layout(location=RENDER_UTILS_ATTR_TEXCOORD01) in vec4 _texCoord01; +#define _texCoord0 _texCoord01.xy +#define _texCoord1 _texCoord01.zw +layout(location=0) out vec4 _fragColor; void main(void) { DeferredFrameTransform deferredTransform = getDeferredFrameTransform(); diff --git a/libraries/render-utils/src/directional_skybox_light_shadow.slf b/libraries/render-utils/src/directional_skybox_light_shadow.slf index e927f53a1f..292f7348e3 100644 --- a/libraries/render-utils/src/directional_skybox_light_shadow.slf +++ b/libraries/render-utils/src/directional_skybox_light_shadow.slf @@ -2,7 +2,7 @@ <$VERSION_HEADER$> // Generated on <$_SCRIBE_DATE$> // -// directional_light_shadow.frag +// directional_skybox_light_shadow.frag // fragment shader // // Created by Zach Pomerantz on 1/18/2016. @@ -15,12 +15,15 @@ <@include Shadow.slh@> <@include DeferredBufferRead.slh@> <@include DeferredGlobalLight.slh@> +<@include render-utils/ShaderConstants.h@> <$declareEvalLightmappedColor()$> <$declareEvalSkyboxGlobalColor(isScattering)$> -in vec2 _texCoord0; -out vec4 _fragColor; +layout(location=RENDER_UTILS_ATTR_TEXCOORD01) in vec4 _texCoord01; +#define _texCoord0 _texCoord01.xy +#define _texCoord1 _texCoord01.zw +layout(location=0) out vec4 _fragColor; void main(void) { DeferredFrameTransform deferredTransform = getDeferredFrameTransform(); diff --git a/libraries/render-utils/src/drawWorkloadProxy.slf b/libraries/render-utils/src/drawWorkloadProxy.slf index 1304e68c7f..32dceab00a 100644 --- a/libraries/render-utils/src/drawWorkloadProxy.slf +++ b/libraries/render-utils/src/drawWorkloadProxy.slf @@ -15,11 +15,13 @@ in vec4 varColor; in vec3 varTexcoord; +in vec3 varEyePos; void main(void) { if (varColor.w > 0.0) { float r = sqrt(dot(varTexcoord.xyz,varTexcoord.xyz)); - float a = paintStripe(r * varColor.w, 0.0, 1.0 / varColor.w, 0.05 / varColor.w); + float d = varColor.w / abs(varEyePos.z); + float a = paintStripe(r * d, 0.0, 1.0 / d, 0.002 / d); if (a <= 0.1 || r > 1.1) { discard; } diff --git a/libraries/render-utils/src/drawWorkloadProxy.slv b/libraries/render-utils/src/drawWorkloadProxy.slv index 64fb335fd6..28a62070f9 100644 --- a/libraries/render-utils/src/drawWorkloadProxy.slv +++ b/libraries/render-utils/src/drawWorkloadProxy.slv @@ -2,7 +2,7 @@ <$VERSION_HEADER$> // Generated on <$_SCRIBE_DATE$> // -// drawItemBounds.slv +// drawWorkloadProxy.vert // vertex shader // // Created by Sam Gateau on 6/29/2015. @@ -15,40 +15,13 @@ <@include gpu/Transform.slh@> <$declareStandardTransform()$> -<@include gpu/Color.slh@> -<$declareColorWheel()$> - -uniform vec4 inColor; - - -struct WorkloadProxy { - vec4 sphere; - vec4 region; -}; - -#if defined(GPU_GL410) -uniform samplerBuffer workloadProxiesBuffer; -WorkloadProxy getWorkloadProxy(int i) { - int offset = 2 * i; - WorkloadProxy proxy; - proxy.sphere = texelFetch(workloadProxiesBuffer, offset); - proxy.region = texelFetch(workloadProxiesBuffer, offset + 1); - return proxy; -} -#else -layout(std140) buffer workloadProxiesBuffer { - WorkloadProxy _proxies[]; -}; -WorkloadProxy getWorkloadProxy(int i) { - WorkloadProxy proxy = _proxies[i]; - return proxy; -} -#endif - +<@include WorkloadResource.slh@> +<$declareWorkloadProxies()$> out vec4 varColor; out vec3 varTexcoord; +out vec3 varEyePos; void main(void) { const vec4 UNIT_SPRITE[3] = vec4[3]( @@ -79,6 +52,7 @@ void main(void) { vec3 dirY = vec3(0.0, 1.0, 0.0); vec4 pos = vec4(proxyPosEye.xyz + proxy.sphere.w * ( dirX * spriteVert.x + dirY * spriteVert.y /* + dirZ * spriteVert.z*/), 1.0); + varEyePos = pos.xyz; varTexcoord = spriteVert.xyz; <$transformEyeToClipPos(cam, pos, gl_Position)$> @@ -86,7 +60,7 @@ void main(void) { int region = floatBitsToInt(proxy.region.x); region = (0x000000FF & region); - varColor = vec4(colorWheel(float(region) / 4.0), proxy.sphere.w); + varColor = vec4(REGION_COLOR[region].xyz, proxy.sphere.w); if (region == 4) { gl_Position = vec4(0.0); diff --git a/libraries/render-utils/src/drawWorkloadView.slf b/libraries/render-utils/src/drawWorkloadView.slf index 1304e68c7f..c44dae4a24 100644 --- a/libraries/render-utils/src/drawWorkloadView.slf +++ b/libraries/render-utils/src/drawWorkloadView.slf @@ -1,7 +1,8 @@ <@include gpu/Config.slh@> <$VERSION_HEADER$> // Generated on <$_SCRIBE_DATE$> -// drawItemBounds.frag +// +// drawWorkloadView.frag // fragment shader // // Created by Sam Gateau on 6/29/15. @@ -15,11 +16,13 @@ in vec4 varColor; in vec3 varTexcoord; +in vec3 varEyePos; void main(void) { if (varColor.w > 0.0) { float r = sqrt(dot(varTexcoord.xyz,varTexcoord.xyz)); - float a = paintStripe(r * varColor.w, 0.0, 1.0 / varColor.w, 0.05 / varColor.w); + float d = varColor.w / abs(varEyePos.z); + float a = paintStripe(r * d, 0.0, 1.0 / d, 0.005 / d); if (a <= 0.1 || r > 1.1) { discard; } diff --git a/libraries/render-utils/src/drawWorkloadView.slv b/libraries/render-utils/src/drawWorkloadView.slv index f5497d250c..291a8c86cd 100644 --- a/libraries/render-utils/src/drawWorkloadView.slv +++ b/libraries/render-utils/src/drawWorkloadView.slv @@ -2,7 +2,7 @@ <$VERSION_HEADER$> // Generated on <$_SCRIBE_DATE$> // -// drawItemBounds.slv +// drawWorkloadView.vert // vertex shader // // Created by Sam Gateau on 6/29/2015. @@ -15,45 +15,12 @@ <@include gpu/Transform.slh@> <$declareStandardTransform()$> -<@include gpu/Color.slh@> -<$declareColorWheel()$> - -uniform vec4 inColor; - - -struct WorkloadView { - vec4 direction_far; - vec4 fov; - vec4 origin; - vec4 backFront[2]; - vec4 regions[3]; -}; - -#if defined(GPU_GL410) -uniform samplerBuffer workloadViewsBuffer; -WorkloadView getWorkloadView(int i) { - int offset = 2 * i; - WorkloadView view; - view.origin = texelFetch(workloadViewsBuffer, offset); - view.radiuses = texelFetch(workloadViewsBuffer, offset + 1); - return view; -} -#else -layout(std140) buffer workloadViewsBuffer { - WorkloadView _views[]; -}; -WorkloadView getWorkloadView(int i) { - WorkloadView view = _views[i]; - return view; -} -#endif - - - - +<@include WorkloadResource.slh@> +<$declareWorkloadViews()$> out vec4 varColor; out vec3 varTexcoord; +out vec3 varEyePos; const int NUM_VERTICES_PER_SEGMENT = 2; const int NUM_SEGMENT_PER_VIEW_REGION = 65; @@ -61,11 +28,14 @@ const int NUM_VERTICES_PER_VIEW_REGION = NUM_SEGMENT_PER_VIEW_REGION * NUM_VERTI const int NUM_REGIONS_PER_VIEW = 3; const int NUM_VERTICES_PER_VIEW = NUM_VERTICES_PER_VIEW_REGION * NUM_REGIONS_PER_VIEW; - -layout(std140) uniform drawMeshBuffer { +struct DrawMesh { vec4 verts[NUM_SEGMENT_PER_VIEW_REGION]; }; +layout(std140, binding=0) uniform DrawMeshBuffer { + DrawMesh _drawMeshBuffer; +}; + void main(void) { int viewID = gl_VertexID / NUM_VERTICES_PER_VIEW; int viewVertexID = gl_VertexID - viewID * NUM_VERTICES_PER_VIEW; @@ -76,7 +46,7 @@ void main(void) { int segmentID = regionVertexID / NUM_VERTICES_PER_SEGMENT; int segmentVertexID = regionVertexID - segmentID * NUM_VERTICES_PER_SEGMENT; - vec4 segment = verts[segmentID]; + vec4 segment = _drawMeshBuffer.verts[segmentID]; vec4 spriteVert = vec4(segment.y, 0.0, segment.x, 1.0); vec3 spriteTan = vec3(segment.x, 0.0, -segment.y); @@ -109,12 +79,13 @@ void main(void) { <$transformModelToEyeDir(cam, obj, originSpaceTan, tanEye)$> lateralDir = normalize(cross(vec3(0.0, 0.0, 1.0), normalize(tanEye))); - posEye.xyz += (0.05 * (regionID + 1)) * (-1.0 + 2.0 * float(segmentVertexID)) * lateralDir; + posEye.xyz += (0.005 * abs(posEye.z) * (regionID + 1)) * (-1.0 + 2.0 * float(segmentVertexID)) * lateralDir; + varEyePos = posEye.xyz; <$transformEyeToClipPos(cam, posEye, gl_Position)$> varTexcoord = spriteVert.xyz; // Convert region to color - varColor = vec4(colorWheel(float(regionID) / 4.0), -1.0); + varColor = vec4(REGION_COLOR[regionID].xyz, -1.0); } diff --git a/libraries/render-utils/src/forward_model.slf b/libraries/render-utils/src/forward_model.slf index ea3a66d21c..f77ab358f2 100644 --- a/libraries/render-utils/src/forward_model.slf +++ b/libraries/render-utils/src/forward_model.slf @@ -11,24 +11,28 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -<@include ForwardGlobalLight.slh@> -<$declareEvalSkyboxGlobalColor()$> - -<@include graphics/Material.slh@> <@include gpu/Transform.slh@> +<@include graphics/Material.slh@> +<@include graphics/MaterialTextures.slh@> +<@include render-utils/ShaderConstants.h@> +<@include ForwardGlobalLight.slh@> + +<$declareEvalSkyboxGlobalColor()$> + + <$declareStandardCameraTransform()$> -<@include MaterialTextures.slh@> <$declareMaterialTextures(ALBEDO, ROUGHNESS, _SCRIBE_NULL, METALLIC, EMISSIVE, OCCLUSION)$> -in vec4 _positionES; -in vec2 _texCoord0; -in vec2 _texCoord1; -in vec3 _normalWS; -in vec3 _color; +layout(location=RENDER_UTILS_ATTR_POSITION_ES) in vec4 _positionES; +layout(location=RENDER_UTILS_ATTR_TEXCOORD01) in vec4 _texCoord01; +#define _texCoord0 _texCoord01.xy +#define _texCoord1 _texCoord01.zw +layout(location=RENDER_UTILS_ATTR_NORMAL_WS) in vec3 _normalWS; +layout(location=RENDER_UTILS_ATTR_COLOR) in vec4 _color; -layout(location = 0) out vec4 _fragColor0; +layout(location=0) out vec4 _fragColor0; void main(void) { Material mat = getMaterial(); @@ -42,7 +46,7 @@ void main(void) { vec3 albedo = getMaterialAlbedo(mat); <$evalMaterialAlbedo(albedoTex, albedo, matKey, albedo)$>; - albedo *= _color; + albedo *= _color.rgb; float roughness = getMaterialRoughness(mat); <$evalMaterialRoughness(roughnessTex, roughness, matKey, roughness)$>; diff --git a/libraries/render-utils/src/forward_model_normal_map.slf b/libraries/render-utils/src/forward_model_normal_map.slf index ac76e909e4..73fae33fb0 100644 --- a/libraries/render-utils/src/forward_model_normal_map.slf +++ b/libraries/render-utils/src/forward_model_normal_map.slf @@ -12,25 +12,26 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -<@include ForwardGlobalLight.slh@> -<$declareEvalSkyboxGlobalColor()$> - -<@include graphics/Material.slh@> - <@include gpu/Transform.slh@> +<@include graphics/Material.slh@> +<@include graphics/MaterialTextures.slh@> +<@include render-utils/ShaderConstants.h@> +<@include ForwardGlobalLight.slh@> + +<$declareEvalSkyboxGlobalColor()$> <$declareStandardCameraTransform()$> -<@include MaterialTextures.slh@> <$declareMaterialTextures(ALBEDO, ROUGHNESS, NORMAL, METALLIC, EMISSIVE, OCCLUSION)$> -in vec4 _positionES; -in vec2 _texCoord0; -in vec2 _texCoord1; -in vec3 _normalWS; -in vec3 _tangentWS; -in vec3 _color; +layout(location=RENDER_UTILS_ATTR_POSITION_ES) in vec4 _positionES; +layout(location=RENDER_UTILS_ATTR_TEXCOORD01) in vec4 _texCoord01; +#define _texCoord0 _texCoord01.xy +#define _texCoord1 _texCoord01.zw +layout(location=RENDER_UTILS_ATTR_NORMAL_WS) in vec3 _normalWS; +layout(location=RENDER_UTILS_ATTR_TANGENT_WS) in vec3 _tangentWS; +layout(location=RENDER_UTILS_ATTR_COLOR) in vec4 _color; -layout(location = 0) out vec4 _fragColor0; +layout(location=0) out vec4 _fragColor0; void main(void) { Material mat = getMaterial(); @@ -44,7 +45,7 @@ void main(void) { vec3 albedo = getMaterialAlbedo(mat); <$evalMaterialAlbedo(albedoTex, albedo, matKey, albedo)$>; - albedo *= _color; + albedo *= _color.rgb; float roughness = getMaterialRoughness(mat); <$evalMaterialRoughness(roughnessTex, roughness, matKey, roughness)$>; diff --git a/libraries/render-utils/src/forward_model_translucent.slf b/libraries/render-utils/src/forward_model_translucent.slf index 70a3233737..5fb2c7c1a7 100644 --- a/libraries/render-utils/src/forward_model_translucent.slf +++ b/libraries/render-utils/src/forward_model_translucent.slf @@ -12,26 +12,25 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +<@include gpu/Transform.slh@> <@include graphics/Material.slh@> - +<@include graphics/MaterialTextures.slh@> +<@include render-utils/ShaderConstants.h@> <@include ForwardGlobalLight.slh@> <$declareEvalGlobalLightingAlphaBlended()$> - -<@include gpu/Transform.slh@> <$declareStandardCameraTransform()$> -<@include MaterialTextures.slh@> <$declareMaterialTextures(ALBEDO, ROUGHNESS, _SCRIBE_NULL, METALLIC, EMISSIVE, OCCLUSION)$> -in vec2 _texCoord0; -in vec2 _texCoord1; -in vec4 _positionES; -in vec3 _normalWS; -in vec3 _color; -in float _alpha; +layout(location=RENDER_UTILS_ATTR_TEXCOORD01) in vec4 _texCoord01; +#define _texCoord0 _texCoord01.xy +#define _texCoord1 _texCoord01.zw +layout(location=RENDER_UTILS_ATTR_POSITION_ES) in vec4 _positionES; +layout(location=RENDER_UTILS_ATTR_NORMAL_WS) in vec3 _normalWS; +layout(location=RENDER_UTILS_ATTR_COLOR) in vec4 _color; -layout(location = 0) out vec4 _fragColor0; +layout(location=0) out vec4 _fragColor0; void main(void) { Material mat = getMaterial(); @@ -39,13 +38,13 @@ void main(void) { <$fetchMaterialTexturesCoord0(matKey, _texCoord0, albedoTex, roughnessTex, _SCRIBE_NULL, metallicTex, emissiveTex)$> <$fetchMaterialTexturesCoord1(matKey, _texCoord1, occlusionTex)$> - float opacity = getMaterialOpacity(mat) * _alpha; + float opacity = getMaterialOpacity(mat) * _color.a; <$evalMaterialOpacity(albedoTex.a, opacity, matKey, opacity)$>; - <$discardTransparent(opacity)$>; + <$discardInvisible(opacity)$>; vec3 albedo = getMaterialAlbedo(mat); <$evalMaterialAlbedo(albedoTex, albedo, matKey, albedo)$>; - albedo *= _color; + albedo *= _color.rgb; float roughness = getMaterialRoughness(mat); <$evalMaterialRoughness(roughnessTex, roughness, matKey, roughness)$>; diff --git a/libraries/render-utils/src/forward_model_unlit.slf b/libraries/render-utils/src/forward_model_unlit.slf index e693a79e53..19b40d884c 100644 --- a/libraries/render-utils/src/forward_model_unlit.slf +++ b/libraries/render-utils/src/forward_model_unlit.slf @@ -14,14 +14,17 @@ <@include LightingModel.slh@> <@include graphics/Material.slh@> +<@include graphics/MaterialTextures.slh@> +<@include render-utils/ShaderConstants.h@> -<@include MaterialTextures.slh@> <$declareMaterialTextures(ALBEDO)$> -in vec2 _texCoord0; -in vec3 _color; +layout(location=RENDER_UTILS_ATTR_TEXCOORD01) in vec4 _texCoord01; +#define _texCoord0 _texCoord01.xy +#define _texCoord1 _texCoord01.zw +layout(location=RENDER_UTILS_ATTR_COLOR) in vec4 _color; -layout(location = 0) out vec4 _fragColor0; +layout(location=0) out vec4 _fragColor0; void main(void) { @@ -35,7 +38,7 @@ void main(void) { vec3 albedo = getMaterialAlbedo(mat); <$evalMaterialAlbedo(albedoTex, albedo, matKey, albedo)$>; - albedo *= _color; + albedo *= _color.rgb; if (opacity != 1.0) { discard; diff --git a/libraries/render-utils/src/forward_simple.slf b/libraries/render-utils/src/forward_simple.slf index 1ac44750a7..ca3a13c024 100644 --- a/libraries/render-utils/src/forward_simple.slf +++ b/libraries/render-utils/src/forward_simple.slf @@ -18,12 +18,14 @@ // the interpolated normal -in vec3 _normalWS; -in vec3 _normalMS; -in vec4 _color; -in vec2 _texCoord0; -in vec4 _positionMS; -in vec4 _positionES; +layout(location=RENDER_UTILS_ATTR_NORMAL_WS) in vec3 _normalWS; +layout(location=RENDER_UTILS_ATTR_NORMAL_MS) in vec3 _normalMS; +layout(location=RENDER_UTILS_ATTR_COLOR) in vec4 _color; +layout(location=RENDER_UTILS_ATTR_TEXCOORD01) in vec4 _texCoord01; +#define _texCoord0 _texCoord01.xy +#define _texCoord1 _texCoord01.zw +layout(location=RENDER_UTILS_ATTR_POSITION_MS) in vec4 _positionMS; +layout(location=RENDER_UTILS_ATTR_POSITION_ES) in vec4 _positionES; // For retro-compatibility #define _normal _normalWS @@ -31,9 +33,9 @@ in vec4 _positionES; #define _position _positionMS #define _eyePosition _positionES -layout(location = 0) out vec4 _fragColor0; +layout(location=0) out vec4 _fragColor0; -//PROCEDURAL_COMMON_BLOCK +<@include procedural/ProceduralCommon.slh@> #line 1001 //PROCEDURAL_BLOCK @@ -49,9 +51,9 @@ void main(void) { #ifdef PROCEDURAL #ifdef PROCEDURAL_V1 - specular = getProceduralColor().rgb; + diffuse = getProceduralColor().rgb; // Procedural Shaders are expected to be Gamma corrected so let's bring back the RGB in linear space for the rest of the pipeline - //specular = pow(specular, vec3(2.2)); + //diffuse = pow(diffuse, vec3(2.2)); emissiveAmount = 1.0; #else emissiveAmount = getProceduralColors(diffuse, specular, shininess); diff --git a/libraries/render-utils/src/forward_simple_textured.slf b/libraries/render-utils/src/forward_simple_textured.slf index 9bdf236743..8570ae6183 100644 --- a/libraries/render-utils/src/forward_simple_textured.slf +++ b/libraries/render-utils/src/forward_simple_textured.slf @@ -2,7 +2,7 @@ <$VERSION_HEADER$> // Generated on <$_SCRIBE_DATE$> // -// forward_simple_textured.slf +// forward_simple_textured.frag // fragment shader // // Created by Clément Brisset on 5/29/15. @@ -19,16 +19,20 @@ <@include gpu/Transform.slh@> <$declareStandardCameraTransform()$> +<@include render-utils/ShaderConstants.h@> + // the albedo texture -uniform sampler2D originalTexture; +layout(binding=0) uniform sampler2D originalTexture; // the interpolated normal -in vec3 _normalWS; -in vec4 _color; -in vec2 _texCoord0; -in vec4 _positionES; +layout(location=RENDER_UTILS_ATTR_NORMAL_WS) in vec3 _normalWS; +layout(location=RENDER_UTILS_ATTR_COLOR) in vec4 _color; +layout(location=RENDER_UTILS_ATTR_TEXCOORD01) in vec4 _texCoord01; +#define _texCoord0 _texCoord01.xy +#define _texCoord1 _texCoord01.zw +layout(location=RENDER_UTILS_ATTR_POSITION_ES) in vec4 _positionES; -layout(location = 0) out vec4 _fragColor0; +layout(location=0) out vec4 _fragColor0; void main(void) { vec4 texel = texture(originalTexture, _texCoord0); diff --git a/libraries/render-utils/src/forward_simple_textured_transparent.slf b/libraries/render-utils/src/forward_simple_textured_transparent.slf index 167d1cb87a..11c44c18a2 100644 --- a/libraries/render-utils/src/forward_simple_textured_transparent.slf +++ b/libraries/render-utils/src/forward_simple_textured_transparent.slf @@ -2,7 +2,7 @@ <$VERSION_HEADER$> // Generated on <$_SCRIBE_DATE$> // -// forward_simple_textured_transparent.slf +// forward_simple_textured_transparent.frag // fragment shader // // Created by Clément Brisset on 5/29/15. @@ -19,16 +19,20 @@ <@include gpu/Transform.slh@> <$declareStandardCameraTransform()$> +<@include render-utils/ShaderConstants.h@> + // the albedo texture -uniform sampler2D originalTexture; +layout(binding=0) uniform sampler2D originalTexture; // the interpolated normal -in vec3 _normalWS; -in vec4 _color; -in vec2 _texCoord0; -in vec4 _positionES; +layout(location=RENDER_UTILS_ATTR_NORMAL_WS) in vec3 _normalWS; +layout(location=RENDER_UTILS_ATTR_COLOR) in vec4 _color; +layout(location=RENDER_UTILS_ATTR_TEXCOORD01) in vec4 _texCoord01; +#define _texCoord0 _texCoord01.xy +#define _texCoord1 _texCoord01.zw +layout(location=RENDER_UTILS_ATTR_POSITION_ES) in vec4 _positionES; -layout(location = 0) out vec4 _fragColor0; +layout(location=0) out vec4 _fragColor0; void main(void) { vec4 texel = texture(originalTexture, _texCoord0); diff --git a/libraries/render-utils/src/forward_simple_textured_unlit.slf b/libraries/render-utils/src/forward_simple_textured_unlit.slf index 0cc241b75e..8ca46da499 100644 --- a/libraries/render-utils/src/forward_simple_textured_unlit.slf +++ b/libraries/render-utils/src/forward_simple_textured_unlit.slf @@ -2,7 +2,7 @@ <$VERSION_HEADER$> // Generated on <$_SCRIBE_DATE$> // -// simple_textured_unlit.frag +// forward_simple_textured_unlit.frag // fragment shader // // Created by Clément Brisset on 5/29/15. @@ -15,13 +15,17 @@ <@include LightingModel.slh@> <@include gpu/Color.slh@> -layout(location = 0) out vec4 _fragColor0; +<@include render-utils/ShaderConstants.h@> + +layout(location=0) out vec4 _fragColor0; // the albedo texture -uniform sampler2D originalTexture; +layout(binding=0) uniform sampler2D originalTexture; -in vec4 _color; -in vec2 _texCoord0; +layout(location=RENDER_UTILS_ATTR_COLOR) in vec4 _color; +layout(location=RENDER_UTILS_ATTR_TEXCOORD01) in vec4 _texCoord01; +#define _texCoord0 _texCoord01.xy +#define _texCoord1 _texCoord01.zw void main(void) { vec4 texel = texture(originalTexture, _texCoord0.st); diff --git a/libraries/render-utils/src/forward_simple_transparent.slf b/libraries/render-utils/src/forward_simple_transparent.slf deleted file mode 100644 index 8be2759571..0000000000 --- a/libraries/render-utils/src/forward_simple_transparent.slf +++ /dev/null @@ -1,91 +0,0 @@ -<@include gpu/Config.slh@> -<$VERSION_HEADER$> -// Generated on <$_SCRIBE_DATE$> -// -// forward_simple_transparent.frag -// fragment shader -// -// Created by Andrzej Kapolka on 9/15/14. -// Copyright 2014 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// -<@include DefaultMaterials.slh@> - -<@include ForwardGlobalLight.slh@> -<$declareEvalGlobalLightingAlphaBlended()$> - -// the interpolated normal -in vec3 _normalWS; -in vec3 _normalMS; -in vec4 _color; -in vec2 _texCoord0; -in vec4 _positionMS; -in vec4 _positionES; - -// For retro-compatibility -#define _normal _normalWS -#define _modelNormal _normalMS -#define _position _positionMS -#define _eyePosition _positionES - -layout(location = 0) out vec4 _fragColor0; - -//PROCEDURAL_COMMON_BLOCK - -#line 1001 -//PROCEDURAL_BLOCK - -#line 2030 -void main(void) { - vec3 normal = normalize(_normalWS.xyz); - vec3 diffuse = _color.rgb; - vec3 specular = DEFAULT_SPECULAR; - float shininess = DEFAULT_SHININESS; - float emissiveAmount = 0.0; - -#ifdef PROCEDURAL - -#ifdef PROCEDURAL_V1 - specular = getProceduralColor().rgb; - // Procedural Shaders are expected to be Gamma corrected so let's bring back the RGB in linear space for the rest of the pipeline - //specular = pow(specular, vec3(2.2)); - emissiveAmount = 1.0; -#else - emissiveAmount = getProceduralColors(diffuse, specular, shininess); -#endif - -#endif - - TransformCamera cam = getTransformCamera(); - vec3 fragPosition = _positionES.xyz; - - if (emissiveAmount > 0.0) { - _fragColor0 = vec4(evalGlobalLightingAlphaBlendedWithHaze( - cam._viewInverse, - 1.0, - DEFAULT_OCCLUSION, - fragPosition, - normal, - specular, - DEFAULT_FRESNEL, - DEFAULT_METALLIC, - DEFAULT_EMISSIVE, - DEFAULT_ROUGHNESS, _color.a), - _color.a); - } else { - _fragColor0 = vec4(evalGlobalLightingAlphaBlendedWithHaze( - cam._viewInverse, - 1.0, - DEFAULT_OCCLUSION, - fragPosition, - normal, - diffuse, - DEFAULT_FRESNEL, - DEFAULT_METALLIC, - DEFAULT_EMISSIVE, - DEFAULT_ROUGHNESS, _color.a), - _color.a); - } -} diff --git a/libraries/render-utils/src/fxaa.slf b/libraries/render-utils/src/fxaa.slf index 94fa75c47f..2dddcc795b 100644 --- a/libraries/render-utils/src/fxaa.slf +++ b/libraries/render-utils/src/fxaa.slf @@ -22,13 +22,13 @@ precision mediump float; precision mediump int; #endif -uniform sampler2D colorTexture; +layout(binding=0) uniform sampler2D colorTexture; //uniform sampler2D historyTexture; -uniform vec2 texcoordOffset; +layout(location=0) uniform vec2 texcoordOffset; -in vec2 varTexCoord0; -layout(location = 0) out vec4 outFragColor; -//layout(location = 0) out vec4 outFragHistory; +layout(location=0) in vec2 varTexCoord0; +layout(location=0) out vec4 outFragColor; +//layout(location=0) out vec4 outFragHistory; void main() { outFragColor = vec4(texture(colorTexture, varTexCoord0).xyz, 1.0/8.0); diff --git a/libraries/render-utils/src/fxaa.slv b/libraries/render-utils/src/fxaa.slv index 35a96ceb24..037adc18bf 100644 --- a/libraries/render-utils/src/fxaa.slv +++ b/libraries/render-utils/src/fxaa.slv @@ -18,7 +18,7 @@ <$declareStandardTransform()$> -out vec2 varTexcoord; +layout(location=0) out vec2 varTexcoord; void main(void) { varTexcoord = inTexCoord0.xy; diff --git a/libraries/render-utils/src/fxaa_blend.slf b/libraries/render-utils/src/fxaa_blend.slf index 996344c881..aca050f047 100644 --- a/libraries/render-utils/src/fxaa_blend.slf +++ b/libraries/render-utils/src/fxaa_blend.slf @@ -12,13 +12,13 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -<@include DeferredBufferWrite.slh@> +<@include gpu/ShaderConstants.h@> -in vec2 varTexCoord0; -out vec4 outFragColor; +layout(location=0) in vec2 varTexCoord0; +layout(location=0) out vec4 outFragColor; -uniform sampler2D colorTexture; -uniform float sharpenIntensity; +layout(binding=0) uniform sampler2D colorTexture; +layout(location=GPU_UNIFORM_EXTRA0) uniform float sharpenIntensity; void main(void) { vec4 pixels[9]; diff --git a/libraries/render-utils/src/glowLine.slf b/libraries/render-utils/src/glowLine.slf index 6a7a6157a4..4e80b3358a 100644 --- a/libraries/render-utils/src/glowLine.slf +++ b/libraries/render-utils/src/glowLine.slf @@ -9,10 +9,11 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -in vec4 _color; +<@include render-utils/ShaderConstants.h@> -in float distanceFromCenter; -out vec4 _fragColor; +layout(location=RENDER_UTILS_ATTR_COLOR) in vec4 _color; +layout(location=0) in float distanceFromCenter; +layout(location=0) out vec4 _fragColor; void main(void) { // The incoming value actually ranges from -1 to 1, so modify it diff --git a/libraries/render-utils/src/glowLine.slv b/libraries/render-utils/src/glowLine.slv index 4532ed7b9f..075b291589 100644 --- a/libraries/render-utils/src/glowLine.slv +++ b/libraries/render-utils/src/glowLine.slv @@ -10,28 +10,34 @@ // <@include gpu/Transform.slh@> +<@include render-utils/ShaderConstants.h@> + <$declareStandardTransform()$> -layout(std140) uniform lineData { +struct LineData { vec4 p1; vec4 p2; vec4 color; float width; }; -out vec4 _color; +layout(std140, binding=0) uniform LineDataBuffer { + LineData _lineData; +}; + +layout(location=RENDER_UTILS_ATTR_COLOR) out vec4 _color; // the distance from the center in 'quad space' -out float distanceFromCenter; +layout(location=0) out float distanceFromCenter; void main(void) { - _color = color; + _color = _lineData.color; TransformCamera cam = getTransformCamera(); TransformObject obj = getTransformObject(); vec4 p1eye, p2eye; - <$transformModelToEyePos(cam, obj, p1, p1eye)$> - <$transformModelToEyePos(cam, obj, p2, p2eye)$> + <$transformModelToEyePos(cam, obj, _lineData.p1, p1eye)$> + <$transformModelToEyePos(cam, obj, _lineData.p2, p2eye)$> p1eye /= p1eye.w; p2eye /= p2eye.w; @@ -40,7 +46,7 @@ void main(void) { // Find the vector from the eye to one of the points vec3 v2 = normalize(p1eye.xyz); // The orthogonal vector is the cross product of these two - vec3 orthogonal = cross(v1, v2) * width; + vec3 orthogonal = cross(v1, v2) * _lineData.width; // Deteremine which end to emit based on the vertex id (even / odd) vec4 eye = (0 == gl_VertexID % 2) ? p1eye : p2eye; diff --git a/libraries/render-utils/src/grid.slf b/libraries/render-utils/src/grid.slf index a680e7093b..c2380c980d 100644 --- a/libraries/render-utils/src/grid.slf +++ b/libraries/render-utils/src/grid.slf @@ -1,7 +1,7 @@ <@include gpu/Config.slh@> <$VERSION_HEADER$> // Generated on <$_SCRIBE_DATE$> -// grid.slf +// grid.frag // fragment shader // // Created by Zach Pomerantz on 2/16/2016. @@ -11,6 +11,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +<@include gpu/ShaderConstants.h@> <@include gpu/Paint.slh@> struct Grid { @@ -19,13 +20,16 @@ struct Grid { vec4 edge; }; -uniform gridBuffer { Grid grid; }; +layout(binding=0) uniform gridBuffer { + Grid grid; +}; + Grid getGrid() { return grid; } -in vec2 varTexCoord0; -in vec4 varColor; +layout(location=GPU_ATTR_TEXCOORD0) in vec2 varTexCoord0; +layout(location=GPU_ATTR_COLOR) in vec4 varColor; -out vec4 outFragColor; +layout(location=0) out vec4 outFragColor; void main(void) { Grid grid = getGrid(); diff --git a/libraries/render-utils/src/hmd_ui.slf b/libraries/render-utils/src/hmd_ui.slf index 959e8d733c..eebeb2e060 100644 --- a/libraries/render-utils/src/hmd_ui.slf +++ b/libraries/render-utils/src/hmd_ui.slf @@ -11,20 +11,23 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +<@include render-utils/ShaderConstants.h@> -uniform sampler2D hudTexture; +layout(binding=0) uniform sampler2D hudTexture; struct HUDData { float alpha; }; -layout(std140) uniform hudBuffer { +layout(std140, binding=0) uniform hudBuffer { HUDData hud; }; -in vec2 _texCoord0; +layout(location=RENDER_UTILS_ATTR_TEXCOORD01) in vec4 _texCoord01; +#define _texCoord0 _texCoord01.xy +#define _texCoord1 _texCoord01.zw -out vec4 fragColor0; +layout(location=0) out vec4 fragColor0; void main() { vec4 color = texture(hudTexture, _texCoord0); diff --git a/libraries/render-utils/src/hmd_ui.slv b/libraries/render-utils/src/hmd_ui.slv index d6e02ff4cb..ab0d77c42a 100644 --- a/libraries/render-utils/src/hmd_ui.slv +++ b/libraries/render-utils/src/hmd_ui.slv @@ -14,20 +14,22 @@ <@include gpu/Inputs.slh@> <@include gpu/Transform.slh@> +<@include render-utils/ShaderConstants.h@> + <$declareStandardTransform()$> struct HUDData { float alpha; }; -layout(std140) uniform hudBuffer { +layout(std140, binding=0) uniform hudBuffer { HUDData hud; }; -out vec2 _texCoord0; +layout(location=RENDER_UTILS_ATTR_TEXCOORD01) out vec4 _texCoord01; void main() { - _texCoord0 = inTexCoord0.st; + _texCoord01.xy = inTexCoord0.st; // standard transform TransformCamera cam = getTransformCamera(); diff --git a/libraries/render-utils/src/lightClusters_drawClusterContent.slf b/libraries/render-utils/src/lightClusters_drawClusterContent.slf index d4d97c5b16..65ae8f423e 100644 --- a/libraries/render-utils/src/lightClusters_drawClusterContent.slf +++ b/libraries/render-utils/src/lightClusters_drawClusterContent.slf @@ -1,7 +1,7 @@ <@include gpu/Config.slh@> <$VERSION_HEADER$> // Generated on <$_SCRIBE_DATE$> -// lightClusters_drawClusterFro Depth.slf +// lightClusters_drawClusterContent.frag // // Created by Sam Gateau on 9/8/2016. // Copyright 2015 High Fidelity, Inc. @@ -24,8 +24,8 @@ <$declareColorWheel()$> -in vec2 varTexCoord0; -out vec4 _fragColor; +layout(location=0) in vec2 varTexCoord0; +layout(location=0) out vec4 _fragColor; void main(void) { diff --git a/libraries/render-utils/src/lightClusters_drawClusterContent.slv b/libraries/render-utils/src/lightClusters_drawClusterContent.slv index b88e2e9ee2..d7e4a66a6a 100644 --- a/libraries/render-utils/src/lightClusters_drawClusterContent.slv +++ b/libraries/render-utils/src/lightClusters_drawClusterContent.slv @@ -2,7 +2,7 @@ <$VERSION_HEADER$> // Generated on <$_SCRIBE_DATE$> // -// lightClusters_drawClusterContent.slv +// lightClusters_drawClusterContent.vert // Vertex shader // // Created by Sam Gateau on 9/8/2016 @@ -21,7 +21,7 @@ <$declareColorWheel()$> -out vec4 varColor; +layout(location=0) out vec4 varColor; void main(void) { diff --git a/libraries/render-utils/src/lightClusters_drawClusterFromDepth.slf b/libraries/render-utils/src/lightClusters_drawClusterFromDepth.slf index c51d45ed44..4efb60a259 100644 --- a/libraries/render-utils/src/lightClusters_drawClusterFromDepth.slf +++ b/libraries/render-utils/src/lightClusters_drawClusterFromDepth.slf @@ -1,7 +1,7 @@ <@include gpu/Config.slh@> <$VERSION_HEADER$> // Generated on <$_SCRIBE_DATE$> -// lightClusters_drawClusterFro Depth.slf +// lightClusters_drawClusterFromDepth.frag // // Created by Sam Gateau on 9/8/2016. // Copyright 2015 High Fidelity, Inc. @@ -21,8 +21,8 @@ <$declareColorWheel()$> -in vec2 varTexCoord0; -out vec4 _fragColor; +layout(location=0) in vec2 varTexCoord0; +layout(location=0) out vec4 _fragColor; void main(void) { diff --git a/libraries/render-utils/src/lightClusters_drawClusterFromDepth.slv b/libraries/render-utils/src/lightClusters_drawClusterFromDepth.slv index 912c39f93c..d35c7cb20b 100644 --- a/libraries/render-utils/src/lightClusters_drawClusterFromDepth.slv +++ b/libraries/render-utils/src/lightClusters_drawClusterFromDepth.slv @@ -2,7 +2,7 @@ <$VERSION_HEADER$> // Generated on <$_SCRIBE_DATE$> // -// lightClusters_drawClusterFrom Depth.slv +// lightClusters_drawClusterFrom Depth.vert // Vertex shader // // Created by Sam Gateau on 9/8/2016 @@ -23,7 +23,7 @@ -out vec4 varColor; +layout(location=0) out vec4 varColor; void main(void) { diff --git a/libraries/render-utils/src/lightClusters_drawGrid.slf b/libraries/render-utils/src/lightClusters_drawGrid.slf index 33c8cc56b8..47ed84eeec 100644 --- a/libraries/render-utils/src/lightClusters_drawGrid.slf +++ b/libraries/render-utils/src/lightClusters_drawGrid.slf @@ -1,7 +1,7 @@ <@include gpu/Config.slh@> <$VERSION_HEADER$> // Generated on <$_SCRIBE_DATE$> -// lightClusters_drawGrid.slf +// lightClusters_drawGrid.frag // // Created by Sam Gateau on 9/8/2016. // Copyright 2015 High Fidelity, Inc. @@ -10,8 +10,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -in vec4 varColor; -out vec4 outFragColor; +layout(location=0) in vec4 varColor; +layout(location=0) out vec4 outFragColor; void main(void) { diff --git a/libraries/render-utils/src/lightClusters_drawGrid.slv b/libraries/render-utils/src/lightClusters_drawGrid.slv index aac7fe59a5..c4aff45beb 100644 --- a/libraries/render-utils/src/lightClusters_drawGrid.slv +++ b/libraries/render-utils/src/lightClusters_drawGrid.slv @@ -2,7 +2,7 @@ <$VERSION_HEADER$> // Generated on <$_SCRIBE_DATE$> // -// lightClusters_drawGrid.slv +// lightClusters_drawGrid.vert // Vertex shader // // Created by Sam Gateau on 9/8/2016 @@ -23,7 +23,7 @@ -out vec4 varColor; +layout(location=0) out vec4 varColor; void main(void) { diff --git a/libraries/render-utils/src/local_lights_drawOutline.slf b/libraries/render-utils/src/local_lights_drawOutline.slf index f4a135ff0d..fc1d416f96 100644 --- a/libraries/render-utils/src/local_lights_drawOutline.slf +++ b/libraries/render-utils/src/local_lights_drawOutline.slf @@ -30,10 +30,13 @@ <@include LightClusterGrid.slh@> +<@include render-utils/ShaderConstants.h@> -in vec2 _texCoord0; -out vec4 _fragColor; +layout(location=RENDER_UTILS_ATTR_TEXCOORD01) in vec4 _texCoord01; +#define _texCoord0 _texCoord01.xy +#define _texCoord1 _texCoord01.zw +layout(location=0) out vec4 _fragColor; void main(void) { diff --git a/libraries/render-utils/src/local_lights_shading.slf b/libraries/render-utils/src/local_lights_shading.slf index b224fcb77d..538bdacc99 100644 --- a/libraries/render-utils/src/local_lights_shading.slf +++ b/libraries/render-utils/src/local_lights_shading.slf @@ -19,8 +19,12 @@ <@include LightLocal.slh@> -in vec2 _texCoord0; -out vec4 _fragColor; +<@include render-utils/ShaderConstants.h@> + +layout(location=RENDER_UTILS_ATTR_TEXCOORD01) in vec4 _texCoord01; +#define _texCoord0 _texCoord01.xy +#define _texCoord1 _texCoord01.zw +layout(location=0) out vec4 _fragColor; void main(void) { _fragColor = vec4(0.0); diff --git a/libraries/render-utils/src/model.slf b/libraries/render-utils/src/model.slf index e5c25c8bc1..2c42ed6083 100644 --- a/libraries/render-utils/src/model.slf +++ b/libraries/render-utils/src/model.slf @@ -2,7 +2,7 @@ <$VERSION_HEADER$> // Generated on <$_SCRIBE_DATE$> // -// model_specular_map.frag +// model.frag // fragment shader // // Created by Andrzej Kapolka on 5/6/14. @@ -13,17 +13,17 @@ // <@include DeferredBufferWrite.slh@> - <@include graphics/Material.slh@> +<@include graphics/MaterialTextures.slh@> +<@include render-utils/ShaderConstants.h@> -<@include MaterialTextures.slh@> <$declareMaterialTextures(ALBEDO, ROUGHNESS, _SCRIBE_NULL, METALLIC, EMISSIVE, OCCLUSION, SCATTERING)$> -in vec2 _texCoord0; -in vec2 _texCoord1; -in vec3 _normalWS; -in vec3 _color; - +layout(location=RENDER_UTILS_ATTR_TEXCOORD01) in vec4 _texCoord01; +#define _texCoord0 _texCoord01.xy +#define _texCoord1 _texCoord01.zw +layout(location=RENDER_UTILS_ATTR_NORMAL_WS) in vec3 _normalWS; +layout(location=RENDER_UTILS_ATTR_COLOR) in vec4 _color; void main(void) { Material mat = getMaterial(); @@ -37,7 +37,7 @@ void main(void) { vec3 albedo = getMaterialAlbedo(mat); <$evalMaterialAlbedo(albedoTex, albedo, matKey, albedo)$>; - albedo *= _color; + albedo *= _color.rgb; float roughness = getMaterialRoughness(mat); <$evalMaterialRoughness(roughnessTex, roughness, matKey, roughness)$>; diff --git a/libraries/render-utils/src/model.slv b/libraries/render-utils/src/model.slv index 7cfedfe877..3763b8d2de 100644 --- a/libraries/render-utils/src/model.slv +++ b/libraries/render-utils/src/model.slv @@ -1,7 +1,7 @@ <@include gpu/Config.slh@> <$VERSION_HEADER$> // Generated on <$_SCRIBE_DATE$> -// model.slv +// model.vert // vertex shader // // Created by Andrzej Kapolka on 10/14/13. @@ -14,25 +14,25 @@ <@include gpu/Inputs.slh@> <@include gpu/Color.slh@> <@include gpu/Transform.slh@> +<@include graphics/MaterialTextures.slh@> +<@include render-utils/ShaderConstants.h@> + <$declareStandardTransform()$> -<@include MaterialTextures.slh@> <$declareMaterialTexMapArrayBuffer()$> -out vec3 _color; -out float _alpha; -out vec2 _texCoord0; -out vec2 _texCoord1; -out vec4 _positionES; -out vec3 _normalWS; +layout(location=RENDER_UTILS_ATTR_POSITION_ES) out vec4 _positionES; +layout(location=RENDER_UTILS_ATTR_TEXCOORD01) out vec4 _texCoord01; +layout(location=RENDER_UTILS_ATTR_NORMAL_WS) out vec3 _normalWS; +layout(location=RENDER_UTILS_ATTR_COLOR) out vec4 _color; void main(void) { - _color = color_sRGBToLinear(inColor.xyz); - _alpha = inColor.w; + _color.rgb = color_sRGBToLinear(inColor.rgb); + _color.a = inColor.a; TexMapArray texMapArray = getTexMapArray(); - <$evalTexMapArrayTexcoord0(texMapArray, inTexCoord0, _texCoord0)$> - <$evalTexMapArrayTexcoord1(texMapArray, inTexCoord0, _texCoord1)$> + <$evalTexMapArrayTexcoord0(texMapArray, inTexCoord0, _texCoord01.xy)$> + <$evalTexMapArrayTexcoord1(texMapArray, inTexCoord0, _texCoord01.zw)$> // standard transform TransformCamera cam = getTransformCamera(); diff --git a/libraries/render-utils/src/model_fade.slf b/libraries/render-utils/src/model_fade.slf index 323d1828a0..b5a2c8d3ef 100644 --- a/libraries/render-utils/src/model_fade.slf +++ b/libraries/render-utils/src/model_fade.slf @@ -16,17 +16,20 @@ <@include graphics/Material.slh@> -<@include MaterialTextures.slh@> +<@include graphics/MaterialTextures.slh@> <$declareMaterialTextures(ALBEDO, ROUGHNESS, _SCRIBE_NULL, METALLIC, EMISSIVE, OCCLUSION)$> <@include Fade.slh@> <$declareFadeFragment()$> -in vec2 _texCoord0; -in vec2 _texCoord1; -in vec3 _normalWS; -in vec3 _color; -in vec4 _positionWS; +<@include render-utils/ShaderConstants.h@> + +layout(location=RENDER_UTILS_ATTR_POSITION_WS) in vec4 _positionWS; +layout(location=RENDER_UTILS_ATTR_TEXCOORD01) in vec4 _texCoord01; +#define _texCoord0 _texCoord01.xy +#define _texCoord1 _texCoord01.zw +layout(location=RENDER_UTILS_ATTR_NORMAL_WS) in vec3 _normalWS; +layout(location=RENDER_UTILS_ATTR_COLOR) in vec4 _color; void main(void) { vec3 fadeEmissive; @@ -46,7 +49,7 @@ void main(void) { vec3 albedo = getMaterialAlbedo(mat); <$evalMaterialAlbedo(albedoTex, albedo, matKey, albedo)$>; - albedo *= _color; + albedo *= _color.rgb; float roughness = getMaterialRoughness(mat); <$evalMaterialRoughness(roughnessTex, roughness, matKey, roughness)$>; diff --git a/libraries/render-utils/src/model_fade.slv b/libraries/render-utils/src/model_fade.slv index 6e3a8271ce..84f4f08fed 100644 --- a/libraries/render-utils/src/model_fade.slv +++ b/libraries/render-utils/src/model_fade.slv @@ -1,7 +1,7 @@ <@include gpu/Config.slh@> <$VERSION_HEADER$> // Generated on <$_SCRIBE_DATE$> -// model_fade.slv +// model_fade.vert // vertex shader // // Created by Olivier Prat on 04/24/17. @@ -16,24 +16,24 @@ <@include gpu/Transform.slh@> <$declareStandardTransform()$> -<@include MaterialTextures.slh@> +<@include graphics/MaterialTextures.slh@> <$declareMaterialTexMapArrayBuffer()$> -out float _alpha; -out vec2 _texCoord0; -out vec2 _texCoord1; -out vec4 _positionES; -out vec4 _positionWS; -out vec3 _normalWS; -out vec3 _color; +<@include render-utils/ShaderConstants.h@> + +layout(location=RENDER_UTILS_ATTR_POSITION_ES) out vec4 _positionES; +layout(location=RENDER_UTILS_ATTR_POSITION_WS) out vec4 _positionWS; +layout(location=RENDER_UTILS_ATTR_TEXCOORD01) out vec4 _texCoord01; +layout(location=RENDER_UTILS_ATTR_NORMAL_WS) out vec3 _normalWS; +layout(location=RENDER_UTILS_ATTR_COLOR) out vec4 _color; void main(void) { - _color = color_sRGBToLinear(inColor.xyz); - _alpha = inColor.w; + _color.rgb = color_sRGBToLinear(inColor.rgb); + _color.a = inColor.w; TexMapArray texMapArray = getTexMapArray(); - <$evalTexMapArrayTexcoord0(texMapArray, inTexCoord0, _texCoord0)$> - <$evalTexMapArrayTexcoord1(texMapArray, inTexCoord0, _texCoord1)$> + <$evalTexMapArrayTexcoord0(texMapArray, inTexCoord0, _texCoord01.xy)$> + <$evalTexMapArrayTexcoord1(texMapArray, inTexCoord0, _texCoord01.zw)$> // standard transform TransformCamera cam = getTransformCamera(); diff --git a/libraries/render-utils/src/model_lightmap.slf b/libraries/render-utils/src/model_lightmap.slf index c4eed4185e..efc36cc14a 100644 --- a/libraries/render-utils/src/model_lightmap.slf +++ b/libraries/render-utils/src/model_lightmap.slf @@ -2,7 +2,7 @@ <$VERSION_HEADER$> // Generated on <$_SCRIBE_DATE$> // -// model_lightmap_specular_map.frag +// model_lightmap.frag // fragment shader // // Created by Samuel Gateau on 11/19/14. @@ -13,17 +13,19 @@ // <@include DeferredBufferWrite.slh@> - <@include graphics/Material.slh@> +<@include graphics/MaterialTextures.slh@> +<@include render-utils/ShaderConstants.h@> -<@include MaterialTextures.slh@> <$declareMaterialTextures(ALBEDO, ROUGHNESS, _SCRIBE_NULL, METALLIC)$> <$declareMaterialLightmap()$> -in vec2 _texCoord0; -in vec2 _texCoord1; -in vec3 _normalWS; -in vec3 _color; + +layout(location=RENDER_UTILS_ATTR_TEXCOORD01) in vec4 _texCoord01; +#define _texCoord0 _texCoord01.xy +#define _texCoord1 _texCoord01.zw +layout(location=RENDER_UTILS_ATTR_NORMAL_WS) in vec3 _normalWS; +layout(location=RENDER_UTILS_ATTR_COLOR) in vec4 _color; void main(void) { Material mat = getMaterial(); @@ -34,7 +36,7 @@ void main(void) { packDeferredFragmentLightmap( normalize(_normalWS), evalOpaqueFinalAlpha(getMaterialOpacity(mat), albedo.a), - getMaterialAlbedo(mat) * albedo.rgb * _color, + getMaterialAlbedo(mat) * albedo.rgb * _color.rgb, getMaterialRoughness(mat) * roughness, getMaterialMetallic(mat) * metallicTex, /*metallicTex, // no use of */getMaterialFresnel(mat), diff --git a/libraries/render-utils/src/model_lightmap.slv b/libraries/render-utils/src/model_lightmap.slv index b3f20357cd..7306e2c831 100644 --- a/libraries/render-utils/src/model_lightmap.slv +++ b/libraries/render-utils/src/model_lightmap.slv @@ -15,25 +15,26 @@ <@include gpu/Inputs.slh@> <@include gpu/Color.slh@> <@include gpu/Transform.slh@> +<@include graphics/MaterialTextures.slh@> +<@include render-utils/ShaderConstants.h@> + <$declareStandardTransform()$> -<@include MaterialTextures.slh@> <$declareMaterialTexMapArrayBuffer()$> -out vec4 _positionES; -out vec2 _texCoord0; -out vec2 _texCoord1; -out vec3 _normalWS; -out vec3 _color; +layout(location=RENDER_UTILS_ATTR_POSITION_ES) out vec4 _positionES; +layout(location=RENDER_UTILS_ATTR_TEXCOORD01) out vec4 _texCoord01; +layout(location=RENDER_UTILS_ATTR_NORMAL_WS) out vec3 _normalWS; +layout(location=RENDER_UTILS_ATTR_COLOR) out vec4 _color; void main(void) { // pass along the color in linear space - _color = color_sRGBToLinear(inColor.xyz); + _color.rgb = color_sRGBToLinear(inColor.xyz); // and the texture coordinates TexMapArray texMapArray = getTexMapArray(); - <$evalTexMapArrayTexcoord0(texMapArray, inTexCoord0, _texCoord0)$> - <$evalTexMapArrayTexcoord1(texMapArray, inTexCoord1, _texCoord1)$> + <$evalTexMapArrayTexcoord0(texMapArray, inTexCoord0, _texCoord01.xy)$> + <$evalTexMapArrayTexcoord1(texMapArray, inTexCoord1, _texCoord01.zw)$> // standard transform TransformCamera cam = getTransformCamera(); diff --git a/libraries/render-utils/src/model_lightmap_fade.slf b/libraries/render-utils/src/model_lightmap_fade.slf index ba651711c3..4cbf3dcdea 100644 --- a/libraries/render-utils/src/model_lightmap_fade.slf +++ b/libraries/render-utils/src/model_lightmap_fade.slf @@ -16,18 +16,21 @@ <@include graphics/Material.slh@> -<@include MaterialTextures.slh@> +<@include graphics/MaterialTextures.slh@> <$declareMaterialTextures(ALBEDO, ROUGHNESS, _SCRIBE_NULL, METALLIC)$> <$declareMaterialLightmap()$> <@include Fade.slh@> <$declareFadeFragment()$> -in vec2 _texCoord0; -in vec2 _texCoord1; -in vec3 _normalWS; -in vec3 _color; -in vec4 _positionWS; +<@include render-utils/ShaderConstants.h@> + +layout(location=RENDER_UTILS_ATTR_TEXCOORD01) in vec4 _texCoord01; +#define _texCoord0 _texCoord01.xy +#define _texCoord1 _texCoord01.zw +layout(location=RENDER_UTILS_ATTR_NORMAL_WS) in vec3 _normalWS; +layout(location=RENDER_UTILS_ATTR_COLOR) in vec4 _color; +layout(location=RENDER_UTILS_ATTR_POSITION_WS) in vec4 _positionWS; void main(void) { vec3 fadeEmissive; @@ -44,7 +47,7 @@ void main(void) { packDeferredFragmentLightmap( normalize(_normalWS), evalOpaqueFinalAlpha(getMaterialOpacity(mat), albedo.a), - getMaterialAlbedo(mat) * albedo.rgb * _color, + getMaterialAlbedo(mat) * albedo.rgb * _color.rgb, getMaterialRoughness(mat) * roughness, getMaterialMetallic(mat) * metallicTex, /*metallicTex, // no use of */getMaterialFresnel(mat), diff --git a/libraries/render-utils/src/model_lightmap_fade.slv b/libraries/render-utils/src/model_lightmap_fade.slv index 31abb36c4b..d174d3c1d3 100644 --- a/libraries/render-utils/src/model_lightmap_fade.slv +++ b/libraries/render-utils/src/model_lightmap_fade.slv @@ -17,24 +17,26 @@ <@include gpu/Transform.slh@> <$declareStandardTransform()$> -<@include MaterialTextures.slh@> +<@include graphics/MaterialTextures.slh@> <$declareMaterialTexMapArrayBuffer()$> -out vec4 _positionES; -out vec2 _texCoord0; -out vec2 _texCoord1; -out vec3 _normalWS; -out vec3 _color; -out vec4 _positionWS; +<@include render-utils/ShaderConstants.h@> + +layout(location=RENDER_UTILS_ATTR_POSITION_ES) out vec4 _positionES; +layout(location=RENDER_UTILS_ATTR_TEXCOORD01) out vec4 _texCoord01; +layout(location=RENDER_UTILS_ATTR_NORMAL_WS) out vec3 _normalWS; +layout(location=RENDER_UTILS_ATTR_COLOR) out vec4 _color; +layout(location=RENDER_UTILS_ATTR_POSITION_WS) out vec4 _positionWS; void main(void) { // pass along the color in linear space - _color = color_sRGBToLinear(inColor.xyz); + _color.rgb = color_sRGBToLinear(inColor.xyz); + _color.a = 1.0; // and the texture coordinates TexMapArray texMapArray = getTexMapArray(); - <$evalTexMapArrayTexcoord0(texMapArray, inTexCoord0, _texCoord0)$> - <$evalTexMapArrayTexcoord1(texMapArray, inTexCoord1, _texCoord1)$> + <$evalTexMapArrayTexcoord0(texMapArray, inTexCoord0, _texCoord01.xy)$> + <$evalTexMapArrayTexcoord1(texMapArray, inTexCoord1, _texCoord01.zw)$> // standard transform TransformCamera cam = getTransformCamera(); diff --git a/libraries/render-utils/src/model_lightmap_normal_map.slf b/libraries/render-utils/src/model_lightmap_normal_map.slf index 57aaa3ed2e..ebafc6dfe2 100644 --- a/libraries/render-utils/src/model_lightmap_normal_map.slf +++ b/libraries/render-utils/src/model_lightmap_normal_map.slf @@ -13,19 +13,20 @@ // <@include DeferredBufferWrite.slh@> - <@include graphics/Material.slh@> +<@include graphics/MaterialTextures.slh@> +<@include render-utils/ShaderConstants.h@> -<@include MaterialTextures.slh@> <$declareMaterialTextures(ALBEDO, ROUGHNESS, NORMAL, METALLIC)$> <$declareMaterialLightmap()$> -in vec4 _positionES; -in vec2 _texCoord0; -in vec2 _texCoord1; -in vec3 _normalWS; -in vec3 _tangentWS; -in vec3 _color; +layout(location=RENDER_UTILS_ATTR_POSITION_ES) in vec4 _positionES; +layout(location=RENDER_UTILS_ATTR_TEXCOORD01) in vec4 _texCoord01; +#define _texCoord0 _texCoord01.xy +#define _texCoord1 _texCoord01.zw +layout(location=RENDER_UTILS_ATTR_NORMAL_WS) in vec3 _normalWS; +layout(location=RENDER_UTILS_ATTR_TANGENT_WS) in vec3 _tangentWS; +layout(location=RENDER_UTILS_ATTR_COLOR) in vec4 _color; void main(void) { Material mat = getMaterial(); @@ -39,7 +40,7 @@ void main(void) { packDeferredFragmentLightmap( normalize(fragNormal.xyz), evalOpaqueFinalAlpha(getMaterialOpacity(mat), albedo.a), - getMaterialAlbedo(mat) * albedo.rgb * _color, + getMaterialAlbedo(mat) * albedo.rgb * _color.rgb, getMaterialRoughness(mat) * roughness, getMaterialMetallic(mat) * metallicTex, /*specular, // no use of */ getMaterialFresnel(mat), diff --git a/libraries/render-utils/src/model_lightmap_normal_map.slv b/libraries/render-utils/src/model_lightmap_normal_map.slv index 15fc4099d5..17794bce5f 100644 --- a/libraries/render-utils/src/model_lightmap_normal_map.slv +++ b/libraries/render-utils/src/model_lightmap_normal_map.slv @@ -15,25 +15,28 @@ <@include gpu/Inputs.slh@> <@include gpu/Color.slh@> <@include gpu/Transform.slh@> +<@include graphics/MaterialTextures.slh@> +<@include render-utils/ShaderConstants.h@> + <$declareStandardTransform()$> -<@include MaterialTextures.slh@> <$declareMaterialTexMapArrayBuffer()$> -out vec4 _positionES; -out vec2 _texCoord0; -out vec2 _texCoord1; -out vec3 _normalWS; -out vec3 _tangentWS; -out vec3 _color; + +layout(location=RENDER_UTILS_ATTR_POSITION_ES) out vec4 _positionES; +layout(location=RENDER_UTILS_ATTR_TEXCOORD01) out vec4 _texCoord01; +layout(location=RENDER_UTILS_ATTR_NORMAL_WS) out vec3 _normalWS; +layout(location=RENDER_UTILS_ATTR_TANGENT_WS) out vec3 _tangentWS; +layout(location=RENDER_UTILS_ATTR_COLOR) out vec4 _color; void main(void) { // pass along the color in linear space - _color = color_sRGBToLinear(inColor.xyz); + _color.rgb = color_sRGBToLinear(inColor.xyz); + _color.a = 1.0; TexMapArray texMapArray = getTexMapArray(); - <$evalTexMapArrayTexcoord0(texMapArray, inTexCoord0, _texCoord0)$> - <$evalTexMapArrayTexcoord1(texMapArray, inTexCoord1, _texCoord1)$> + <$evalTexMapArrayTexcoord0(texMapArray, inTexCoord0, _texCoord01.xy)$> + <$evalTexMapArrayTexcoord1(texMapArray, inTexCoord1, _texCoord01.zw)$> // standard transform TransformCamera cam = getTransformCamera(); diff --git a/libraries/render-utils/src/model_lightmap_normal_map_fade.slf b/libraries/render-utils/src/model_lightmap_normal_map_fade.slf index 2cd5ac433f..a9bac0e051 100644 --- a/libraries/render-utils/src/model_lightmap_normal_map_fade.slf +++ b/libraries/render-utils/src/model_lightmap_normal_map_fade.slf @@ -16,20 +16,23 @@ <@include graphics/Material.slh@> -<@include MaterialTextures.slh@> +<@include graphics/MaterialTextures.slh@> <$declareMaterialTextures(ALBEDO, ROUGHNESS, NORMAL, METALLIC)$> <$declareMaterialLightmap()$> <@include Fade.slh@> <$declareFadeFragment()$> -in vec4 _positionES; -in vec2 _texCoord0; -in vec2 _texCoord1; -in vec3 _normalWS; -in vec3 _tangentWS; -in vec3 _color; -in vec4 _positionWS; +<@include render-utils/ShaderConstants.h@> + +layout(location=RENDER_UTILS_ATTR_POSITION_ES) in vec4 _positionES; +layout(location=RENDER_UTILS_ATTR_TEXCOORD01) in vec4 _texCoord01; +#define _texCoord0 _texCoord01.xy +#define _texCoord1 _texCoord01.zw +layout(location=RENDER_UTILS_ATTR_NORMAL_WS) in vec3 _normalWS; +layout(location=RENDER_UTILS_ATTR_TANGENT_WS) in vec3 _tangentWS; +layout(location=RENDER_UTILS_ATTR_COLOR) in vec4 _color; +layout(location=RENDER_UTILS_ATTR_POSITION_WS) in vec4 _positionWS; void main(void) { vec3 fadeEmissive; @@ -49,7 +52,7 @@ void main(void) { packDeferredFragmentLightmap( normalize(fragNormal.xyz), evalOpaqueFinalAlpha(getMaterialOpacity(mat), albedo.a), - getMaterialAlbedo(mat) * albedo.rgb * _color, + getMaterialAlbedo(mat) * albedo.rgb * _color.rgb, getMaterialRoughness(mat) * roughness, getMaterialMetallic(mat) * metallicTex, /*specular, // no use of */ getMaterialFresnel(mat), diff --git a/libraries/render-utils/src/model_lightmap_normal_map_fade.slv b/libraries/render-utils/src/model_lightmap_normal_map_fade.slv index 8fc3fa28d8..50a2bc43c9 100644 --- a/libraries/render-utils/src/model_lightmap_normal_map_fade.slv +++ b/libraries/render-utils/src/model_lightmap_normal_map_fade.slv @@ -15,26 +15,28 @@ <@include gpu/Inputs.slh@> <@include gpu/Color.slh@> <@include gpu/Transform.slh@> -<$declareStandardTransform()$> +<@include graphics/MaterialTextures.slh@> +<@include render-utils/ShaderConstants.h@> -<@include MaterialTextures.slh@> +<$declareStandardTransform()$> <$declareMaterialTexMapArrayBuffer()$> -out vec4 _positionES; -out vec2 _texCoord0; -out vec2 _texCoord1; -out vec3 _normalWS; -out vec3 _tangentWS; -out vec3 _color; -out vec4 _positionWS; + +layout(location=RENDER_UTILS_ATTR_POSITION_ES) out vec4 _positionES; +layout(location=RENDER_UTILS_ATTR_TEXCOORD01) out vec4 _texCoord01; +layout(location=RENDER_UTILS_ATTR_NORMAL_WS) out vec3 _normalWS; +layout(location=RENDER_UTILS_ATTR_TANGENT_WS) out vec3 _tangentWS; +layout(location=RENDER_UTILS_ATTR_COLOR) out vec4 _color; +layout(location=RENDER_UTILS_ATTR_POSITION_WS) out vec4 _positionWS; void main(void) { // pass along the color in linear space - _color = color_sRGBToLinear(inColor.xyz); + _color.rgb = color_sRGBToLinear(inColor.xyz); + _color.a = 1.0; TexMapArray texMapArray = getTexMapArray(); - <$evalTexMapArrayTexcoord0(texMapArray, inTexCoord0, _texCoord0)$> - <$evalTexMapArrayTexcoord1(texMapArray, inTexCoord1, _texCoord1)$> + <$evalTexMapArrayTexcoord0(texMapArray, inTexCoord0, _texCoord01.xy)$> + <$evalTexMapArrayTexcoord1(texMapArray, inTexCoord1, _texCoord01.zw)$> // standard transform TransformCamera cam = getTransformCamera(); diff --git a/libraries/render-utils/src/model_normal_map.slf b/libraries/render-utils/src/model_normal_map.slf index b13377af21..5f30830511 100644 --- a/libraries/render-utils/src/model_normal_map.slf +++ b/libraries/render-utils/src/model_normal_map.slf @@ -13,18 +13,19 @@ // <@include DeferredBufferWrite.slh@> - <@include graphics/Material.slh@> +<@include graphics/MaterialTextures.slh@> +<@include render-utils/ShaderConstants.h@> -<@include MaterialTextures.slh@> <$declareMaterialTextures(ALBEDO, ROUGHNESS, NORMAL, METALLIC, EMISSIVE, OCCLUSION, SCATTERING)$> -in vec4 _positionES; -in vec2 _texCoord0; -in vec2 _texCoord1; -in vec3 _normalWS; -in vec3 _tangentWS; -in vec3 _color; +layout(location=RENDER_UTILS_ATTR_POSITION_ES) in vec4 _positionES; +layout(location=RENDER_UTILS_ATTR_TEXCOORD01) in vec4 _texCoord01; +#define _texCoord0 _texCoord01.xy +#define _texCoord1 _texCoord01.zw +layout(location=RENDER_UTILS_ATTR_NORMAL_WS) in vec3 _normalWS; +layout(location=RENDER_UTILS_ATTR_TANGENT_WS) in vec3 _tangentWS; +layout(location=RENDER_UTILS_ATTR_COLOR) in vec4 _color; void main(void) { Material mat = getMaterial(); @@ -38,7 +39,7 @@ void main(void) { vec3 albedo = getMaterialAlbedo(mat); <$evalMaterialAlbedo(albedoTex, albedo, matKey, albedo)$>; - albedo *= _color; + albedo *= _color.rgb; float roughness = getMaterialRoughness(mat); <$evalMaterialRoughness(roughnessTex, roughness, matKey, roughness)$>; diff --git a/libraries/render-utils/src/model_normal_map.slv b/libraries/render-utils/src/model_normal_map.slv index cc99c6d22d..4a1a0c9264 100644 --- a/libraries/render-utils/src/model_normal_map.slv +++ b/libraries/render-utils/src/model_normal_map.slv @@ -15,27 +15,28 @@ <@include gpu/Inputs.slh@> <@include gpu/Color.slh@> <@include gpu/Transform.slh@> +<@include graphics/MaterialTextures.slh@> +<@include render-utils/ShaderConstants.h@> + <$declareStandardTransform()$> -<@include MaterialTextures.slh@> <$declareMaterialTexMapArrayBuffer()$> -out vec4 _positionES; -out vec2 _texCoord0; -out vec2 _texCoord1; -out vec3 _normalWS; -out vec3 _tangentWS; -out vec3 _color; -out float _alpha; + +layout(location=RENDER_UTILS_ATTR_POSITION_ES) out vec4 _positionES; +layout(location=RENDER_UTILS_ATTR_TEXCOORD01) out vec4 _texCoord01; +layout(location=RENDER_UTILS_ATTR_NORMAL_WS) out vec3 _normalWS; +layout(location=RENDER_UTILS_ATTR_TANGENT_WS) out vec3 _tangentWS; +layout(location=RENDER_UTILS_ATTR_COLOR) out vec4 _color; void main(void) { // pass along the color - _color = color_sRGBToLinear(inColor.rgb); - _alpha = inColor.a; + _color.rgb = color_sRGBToLinear(inColor.rgb); + _color.a = inColor.a; TexMapArray texMapArray = getTexMapArray(); - <$evalTexMapArrayTexcoord0(texMapArray, inTexCoord0, _texCoord0)$> - <$evalTexMapArrayTexcoord1(texMapArray, inTexCoord0, _texCoord1)$> + <$evalTexMapArrayTexcoord0(texMapArray, inTexCoord0, _texCoord01.xy)$> + <$evalTexMapArrayTexcoord1(texMapArray, inTexCoord0, _texCoord01.zw)$> // standard transform TransformCamera cam = getTransformCamera(); diff --git a/libraries/render-utils/src/model_normal_map_fade.slf b/libraries/render-utils/src/model_normal_map_fade.slf index 7ece8fea38..499f376efa 100644 --- a/libraries/render-utils/src/model_normal_map_fade.slf +++ b/libraries/render-utils/src/model_normal_map_fade.slf @@ -13,22 +13,23 @@ // <@include DeferredBufferWrite.slh@> - <@include graphics/Material.slh@> +<@include graphics/MaterialTextures.slh@> +<@include render-utils/ShaderConstants.h@> +<@include Fade.slh@> -<@include MaterialTextures.slh@> <$declareMaterialTextures(ALBEDO, ROUGHNESS, NORMAL, METALLIC, EMISSIVE, OCCLUSION)$> -<@include Fade.slh@> <$declareFadeFragment()$> -in vec4 _positionES; -in vec2 _texCoord0; -in vec2 _texCoord1; -in vec3 _normalWS; -in vec3 _tangentWS; -in vec3 _color; -in vec4 _positionWS; +layout(location=RENDER_UTILS_ATTR_POSITION_ES) in vec4 _positionES; +layout(location=RENDER_UTILS_ATTR_POSITION_WS) in vec4 _positionWS; +layout(location=RENDER_UTILS_ATTR_TEXCOORD01) in vec4 _texCoord01; +#define _texCoord0 _texCoord01.xy +#define _texCoord1 _texCoord01.zw +layout(location=RENDER_UTILS_ATTR_NORMAL_WS) in vec3 _normalWS; +layout(location=RENDER_UTILS_ATTR_TANGENT_WS) in vec3 _tangentWS; +layout(location=RENDER_UTILS_ATTR_COLOR) in vec4 _color; void main(void) { vec3 fadeEmissive; @@ -48,7 +49,7 @@ void main(void) { vec3 albedo = getMaterialAlbedo(mat); <$evalMaterialAlbedo(albedoTex, albedo, matKey, albedo)$>; - albedo *= _color; + albedo *= _color.rgb; float roughness = getMaterialRoughness(mat); <$evalMaterialRoughness(roughnessTex, roughness, matKey, roughness)$>; diff --git a/libraries/render-utils/src/model_normal_map_fade.slv b/libraries/render-utils/src/model_normal_map_fade.slv index a75087f93d..090027ac79 100644 --- a/libraries/render-utils/src/model_normal_map_fade.slv +++ b/libraries/render-utils/src/model_normal_map_fade.slv @@ -15,28 +15,28 @@ <@include gpu/Inputs.slh@> <@include gpu/Color.slh@> <@include gpu/Transform.slh@> -<$declareStandardTransform()$> +<@include graphics/MaterialTextures.slh@> +<@include render-utils/ShaderConstants.h@> -<@include MaterialTextures.slh@> +<$declareStandardTransform()$> <$declareMaterialTexMapArrayBuffer()$> -out vec4 _positionES; -out vec4 _positionWS; -out vec2 _texCoord0; -out vec2 _texCoord1; -out vec3 _normalWS; -out vec3 _tangentWS; -out vec3 _color; -out float _alpha; + +layout(location=RENDER_UTILS_ATTR_POSITION_ES) out vec4 _positionES; +layout(location=RENDER_UTILS_ATTR_POSITION_WS) out vec4 _positionWS; +layout(location=RENDER_UTILS_ATTR_TEXCOORD01) out vec4 _texCoord01; +layout(location=RENDER_UTILS_ATTR_NORMAL_WS) out vec3 _normalWS; +layout(location=RENDER_UTILS_ATTR_TANGENT_WS) out vec3 _tangentWS; +layout(location=RENDER_UTILS_ATTR_COLOR) out vec4 _color; void main(void) { // pass along the color - _color = color_sRGBToLinear(inColor.rgb); - _alpha = inColor.a; + _color.rgb = color_sRGBToLinear(inColor.rgb); + _color.a = inColor.a; TexMapArray texMapArray = getTexMapArray(); - <$evalTexMapArrayTexcoord0(texMapArray, inTexCoord0, _texCoord0)$> - <$evalTexMapArrayTexcoord1(texMapArray, inTexCoord0, _texCoord1)$> + <$evalTexMapArrayTexcoord0(texMapArray, inTexCoord0, _texCoord01.xy)$> + <$evalTexMapArrayTexcoord1(texMapArray, inTexCoord0, _texCoord01.zw)$> // standard transform TransformCamera cam = getTransformCamera(); diff --git a/libraries/render-utils/src/model_shadow.slf b/libraries/render-utils/src/model_shadow.slf index 178ea7b387..6426759ec7 100644 --- a/libraries/render-utils/src/model_shadow.slf +++ b/libraries/render-utils/src/model_shadow.slf @@ -12,7 +12,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -layout(location = 0) out vec4 _fragColor; +layout(location=0) out vec4 _fragColor; void main(void) { // pass-through to set z-buffer diff --git a/libraries/render-utils/src/model_shadow_fade.slf b/libraries/render-utils/src/model_shadow_fade.slf index 403f32c457..c6c8c23f65 100644 --- a/libraries/render-utils/src/model_shadow_fade.slf +++ b/libraries/render-utils/src/model_shadow_fade.slf @@ -13,11 +13,14 @@ // <@include Fade.slh@> +<@include render-utils/ShaderConstants.h@> + <$declareFadeFragment()$> -layout(location = 0) out vec4 _fragColor; -in vec4 _positionWS; +layout(location=RENDER_UTILS_ATTR_POSITION_WS) in vec4 _positionWS; + +layout(location=0) out vec4 _fragColor; void main(void) { FadeObjectParams fadeParams; diff --git a/libraries/render-utils/src/model_shadow_fade.slv b/libraries/render-utils/src/model_shadow_fade.slv index 72e4a1a823..cf180c2dc8 100644 --- a/libraries/render-utils/src/model_shadow_fade.slv +++ b/libraries/render-utils/src/model_shadow_fade.slv @@ -13,12 +13,13 @@ // <@include gpu/Inputs.slh@> - <@include gpu/Transform.slh@> +<@include render-utils/ShaderConstants.h@> <$declareStandardTransform()$> -out vec4 _positionWS; + +layout(location=RENDER_UTILS_ATTR_POSITION_WS) out vec4 _positionWS; void main(void) { // standard transform diff --git a/libraries/render-utils/src/model_translucent.slf b/libraries/render-utils/src/model_translucent.slf index b808ca4bab..00a871ace1 100644 --- a/libraries/render-utils/src/model_translucent.slf +++ b/libraries/render-utils/src/model_translucent.slf @@ -12,29 +12,29 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +<@include gpu/Transform.slh@> <@include graphics/Material.slh@> - +<@include graphics/MaterialTextures.slh@> +<@include render-utils/ShaderConstants.h@> +<@include LightLocal.slh@> +<@include ShadingModel.slh@> <@include DeferredGlobalLight.slh@> <$declareEvalGlobalLightingAlphaBlendedWithHaze()$> -<@include LightLocal.slh@> - -<@include gpu/Transform.slh@> <$declareStandardCameraTransform()$> -<@include MaterialTextures.slh@> <$declareMaterialTextures(ALBEDO, ROUGHNESS, _SCRIBE_NULL, _SCRIBE_NULL, EMISSIVE, OCCLUSION)$> -in vec2 _texCoord0; -in vec2 _texCoord1; -in vec4 _positionES; -in vec4 _positionWS; -in vec3 _normalWS; -in vec3 _color; -in float _alpha; +layout(location=RENDER_UTILS_ATTR_TEXCOORD01) in vec4 _texCoord01; +#define _texCoord0 _texCoord01.xy +#define _texCoord1 _texCoord01.zw +layout(location=RENDER_UTILS_ATTR_POSITION_ES) in vec4 _positionES; +layout(location=RENDER_UTILS_ATTR_POSITION_WS) in vec4 _positionWS; +layout(location=RENDER_UTILS_ATTR_NORMAL_WS) in vec3 _normalWS; +layout(location=RENDER_UTILS_ATTR_COLOR) in vec4 _color; -out vec4 _fragColor; +layout(location=0) out vec4 _fragColor; void main(void) { Material mat = getMaterial(); @@ -42,13 +42,13 @@ void main(void) { <$fetchMaterialTexturesCoord0(matKey, _texCoord0, albedoTex, roughnessTex, _SCRIBE_NULL, _SCRIBE_NULL, emissiveTex)$> <$fetchMaterialTexturesCoord1(matKey, _texCoord1, occlusionTex)$> - float opacity = getMaterialOpacity(mat) * _alpha; + float opacity = getMaterialOpacity(mat) * _color.a; <$evalMaterialOpacity(albedoTex.a, opacity, matKey, opacity)$>; - <$discardTransparent(opacity)$>; + <$discardInvisible(opacity)$>; vec3 albedo = getMaterialAlbedo(mat); <$evalMaterialAlbedo(albedoTex, albedo, matKey, albedo)$>; - albedo *= _color; + albedo *= _color.rgb; float roughness = getMaterialRoughness(mat); <$evalMaterialRoughness(roughnessTex, roughness, matKey, roughness)$>; @@ -83,7 +83,7 @@ void main(void) { 1.0, occlusionTex, fragPositionES, - fragPositionWS, + fragPositionWS, albedo, fresnel, metallic, diff --git a/libraries/render-utils/src/model_translucent.slv b/libraries/render-utils/src/model_translucent.slv index 61a1c96ce8..70e61cd788 100644 --- a/libraries/render-utils/src/model_translucent.slv +++ b/libraries/render-utils/src/model_translucent.slv @@ -1,7 +1,7 @@ <@include gpu/Config.slh@> <$VERSION_HEADER$> // Generated on <$_SCRIBE_DATE$> -// model_translucent.slv +// model_translucent.vert // vertex shader // // Created by Olivier Prat on 15/01/18. @@ -16,24 +16,24 @@ <@include gpu/Transform.slh@> <$declareStandardTransform()$> -<@include MaterialTextures.slh@> +<@include graphics/MaterialTextures.slh@> <$declareMaterialTexMapArrayBuffer()$> -out float _alpha; -out vec2 _texCoord0; -out vec2 _texCoord1; -out vec4 _positionES; -out vec4 _positionWS; -out vec3 _normalWS; -out vec3 _color; +<@include render-utils/ShaderConstants.h@> + +layout(location=RENDER_UTILS_ATTR_TEXCOORD01) out vec4 _texCoord01; +layout(location=RENDER_UTILS_ATTR_POSITION_ES) out vec4 _positionES; +layout(location=RENDER_UTILS_ATTR_POSITION_WS) out vec4 _positionWS; +layout(location=RENDER_UTILS_ATTR_NORMAL_WS) out vec3 _normalWS; +layout(location=RENDER_UTILS_ATTR_COLOR) out vec4 _color; void main(void) { - _color = color_sRGBToLinear(inColor.xyz); - _alpha = inColor.w; + _color.rgb = color_sRGBToLinear(inColor.rgb); + _color.a = inColor.a; TexMapArray texMapArray = getTexMapArray(); - <$evalTexMapArrayTexcoord0(texMapArray, inTexCoord0, _texCoord0)$> - <$evalTexMapArrayTexcoord1(texMapArray, inTexCoord0, _texCoord1)$> + <$evalTexMapArrayTexcoord0(texMapArray, inTexCoord0, _texCoord01.xy)$> + <$evalTexMapArrayTexcoord1(texMapArray, inTexCoord0, _texCoord01.zw)$> // standard transform TransformCamera cam = getTransformCamera(); diff --git a/libraries/render-utils/src/model_translucent_fade.slf b/libraries/render-utils/src/model_translucent_fade.slf index a93adee96b..3cebc59ea7 100644 --- a/libraries/render-utils/src/model_translucent_fade.slf +++ b/libraries/render-utils/src/model_translucent_fade.slf @@ -16,21 +16,23 @@ <@include gpu/Transform.slh@> <$declareStandardCameraTransform()$> -<@include MaterialTextures.slh@> +<@include graphics/MaterialTextures.slh@> <$declareMaterialTextures(ALBEDO, ROUGHNESS, _SCRIBE_NULL, _SCRIBE_NULL, EMISSIVE, OCCLUSION)$> <@include Fade.slh@> <$declareFadeFragment()$> -in vec2 _texCoord0; -in vec2 _texCoord1; -in vec4 _positionES; -in vec4 _positionWS; -in vec3 _normalWS; -in vec3 _color; -in float _alpha; +<@include render-utils/ShaderConstants.h@> -out vec4 _fragColor; +layout(location=RENDER_UTILS_ATTR_TEXCOORD01) in vec4 _texCoord01; +#define _texCoord0 _texCoord01.xy +#define _texCoord1 _texCoord01.zw +layout(location=RENDER_UTILS_ATTR_POSITION_ES) in vec4 _positionES; +layout(location=RENDER_UTILS_ATTR_POSITION_WS) in vec4 _positionWS; +layout(location=RENDER_UTILS_ATTR_NORMAL_WS) in vec3 _normalWS; +layout(location=RENDER_UTILS_ATTR_COLOR) in vec4 _color; + +layout(location=0) out vec4 _fragColor; void main(void) { vec3 fadeEmissive; @@ -44,13 +46,13 @@ void main(void) { <$fetchMaterialTexturesCoord0(matKey, _texCoord0, albedoTex, roughnessTex, _SCRIBE_NULL, _SCRIBE_NULL, emissiveTex)$> <$fetchMaterialTexturesCoord1(matKey, _texCoord1, occlusionTex)$> - float opacity = getMaterialOpacity(mat) * _alpha; + float opacity = getMaterialOpacity(mat) * _color.a; <$evalMaterialOpacity(albedoTex.a, opacity, matKey, opacity)$>; <$discardTransparent(opacity)$>; vec3 albedo = getMaterialAlbedo(mat); <$evalMaterialAlbedo(albedoTex, albedo, matKey, albedo)$>; - albedo *= _color; + albedo *= _color.rgb; float roughness = getMaterialRoughness(mat); <$evalMaterialRoughness(roughnessTex, roughness, matKey, roughness)$>; diff --git a/libraries/render-utils/src/model_translucent_normal_map.slf b/libraries/render-utils/src/model_translucent_normal_map.slf index 750149dc1b..45eee9d160 100644 --- a/libraries/render-utils/src/model_translucent_normal_map.slf +++ b/libraries/render-utils/src/model_translucent_normal_map.slf @@ -23,19 +23,21 @@ <@include gpu/Transform.slh@> <$declareStandardCameraTransform()$> -<@include MaterialTextures.slh@> +<@include graphics/MaterialTextures.slh@> <$declareMaterialTextures(ALBEDO, ROUGHNESS, NORMAL, _SCRIBE_NULL, EMISSIVE, OCCLUSION)$> -in vec2 _texCoord0; -in vec2 _texCoord1; -in vec4 _positionES; -in vec4 _positionWS; -in vec3 _normalWS; -in vec3 _tangentWS; -in vec3 _color; -in float _alpha; +<@include render-utils/ShaderConstants.h@> -out vec4 _fragColor; +layout(location=RENDER_UTILS_ATTR_TEXCOORD01) in vec4 _texCoord01; +#define _texCoord0 _texCoord01.xy +#define _texCoord1 _texCoord01.zw +layout(location=RENDER_UTILS_ATTR_POSITION_ES) in vec4 _positionES; +layout(location=RENDER_UTILS_ATTR_POSITION_WS) in vec4 _positionWS; +layout(location=RENDER_UTILS_ATTR_NORMAL_WS) in vec3 _normalWS; +layout(location=RENDER_UTILS_ATTR_TANGENT_WS) in vec3 _tangentWS; +layout(location=RENDER_UTILS_ATTR_COLOR) in vec4 _color; + +layout(location=0) out vec4 _fragColor; void main(void) { Material mat = getMaterial(); @@ -43,13 +45,13 @@ void main(void) { <$fetchMaterialTexturesCoord0(matKey, _texCoord0, albedoTex, roughnessTex, normalTex, _SCRIBE_NULL, emissiveTex)$> <$fetchMaterialTexturesCoord1(matKey, _texCoord1, occlusionTex)$> - float opacity = getMaterialOpacity(mat) * _alpha; + float opacity = getMaterialOpacity(mat) * _color.a; <$evalMaterialOpacity(albedoTex.a, opacity, matKey, opacity)$>; - <$discardTransparent(opacity)$>; + <$discardInvisible(opacity)$>; vec3 albedo = getMaterialAlbedo(mat); <$evalMaterialAlbedo(albedoTex, albedo, matKey, albedo)$>; - albedo *= _color; + albedo *= _color.rgb; float roughness = getMaterialRoughness(mat); <$evalMaterialRoughness(roughnessTex, roughness, matKey, roughness)$>; diff --git a/libraries/render-utils/src/model_translucent_normal_map.slv b/libraries/render-utils/src/model_translucent_normal_map.slv index 21d56418c0..299e1f53e1 100644 --- a/libraries/render-utils/src/model_translucent_normal_map.slv +++ b/libraries/render-utils/src/model_translucent_normal_map.slv @@ -1,7 +1,7 @@ <@include gpu/Config.slh@> <$VERSION_HEADER$> // Generated on <$_SCRIBE_DATE$> -// model_translucent_normal_map.slv +// model_translucent_normal_map.vert // vertex shader // // Created by Olivier Prat on 23/01/18. @@ -16,25 +16,25 @@ <@include gpu/Transform.slh@> <$declareStandardTransform()$> -<@include MaterialTextures.slh@> +<@include graphics/MaterialTextures.slh@> <$declareMaterialTexMapArrayBuffer()$> -out float _alpha; -out vec2 _texCoord0; -out vec2 _texCoord1; -out vec4 _positionES; -out vec4 _positionWS; -out vec3 _normalWS; -out vec3 _tangentWS; -out vec3 _color; +<@include render-utils/ShaderConstants.h@> + +layout(location=RENDER_UTILS_ATTR_TEXCOORD01) out vec4 _texCoord01; +layout(location=RENDER_UTILS_ATTR_POSITION_ES) out vec4 _positionES; +layout(location=RENDER_UTILS_ATTR_POSITION_WS) out vec4 _positionWS; +layout(location=RENDER_UTILS_ATTR_NORMAL_WS) out vec3 _normalWS; +layout(location=RENDER_UTILS_ATTR_TANGENT_WS) out vec3 _tangentWS; +layout(location=RENDER_UTILS_ATTR_COLOR) out vec4 _color; void main(void) { - _color = color_sRGBToLinear(inColor.xyz); - _alpha = inColor.w; + _color.rgb = color_sRGBToLinear(inColor.rgb); + _color.a = inColor.a; TexMapArray texMapArray = getTexMapArray(); - <$evalTexMapArrayTexcoord0(texMapArray, inTexCoord0, _texCoord0)$> - <$evalTexMapArrayTexcoord1(texMapArray, inTexCoord0, _texCoord1)$> + <$evalTexMapArrayTexcoord0(texMapArray, inTexCoord0, _texCoord01.xy)$> + <$evalTexMapArrayTexcoord1(texMapArray, inTexCoord0, _texCoord01.zw)$> // standard transform TransformCamera cam = getTransformCamera(); diff --git a/libraries/render-utils/src/model_translucent_normal_map_fade.slf b/libraries/render-utils/src/model_translucent_normal_map_fade.slf index c7615626ce..2ede2bfbaa 100644 --- a/libraries/render-utils/src/model_translucent_normal_map_fade.slf +++ b/libraries/render-utils/src/model_translucent_normal_map_fade.slf @@ -12,6 +12,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +<@include render-utils/ShaderConstants.h@> <@include graphics/Material.slh@> <@include DeferredGlobalLight.slh@> @@ -23,22 +24,24 @@ <@include gpu/Transform.slh@> <$declareStandardCameraTransform()$> -<@include MaterialTextures.slh@> +<@include graphics/MaterialTextures.slh@> <$declareMaterialTextures(ALBEDO, ROUGHNESS, NORMAL, _SCRIBE_NULL, EMISSIVE, OCCLUSION)$> <@include Fade.slh@> <$declareFadeFragment()$> -in vec2 _texCoord0; -in vec2 _texCoord1; -in vec4 _positionES; -in vec3 _normalWS; -in vec3 _tangentWS; -in vec3 _color; -in float _alpha; -in vec4 _positionWS; +<@include render-utils/ShaderConstants.h@> -out vec4 _fragColor; +layout(location=RENDER_UTILS_ATTR_TEXCOORD01) in vec4 _texCoord01; +#define _texCoord0 _texCoord01.xy +#define _texCoord1 _texCoord01.zw +layout(location=RENDER_UTILS_ATTR_POSITION_ES) in vec4 _positionES; +layout(location=RENDER_UTILS_ATTR_NORMAL_WS) in vec3 _normalWS; +layout(location=RENDER_UTILS_ATTR_TANGENT_WS) in vec3 _tangentWS; +layout(location=RENDER_UTILS_ATTR_COLOR) in vec4 _color; +layout(location=RENDER_UTILS_ATTR_POSITION_WS) in vec4 _positionWS; + +layout(location=0) out vec4 _fragColor; void main(void) { vec3 fadeEmissive; @@ -52,13 +55,13 @@ void main(void) { <$fetchMaterialTexturesCoord0(matKey, _texCoord0, albedoTex, roughnessTex, normalTex, _SCRIBE_NULL, emissiveTex)$> <$fetchMaterialTexturesCoord1(matKey, _texCoord1, occlusionTex)$> - float opacity = getMaterialOpacity(mat) * _alpha; + float opacity = getMaterialOpacity(mat) * _color.a; <$evalMaterialOpacity(albedoTex.a, opacity, matKey, opacity)$>; - <$discardTransparent(opacity)$>; + <$discardInvisible(opacity)$>; vec3 albedo = getMaterialAlbedo(mat); <$evalMaterialAlbedo(albedoTex, albedo, matKey, albedo)$>; - albedo *= _color; + albedo *= _color.rgb; float roughness = getMaterialRoughness(mat); <$evalMaterialRoughness(roughnessTex, roughness, matKey, roughness)$>; diff --git a/libraries/render-utils/src/model_translucent_unlit.slf b/libraries/render-utils/src/model_translucent_unlit.slf index e5507dd2e0..1e468791f4 100644 --- a/libraries/render-utils/src/model_translucent_unlit.slf +++ b/libraries/render-utils/src/model_translucent_unlit.slf @@ -14,28 +14,31 @@ <@include graphics/Material.slh@> -<@include MaterialTextures.slh@> +<@include graphics/MaterialTextures.slh@> <$declareMaterialTextures(ALBEDO, ROUGHNESS, _SCRIBE_NULL, _SCRIBE_NULL, EMISSIVE, OCCLUSION)$> <@include LightingModel.slh@> -in vec2 _texCoord0; -in vec3 _color; -in float _alpha; +<@include render-utils/ShaderConstants.h@> -out vec4 _fragColor; +layout(location=RENDER_UTILS_ATTR_TEXCOORD01) in vec4 _texCoord01; +#define _texCoord0 _texCoord01.xy +#define _texCoord1 _texCoord01.zw +layout(location=RENDER_UTILS_ATTR_COLOR) in vec4 _color; + +layout(location=0) out vec4 _fragColor; void main(void) { Material mat = getMaterial(); BITFIELD matKey = getMaterialKey(mat); <$fetchMaterialTexturesCoord0(matKey, _texCoord0, albedoTex)$> - float opacity = getMaterialOpacity(mat) * _alpha; + float opacity = getMaterialOpacity(mat) * _color.a; <$evalMaterialOpacity(albedoTex.a, opacity, matKey, opacity)$>; - <$discardTransparent(opacity)$>; + <$discardInvisible(opacity)$>; vec3 albedo = getMaterialAlbedo(mat); <$evalMaterialAlbedo(albedoTex, albedo, matKey, albedo)$>; - albedo *= _color; + albedo *= _color.rgb; _fragColor = vec4(albedo * isUnlitEnabled(), opacity); } diff --git a/libraries/render-utils/src/model_translucent_unlit_fade.slf b/libraries/render-utils/src/model_translucent_unlit_fade.slf index 016db4639f..cbbaae8641 100644 --- a/libraries/render-utils/src/model_translucent_unlit_fade.slf +++ b/libraries/render-utils/src/model_translucent_unlit_fade.slf @@ -14,19 +14,22 @@ <@include graphics/Material.slh@> -<@include MaterialTextures.slh@> +<@include graphics/MaterialTextures.slh@> <$declareMaterialTextures(ALBEDO, ROUGHNESS, _SCRIBE_NULL, _SCRIBE_NULL, EMISSIVE, OCCLUSION)$> <@include LightingModel.slh@> <@include Fade.slh@> <$declareFadeFragment()$> -in vec2 _texCoord0; -in vec3 _color; -in float _alpha; -in vec4 _positionWS; +<@include render-utils/ShaderConstants.h@> -out vec4 _fragColor; +layout(location=RENDER_UTILS_ATTR_TEXCOORD01) in vec4 _texCoord01; +#define _texCoord0 _texCoord01.xy +#define _texCoord1 _texCoord01.zw +layout(location=RENDER_UTILS_ATTR_COLOR) in vec4 _color; +layout(location=RENDER_UTILS_ATTR_POSITION_WS) in vec4 _positionWS; + +layout(location=0) out vec4 _fragColor; void main(void) { vec3 fadeEmissive; @@ -39,13 +42,13 @@ void main(void) { BITFIELD matKey = getMaterialKey(mat); <$fetchMaterialTexturesCoord0(matKey, _texCoord0, albedoTex)$> - float opacity = getMaterialOpacity(mat) * _alpha; + float opacity = getMaterialOpacity(mat) * _color.a; <$evalMaterialOpacity(albedoTex.a, opacity, matKey, opacity)$>; - <$discardTransparent(opacity)$>; + <$discardInvisible(opacity)$>; vec3 albedo = getMaterialAlbedo(mat); <$evalMaterialAlbedo(albedoTex, albedo, matKey, albedo)$>; - albedo *= _color; + albedo *= _color.rgb; albedo += fadeEmissive; _fragColor = vec4(albedo * isUnlitEnabled(), opacity); } diff --git a/libraries/render-utils/src/model_unlit.slf b/libraries/render-utils/src/model_unlit.slf index d4c1334e12..b14a807eb5 100644 --- a/libraries/render-utils/src/model_unlit.slf +++ b/libraries/render-utils/src/model_unlit.slf @@ -15,14 +15,16 @@ <@include DeferredBufferWrite.slh@> <@include LightingModel.slh@> <@include graphics/Material.slh@> +<@include graphics/MaterialTextures.slh@> +<@include render-utils/ShaderConstants.h@> -<@include MaterialTextures.slh@> <$declareMaterialTextures(ALBEDO)$> -in vec2 _texCoord0; -in vec3 _normalWS; -in vec3 _color; -in float _alpha; +layout(location=RENDER_UTILS_ATTR_TEXCOORD01) in vec4 _texCoord01; +#define _texCoord0 _texCoord01.xy +#define _texCoord1 _texCoord01.zw +layout(location=RENDER_UTILS_ATTR_NORMAL_WS) in vec3 _normalWS; +layout(location=RENDER_UTILS_ATTR_COLOR) in vec4 _color; void main(void) { @@ -36,7 +38,7 @@ void main(void) { vec3 albedo = getMaterialAlbedo(mat); <$evalMaterialAlbedo(albedoTex, albedo, matKey, albedo)$>; - albedo *= _color; + albedo *= _color.rgb; packDeferredFragmentUnlit( normalize(_normalWS), diff --git a/libraries/render-utils/src/model_unlit_fade.slf b/libraries/render-utils/src/model_unlit_fade.slf index d8f8cfce38..cb5c72bdf2 100644 --- a/libraries/render-utils/src/model_unlit_fade.slf +++ b/libraries/render-utils/src/model_unlit_fade.slf @@ -15,18 +15,20 @@ <@include DeferredBufferWrite.slh@> <@include LightingModel.slh@> <@include graphics/Material.slh@> - +<@include graphics/MaterialTextures.slh@> +<@include render-utils/ShaderConstants.h@> <@include Fade.slh@> + <$declareFadeFragment()$> -<@include MaterialTextures.slh@> <$declareMaterialTextures(ALBEDO)$> -in vec2 _texCoord0; -in vec3 _normalWS; -in vec3 _color; -in float _alpha; -in vec4 _positionWS; +layout(location=RENDER_UTILS_ATTR_POSITION_WS) in vec4 _positionWS; +layout(location=RENDER_UTILS_ATTR_TEXCOORD01) in vec4 _texCoord01; +#define _texCoord0 _texCoord01.xy +#define _texCoord1 _texCoord01.zw +layout(location=RENDER_UTILS_ATTR_NORMAL_WS) in vec3 _normalWS; +layout(location=RENDER_UTILS_ATTR_COLOR) in vec4 _color; void main(void) { vec3 fadeEmissive; @@ -45,7 +47,7 @@ void main(void) { vec3 albedo = getMaterialAlbedo(mat); <$evalMaterialAlbedo(albedoTex, albedo, matKey, albedo)$>; - albedo *= _color; + albedo *= _color.rgb; albedo += fadeEmissive; packDeferredFragmentUnlit( normalize(_normalWS), diff --git a/libraries/render-utils/src/nop.slf b/libraries/render-utils/src/nop.slf index f87db4e138..dd54f839ea 100644 --- a/libraries/render-utils/src/nop.slf +++ b/libraries/render-utils/src/nop.slf @@ -12,5 +12,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +layout(location=0) out vec4 _fragColor; + void main(void) { } diff --git a/libraries/render-utils/src/skin_model_shadow.slf b/libraries/render-utils/src/parabola.slf similarity index 50% rename from libraries/render-utils/src/skin_model_shadow.slf rename to libraries/render-utils/src/parabola.slf index e464d6e6c8..ea51d7e3af 100644 --- a/libraries/render-utils/src/skin_model_shadow.slf +++ b/libraries/render-utils/src/parabola.slf @@ -2,19 +2,17 @@ <$VERSION_HEADER$> // Generated on <$_SCRIBE_DATE$> // -// skin_model_shadow.frag -// fragment shader -// -// Created by Andrzej Kapolka on 3/24/14. -// Copyright 2013 High Fidelity, Inc. +// Created by Sam Gondelman on 7/18/2018 +// Copyright 2018 High Fidelity, Inc. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -layout(location = 0) out vec4 _fragColor; +<@include DeferredBufferWrite.slh@> + +layout(location=0) in vec4 _color; void main(void) { - // pass-through to set z-buffer - _fragColor = vec4(1.0, 1.0, 1.0, 0.0); + packDeferredFragmentUnlit(vec3(1.0, 0.0, 0.0), 1.0, _color.rgb); } diff --git a/libraries/render-utils/src/parabola.slv b/libraries/render-utils/src/parabola.slv new file mode 100644 index 0000000000..31b3ab8fae --- /dev/null +++ b/libraries/render-utils/src/parabola.slv @@ -0,0 +1,58 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// +// Created by Sam Gondelman on 7/18/2018 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +<@include gpu/Transform.slh@> +<$declareStandardTransform()$> + +struct ParabolaData { + vec3 velocity; + float parabolicDistance; + vec3 acceleration; + float width; + vec4 color; + int numSections; + ivec3 spare; +}; + +layout(std140, binding=0) uniform parabolaData { + ParabolaData _parabolaData; +}; + +layout(location=0) out vec4 _color; + +void main(void) { + _color = _parabolaData.color; + + float t = _parabolaData.parabolicDistance * (float(gl_VertexID / 2) / float(_parabolaData.numSections)); + + vec4 pos = vec4(_parabolaData.velocity * t + 0.5 * _parabolaData.acceleration * t * t, 1); + const float EPSILON = 0.00001; + vec4 normal; + + TransformCamera cam = getTransformCamera(); + TransformObject obj = getTransformObject(); + if (dot(_parabolaData.acceleration, _parabolaData.acceleration) < EPSILON) { + // Handle case where acceleration == (0, 0, 0) + vec3 eyeUp = vec3(0, 1, 0); + vec3 worldUp; + <$transformEyeToWorldDir(cam, eyeUp, worldUp)$> + normal = vec4(normalize(cross(_parabolaData.velocity, worldUp)), 0); + } else { + normal = vec4(normalize(cross(_parabolaData.velocity, _parabolaData.acceleration)), 0); + } + if (gl_VertexID % 2 == 0) { + pos += 0.5 * _parabolaData.width * normal; + } else { + pos -= 0.5 * _parabolaData.width * normal; + } + + <$transformModelToClipPos(cam, obj, pos, gl_Position)$> +} \ No newline at end of file diff --git a/libraries/render-utils/src/render-utils/ShaderConstants.h b/libraries/render-utils/src/render-utils/ShaderConstants.h new file mode 100644 index 0000000000..6a88a62287 --- /dev/null +++ b/libraries/render-utils/src/render-utils/ShaderConstants.h @@ -0,0 +1,227 @@ +// + +// <@if not RENDER_UTILS_SHADER_CONSTANTS_H@> +// <@def RENDER_UTILS_SHADER_CONSTANTS_H@> + +// Hack comment to absorb the extra '//' scribe prepends + +#ifndef RENDER_UTILS_SHADER_CONSTANTS_H +#define RENDER_UTILS_SHADER_CONSTANTS_H + +#define RENDER_UTILS_ATTR_TEXCOORD01 0 +#define RENDER_UTILS_ATTR_COLOR 1 + +// World space +#define RENDER_UTILS_ATTR_POSITION_WS 2 +#define RENDER_UTILS_ATTR_NORMAL_WS 3 +#define RENDER_UTILS_ATTR_TANGENT_WS 4 + +// Model space +#define RENDER_UTILS_ATTR_POSITION_MS 5 +#define RENDER_UTILS_ATTR_NORMAL_MS 6 + +// Eye space +#define RENDER_UTILS_ATTR_POSITION_ES 7 + +// don't conflict with GPU_ATTR_V2F_STEREO_SIDE in the GPU shader constants +#define RENDER_UTILS_ATTR_DO_NOT_USE 8 + +// Fade +#define RENDER_UTILS_ATTR_FADE1 9 +#define RENDER_UTILS_ATTR_FADE2 10 +#define RENDER_UTILS_ATTR_FADE3 11 + + +#define RENDER_UTILS_BUFFER_DEFERRED_FRAME_TRANSFORM 0 +#define RENDER_UTILS_BUFFER_LIGHT_MODEL 3 +#define RENDER_UTILS_BUFFER_AMBIENT_LIGHT 6 +#define RENDER_UTILS_BUFFER_LIGHT_INDEX 7 + +#define RENDER_UTILS_UNIFORM_LIGHT_RADIUS 0 +#define RENDER_UTILS_UNIFORM_LIGHT_TEXCOORD_TRANSFORM 1 + +// Deferred lighting resolution +#define RENDER_UTILS_TEXTURE_DEFERRRED_COLOR 0 +#define RENDER_UTILS_TEXTURE_DEFERRRED_NORMAL 1 +#define RENDER_UTILS_TEXTURE_DEFERRRED_SPECULAR 2 +#define RENDER_UTILS_TEXTURE_DEFERRRED_DEPTH 3 +#define RENDER_UTILS_TEXTURE_DEFERRED_OBSCURANCE 4 +#define RENDER_UTILS_TEXTURE_DEFERRRED_LINEAR_Z_EYE 5 +#define RENDER_UTILS_TEXTURE_DEFERRED_CURVATURE 6 +#define RENDER_UTILS_TEXTURE_DEFERRED_DIFFUSED_CURVATURE 7 +#define RENDER_UTILS_TEXTURE_DEFERRED_LIGHTING 10 +#define RENDER_UTILS_TEXTURE_SKYBOX 11 + +#define RENDER_UTILS_BUFFER_SHADOW_PARAMS 2 +#define RENDER_UTILS_TEXTURE_SHADOW 12 + +#define RENDER_UTILS_BUFFER_LIGHT_CLUSTER_FRUSTUM_GRID 10 +#define RENDER_UTILS_BUFFER_LIGHT_CLUSTER_GRID 11 +#define RENDER_UTILS_BUFFER_LIGHT_CLUSTER_CONTENT 12 + +// Haze +#define RENDER_UTILS_BUFFER_HAZE_PARAMS 7 +#define RENDER_UTILS_TEXTURE_HAZE_COLOR 0 +#define RENDER_UTILS_TEXTURE_HAZE_LINEAR_DEPTH 1 + +// Fading +#define RENDER_UTILS_BUFFER_FADE_PARAMS 8 +#define RENDER_UTILS_BUFFER_FADE_OBJECT_PARAMS 9 +#define RENDER_UTILS_TEXTURE_FADE_MASK 10 + +// Highlighting +#define RENDER_UTILS_BUFFER_HIGHLIGHT_PARAMS 2 +#define RENDER_UTILS_TEXTURE_HIGHLIGHT_SCENE_DEPTH 0 +#define RENDER_UTILS_TEXTURE_HIGHLIGHT_DEPTH 1 + +// Subsurface scattering +#define RENDER_UTILS_BUFFER_SSSC_PARAMS 13 +#define RENDER_UTILS_TEXTURE_SSSC_PROFILE 12 +#define RENDER_UTILS_TEXTURE_SSSC_LUT 8 +#define RENDER_UTILS_TEXTURE_SSSC_SPECULAR_BECKMANN 9 + +// Ambient occlusion +#define RENDER_UTILS_BUFFER_SSAO_PARAMS 2 +#define RENDER_UTILS_BUFFER_SSAO_DEBUG_PARAMS 3 +#define RENDER_UTILS_TEXTURE_SSAO_PYRAMID 1 +#define RENDER_UTILS_TEXTURE_SSAO_OCCLUSION 0 + +// Temporal anti-aliasing +#define RENDER_UTILS_BUFFER_TAA_PARAMS 2 +#define RENDER_UTILS_TEXTURE_TAA_HISTORY 0 +#define RENDER_UTILS_TEXTURE_TAA_SOURCE 1 +#define RENDER_UTILS_TEXTURE_TAA_VELOCITY 2 +#define RENDER_UTILS_TEXTURE_TAA_DEPTH 3 +#define RENDER_UTILS_TEXTURE_TAA_NEXT 4 + +// Surface Geometry +#define RENDER_UTILS_BUFFER_SG_PARAMS 1 +#define RENDER_UTILS_TEXTURE_SG_DEPTH 0 +#define RENDER_UTILS_TEXTURE_SG_NORMAL 1 + +// Blur +#define RENDER_UTILS_BUFFER_BLUR_PARAMS 0 +#define RENDER_UTILS_TEXTURE_BLUR_SOURCE 0 +#define RENDER_UTILS_TEXTURE_BLUR_DEPTH 1 + +// Tone Mapping +#define RENDER_UTILS_BUFFER_TM_PARAMS 0 +#define RENDER_UTILS_TEXTURE_TM_COLOR 0 + +// Bloom +#define RENDER_UTILS_BUFFER_BLOOM_PARAMS 1 +#define RENDER_UTILS_TEXTURE_BLOOM_COLOR 0 + +// SDF Text rendering +#define RENDER_UTILS_TEXTURE_TEXT_FONT 0 +#define RENDER_UTILS_UNIFORM_TEXT_COLOR 0 +#define RENDER_UTILS_UNIFORM_TEXT_OUTLINE 1 + + +// Debugging +#define RENDER_UTILS_BUFFER_DEBUG_SKYBOX 5 +#define RENDER_UTILS_DEBUG_TEXTURE0 11 +#define RENDER_UTILS_BUFFER_DEBUG_DEFERRED_PARAMS 1 + +// + +namespace render_utils { namespace slot { + +namespace uniform { +enum Uniform { + TextColor = RENDER_UTILS_UNIFORM_TEXT_COLOR, + TextOutline = RENDER_UTILS_UNIFORM_TEXT_OUTLINE, + TaaSharpenIntensity = GPU_UNIFORM_EXTRA0, + HighlightOutlineWidth = GPU_UNIFORM_EXTRA0, + LightRadius = RENDER_UTILS_UNIFORM_LIGHT_RADIUS, + TexcoordTransform = RENDER_UTILS_UNIFORM_LIGHT_TEXCOORD_TRANSFORM, +}; +} + +namespace buffer { +enum Buffer { + DeferredFrameTransform = RENDER_UTILS_BUFFER_DEFERRED_FRAME_TRANSFORM, + LightModel = RENDER_UTILS_BUFFER_LIGHT_MODEL, + AmbientLight = RENDER_UTILS_BUFFER_AMBIENT_LIGHT, + HazeParams = RENDER_UTILS_BUFFER_HAZE_PARAMS, + FadeParameters = RENDER_UTILS_BUFFER_FADE_PARAMS, + FadeObjectParameters = RENDER_UTILS_BUFFER_FADE_OBJECT_PARAMS, + LightClusterFrustumGrid = RENDER_UTILS_BUFFER_LIGHT_CLUSTER_FRUSTUM_GRID, + LightClusterGrid = RENDER_UTILS_BUFFER_LIGHT_CLUSTER_GRID, + LightClusterContent = RENDER_UTILS_BUFFER_LIGHT_CLUSTER_CONTENT, + SsscParams = RENDER_UTILS_BUFFER_SSSC_PARAMS, + SsaoParams = RENDER_UTILS_BUFFER_SSAO_PARAMS, + SsaoDebugParams = RENDER_UTILS_BUFFER_SSAO_DEBUG_PARAMS, + LightIndex = RENDER_UTILS_BUFFER_LIGHT_INDEX, + TaaParams = RENDER_UTILS_BUFFER_TAA_PARAMS, + HighlightParams = RENDER_UTILS_BUFFER_HIGHLIGHT_PARAMS, + DebugSkyboxParams = RENDER_UTILS_BUFFER_DEBUG_SKYBOX, + SurfaceGeometryParams = RENDER_UTILS_BUFFER_SG_PARAMS, + BlurParams = RENDER_UTILS_BUFFER_BLUR_PARAMS, + BloomParams = RENDER_UTILS_BUFFER_BLOOM_PARAMS, + ToneMappingParams = RENDER_UTILS_BUFFER_TM_PARAMS, + ShadowParams = RENDER_UTILS_BUFFER_SHADOW_PARAMS, + DebugDeferredParams = RENDER_UTILS_BUFFER_DEBUG_DEFERRED_PARAMS, +}; +} // namespace buffer + +namespace texture { +enum Texture { + DeferredColor = RENDER_UTILS_TEXTURE_DEFERRRED_COLOR, + DeferredNormal = RENDER_UTILS_TEXTURE_DEFERRRED_NORMAL, + DeferredSpecular = RENDER_UTILS_TEXTURE_DEFERRRED_SPECULAR, + DeferredDepth = RENDER_UTILS_TEXTURE_DEFERRRED_DEPTH, + DeferredLinearZEye = RENDER_UTILS_TEXTURE_DEFERRRED_LINEAR_Z_EYE, + DeferredObscurance = RENDER_UTILS_TEXTURE_DEFERRED_OBSCURANCE, + DeferredLighting = RENDER_UTILS_TEXTURE_DEFERRED_LIGHTING, + DeferredCurvature = RENDER_UTILS_TEXTURE_DEFERRED_CURVATURE, + DeferredDiffusedCurvature = RENDER_UTILS_TEXTURE_DEFERRED_DIFFUSED_CURVATURE, + SsscLut = RENDER_UTILS_TEXTURE_SSSC_LUT, + SsscSpecularBeckmann = RENDER_UTILS_TEXTURE_SSSC_SPECULAR_BECKMANN, + SsscProfile = RENDER_UTILS_TEXTURE_SSSC_PROFILE, + FadeMask = RENDER_UTILS_TEXTURE_FADE_MASK, + Skybox = RENDER_UTILS_TEXTURE_SKYBOX, + HazeColor = RENDER_UTILS_TEXTURE_HAZE_COLOR, + HazeLinearDepth = RENDER_UTILS_TEXTURE_HAZE_LINEAR_DEPTH, + Shadow = RENDER_UTILS_TEXTURE_SHADOW, + TaaHistory = RENDER_UTILS_TEXTURE_TAA_HISTORY, + TaaSource = RENDER_UTILS_TEXTURE_TAA_SOURCE, + TaaVelocity = RENDER_UTILS_TEXTURE_TAA_VELOCITY, + TaaDepth = RENDER_UTILS_TEXTURE_TAA_DEPTH, + TaaNext = RENDER_UTILS_TEXTURE_TAA_NEXT, + SsaoOcclusion = RENDER_UTILS_TEXTURE_SSAO_OCCLUSION, + SsaoPyramid = RENDER_UTILS_TEXTURE_SSAO_PYRAMID, + HighlightSceneDepth = RENDER_UTILS_TEXTURE_HIGHLIGHT_SCENE_DEPTH, + HighlightDepth = RENDER_UTILS_TEXTURE_HIGHLIGHT_DEPTH, + SurfaceGeometryDepth = RENDER_UTILS_TEXTURE_SG_DEPTH, + SurfaceGeometryNormal = RENDER_UTILS_TEXTURE_SG_NORMAL, + BlurSource = RENDER_UTILS_TEXTURE_BLUR_SOURCE, + BlurDepth = RENDER_UTILS_TEXTURE_BLUR_DEPTH, + BloomColor = RENDER_UTILS_TEXTURE_BLOOM_COLOR, + ToneMappingColor = RENDER_UTILS_TEXTURE_TM_COLOR, + TextFont = RENDER_UTILS_TEXTURE_TEXT_FONT, + DebugTexture0 = RENDER_UTILS_DEBUG_TEXTURE0, +}; +} // namespace texture + +} } // namespace render_utils::slot + +// !> +// Hack Comment + +#endif // RENDER_UTILS_SHADER_CONSTANTS_H + +// <@if 1@> +// Trigger Scribe include +// <@endif@> + +// <@endif@> + +// Hack Comment diff --git a/libraries/render-utils/src/render-utils/animdebugdraw.slp b/libraries/render-utils/src/render-utils/animdebugdraw.slp new file mode 100644 index 0000000000..e69de29bb2 diff --git a/libraries/render-utils/src/render-utils/bloomApply.slp b/libraries/render-utils/src/render-utils/bloomApply.slp new file mode 100644 index 0000000000..2fd542f895 --- /dev/null +++ b/libraries/render-utils/src/render-utils/bloomApply.slp @@ -0,0 +1,2 @@ +VERTEX gpu::vertex::DrawTransformUnitQuad +FRAGMENT BloomApply diff --git a/libraries/render-utils/src/render-utils/bloomThreshold.slp b/libraries/render-utils/src/render-utils/bloomThreshold.slp new file mode 100644 index 0000000000..9b39c9fb5c --- /dev/null +++ b/libraries/render-utils/src/render-utils/bloomThreshold.slp @@ -0,0 +1,2 @@ +VERTEX gpu::vertex::DrawTransformUnitQuad +FRAGMENT BloomThreshold diff --git a/libraries/render-utils/src/render-utils/directional_ambient_light.slp b/libraries/render-utils/src/render-utils/directional_ambient_light.slp new file mode 100644 index 0000000000..3aeca942ab --- /dev/null +++ b/libraries/render-utils/src/render-utils/directional_ambient_light.slp @@ -0,0 +1 @@ +VERTEX deferred_light diff --git a/libraries/render-utils/src/render-utils/directional_ambient_light_shadow.slp b/libraries/render-utils/src/render-utils/directional_ambient_light_shadow.slp new file mode 100644 index 0000000000..3aeca942ab --- /dev/null +++ b/libraries/render-utils/src/render-utils/directional_ambient_light_shadow.slp @@ -0,0 +1 @@ +VERTEX deferred_light diff --git a/libraries/render-utils/src/render-utils/directional_skybox_light.slp b/libraries/render-utils/src/render-utils/directional_skybox_light.slp new file mode 100644 index 0000000000..3aeca942ab --- /dev/null +++ b/libraries/render-utils/src/render-utils/directional_skybox_light.slp @@ -0,0 +1 @@ +VERTEX deferred_light diff --git a/libraries/render-utils/src/render-utils/directional_skybox_light_shadow.slp b/libraries/render-utils/src/render-utils/directional_skybox_light_shadow.slp new file mode 100644 index 0000000000..3aeca942ab --- /dev/null +++ b/libraries/render-utils/src/render-utils/directional_skybox_light_shadow.slp @@ -0,0 +1 @@ +VERTEX deferred_light diff --git a/libraries/render-utils/src/render-utils/drawWorkloadProxy.slp b/libraries/render-utils/src/render-utils/drawWorkloadProxy.slp new file mode 100644 index 0000000000..e69de29bb2 diff --git a/libraries/render-utils/src/render-utils/drawWorkloadView.slp b/libraries/render-utils/src/render-utils/drawWorkloadView.slp new file mode 100644 index 0000000000..e69de29bb2 diff --git a/libraries/render-utils/src/render-utils/forward_model.slp b/libraries/render-utils/src/render-utils/forward_model.slp new file mode 100644 index 0000000000..81ac672062 --- /dev/null +++ b/libraries/render-utils/src/render-utils/forward_model.slp @@ -0,0 +1 @@ +VERTEX model diff --git a/libraries/render-utils/src/render-utils/forward_model_normal_map.slp b/libraries/render-utils/src/render-utils/forward_model_normal_map.slp new file mode 100644 index 0000000000..c50be6285b --- /dev/null +++ b/libraries/render-utils/src/render-utils/forward_model_normal_map.slp @@ -0,0 +1 @@ +VERTEX model_normal_map diff --git a/libraries/render-utils/src/render-utils/forward_model_normal_map_translucent.slp b/libraries/render-utils/src/render-utils/forward_model_normal_map_translucent.slp new file mode 100644 index 0000000000..0979918b98 --- /dev/null +++ b/libraries/render-utils/src/render-utils/forward_model_normal_map_translucent.slp @@ -0,0 +1,2 @@ +VERTEX model_normal_map +FRAGMENT forward_model_translucent diff --git a/libraries/render-utils/src/render-utils/forward_model_translucent.slp b/libraries/render-utils/src/render-utils/forward_model_translucent.slp new file mode 100644 index 0000000000..81ac672062 --- /dev/null +++ b/libraries/render-utils/src/render-utils/forward_model_translucent.slp @@ -0,0 +1 @@ +VERTEX model diff --git a/libraries/render-utils/src/render-utils/forward_model_unlit.slp b/libraries/render-utils/src/render-utils/forward_model_unlit.slp new file mode 100644 index 0000000000..81ac672062 --- /dev/null +++ b/libraries/render-utils/src/render-utils/forward_model_unlit.slp @@ -0,0 +1 @@ +VERTEX model diff --git a/libraries/render-utils/src/render-utils/forward_simple_textured.slp b/libraries/render-utils/src/render-utils/forward_simple_textured.slp new file mode 100644 index 0000000000..10e6b388c4 --- /dev/null +++ b/libraries/render-utils/src/render-utils/forward_simple_textured.slp @@ -0,0 +1 @@ +VERTEX simple diff --git a/libraries/render-utils/src/render-utils/forward_simple_textured_transparent.slp b/libraries/render-utils/src/render-utils/forward_simple_textured_transparent.slp new file mode 100644 index 0000000000..10e6b388c4 --- /dev/null +++ b/libraries/render-utils/src/render-utils/forward_simple_textured_transparent.slp @@ -0,0 +1 @@ +VERTEX simple diff --git a/libraries/render-utils/src/render-utils/forward_simple_textured_unlit.slp b/libraries/render-utils/src/render-utils/forward_simple_textured_unlit.slp new file mode 100644 index 0000000000..10e6b388c4 --- /dev/null +++ b/libraries/render-utils/src/render-utils/forward_simple_textured_unlit.slp @@ -0,0 +1 @@ +VERTEX simple diff --git a/libraries/render-utils/src/render-utils/forward_skin_model.slp b/libraries/render-utils/src/render-utils/forward_skin_model.slp new file mode 100644 index 0000000000..962cf69ac2 --- /dev/null +++ b/libraries/render-utils/src/render-utils/forward_skin_model.slp @@ -0,0 +1,2 @@ +VERTEX skin_model +FRAGMENT forward_model diff --git a/libraries/render-utils/src/render-utils/forward_skin_model_dq.slp b/libraries/render-utils/src/render-utils/forward_skin_model_dq.slp new file mode 100644 index 0000000000..8fe119440f --- /dev/null +++ b/libraries/render-utils/src/render-utils/forward_skin_model_dq.slp @@ -0,0 +1,2 @@ +VERTEX skin_model_dq +FRAGMENT forward_model diff --git a/libraries/render-utils/src/render-utils/forward_skin_model_normal_map.slp b/libraries/render-utils/src/render-utils/forward_skin_model_normal_map.slp new file mode 100644 index 0000000000..5bae303829 --- /dev/null +++ b/libraries/render-utils/src/render-utils/forward_skin_model_normal_map.slp @@ -0,0 +1,2 @@ +VERTEX skin_model_normal_map +FRAGMENT forward_model_normal_map diff --git a/libraries/render-utils/src/render-utils/forward_skin_model_normal_map_dq.slp b/libraries/render-utils/src/render-utils/forward_skin_model_normal_map_dq.slp new file mode 100644 index 0000000000..551b8367c7 --- /dev/null +++ b/libraries/render-utils/src/render-utils/forward_skin_model_normal_map_dq.slp @@ -0,0 +1,2 @@ +VERTEX skin_model_normal_map_dq +FRAGMENT forward_model_normal_map diff --git a/libraries/render-utils/src/render-utils/forward_skin_translucent.slp b/libraries/render-utils/src/render-utils/forward_skin_translucent.slp new file mode 100644 index 0000000000..1468d52428 --- /dev/null +++ b/libraries/render-utils/src/render-utils/forward_skin_translucent.slp @@ -0,0 +1,2 @@ +VERTEX skin_model +FRAGMENT forward_model_translucent diff --git a/libraries/render-utils/src/render-utils/forward_skin_translucent_dq.slp b/libraries/render-utils/src/render-utils/forward_skin_translucent_dq.slp new file mode 100644 index 0000000000..688bf51ba2 --- /dev/null +++ b/libraries/render-utils/src/render-utils/forward_skin_translucent_dq.slp @@ -0,0 +1,2 @@ +VERTEX skin_model_dq +FRAGMENT forward_model_translucent diff --git a/libraries/render-utils/src/render-utils/forward_skin_translucent_normal_map.slp b/libraries/render-utils/src/render-utils/forward_skin_translucent_normal_map.slp new file mode 100644 index 0000000000..fe3db07670 --- /dev/null +++ b/libraries/render-utils/src/render-utils/forward_skin_translucent_normal_map.slp @@ -0,0 +1,2 @@ +VERTEX skin_model_normal_map +FRAGMENT forward_model_translucent diff --git a/libraries/render-utils/src/render-utils/forward_skin_translucent_normal_map_dq.slp b/libraries/render-utils/src/render-utils/forward_skin_translucent_normal_map_dq.slp new file mode 100644 index 0000000000..2012a77e89 --- /dev/null +++ b/libraries/render-utils/src/render-utils/forward_skin_translucent_normal_map_dq.slp @@ -0,0 +1,2 @@ +VERTEX skin_model_normal_map_dq +FRAGMENT forward_model_translucent diff --git a/libraries/render-utils/src/render-utils/fxaa_blend.slp b/libraries/render-utils/src/render-utils/fxaa_blend.slp new file mode 100644 index 0000000000..c2c4bfbebd --- /dev/null +++ b/libraries/render-utils/src/render-utils/fxaa_blend.slp @@ -0,0 +1 @@ +VERTEX gpu::vertex::DrawUnitQuadTexcoord diff --git a/libraries/render-utils/src/render-utils/glowLine.slp b/libraries/render-utils/src/render-utils/glowLine.slp new file mode 100644 index 0000000000..e69de29bb2 diff --git a/libraries/render-utils/src/render-utils/grid.slp b/libraries/render-utils/src/render-utils/grid.slp new file mode 100644 index 0000000000..c81b208f63 --- /dev/null +++ b/libraries/render-utils/src/render-utils/grid.slp @@ -0,0 +1 @@ +VERTEX standardTransformPNTC diff --git a/libraries/render-utils/src/render-utils/haze.slp b/libraries/render-utils/src/render-utils/haze.slp new file mode 100644 index 0000000000..805b855c8d --- /dev/null +++ b/libraries/render-utils/src/render-utils/haze.slp @@ -0,0 +1,2 @@ +VERTEX gpu::vertex::DrawViewportQuadTransformTexcoord +FRAGMENT Haze diff --git a/libraries/render-utils/src/render-utils/highlight.slp b/libraries/render-utils/src/render-utils/highlight.slp new file mode 100644 index 0000000000..269774815b --- /dev/null +++ b/libraries/render-utils/src/render-utils/highlight.slp @@ -0,0 +1,2 @@ +VERTEX gpu::vertex::DrawViewportQuadTransformTexcoord +FRAGMENT Highlight diff --git a/libraries/render-utils/src/render-utils/highlight_aabox.slp b/libraries/render-utils/src/render-utils/highlight_aabox.slp new file mode 100644 index 0000000000..a6f94fa285 --- /dev/null +++ b/libraries/render-utils/src/render-utils/highlight_aabox.slp @@ -0,0 +1,2 @@ +VERTEX Highlight_aabox +FRAGMENT nop diff --git a/libraries/render-utils/src/render-utils/highlight_filled.slp b/libraries/render-utils/src/render-utils/highlight_filled.slp new file mode 100644 index 0000000000..166afd74be --- /dev/null +++ b/libraries/render-utils/src/render-utils/highlight_filled.slp @@ -0,0 +1,2 @@ +VERTEX gpu::vertex::DrawViewportQuadTransformTexcoord +FRAGMENT Highlight_filled diff --git a/libraries/render-utils/src/render-utils/hmd_ui.slp b/libraries/render-utils/src/render-utils/hmd_ui.slp new file mode 100644 index 0000000000..e69de29bb2 diff --git a/libraries/render-utils/src/render-utils/lightClusters_drawClusterContent.slp b/libraries/render-utils/src/render-utils/lightClusters_drawClusterContent.slp new file mode 100644 index 0000000000..c2c4bfbebd --- /dev/null +++ b/libraries/render-utils/src/render-utils/lightClusters_drawClusterContent.slp @@ -0,0 +1 @@ +VERTEX gpu::vertex::DrawUnitQuadTexcoord diff --git a/libraries/render-utils/src/render-utils/lightClusters_drawClusterFromDepth.slp b/libraries/render-utils/src/render-utils/lightClusters_drawClusterFromDepth.slp new file mode 100644 index 0000000000..c2c4bfbebd --- /dev/null +++ b/libraries/render-utils/src/render-utils/lightClusters_drawClusterFromDepth.slp @@ -0,0 +1 @@ +VERTEX gpu::vertex::DrawUnitQuadTexcoord diff --git a/libraries/render-utils/src/render-utils/lightClusters_drawGrid.slp b/libraries/render-utils/src/render-utils/lightClusters_drawGrid.slp new file mode 100644 index 0000000000..e69de29bb2 diff --git a/libraries/render-utils/src/render-utils/local_lights_drawOutline.slp b/libraries/render-utils/src/render-utils/local_lights_drawOutline.slp new file mode 100644 index 0000000000..3aeca942ab --- /dev/null +++ b/libraries/render-utils/src/render-utils/local_lights_drawOutline.slp @@ -0,0 +1 @@ +VERTEX deferred_light diff --git a/libraries/render-utils/src/render-utils/local_lights_shading.slp b/libraries/render-utils/src/render-utils/local_lights_shading.slp new file mode 100644 index 0000000000..3aeca942ab --- /dev/null +++ b/libraries/render-utils/src/render-utils/local_lights_shading.slp @@ -0,0 +1 @@ +VERTEX deferred_light diff --git a/libraries/render-utils/src/render-utils/model.slp b/libraries/render-utils/src/render-utils/model.slp new file mode 100644 index 0000000000..e69de29bb2 diff --git a/libraries/render-utils/src/render-utils/model_fade.slp b/libraries/render-utils/src/render-utils/model_fade.slp new file mode 100644 index 0000000000..e69de29bb2 diff --git a/libraries/render-utils/src/render-utils/model_lightmap.slp b/libraries/render-utils/src/render-utils/model_lightmap.slp new file mode 100644 index 0000000000..e69de29bb2 diff --git a/libraries/render-utils/src/render-utils/model_lightmap_fade.slp b/libraries/render-utils/src/render-utils/model_lightmap_fade.slp new file mode 100644 index 0000000000..e69de29bb2 diff --git a/libraries/render-utils/src/render-utils/model_lightmap_normal_map.slp b/libraries/render-utils/src/render-utils/model_lightmap_normal_map.slp new file mode 100644 index 0000000000..e69de29bb2 diff --git a/libraries/render-utils/src/render-utils/model_lightmap_normal_map_fade.slp b/libraries/render-utils/src/render-utils/model_lightmap_normal_map_fade.slp new file mode 100644 index 0000000000..e69de29bb2 diff --git a/libraries/render-utils/src/render-utils/model_normal_map.slp b/libraries/render-utils/src/render-utils/model_normal_map.slp new file mode 100644 index 0000000000..e69de29bb2 diff --git a/libraries/render-utils/src/render-utils/model_normal_map_fade.slp b/libraries/render-utils/src/render-utils/model_normal_map_fade.slp new file mode 100644 index 0000000000..e69de29bb2 diff --git a/libraries/render-utils/src/render-utils/model_shadow.slp b/libraries/render-utils/src/render-utils/model_shadow.slp new file mode 100644 index 0000000000..e69de29bb2 diff --git a/libraries/render-utils/src/render-utils/model_shadow_fade.slp b/libraries/render-utils/src/render-utils/model_shadow_fade.slp new file mode 100644 index 0000000000..e69de29bb2 diff --git a/libraries/render-utils/src/render-utils/model_translucent.slp b/libraries/render-utils/src/render-utils/model_translucent.slp new file mode 100644 index 0000000000..e69de29bb2 diff --git a/libraries/render-utils/src/render-utils/model_translucent_fade.slp b/libraries/render-utils/src/render-utils/model_translucent_fade.slp new file mode 100644 index 0000000000..ba3a685b5b --- /dev/null +++ b/libraries/render-utils/src/render-utils/model_translucent_fade.slp @@ -0,0 +1 @@ +VERTEX model_fade diff --git a/libraries/render-utils/src/render-utils/model_translucent_normal_map.slp b/libraries/render-utils/src/render-utils/model_translucent_normal_map.slp new file mode 100644 index 0000000000..e69de29bb2 diff --git a/libraries/render-utils/src/render-utils/model_translucent_normal_map_fade.slp b/libraries/render-utils/src/render-utils/model_translucent_normal_map_fade.slp new file mode 100644 index 0000000000..01953a9891 --- /dev/null +++ b/libraries/render-utils/src/render-utils/model_translucent_normal_map_fade.slp @@ -0,0 +1 @@ +VERTEX model_translucent_normal_map diff --git a/libraries/render-utils/src/render-utils/model_translucent_unlit.slp b/libraries/render-utils/src/render-utils/model_translucent_unlit.slp new file mode 100644 index 0000000000..81ac672062 --- /dev/null +++ b/libraries/render-utils/src/render-utils/model_translucent_unlit.slp @@ -0,0 +1 @@ +VERTEX model diff --git a/libraries/render-utils/src/render-utils/model_translucent_unlit_fade.slp b/libraries/render-utils/src/render-utils/model_translucent_unlit_fade.slp new file mode 100644 index 0000000000..ba3a685b5b --- /dev/null +++ b/libraries/render-utils/src/render-utils/model_translucent_unlit_fade.slp @@ -0,0 +1 @@ +VERTEX model_fade diff --git a/libraries/render-utils/src/render-utils/model_unlit.slp b/libraries/render-utils/src/render-utils/model_unlit.slp new file mode 100644 index 0000000000..81ac672062 --- /dev/null +++ b/libraries/render-utils/src/render-utils/model_unlit.slp @@ -0,0 +1 @@ +VERTEX model diff --git a/libraries/render-utils/src/render-utils/model_unlit_fade.slp b/libraries/render-utils/src/render-utils/model_unlit_fade.slp new file mode 100644 index 0000000000..ba3a685b5b --- /dev/null +++ b/libraries/render-utils/src/render-utils/model_unlit_fade.slp @@ -0,0 +1 @@ +VERTEX model_fade diff --git a/libraries/render-utils/src/render-utils/parabola.slp b/libraries/render-utils/src/render-utils/parabola.slp new file mode 100644 index 0000000000..e69de29bb2 diff --git a/libraries/render-utils/src/render-utils/sdf_text3D.slp b/libraries/render-utils/src/render-utils/sdf_text3D.slp new file mode 100644 index 0000000000..e69de29bb2 diff --git a/libraries/render-utils/src/render-utils/sdf_text3D_transparent.slp b/libraries/render-utils/src/render-utils/sdf_text3D_transparent.slp new file mode 100644 index 0000000000..3eea3a0da0 --- /dev/null +++ b/libraries/render-utils/src/render-utils/sdf_text3D_transparent.slp @@ -0,0 +1 @@ +VERTEX sdf_text3D diff --git a/libraries/render-utils/src/render-utils/simple.slp b/libraries/render-utils/src/render-utils/simple.slp new file mode 100644 index 0000000000..8a6e2e4f99 --- /dev/null +++ b/libraries/render-utils/src/render-utils/simple.slp @@ -0,0 +1 @@ +FRAGMENT forward_simple_textured diff --git a/libraries/render-utils/src/render-utils/simpleTranslucent.slp b/libraries/render-utils/src/render-utils/simpleTranslucent.slp new file mode 100644 index 0000000000..0163b09b84 --- /dev/null +++ b/libraries/render-utils/src/render-utils/simpleTranslucent.slp @@ -0,0 +1,2 @@ +VERTEX simple +FRAGMENT forward_simple_textured_transparent diff --git a/libraries/render-utils/src/render-utils/simpleTranslucentUnlit.slp b/libraries/render-utils/src/render-utils/simpleTranslucentUnlit.slp new file mode 100644 index 0000000000..f1d1ec39be --- /dev/null +++ b/libraries/render-utils/src/render-utils/simpleTranslucentUnlit.slp @@ -0,0 +1,2 @@ +VERTEX simple +FRAGMENT simple_transparent_textured_unlit diff --git a/libraries/render-utils/src/render-utils/simpleUnlit.slp b/libraries/render-utils/src/render-utils/simpleUnlit.slp new file mode 100644 index 0000000000..ab491aa290 --- /dev/null +++ b/libraries/render-utils/src/render-utils/simpleUnlit.slp @@ -0,0 +1,2 @@ +VERTEX simple +FRAGMENT forward_simple_textured_unlit diff --git a/libraries/render-utils/src/render-utils/simple_opaque_web_browser.slp b/libraries/render-utils/src/render-utils/simple_opaque_web_browser.slp new file mode 100644 index 0000000000..10e6b388c4 --- /dev/null +++ b/libraries/render-utils/src/render-utils/simple_opaque_web_browser.slp @@ -0,0 +1 @@ +VERTEX simple diff --git a/libraries/render-utils/src/render-utils/simple_textured.slp b/libraries/render-utils/src/render-utils/simple_textured.slp new file mode 100644 index 0000000000..10e6b388c4 --- /dev/null +++ b/libraries/render-utils/src/render-utils/simple_textured.slp @@ -0,0 +1 @@ +VERTEX simple diff --git a/libraries/render-utils/src/render-utils/simple_textured_fade.slp b/libraries/render-utils/src/render-utils/simple_textured_fade.slp new file mode 100644 index 0000000000..9be0f525ad --- /dev/null +++ b/libraries/render-utils/src/render-utils/simple_textured_fade.slp @@ -0,0 +1 @@ +VERTEX simple_fade diff --git a/libraries/render-utils/src/render-utils/simple_textured_unlit.slp b/libraries/render-utils/src/render-utils/simple_textured_unlit.slp new file mode 100644 index 0000000000..10e6b388c4 --- /dev/null +++ b/libraries/render-utils/src/render-utils/simple_textured_unlit.slp @@ -0,0 +1 @@ +VERTEX simple diff --git a/libraries/render-utils/src/render-utils/simple_textured_unlit_fade.slp b/libraries/render-utils/src/render-utils/simple_textured_unlit_fade.slp new file mode 100644 index 0000000000..9be0f525ad --- /dev/null +++ b/libraries/render-utils/src/render-utils/simple_textured_unlit_fade.slp @@ -0,0 +1 @@ +VERTEX simple_fade diff --git a/libraries/render-utils/src/render-utils/simple_transparent_textured.slp b/libraries/render-utils/src/render-utils/simple_transparent_textured.slp new file mode 100644 index 0000000000..10e6b388c4 --- /dev/null +++ b/libraries/render-utils/src/render-utils/simple_transparent_textured.slp @@ -0,0 +1 @@ +VERTEX simple diff --git a/libraries/render-utils/src/render-utils/simple_transparent_textured_fade.slp b/libraries/render-utils/src/render-utils/simple_transparent_textured_fade.slp new file mode 100644 index 0000000000..9be0f525ad --- /dev/null +++ b/libraries/render-utils/src/render-utils/simple_transparent_textured_fade.slp @@ -0,0 +1 @@ +VERTEX simple_fade diff --git a/libraries/render-utils/src/render-utils/simple_transparent_textured_unlit.slp b/libraries/render-utils/src/render-utils/simple_transparent_textured_unlit.slp new file mode 100644 index 0000000000..10e6b388c4 --- /dev/null +++ b/libraries/render-utils/src/render-utils/simple_transparent_textured_unlit.slp @@ -0,0 +1 @@ +VERTEX simple diff --git a/libraries/render-utils/src/render-utils/simple_transparent_textured_unlit_fade.slp b/libraries/render-utils/src/render-utils/simple_transparent_textured_unlit_fade.slp new file mode 100644 index 0000000000..9be0f525ad --- /dev/null +++ b/libraries/render-utils/src/render-utils/simple_transparent_textured_unlit_fade.slp @@ -0,0 +1 @@ +VERTEX simple_fade diff --git a/libraries/render-utils/src/render-utils/simple_transparent_web_browser.slp b/libraries/render-utils/src/render-utils/simple_transparent_web_browser.slp new file mode 100644 index 0000000000..10e6b388c4 --- /dev/null +++ b/libraries/render-utils/src/render-utils/simple_transparent_web_browser.slp @@ -0,0 +1 @@ +VERTEX simple diff --git a/libraries/render-utils/src/render-utils/skin_model.slp b/libraries/render-utils/src/render-utils/skin_model.slp new file mode 100644 index 0000000000..d6466a6aa4 --- /dev/null +++ b/libraries/render-utils/src/render-utils/skin_model.slp @@ -0,0 +1 @@ +FRAGMENT model diff --git a/libraries/render-utils/src/render-utils/skin_model_dq.slp b/libraries/render-utils/src/render-utils/skin_model_dq.slp new file mode 100644 index 0000000000..d6466a6aa4 --- /dev/null +++ b/libraries/render-utils/src/render-utils/skin_model_dq.slp @@ -0,0 +1 @@ +FRAGMENT model diff --git a/libraries/render-utils/src/render-utils/skin_model_fade.slp b/libraries/render-utils/src/render-utils/skin_model_fade.slp new file mode 100644 index 0000000000..2b354b0832 --- /dev/null +++ b/libraries/render-utils/src/render-utils/skin_model_fade.slp @@ -0,0 +1 @@ +FRAGMENT model_fade diff --git a/libraries/render-utils/src/render-utils/skin_model_fade_dq.slp b/libraries/render-utils/src/render-utils/skin_model_fade_dq.slp new file mode 100644 index 0000000000..2b354b0832 --- /dev/null +++ b/libraries/render-utils/src/render-utils/skin_model_fade_dq.slp @@ -0,0 +1 @@ +FRAGMENT model_fade diff --git a/libraries/render-utils/src/render-utils/skin_model_normal_map.slp b/libraries/render-utils/src/render-utils/skin_model_normal_map.slp new file mode 100644 index 0000000000..c9d4016041 --- /dev/null +++ b/libraries/render-utils/src/render-utils/skin_model_normal_map.slp @@ -0,0 +1 @@ +FRAGMENT model_normal_map diff --git a/libraries/render-utils/src/render-utils/skin_model_normal_map_dq.slp b/libraries/render-utils/src/render-utils/skin_model_normal_map_dq.slp new file mode 100644 index 0000000000..c9d4016041 --- /dev/null +++ b/libraries/render-utils/src/render-utils/skin_model_normal_map_dq.slp @@ -0,0 +1 @@ +FRAGMENT model_normal_map diff --git a/libraries/render-utils/src/render-utils/skin_model_normal_map_fade.slp b/libraries/render-utils/src/render-utils/skin_model_normal_map_fade.slp new file mode 100644 index 0000000000..36e92e03e8 --- /dev/null +++ b/libraries/render-utils/src/render-utils/skin_model_normal_map_fade.slp @@ -0,0 +1 @@ +FRAGMENT model_normal_map_fade diff --git a/libraries/render-utils/src/render-utils/skin_model_normal_map_fade_dq.slp b/libraries/render-utils/src/render-utils/skin_model_normal_map_fade_dq.slp new file mode 100644 index 0000000000..36e92e03e8 --- /dev/null +++ b/libraries/render-utils/src/render-utils/skin_model_normal_map_fade_dq.slp @@ -0,0 +1 @@ +FRAGMENT model_normal_map_fade diff --git a/libraries/render-utils/src/render-utils/skin_model_normal_map_translucent.slp b/libraries/render-utils/src/render-utils/skin_model_normal_map_translucent.slp new file mode 100644 index 0000000000..c1b0ee2841 --- /dev/null +++ b/libraries/render-utils/src/render-utils/skin_model_normal_map_translucent.slp @@ -0,0 +1,2 @@ +VERTEX skin_model_normal_map_fade +FRAGMENT model_translucent_normal_map diff --git a/libraries/render-utils/src/render-utils/skin_model_normal_map_translucent_dq.slp b/libraries/render-utils/src/render-utils/skin_model_normal_map_translucent_dq.slp new file mode 100644 index 0000000000..58947f1bae --- /dev/null +++ b/libraries/render-utils/src/render-utils/skin_model_normal_map_translucent_dq.slp @@ -0,0 +1,2 @@ +VERTEX skin_model_normal_map_fade_dq +FRAGMENT model_translucent_normal_map diff --git a/libraries/render-utils/src/render-utils/skin_model_normal_map_translucent_fade.slp b/libraries/render-utils/src/render-utils/skin_model_normal_map_translucent_fade.slp new file mode 100644 index 0000000000..6698d1b7be --- /dev/null +++ b/libraries/render-utils/src/render-utils/skin_model_normal_map_translucent_fade.slp @@ -0,0 +1,2 @@ +VERTEX skin_model_normal_map_fade +FRAGMENT model_translucent_normal_map_fade diff --git a/libraries/render-utils/src/render-utils/skin_model_normal_map_translucent_fade_dq.slp b/libraries/render-utils/src/render-utils/skin_model_normal_map_translucent_fade_dq.slp new file mode 100644 index 0000000000..d2e938bd2a --- /dev/null +++ b/libraries/render-utils/src/render-utils/skin_model_normal_map_translucent_fade_dq.slp @@ -0,0 +1,2 @@ +VERTEX skin_model_normal_map_fade_dq +FRAGMENT model_translucent_normal_map_fade diff --git a/libraries/render-utils/src/render-utils/skin_model_shadow.slp b/libraries/render-utils/src/render-utils/skin_model_shadow.slp new file mode 100644 index 0000000000..f356a5638d --- /dev/null +++ b/libraries/render-utils/src/render-utils/skin_model_shadow.slp @@ -0,0 +1 @@ +FRAGMENT model_shadow diff --git a/libraries/render-utils/src/render-utils/skin_model_shadow_dq.slp b/libraries/render-utils/src/render-utils/skin_model_shadow_dq.slp new file mode 100644 index 0000000000..f356a5638d --- /dev/null +++ b/libraries/render-utils/src/render-utils/skin_model_shadow_dq.slp @@ -0,0 +1 @@ +FRAGMENT model_shadow diff --git a/libraries/render-utils/src/render-utils/skin_model_shadow_fade.slp b/libraries/render-utils/src/render-utils/skin_model_shadow_fade.slp new file mode 100644 index 0000000000..f356a5638d --- /dev/null +++ b/libraries/render-utils/src/render-utils/skin_model_shadow_fade.slp @@ -0,0 +1 @@ +FRAGMENT model_shadow diff --git a/libraries/render-utils/src/render-utils/skin_model_shadow_fade_dq.slp b/libraries/render-utils/src/render-utils/skin_model_shadow_fade_dq.slp new file mode 100644 index 0000000000..a7e3f3328b --- /dev/null +++ b/libraries/render-utils/src/render-utils/skin_model_shadow_fade_dq.slp @@ -0,0 +1 @@ +FRAGMENT model_shadow_fade diff --git a/libraries/render-utils/src/render-utils/skin_model_translucent.slp b/libraries/render-utils/src/render-utils/skin_model_translucent.slp new file mode 100644 index 0000000000..469224f9fd --- /dev/null +++ b/libraries/render-utils/src/render-utils/skin_model_translucent.slp @@ -0,0 +1,2 @@ +VERTEX skin_model_fade +FRAGMENT model_translucent diff --git a/libraries/render-utils/src/render-utils/skin_model_translucent_dq.slp b/libraries/render-utils/src/render-utils/skin_model_translucent_dq.slp new file mode 100644 index 0000000000..fdac5044ce --- /dev/null +++ b/libraries/render-utils/src/render-utils/skin_model_translucent_dq.slp @@ -0,0 +1,2 @@ +VERTEX skin_model_fade_dq +FRAGMENT model_translucent diff --git a/libraries/render-utils/src/render-utils/skin_model_translucent_fade.slp b/libraries/render-utils/src/render-utils/skin_model_translucent_fade.slp new file mode 100644 index 0000000000..c6ff435342 --- /dev/null +++ b/libraries/render-utils/src/render-utils/skin_model_translucent_fade.slp @@ -0,0 +1,2 @@ +VERTEX skin_model_fade +FRAGMENT model_translucent_fade diff --git a/libraries/render-utils/src/render-utils/skin_model_translucent_fade_dq.slp b/libraries/render-utils/src/render-utils/skin_model_translucent_fade_dq.slp new file mode 100644 index 0000000000..7361a0fd71 --- /dev/null +++ b/libraries/render-utils/src/render-utils/skin_model_translucent_fade_dq.slp @@ -0,0 +1,2 @@ +VERTEX skin_model_fade_dq +FRAGMENT model_translucent_fade diff --git a/libraries/render-utils/src/render-utils/ssao_debugOcclusion.slp b/libraries/render-utils/src/render-utils/ssao_debugOcclusion.slp new file mode 100644 index 0000000000..d4d8ec4b01 --- /dev/null +++ b/libraries/render-utils/src/render-utils/ssao_debugOcclusion.slp @@ -0,0 +1 @@ +VERTEX gpu::vertex::DrawViewportQuadTransformTexcoord diff --git a/libraries/render-utils/src/render-utils/ssao_makeHorizontalBlur.slp b/libraries/render-utils/src/render-utils/ssao_makeHorizontalBlur.slp new file mode 100644 index 0000000000..d4d8ec4b01 --- /dev/null +++ b/libraries/render-utils/src/render-utils/ssao_makeHorizontalBlur.slp @@ -0,0 +1 @@ +VERTEX gpu::vertex::DrawViewportQuadTransformTexcoord diff --git a/libraries/render-utils/src/render-utils/ssao_makeOcclusion.slp b/libraries/render-utils/src/render-utils/ssao_makeOcclusion.slp new file mode 100644 index 0000000000..d4d8ec4b01 --- /dev/null +++ b/libraries/render-utils/src/render-utils/ssao_makeOcclusion.slp @@ -0,0 +1 @@ +VERTEX gpu::vertex::DrawViewportQuadTransformTexcoord diff --git a/libraries/render-utils/src/render-utils/ssao_makeVerticalBlur.slp b/libraries/render-utils/src/render-utils/ssao_makeVerticalBlur.slp new file mode 100644 index 0000000000..d4d8ec4b01 --- /dev/null +++ b/libraries/render-utils/src/render-utils/ssao_makeVerticalBlur.slp @@ -0,0 +1 @@ +VERTEX gpu::vertex::DrawViewportQuadTransformTexcoord diff --git a/libraries/render-utils/src/render-utils/standardDrawTexture.slp b/libraries/render-utils/src/render-utils/standardDrawTexture.slp new file mode 100644 index 0000000000..c81b208f63 --- /dev/null +++ b/libraries/render-utils/src/render-utils/standardDrawTexture.slp @@ -0,0 +1 @@ +VERTEX standardTransformPNTC diff --git a/libraries/render-utils/src/render-utils/standardDrawTextureNoBlend.slp b/libraries/render-utils/src/render-utils/standardDrawTextureNoBlend.slp new file mode 100644 index 0000000000..c81b208f63 --- /dev/null +++ b/libraries/render-utils/src/render-utils/standardDrawTextureNoBlend.slp @@ -0,0 +1 @@ +VERTEX standardTransformPNTC diff --git a/libraries/render-utils/src/render-utils/stencil_drawMask.slp b/libraries/render-utils/src/render-utils/stencil_drawMask.slp new file mode 100644 index 0000000000..c2c4bfbebd --- /dev/null +++ b/libraries/render-utils/src/render-utils/stencil_drawMask.slp @@ -0,0 +1 @@ +VERTEX gpu::vertex::DrawUnitQuadTexcoord diff --git a/libraries/render-utils/src/render-utils/subsurfaceScattering_drawScattering.slp b/libraries/render-utils/src/render-utils/subsurfaceScattering_drawScattering.slp new file mode 100644 index 0000000000..c2c4bfbebd --- /dev/null +++ b/libraries/render-utils/src/render-utils/subsurfaceScattering_drawScattering.slp @@ -0,0 +1 @@ +VERTEX gpu::vertex::DrawUnitQuadTexcoord diff --git a/libraries/render-utils/src/render-utils/subsurfaceScattering_makeLUT.slp b/libraries/render-utils/src/render-utils/subsurfaceScattering_makeLUT.slp new file mode 100644 index 0000000000..c2c4bfbebd --- /dev/null +++ b/libraries/render-utils/src/render-utils/subsurfaceScattering_makeLUT.slp @@ -0,0 +1 @@ +VERTEX gpu::vertex::DrawUnitQuadTexcoord diff --git a/libraries/render-utils/src/render-utils/subsurfaceScattering_makeProfile.slp b/libraries/render-utils/src/render-utils/subsurfaceScattering_makeProfile.slp new file mode 100644 index 0000000000..c2c4bfbebd --- /dev/null +++ b/libraries/render-utils/src/render-utils/subsurfaceScattering_makeProfile.slp @@ -0,0 +1 @@ +VERTEX gpu::vertex::DrawUnitQuadTexcoord diff --git a/libraries/render-utils/src/render-utils/subsurfaceScattering_makeSpecularBeckmann.slp b/libraries/render-utils/src/render-utils/subsurfaceScattering_makeSpecularBeckmann.slp new file mode 100644 index 0000000000..c2c4bfbebd --- /dev/null +++ b/libraries/render-utils/src/render-utils/subsurfaceScattering_makeSpecularBeckmann.slp @@ -0,0 +1 @@ +VERTEX gpu::vertex::DrawUnitQuadTexcoord diff --git a/libraries/render-utils/src/render-utils/surfaceGeometry_downsampleDepthNormal.slp b/libraries/render-utils/src/render-utils/surfaceGeometry_downsampleDepthNormal.slp new file mode 100644 index 0000000000..d4d8ec4b01 --- /dev/null +++ b/libraries/render-utils/src/render-utils/surfaceGeometry_downsampleDepthNormal.slp @@ -0,0 +1 @@ +VERTEX gpu::vertex::DrawViewportQuadTransformTexcoord diff --git a/libraries/render-utils/src/render-utils/surfaceGeometry_makeCurvature.slp b/libraries/render-utils/src/render-utils/surfaceGeometry_makeCurvature.slp new file mode 100644 index 0000000000..d4d8ec4b01 --- /dev/null +++ b/libraries/render-utils/src/render-utils/surfaceGeometry_makeCurvature.slp @@ -0,0 +1 @@ +VERTEX gpu::vertex::DrawViewportQuadTransformTexcoord diff --git a/libraries/render-utils/src/render-utils/surfaceGeometry_makeLinearDepth.slp b/libraries/render-utils/src/render-utils/surfaceGeometry_makeLinearDepth.slp new file mode 100644 index 0000000000..d4d8ec4b01 --- /dev/null +++ b/libraries/render-utils/src/render-utils/surfaceGeometry_makeLinearDepth.slp @@ -0,0 +1 @@ +VERTEX gpu::vertex::DrawViewportQuadTransformTexcoord diff --git a/libraries/render-utils/src/render-utils/taa.slp b/libraries/render-utils/src/render-utils/taa.slp new file mode 100644 index 0000000000..c2c4bfbebd --- /dev/null +++ b/libraries/render-utils/src/render-utils/taa.slp @@ -0,0 +1 @@ +VERTEX gpu::vertex::DrawUnitQuadTexcoord diff --git a/libraries/render-utils/src/render-utils/taa_blend.slp b/libraries/render-utils/src/render-utils/taa_blend.slp new file mode 100644 index 0000000000..c2c4bfbebd --- /dev/null +++ b/libraries/render-utils/src/render-utils/taa_blend.slp @@ -0,0 +1 @@ +VERTEX gpu::vertex::DrawUnitQuadTexcoord diff --git a/libraries/render-utils/src/render-utils/toneMapping.slp b/libraries/render-utils/src/render-utils/toneMapping.slp new file mode 100644 index 0000000000..d4d8ec4b01 --- /dev/null +++ b/libraries/render-utils/src/render-utils/toneMapping.slp @@ -0,0 +1 @@ +VERTEX gpu::vertex::DrawViewportQuadTransformTexcoord diff --git a/libraries/render-utils/src/render-utils/velocityBuffer_cameraMotion.slp b/libraries/render-utils/src/render-utils/velocityBuffer_cameraMotion.slp new file mode 100644 index 0000000000..d4d8ec4b01 --- /dev/null +++ b/libraries/render-utils/src/render-utils/velocityBuffer_cameraMotion.slp @@ -0,0 +1 @@ +VERTEX gpu::vertex::DrawViewportQuadTransformTexcoord diff --git a/libraries/render-utils/src/render-utils/zone_drawAmbient.slp b/libraries/render-utils/src/render-utils/zone_drawAmbient.slp new file mode 100644 index 0000000000..a5c2bb33e6 --- /dev/null +++ b/libraries/render-utils/src/render-utils/zone_drawAmbient.slp @@ -0,0 +1 @@ +VERTEX gpu::vertex::DrawTransformUnitQuad diff --git a/libraries/render-utils/src/render-utils/zone_drawKeyLight.slp b/libraries/render-utils/src/render-utils/zone_drawKeyLight.slp new file mode 100644 index 0000000000..a5c2bb33e6 --- /dev/null +++ b/libraries/render-utils/src/render-utils/zone_drawKeyLight.slp @@ -0,0 +1 @@ +VERTEX gpu::vertex::DrawTransformUnitQuad diff --git a/libraries/render-utils/src/render-utils/zone_drawSkybox.slp b/libraries/render-utils/src/render-utils/zone_drawSkybox.slp new file mode 100644 index 0000000000..a5c2bb33e6 --- /dev/null +++ b/libraries/render-utils/src/render-utils/zone_drawSkybox.slp @@ -0,0 +1 @@ +VERTEX gpu::vertex::DrawTransformUnitQuad diff --git a/libraries/render-utils/src/sdf_text3D.slf b/libraries/render-utils/src/sdf_text3D.slf index 08e50ee8c5..2fbaa03900 100644 --- a/libraries/render-utils/src/sdf_text3D.slf +++ b/libraries/render-utils/src/sdf_text3D.slf @@ -11,14 +11,17 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html <@include DeferredBufferWrite.slh@> +<@include render-utils/ShaderConstants.h@> -uniform sampler2D Font; -uniform bool Outline; -uniform vec4 Color; +layout(binding=0) uniform sampler2D Font; +layout(location=RENDER_UTILS_UNIFORM_TEXT_OUTLINE) uniform bool Outline; +layout(location=RENDER_UTILS_UNIFORM_TEXT_COLOR) uniform vec4 Color; // the interpolated normal -in vec3 _normalWS; -in vec2 _texCoord0; +layout(location=RENDER_UTILS_ATTR_NORMAL_WS) in vec3 _normalWS; +layout(location=RENDER_UTILS_ATTR_TEXCOORD01) in vec4 _texCoord01; +#define _texCoord0 _texCoord01.xy +#define _texCoord1 _texCoord01.zw #define TAA_TEXTURE_LOD_BIAS -3.0 diff --git a/libraries/render-utils/src/sdf_text3D.slv b/libraries/render-utils/src/sdf_text3D.slv index bcf42c3cff..04ee44510a 100644 --- a/libraries/render-utils/src/sdf_text3D.slv +++ b/libraries/render-utils/src/sdf_text3D.slv @@ -11,17 +11,17 @@ // <@include gpu/Inputs.slh@> - <@include gpu/Transform.slh@> +<@include render-utils/ShaderConstants.h@> <$declareStandardTransform()$> // the interpolated normal -out vec3 _normalWS; -out vec2 _texCoord0; +layout(location=RENDER_UTILS_ATTR_NORMAL_WS) out vec3 _normalWS; +layout(location=RENDER_UTILS_ATTR_TEXCOORD01) out vec4 _texCoord01; void main() { - _texCoord0 = inTexCoord0.xy; + _texCoord01.xy = inTexCoord0.xy; // standard transform TransformCamera cam = getTransformCamera(); diff --git a/libraries/render-utils/src/sdf_text3D_transparent.slf b/libraries/render-utils/src/sdf_text3D_transparent.slf index 10eb88b198..218236c26b 100644 --- a/libraries/render-utils/src/sdf_text3D_transparent.slf +++ b/libraries/render-utils/src/sdf_text3D_transparent.slf @@ -11,14 +11,17 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html <@include DeferredBufferWrite.slh@> +<@include render-utils/ShaderConstants.h@> -uniform sampler2D Font; -uniform bool Outline; -uniform vec4 Color; +layout(binding=0) uniform sampler2D Font; +layout(location=RENDER_UTILS_UNIFORM_TEXT_OUTLINE) uniform bool Outline; +layout(location=RENDER_UTILS_UNIFORM_TEXT_COLOR) uniform vec4 Color; // the interpolated normal -in vec3 _normalWS; -in vec2 _texCoord0; +layout(location=RENDER_UTILS_ATTR_NORMAL_WS) in vec3 _normalWS; +layout(location=RENDER_UTILS_ATTR_TEXCOORD01) in vec4 _texCoord01; +#define _texCoord0 _texCoord01.xy +#define _texCoord1 _texCoord01.zw const float gamma = 2.2; const float smoothing = 32.0; diff --git a/libraries/render-utils/src/simple.slf b/libraries/render-utils/src/simple.slf index 7591dc1882..a7f5151880 100644 --- a/libraries/render-utils/src/simple.slf +++ b/libraries/render-utils/src/simple.slf @@ -14,13 +14,17 @@ <@include DeferredBufferWrite.slh@> +<@include render-utils/ShaderConstants.h@> + // the interpolated normal -in vec3 _normalWS; -in vec3 _normalMS; -in vec4 _color; -in vec2 _texCoord0; -in vec4 _positionMS; -in vec4 _positionES; +layout(location=RENDER_UTILS_ATTR_NORMAL_WS) in vec3 _normalWS; +layout(location=RENDER_UTILS_ATTR_NORMAL_MS) in vec3 _normalMS; +layout(location=RENDER_UTILS_ATTR_COLOR) in vec4 _color; +layout(location=RENDER_UTILS_ATTR_TEXCOORD01) in vec4 _texCoord01; +#define _texCoord0 _texCoord01.xy +#define _texCoord1 _texCoord01.zw +layout(location=RENDER_UTILS_ATTR_POSITION_MS) in vec4 _positionMS; +layout(location=RENDER_UTILS_ATTR_POSITION_ES) in vec4 _positionES; // For retro-compatibility #define _normal _normalWS @@ -28,7 +32,7 @@ in vec4 _positionES; #define _position _positionMS #define _eyePosition _positionES -//PROCEDURAL_COMMON_BLOCK +<@include procedural/ProceduralCommon.slh@> #line 1001 //PROCEDURAL_BLOCK @@ -44,9 +48,9 @@ void main(void) { #ifdef PROCEDURAL #ifdef PROCEDURAL_V1 - specular = getProceduralColor().rgb; + diffuse = getProceduralColor().rgb; // Procedural Shaders are expected to be Gamma corrected so let's bring back the RGB in linear space for the rest of the pipeline - //specular = pow(specular, vec3(2.2)); + //diffuse = pow(diffuse, vec3(2.2)); emissiveAmount = 1.0; #else emissiveAmount = getProceduralColors(diffuse, specular, shininess); diff --git a/libraries/render-utils/src/simple.slv b/libraries/render-utils/src/simple.slv index 01338be15f..0dd4e55f26 100644 --- a/libraries/render-utils/src/simple.slv +++ b/libraries/render-utils/src/simple.slv @@ -17,17 +17,19 @@ <@include gpu/Transform.slh@> <$declareStandardTransform()$> +<@include render-utils/ShaderConstants.h@> + // the interpolated normal -out vec3 _normalWS; -out vec3 _normalMS; -out vec4 _color; -out vec2 _texCoord0; -out vec4 _positionMS; -out vec4 _positionES; +layout(location=RENDER_UTILS_ATTR_NORMAL_WS) out vec3 _normalWS; +layout(location=RENDER_UTILS_ATTR_NORMAL_MS) out vec3 _normalMS; +layout(location=RENDER_UTILS_ATTR_COLOR) out vec4 _color; +layout(location=RENDER_UTILS_ATTR_TEXCOORD01) out vec4 _texCoord01; +layout(location=RENDER_UTILS_ATTR_POSITION_MS) out vec4 _positionMS; +layout(location=RENDER_UTILS_ATTR_POSITION_ES) out vec4 _positionES; void main(void) { _color = color_sRGBAToLinear(inColor); - _texCoord0 = inTexCoord0.st; + _texCoord01.xy = inTexCoord0.st; _positionMS = inPosition; _normalMS = inNormal.xyz; diff --git a/libraries/render-utils/src/simple_fade.slf b/libraries/render-utils/src/simple_fade.slf index 0710c3e10b..e9f94c29bc 100644 --- a/libraries/render-utils/src/simple_fade.slf +++ b/libraries/render-utils/src/simple_fade.slf @@ -17,14 +17,18 @@ <@include Fade.slh@> <$declareFadeFragmentInstanced()$> +<@include render-utils/ShaderConstants.h@> + // the interpolated normal -in vec3 _normalWS; -in vec3 _normalMS; -in vec4 _color; -in vec2 _texCoord0; -in vec4 _positionMS; -in vec4 _positionES; -in vec4 _positionWS; +layout(location=RENDER_UTILS_ATTR_NORMAL_WS) in vec3 _normalWS; +layout(location=RENDER_UTILS_ATTR_NORMAL_MS) in vec3 _normalMS; +layout(location=RENDER_UTILS_ATTR_COLOR) in vec4 _color; +layout(location=RENDER_UTILS_ATTR_TEXCOORD01) in vec4 _texCoord01; +#define _texCoord0 _texCoord01.xy +#define _texCoord1 _texCoord01.zw +layout(location=RENDER_UTILS_ATTR_POSITION_MS) in vec4 _positionMS; +layout(location=RENDER_UTILS_ATTR_POSITION_ES) in vec4 _positionES; +layout(location=RENDER_UTILS_ATTR_POSITION_WS) in vec4 _positionWS; // For retro-compatibility #define _normal _normalWS @@ -32,7 +36,7 @@ in vec4 _positionWS; #define _position _positionMS #define _eyePosition _positionES -//PROCEDURAL_COMMON_BLOCK +<@include procedural/ProceduralCommon.slh@> #line 1001 //PROCEDURAL_BLOCK diff --git a/libraries/render-utils/src/simple_fade.slv b/libraries/render-utils/src/simple_fade.slv index c7d7c5d1b3..0bbd8eac39 100644 --- a/libraries/render-utils/src/simple_fade.slv +++ b/libraries/render-utils/src/simple_fade.slv @@ -20,18 +20,20 @@ <@include Fade.slh@> <$declareFadeVertexInstanced()$> +<@include render-utils/ShaderConstants.h@> + // the interpolated normal -out vec3 _normalWS; -out vec3 _normalMS; -out vec4 _color; -out vec2 _texCoord0; -out vec4 _positionMS; -out vec4 _positionES; -out vec4 _positionWS; +layout(location=RENDER_UTILS_ATTR_NORMAL_WS) out vec3 _normalWS; +layout(location=RENDER_UTILS_ATTR_NORMAL_MS) out vec3 _normalMS; +layout(location=RENDER_UTILS_ATTR_COLOR) out vec4 _color; +layout(location=RENDER_UTILS_ATTR_TEXCOORD01) out vec4 _texCoord01; +layout(location=RENDER_UTILS_ATTR_POSITION_MS) out vec4 _positionMS; +layout(location=RENDER_UTILS_ATTR_POSITION_ES) out vec4 _positionES; +layout(location=RENDER_UTILS_ATTR_POSITION_WS) out vec4 _positionWS; void main(void) { _color = color_sRGBAToLinear(inColor); - _texCoord0 = inTexCoord0.st; + _texCoord01.xy = inTexCoord0.st; _positionMS = inPosition; _normalMS = inNormal.xyz; diff --git a/libraries/render-utils/src/simple_opaque_web_browser.slf b/libraries/render-utils/src/simple_opaque_web_browser.slf index 3492e0cc90..cf4828d3b3 100644 --- a/libraries/render-utils/src/simple_opaque_web_browser.slf +++ b/libraries/render-utils/src/simple_opaque_web_browser.slf @@ -2,7 +2,7 @@ <$VERSION_HEADER$> // Generated on <$_SCRIBE_DATE$> // -// simple_opaque_web_browser.slf +// simple_opaque_web_browser.frag // fragment shader // // Created by Anthony Thibault on 7/25/16. @@ -15,13 +15,17 @@ <@include gpu/Color.slh@> <@include DeferredBufferWrite.slh@> +<@include render-utils/ShaderConstants.h@> + // the albedo texture -uniform sampler2D originalTexture; +layout(binding=0) uniform sampler2D originalTexture; // the interpolated normal -in vec3 _normalWS; -in vec4 _color; -in vec2 _texCoord0; +layout(location=RENDER_UTILS_ATTR_NORMAL_WS) in vec3 _normalWS; +layout(location=RENDER_UTILS_ATTR_COLOR) in vec4 _color; +layout(location=RENDER_UTILS_ATTR_TEXCOORD01) in vec4 _texCoord01; +#define _texCoord0 _texCoord01.xy +#define _texCoord1 _texCoord01.zw void main(void) { vec4 texel = texture(originalTexture, _texCoord0.st); diff --git a/libraries/render-utils/src/simple_textured.slf b/libraries/render-utils/src/simple_textured.slf index 072e2e8e18..7676844084 100644 --- a/libraries/render-utils/src/simple_textured.slf +++ b/libraries/render-utils/src/simple_textured.slf @@ -2,10 +2,10 @@ <$VERSION_HEADER$> // Generated on <$_SCRIBE_DATE$> // -// simple_textured.slf +// simple_textured.frag // fragment shader // -// Created by Clément Brisset on 5/29/15. +// Created by Clement Brisset on 5/29/15. // Copyright 2014 High Fidelity, Inc. // // Distributed under the Apache License, Version 2.0. @@ -14,13 +14,17 @@ <@include DeferredBufferWrite.slh@> +<@include render-utils/ShaderConstants.h@> + // the albedo texture -uniform sampler2D originalTexture; +layout(binding=0) uniform sampler2D originalTexture; // the interpolated normal -in vec3 _normalWS; -in vec4 _color; -in vec2 _texCoord0; +layout(location=RENDER_UTILS_ATTR_NORMAL_WS) in vec3 _normalWS; +layout(location=RENDER_UTILS_ATTR_COLOR) in vec4 _color; +layout(location=RENDER_UTILS_ATTR_TEXCOORD01) in vec4 _texCoord01; +#define _texCoord0 _texCoord01.xy +#define _texCoord1 _texCoord01.zw void main(void) { vec4 texel = texture(originalTexture, _texCoord0); diff --git a/libraries/render-utils/src/simple_textured_fade.slf b/libraries/render-utils/src/simple_textured_fade.slf index 9ec02798ef..600f19be0f 100644 --- a/libraries/render-utils/src/simple_textured_fade.slf +++ b/libraries/render-utils/src/simple_textured_fade.slf @@ -2,7 +2,7 @@ <$VERSION_HEADER$> // Generated on <$_SCRIBE_DATE$> // -// simple_textured_fade.slf +// simple_textured_fade.frag // fragment shader // // Created by Olivier Prat on 06/05/17. @@ -17,14 +17,18 @@ <@include Fade.slh@> +<@include render-utils/ShaderConstants.h@> + // the albedo texture -uniform sampler2D originalTexture; +layout(binding=0) uniform sampler2D originalTexture; // the interpolated normal -in vec3 _normalWS; -in vec4 _color; -in vec2 _texCoord0; -in vec4 _positionWS; +layout(location=RENDER_UTILS_ATTR_NORMAL_WS) in vec3 _normalWS; +layout(location=RENDER_UTILS_ATTR_COLOR) in vec4 _color; +layout(location=RENDER_UTILS_ATTR_TEXCOORD01) in vec4 _texCoord01; +#define _texCoord0 _texCoord01.xy +#define _texCoord1 _texCoord01.zw +layout(location=RENDER_UTILS_ATTR_POSITION_WS) in vec4 _positionWS; // Declare after all samplers to prevent sampler location mix up with originalTexture <$declareFadeFragmentInstanced()$> diff --git a/libraries/render-utils/src/simple_textured_unlit.slf b/libraries/render-utils/src/simple_textured_unlit.slf index c5cca9e0f8..e3d9b9daf6 100644 --- a/libraries/render-utils/src/simple_textured_unlit.slf +++ b/libraries/render-utils/src/simple_textured_unlit.slf @@ -5,7 +5,7 @@ // simple_textured_unlit.frag // fragment shader // -// Created by Clément Brisset on 5/29/15. +// Created by Clement Brisset on 5/29/15. // Copyright 2014 High Fidelity, Inc. // // Distributed under the Apache License, Version 2.0. @@ -15,13 +15,17 @@ <@include gpu/Color.slh@> <@include DeferredBufferWrite.slh@> +<@include render-utils/ShaderConstants.h@> + // the albedo texture -uniform sampler2D originalTexture; +layout(binding=0) uniform sampler2D originalTexture; // the interpolated normal -in vec3 _normalWS; -in vec4 _color; -in vec2 _texCoord0; +layout(location=RENDER_UTILS_ATTR_NORMAL_WS) in vec3 _normalWS; +layout(location=RENDER_UTILS_ATTR_COLOR) in vec4 _color; +layout(location=RENDER_UTILS_ATTR_TEXCOORD01) in vec4 _texCoord01; +#define _texCoord0 _texCoord01.xy +#define _texCoord1 _texCoord01.zw void main(void) { vec4 texel = texture(originalTexture, _texCoord0.st); diff --git a/libraries/render-utils/src/simple_textured_unlit_fade.slf b/libraries/render-utils/src/simple_textured_unlit_fade.slf index a8d0f3bffe..bffadbe819 100644 --- a/libraries/render-utils/src/simple_textured_unlit_fade.slf +++ b/libraries/render-utils/src/simple_textured_unlit_fade.slf @@ -17,14 +17,18 @@ <@include Fade.slh@> +<@include render-utils/ShaderConstants.h@> + // the albedo texture -uniform sampler2D originalTexture; +layout(binding=0) uniform sampler2D originalTexture; // the interpolated normal -in vec3 _normalWS; -in vec4 _color; -in vec2 _texCoord0; -in vec4 _positionWS; +layout(location=RENDER_UTILS_ATTR_NORMAL_WS) in vec3 _normalWS; +layout(location=RENDER_UTILS_ATTR_COLOR) in vec4 _color; +layout(location=RENDER_UTILS_ATTR_TEXCOORD01) in vec4 _texCoord01; +#define _texCoord0 _texCoord01.xy +#define _texCoord1 _texCoord01.zw +layout(location=RENDER_UTILS_ATTR_POSITION_WS) in vec4 _positionWS; // Declare after all samplers to prevent sampler location mix up with originalTexture <$declareFadeFragmentInstanced()$> diff --git a/libraries/render-utils/src/simple_transparent.slf b/libraries/render-utils/src/simple_transparent.slf index ee79d2c0c4..5db54aa770 100644 --- a/libraries/render-utils/src/simple_transparent.slf +++ b/libraries/render-utils/src/simple_transparent.slf @@ -11,16 +11,24 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +<@include DefaultMaterials.slh@> -<@include DeferredBufferWrite.slh@> +<@include DeferredGlobalLight.slh@> +<$declareEvalGlobalLightingAlphaBlendedWithHaze()$> + +<@include render-utils/ShaderConstants.h@> // the interpolated normal -in vec3 _normalWS; -in vec3 _normalMS; -in vec4 _color; -in vec2 _texCoord0; -in vec4 _positionMS; -in vec4 _positionES; +layout(location=RENDER_UTILS_ATTR_NORMAL_WS) in vec3 _normalWS; +layout(location=RENDER_UTILS_ATTR_NORMAL_MS) in vec3 _normalMS; +layout(location=RENDER_UTILS_ATTR_COLOR) in vec4 _color; +layout(location=RENDER_UTILS_ATTR_TEXCOORD01) in vec4 _texCoord01; +#define _texCoord0 _texCoord01.xy +#define _texCoord1 _texCoord01.zw +layout(location=RENDER_UTILS_ATTR_POSITION_MS) in vec4 _positionMS; +layout(location=RENDER_UTILS_ATTR_POSITION_ES) in vec4 _positionES; + +layout(location=0) out vec4 _fragColor0; // For retro-compatibility #define _normal _normalWS @@ -28,7 +36,7 @@ in vec4 _positionES; #define _position _positionMS #define _eyePosition _positionES -//PROCEDURAL_COMMON_BLOCK +<@include procedural/ProceduralCommon.slh@> #line 1001 //PROCEDURAL_BLOCK @@ -44,9 +52,9 @@ void main(void) { #ifdef PROCEDURAL #ifdef PROCEDURAL_V1 - specular = getProceduralColor().rgb; + diffuse = getProceduralColor().rgb; // Procedural Shaders are expected to be Gamma corrected so let's bring back the RGB in linear space for the rest of the pipeline - //specular = pow(specular, vec3(2.2)); + //diffuse = pow(diffuse, vec3(2.2)); emissiveAmount = 1.0; #else emissiveAmount = getProceduralColors(diffuse, specular, shininess); @@ -54,19 +62,23 @@ void main(void) { #endif + TransformCamera cam = getTransformCamera(); + vec3 fragPosition = _positionES.xyz; + if (emissiveAmount > 0.0) { - packDeferredFragmentTranslucent( - normal, - _color.a, - specular, - DEFAULT_FRESNEL, - DEFAULT_ROUGHNESS); + _fragColor0 = vec4(diffuse, _color.a); } else { - packDeferredFragmentTranslucent( + _fragColor0 = vec4(evalGlobalLightingAlphaBlendedWithHaze( + cam._viewInverse, + 1.0, + DEFAULT_OCCLUSION, + fragPosition, normal, - _color.a, diffuse, DEFAULT_FRESNEL, - DEFAULT_ROUGHNESS); + length(specular), + DEFAULT_EMISSIVE, + max(0.0, 1.0 - shininess / 128.0), _color.a), + _color.a); } } diff --git a/libraries/render-utils/src/simple_transparent_textured.slf b/libraries/render-utils/src/simple_transparent_textured.slf index 96895f74f3..0e6198de68 100644 --- a/libraries/render-utils/src/simple_transparent_textured.slf +++ b/libraries/render-utils/src/simple_transparent_textured.slf @@ -2,7 +2,7 @@ <$VERSION_HEADER$> // Generated on <$_SCRIBE_DATE$> // -// simple_transparent_textured.slf +// simple_transparent_textured.frag // fragment shader // // Created by Sam Gateau on 4/3/17. @@ -14,13 +14,17 @@ <@include DeferredBufferWrite.slh@> +<@include render-utils/ShaderConstants.h@> + // the albedo texture -uniform sampler2D originalTexture; +layout(location=0) uniform sampler2D originalTexture; // the interpolated normal -in vec3 _normalWS; -in vec4 _color; -in vec2 _texCoord0; +layout(location=RENDER_UTILS_ATTR_NORMAL_WS) in vec3 _normalWS; +layout(location=RENDER_UTILS_ATTR_COLOR) in vec4 _color; +layout(location=RENDER_UTILS_ATTR_TEXCOORD01) in vec4 _texCoord01; +#define _texCoord0 _texCoord01.xy +#define _texCoord1 _texCoord01.zw void main(void) { vec4 texel = texture(originalTexture, _texCoord0); diff --git a/libraries/render-utils/src/simple_transparent_textured_fade.slf b/libraries/render-utils/src/simple_transparent_textured_fade.slf index 947640687c..44a3fe2e01 100644 --- a/libraries/render-utils/src/simple_transparent_textured_fade.slf +++ b/libraries/render-utils/src/simple_transparent_textured_fade.slf @@ -2,7 +2,7 @@ <$VERSION_HEADER$> // Generated on <$_SCRIBE_DATE$> // -// simple_transparent_textured_fade.slf +// simple_transparent_textured_fade.frag // fragment shader // // Created by Olivier Prat on 06/05/17. @@ -23,14 +23,18 @@ <@include Fade.slh@> -// the albedo texture -uniform sampler2D originalTexture; +<@include render-utils/ShaderConstants.h@> -in vec4 _positionES; -in vec3 _normalWS; -in vec4 _color; -in vec2 _texCoord0; -in vec4 _positionWS; +// the albedo texture +layout(binding=0) uniform sampler2D originalTexture; + +layout(location=RENDER_UTILS_ATTR_POSITION_ES) in vec4 _positionES; +layout(location=RENDER_UTILS_ATTR_NORMAL_WS) in vec3 _normalWS; +layout(location=RENDER_UTILS_ATTR_COLOR) in vec4 _color; +layout(location=RENDER_UTILS_ATTR_TEXCOORD01) in vec4 _texCoord01; +#define _texCoord0 _texCoord01.xy +#define _texCoord1 _texCoord01.zw +layout(location=RENDER_UTILS_ATTR_POSITION_WS) in vec4 _positionWS; // Declare after all samplers to prevent sampler location mix up with originalTexture <$declareFadeFragmentInstanced()$> diff --git a/libraries/render-utils/src/simple_transparent_textured_unlit.slf b/libraries/render-utils/src/simple_transparent_textured_unlit.slf index 7582af59c6..9d43e41c2f 100644 --- a/libraries/render-utils/src/simple_transparent_textured_unlit.slf +++ b/libraries/render-utils/src/simple_transparent_textured_unlit.slf @@ -2,7 +2,7 @@ <$VERSION_HEADER$> // Generated on <$_SCRIBE_DATE$> // -// simple_transparent_textured_unlit.slf +// simple_transparent_textured_unlit.frag // fragment shader // // Created by Sam Gateau on 4/3/17. @@ -14,13 +14,17 @@ <@include gpu/Color.slh@> +<@include render-utils/ShaderConstants.h@> + // the albedo texture -uniform sampler2D originalTexture; +layout(binding=0) uniform sampler2D originalTexture; -in vec4 _color; -in vec2 _texCoord0; +layout(location=RENDER_UTILS_ATTR_COLOR) in vec4 _color; +layout(location=RENDER_UTILS_ATTR_TEXCOORD01) in vec4 _texCoord01; +#define _texCoord0 _texCoord01.xy +#define _texCoord1 _texCoord01.zw -layout(location = 0) out vec4 _fragColor0; +layout(location=0) out vec4 _fragColor0; void main(void) { vec4 texel = texture(originalTexture, _texCoord0.st); diff --git a/libraries/render-utils/src/simple_transparent_textured_unlit_fade.slf b/libraries/render-utils/src/simple_transparent_textured_unlit_fade.slf index b4f95fc317..43c28c41c3 100644 --- a/libraries/render-utils/src/simple_transparent_textured_unlit_fade.slf +++ b/libraries/render-utils/src/simple_transparent_textured_unlit_fade.slf @@ -2,7 +2,7 @@ <$VERSION_HEADER$> // Generated on <$_SCRIBE_DATE$> // -// simple_transparent_textured_unlit_fade.slf +// simple_transparent_textured_unlit_fade.frag // fragment shader // // Created by Olivier Prat on 06/05/17. @@ -16,14 +16,18 @@ <@include Fade.slh@> +<@include render-utils/ShaderConstants.h@> + // the albedo texture -uniform sampler2D originalTexture; +layout(binding=0) uniform sampler2D originalTexture; -in vec4 _color; -in vec2 _texCoord0; -in vec4 _positionWS; +layout(location=RENDER_UTILS_ATTR_COLOR) in vec4 _color; +layout(location=RENDER_UTILS_ATTR_TEXCOORD01) in vec4 _texCoord01; +#define _texCoord0 _texCoord01.xy +#define _texCoord1 _texCoord01.zw +layout(location=RENDER_UTILS_ATTR_POSITION_WS) in vec4 _positionWS; -layout(location = 0) out vec4 _fragColor0; +layout(location=0) out vec4 _fragColor0; // Declare after all samplers to prevent sampler location mix up with originalTexture <$declareFadeFragmentInstanced()$> diff --git a/libraries/render-utils/src/simple_transparent_web_browser.slf b/libraries/render-utils/src/simple_transparent_web_browser.slf index bb2a0846ed..df92d238bf 100644 --- a/libraries/render-utils/src/simple_transparent_web_browser.slf +++ b/libraries/render-utils/src/simple_transparent_web_browser.slf @@ -2,7 +2,7 @@ <$VERSION_HEADER$> // Generated on <$_SCRIBE_DATE$> // -// simple_transparent_web_browser.slf +// simple_transparent_web_browser.frag // fragment shader // // Created by Anthony Thibault on 7/25/16. @@ -15,13 +15,17 @@ <@include gpu/Color.slh@> <@include DeferredBufferWrite.slh@> +<@include render-utils/ShaderConstants.h@> + // the albedo texture -uniform sampler2D originalTexture; +layout(binding=0) uniform sampler2D originalTexture; // the interpolated normal -in vec3 _normalWS; -in vec4 _color; -in vec2 _texCoord0; +layout(location=RENDER_UTILS_ATTR_NORMAL_WS) in vec3 _normalWS; +layout(location=RENDER_UTILS_ATTR_COLOR) in vec4 _color; +layout(location=RENDER_UTILS_ATTR_TEXCOORD01) in vec4 _texCoord01; +#define _texCoord0 _texCoord01.xy +#define _texCoord1 _texCoord01.zw void main(void) { vec4 texel = texture(originalTexture, _texCoord0.st); diff --git a/libraries/render-utils/src/skin_model.slv b/libraries/render-utils/src/skin_model.slv index 1d5013e623..d44ca0ae3f 100644 --- a/libraries/render-utils/src/skin_model.slv +++ b/libraries/render-utils/src/skin_model.slv @@ -20,15 +20,15 @@ <@include Skinning.slh@> <$declareUseDualQuaternionSkinning()$> -<@include MaterialTextures.slh@> +<@include graphics/MaterialTextures.slh@> <$declareMaterialTexMapArrayBuffer()$> -out vec4 _positionES; -out vec2 _texCoord0; -out vec2 _texCoord1; -out vec3 _normalWS; -out vec3 _color; -out float _alpha; +<@include render-utils/ShaderConstants.h@> + +layout(location=RENDER_UTILS_ATTR_POSITION_ES) out vec4 _positionES; +layout(location=RENDER_UTILS_ATTR_TEXCOORD01) out vec4 _texCoord01; +layout(location=RENDER_UTILS_ATTR_NORMAL_WS) out vec3 _normalWS; +layout(location=RENDER_UTILS_ATTR_COLOR) out vec4 _color; void main(void) { vec4 position = vec4(0.0, 0.0, 0.0, 0.0); @@ -37,12 +37,12 @@ void main(void) { skinPositionNormal(inSkinClusterIndex, inSkinClusterWeight, inPosition, inNormal.xyz, position, interpolatedNormal); // pass along the color - _color = color_sRGBToLinear(inColor.rgb); - _alpha = inColor.a; + _color.rgb = color_sRGBToLinear(inColor.rgb); + _color.a = inColor.a; TexMapArray texMapArray = getTexMapArray(); - <$evalTexMapArrayTexcoord0(texMapArray, inTexCoord0, _texCoord0)$> - <$evalTexMapArrayTexcoord1(texMapArray, inTexCoord0, _texCoord1)$> + <$evalTexMapArrayTexcoord0(texMapArray, inTexCoord0, _texCoord01.xy)$> + <$evalTexMapArrayTexcoord1(texMapArray, inTexCoord0, _texCoord01.zw)$> // standard transform TransformCamera cam = getTransformCamera(); diff --git a/libraries/render-utils/src/skin_model_dq.slv b/libraries/render-utils/src/skin_model_dq.slv index 21191e966d..ff73c7a398 100644 --- a/libraries/render-utils/src/skin_model_dq.slv +++ b/libraries/render-utils/src/skin_model_dq.slv @@ -2,7 +2,7 @@ <$VERSION_HEADER$> // Generated on <$_SCRIBE_DATE$> // -// skin_model.vert +// skin_model_dq.vert // vertex shader // // Created by Andrzej Kapolka on 10/14/13. @@ -20,15 +20,15 @@ <@include Skinning.slh@> <$declareUseDualQuaternionSkinning(1)$> -<@include MaterialTextures.slh@> +<@include graphics/MaterialTextures.slh@> <$declareMaterialTexMapArrayBuffer()$> -out vec4 _positionES; -out vec2 _texCoord0; -out vec2 _texCoord1; -out vec3 _normalWS; -out vec3 _color; -out float _alpha; +<@include render-utils/ShaderConstants.h@> + +layout(location=RENDER_UTILS_ATTR_POSITION_ES) out vec4 _positionES; +layout(location=RENDER_UTILS_ATTR_TEXCOORD01) out vec4 _texCoord01; +layout(location=RENDER_UTILS_ATTR_NORMAL_WS) out vec3 _normalWS; +layout(location=RENDER_UTILS_ATTR_COLOR) out vec4 _color; void main(void) { vec4 position = vec4(0.0, 0.0, 0.0, 0.0); @@ -37,12 +37,12 @@ void main(void) { skinPositionNormal(inSkinClusterIndex, inSkinClusterWeight, inPosition, inNormal.xyz, position, interpolatedNormal); // pass along the color - _color = color_sRGBToLinear(inColor.rgb); - _alpha = inColor.a; + _color.rgb = color_sRGBToLinear(inColor.rgb); + _color.a = inColor.a; TexMapArray texMapArray = getTexMapArray(); - <$evalTexMapArrayTexcoord0(texMapArray, inTexCoord0, _texCoord0)$> - <$evalTexMapArrayTexcoord1(texMapArray, inTexCoord0, _texCoord1)$> + <$evalTexMapArrayTexcoord0(texMapArray, inTexCoord0, _texCoord01.xy)$> + <$evalTexMapArrayTexcoord1(texMapArray, inTexCoord0, _texCoord01.zw)$> // standard transform TransformCamera cam = getTransformCamera(); diff --git a/libraries/render-utils/src/skin_model_fade.slv b/libraries/render-utils/src/skin_model_fade.slv index 6c3df7586d..1689476d38 100644 --- a/libraries/render-utils/src/skin_model_fade.slv +++ b/libraries/render-utils/src/skin_model_fade.slv @@ -20,16 +20,16 @@ <@include Skinning.slh@> <$declareUseDualQuaternionSkinning()$> -<@include MaterialTextures.slh@> +<@include graphics/MaterialTextures.slh@> <$declareMaterialTexMapArrayBuffer()$> -out vec4 _positionES; -out vec2 _texCoord0; -out vec2 _texCoord1; -out vec3 _normalWS; -out vec3 _color; -out float _alpha; -out vec4 _positionWS; +<@include render-utils/ShaderConstants.h@> + +layout(location=RENDER_UTILS_ATTR_POSITION_ES) out vec4 _positionES; +layout(location=RENDER_UTILS_ATTR_TEXCOORD01) out vec4 _texCoord01; +layout(location=RENDER_UTILS_ATTR_NORMAL_WS) out vec3 _normalWS; +layout(location=RENDER_UTILS_ATTR_COLOR) out vec4 _color; +layout(location=RENDER_UTILS_ATTR_POSITION_WS) out vec4 _positionWS; void main(void) { vec4 position = vec4(0.0, 0.0, 0.0, 0.0); @@ -38,12 +38,12 @@ void main(void) { skinPositionNormal(inSkinClusterIndex, inSkinClusterWeight, inPosition, inNormal.xyz, position, interpolatedNormal); // pass along the color - _color = color_sRGBToLinear(inColor.rgb); - _alpha = inColor.a; + _color.rgb = color_sRGBToLinear(inColor.rgb); + _color.a = inColor.a; TexMapArray texMapArray = getTexMapArray(); - <$evalTexMapArrayTexcoord0(texMapArray, inTexCoord0, _texCoord0)$> - <$evalTexMapArrayTexcoord1(texMapArray, inTexCoord0, _texCoord1)$> + <$evalTexMapArrayTexcoord0(texMapArray, inTexCoord0, _texCoord01.xy)$> + <$evalTexMapArrayTexcoord1(texMapArray, inTexCoord0, _texCoord01.zw)$> // standard transform TransformCamera cam = getTransformCamera(); diff --git a/libraries/render-utils/src/skin_model_fade_dq.slv b/libraries/render-utils/src/skin_model_fade_dq.slv index 9d9ddaeaf8..6e64305de7 100644 --- a/libraries/render-utils/src/skin_model_fade_dq.slv +++ b/libraries/render-utils/src/skin_model_fade_dq.slv @@ -20,16 +20,16 @@ <@include Skinning.slh@> <$declareUseDualQuaternionSkinning(1)$> -<@include MaterialTextures.slh@> +<@include graphics/MaterialTextures.slh@> <$declareMaterialTexMapArrayBuffer()$> -out vec4 _positionES; -out vec2 _texCoord0; -out vec2 _texCoord1; -out vec3 _normalWS; -out vec3 _color; -out float _alpha; -out vec4 _positionWS; +<@include render-utils/ShaderConstants.h@> + +layout(location=RENDER_UTILS_ATTR_POSITION_ES) out vec4 _positionES; +layout(location=RENDER_UTILS_ATTR_TEXCOORD01) out vec4 _texCoord01; +layout(location=RENDER_UTILS_ATTR_NORMAL_WS) out vec3 _normalWS; +layout(location=RENDER_UTILS_ATTR_COLOR) out vec4 _color; +layout(location=RENDER_UTILS_ATTR_POSITION_WS) out vec4 _positionWS; void main(void) { vec4 position = vec4(0.0, 0.0, 0.0, 0.0); @@ -38,12 +38,12 @@ void main(void) { skinPositionNormal(inSkinClusterIndex, inSkinClusterWeight, inPosition, inNormal.xyz, position, interpolatedNormal); // pass along the color - _color = color_sRGBToLinear(inColor.rgb); - _alpha = inColor.a; + _color.rgb = color_sRGBToLinear(inColor.rgb); + _color.a = inColor.a; TexMapArray texMapArray = getTexMapArray(); - <$evalTexMapArrayTexcoord0(texMapArray, inTexCoord0, _texCoord0)$> - <$evalTexMapArrayTexcoord1(texMapArray, inTexCoord0, _texCoord1)$> + <$evalTexMapArrayTexcoord0(texMapArray, inTexCoord0, _texCoord01.xy)$> + <$evalTexMapArrayTexcoord1(texMapArray, inTexCoord0, _texCoord01.zw)$> // standard transform TransformCamera cam = getTransformCamera(); diff --git a/libraries/render-utils/src/skin_model_normal_map.slv b/libraries/render-utils/src/skin_model_normal_map.slv index fd3efd087e..f67220c6bd 100644 --- a/libraries/render-utils/src/skin_model_normal_map.slv +++ b/libraries/render-utils/src/skin_model_normal_map.slv @@ -20,16 +20,16 @@ <@include Skinning.slh@> <$declareUseDualQuaternionSkinning()$> -<@include MaterialTextures.slh@> +<@include graphics/MaterialTextures.slh@> <$declareMaterialTexMapArrayBuffer()$> -out vec4 _positionES; -out vec2 _texCoord0; -out vec2 _texCoord1; -out vec3 _normalWS; -out vec3 _tangentWS; -out vec3 _color; -out float _alpha; +<@include render-utils/ShaderConstants.h@> + +layout(location=RENDER_UTILS_ATTR_POSITION_ES) out vec4 _positionES; +layout(location=RENDER_UTILS_ATTR_TEXCOORD01) out vec4 _texCoord01; +layout(location=RENDER_UTILS_ATTR_NORMAL_WS) out vec3 _normalWS; +layout(location=RENDER_UTILS_ATTR_TANGENT_WS) out vec3 _tangentWS; +layout(location=RENDER_UTILS_ATTR_COLOR) out vec4 _color; void main(void) { vec4 position = vec4(0.0, 0.0, 0.0, 0.0); @@ -39,12 +39,12 @@ void main(void) { skinPositionNormalTangent(inSkinClusterIndex, inSkinClusterWeight, inPosition, inNormal.xyz, inTangent.xyz, position, interpolatedNormal.xyz, interpolatedTangent.xyz); // pass along the color - _color = color_sRGBToLinear(inColor.rgb); - _alpha = inColor.a; + _color.rgb = color_sRGBToLinear(inColor.rgb); + _color.a = inColor.a; TexMapArray texMapArray = getTexMapArray(); - <$evalTexMapArrayTexcoord0(texMapArray, inTexCoord0, _texCoord0)$> - <$evalTexMapArrayTexcoord1(texMapArray, inTexCoord0, _texCoord1)$> + <$evalTexMapArrayTexcoord0(texMapArray, inTexCoord0, _texCoord01.xy)$> + <$evalTexMapArrayTexcoord1(texMapArray, inTexCoord0, _texCoord01.zw)$> interpolatedNormal = vec4(normalize(interpolatedNormal.xyz), 0.0); interpolatedTangent = vec4(normalize(interpolatedTangent.xyz), 0.0); diff --git a/libraries/render-utils/src/skin_model_normal_map_dq.slv b/libraries/render-utils/src/skin_model_normal_map_dq.slv index 4c56e0d64d..b5ffbdc49d 100644 --- a/libraries/render-utils/src/skin_model_normal_map_dq.slv +++ b/libraries/render-utils/src/skin_model_normal_map_dq.slv @@ -2,7 +2,7 @@ <$VERSION_HEADER$> // Generated on <$_SCRIBE_DATE$> // -// skin_model_normal_map.vert +// skin_model_normal_map_dq.vert // vertex shader // // Created by Andrzej Kapolka on 10/29/13. @@ -15,21 +15,20 @@ <@include gpu/Inputs.slh@> <@include gpu/Color.slh@> <@include gpu/Transform.slh@> +<@include render-utils/ShaderConstants.h@> <$declareStandardTransform()$> <@include Skinning.slh@> <$declareUseDualQuaternionSkinning(1)$> -<@include MaterialTextures.slh@> +<@include graphics/MaterialTextures.slh@> <$declareMaterialTexMapArrayBuffer()$> -out vec4 _positionES; -out vec2 _texCoord0; -out vec2 _texCoord1; -out vec3 _normalWS; -out vec3 _tangentWS; -out vec3 _color; -out float _alpha; +layout(location=RENDER_UTILS_ATTR_POSITION_ES) out vec4 _positionES; +layout(location=RENDER_UTILS_ATTR_TEXCOORD01) out vec4 _texCoord01; +layout(location=RENDER_UTILS_ATTR_NORMAL_WS) out vec3 _normalWS; +layout(location=RENDER_UTILS_ATTR_TANGENT_WS) out vec3 _tangentWS; +layout(location=RENDER_UTILS_ATTR_COLOR) out vec4 _color; void main(void) { vec4 position = vec4(0.0, 0.0, 0.0, 0.0); @@ -39,12 +38,12 @@ void main(void) { skinPositionNormalTangent(inSkinClusterIndex, inSkinClusterWeight, inPosition, inNormal.xyz, inTangent.xyz, position, interpolatedNormal.xyz, interpolatedTangent.xyz); // pass along the color - _color = color_sRGBToLinear(inColor.rgb); - _alpha = inColor.a; + _color.rgb = color_sRGBToLinear(inColor.rgb); + _color.a = inColor.a; TexMapArray texMapArray = getTexMapArray(); - <$evalTexMapArrayTexcoord0(texMapArray, inTexCoord0, _texCoord0)$> - <$evalTexMapArrayTexcoord1(texMapArray, inTexCoord0, _texCoord1)$> + <$evalTexMapArrayTexcoord0(texMapArray, inTexCoord0, _texCoord01.xy)$> + <$evalTexMapArrayTexcoord1(texMapArray, inTexCoord0, _texCoord01.zw)$> interpolatedNormal = vec4(normalize(interpolatedNormal.xyz), 0.0); interpolatedTangent = vec4(normalize(interpolatedTangent.xyz), 0.0); diff --git a/libraries/render-utils/src/skin_model_normal_map_fade.slv b/libraries/render-utils/src/skin_model_normal_map_fade.slv index 47cc790f70..5759416d44 100644 --- a/libraries/render-utils/src/skin_model_normal_map_fade.slv +++ b/libraries/render-utils/src/skin_model_normal_map_fade.slv @@ -20,17 +20,17 @@ <@include Skinning.slh@> <$declareUseDualQuaternionSkinning()$> -<@include MaterialTextures.slh@> +<@include graphics/MaterialTextures.slh@> <$declareMaterialTexMapArrayBuffer()$> -out vec4 _positionES; -out vec2 _texCoord0; -out vec2 _texCoord1; -out vec3 _normalWS; -out vec3 _tangentWS; -out vec3 _color; -out float _alpha; -out vec4 _positionWS; +<@include render-utils/ShaderConstants.h@> + +layout(location=RENDER_UTILS_ATTR_POSITION_ES) out vec4 _positionES; +layout(location=RENDER_UTILS_ATTR_TEXCOORD01) out vec4 _texCoord01; +layout(location=RENDER_UTILS_ATTR_NORMAL_WS) out vec3 _normalWS; +layout(location=RENDER_UTILS_ATTR_TANGENT_WS) out vec3 _tangentWS; +layout(location=RENDER_UTILS_ATTR_COLOR) out vec4 _color; +layout(location=RENDER_UTILS_ATTR_POSITION_WS) out vec4 _positionWS; void main(void) { vec4 position = vec4(0.0, 0.0, 0.0, 0.0); @@ -40,12 +40,12 @@ void main(void) { skinPositionNormalTangent(inSkinClusterIndex, inSkinClusterWeight, inPosition, inNormal.xyz, inTangent.xyz, position, interpolatedNormal.xyz, interpolatedTangent.xyz); // pass along the color - _color = color_sRGBToLinear(inColor.rgb); - _alpha = inColor.a; + _color.rgb = color_sRGBToLinear(inColor.rgb); + _color.a = inColor.a; TexMapArray texMapArray = getTexMapArray(); - <$evalTexMapArrayTexcoord0(texMapArray, inTexCoord0, _texCoord0)$> - <$evalTexMapArrayTexcoord1(texMapArray, inTexCoord0, _texCoord1)$> + <$evalTexMapArrayTexcoord0(texMapArray, inTexCoord0, _texCoord01.xy)$> + <$evalTexMapArrayTexcoord1(texMapArray, inTexCoord0, _texCoord01.zw)$> interpolatedNormal = vec4(normalize(interpolatedNormal.xyz), 0.0); interpolatedTangent = vec4(normalize(interpolatedTangent.xyz), 0.0); diff --git a/libraries/render-utils/src/skin_model_normal_map_fade_dq.slv b/libraries/render-utils/src/skin_model_normal_map_fade_dq.slv index 092d7b214f..e48bf47758 100644 --- a/libraries/render-utils/src/skin_model_normal_map_fade_dq.slv +++ b/libraries/render-utils/src/skin_model_normal_map_fade_dq.slv @@ -20,17 +20,17 @@ <@include Skinning.slh@> <$declareUseDualQuaternionSkinning(1)$> -<@include MaterialTextures.slh@> +<@include graphics/MaterialTextures.slh@> <$declareMaterialTexMapArrayBuffer()$> -out vec4 _positionES; -out vec2 _texCoord0; -out vec2 _texCoord1; -out vec3 _normalWS; -out vec3 _tangentWS; -out vec3 _color; -out float _alpha; -out vec4 _positionWS; +<@include render-utils/ShaderConstants.h@> + +layout(location=RENDER_UTILS_ATTR_POSITION_ES) out vec4 _positionES; +layout(location=RENDER_UTILS_ATTR_TEXCOORD01) out vec4 _texCoord01; +layout(location=RENDER_UTILS_ATTR_NORMAL_WS) out vec3 _normalWS; +layout(location=RENDER_UTILS_ATTR_TANGENT_WS) out vec3 _tangentWS; +layout(location=RENDER_UTILS_ATTR_COLOR) out vec4 _color; +layout(location=RENDER_UTILS_ATTR_POSITION_WS) out vec4 _positionWS; void main(void) { vec4 position = vec4(0.0, 0.0, 0.0, 0.0); @@ -40,12 +40,12 @@ void main(void) { skinPositionNormalTangent(inSkinClusterIndex, inSkinClusterWeight, inPosition, inNormal.xyz, inTangent.xyz, position, interpolatedNormal.xyz, interpolatedTangent.xyz); // pass along the color - _color = color_sRGBToLinear(inColor.rgb); - _alpha = inColor.a; + _color.rgb = color_sRGBToLinear(inColor.rgb); + _color.a = inColor.a; TexMapArray texMapArray = getTexMapArray(); - <$evalTexMapArrayTexcoord0(texMapArray, inTexCoord0, _texCoord0)$> - <$evalTexMapArrayTexcoord1(texMapArray, inTexCoord0, _texCoord1)$> + <$evalTexMapArrayTexcoord0(texMapArray, inTexCoord0, _texCoord01.xy)$> + <$evalTexMapArrayTexcoord1(texMapArray, inTexCoord0, _texCoord01.zw)$> interpolatedNormal = vec4(normalize(interpolatedNormal.xyz), 0.0); interpolatedTangent = vec4(normalize(interpolatedTangent.xyz), 0.0); diff --git a/libraries/render-utils/src/skin_model_shadow_dq.slv b/libraries/render-utils/src/skin_model_shadow_dq.slv index 74cd4076bc..a60eed62d3 100644 --- a/libraries/render-utils/src/skin_model_shadow_dq.slv +++ b/libraries/render-utils/src/skin_model_shadow_dq.slv @@ -2,7 +2,7 @@ <$VERSION_HEADER$> // Generated on <$_SCRIBE_DATE$> // -// skin_model_shadow.vert +// skin_model_shadow_dq.vert // vertex shader // // Created by Andrzej Kapolka on 3/24/14. diff --git a/libraries/render-utils/src/skin_model_shadow_fade.slf b/libraries/render-utils/src/skin_model_shadow_fade.slf deleted file mode 100644 index 214fc72e42..0000000000 --- a/libraries/render-utils/src/skin_model_shadow_fade.slf +++ /dev/null @@ -1,30 +0,0 @@ -<@include gpu/Config.slh@> -<$VERSION_HEADER$> -// Generated on <$_SCRIBE_DATE$> -// -// skin_model_shadow_fade.frag -// fragment shader -// -// Created by Olivier Prat on 06/08/17. -// Copyright 2017 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -<@include Fade.slh@> -<$declareFadeFragment()$> - -in vec4 _positionWS; - -layout(location = 0) out vec4 _fragColor; - -void main(void) { - FadeObjectParams fadeParams; - - <$fetchFadeObjectParams(fadeParams)$> - applyFadeClip(fadeParams, _positionWS.xyz); - - // pass-through to set z-buffer - _fragColor = vec4(1.0, 1.0, 1.0, 0.0); -} diff --git a/libraries/render-utils/src/skin_model_shadow_fade.slv b/libraries/render-utils/src/skin_model_shadow_fade.slv index e3ddd25e81..4881cb9f62 100644 --- a/libraries/render-utils/src/skin_model_shadow_fade.slv +++ b/libraries/render-utils/src/skin_model_shadow_fade.slv @@ -14,12 +14,14 @@ <@include gpu/Inputs.slh@> <@include gpu/Transform.slh@> +<@include render-utils/ShaderConstants.h@> + <$declareStandardTransform()$> <@include Skinning.slh@> <$declareUseDualQuaternionSkinning()$> -out vec4 _positionWS; +layout(location=RENDER_UTILS_ATTR_POSITION_WS) out vec4 _positionWS; void main(void) { vec4 position = vec4(0.0, 0.0, 0.0, 0.0); diff --git a/libraries/render-utils/src/skin_model_shadow_fade_dq.slv b/libraries/render-utils/src/skin_model_shadow_fade_dq.slv index dcb15c6e84..c45107b47c 100644 --- a/libraries/render-utils/src/skin_model_shadow_fade_dq.slv +++ b/libraries/render-utils/src/skin_model_shadow_fade_dq.slv @@ -2,7 +2,7 @@ <$VERSION_HEADER$> // Generated on <$_SCRIBE_DATE$> // -// skin_model_shadow_fade.vert +// skin_model_shadow_fade_dq.vert // vertex shader // // Created by Olivier Prat on 06/045/17. @@ -14,12 +14,14 @@ <@include gpu/Inputs.slh@> <@include gpu/Transform.slh@> +<@include render-utils/ShaderConstants.h@> +<@include Skinning.slh@> + <$declareStandardTransform()$> -<@include Skinning.slh@> <$declareUseDualQuaternionSkinning(1)$> -out vec4 _positionWS; +layout(location=RENDER_UTILS_ATTR_POSITION_WS) out vec4 _positionWS; void main(void) { vec4 position = vec4(0.0, 0.0, 0.0, 0.0); diff --git a/libraries/render-utils/src/ssao.slh b/libraries/render-utils/src/ssao.slh index c9b27bc2c8..5341a1682a 100644 --- a/libraries/render-utils/src/ssao.slh +++ b/libraries/render-utils/src/ssao.slh @@ -11,6 +11,8 @@ <@if not SSAO_SLH@> <@def SSAO_SLH@> +<@include render-utils/ShaderConstants.h@> + <@func declarePackOcclusionDepth()@> const float FAR_PLANE_Z = -300.0; @@ -42,7 +44,7 @@ struct AmbientOcclusionParams { float _gaussianCoefs[8]; }; -uniform ambientOcclusionParamsBuffer { +layout(binding=RENDER_UTILS_BUFFER_SSAO_PARAMS) uniform ambientOcclusionParamsBuffer { AmbientOcclusionParams params; }; @@ -230,7 +232,7 @@ vec3 getTapLocationClamped(int sampleNumber, float spinAngle, float outerRadius, // the depth pyramid texture -uniform sampler2D pyramidMap; +layout(binding=RENDER_UTILS_TEXTURE_SSAO_PYRAMID) uniform sampler2D pyramidMap; float getZEye(ivec2 pixel, int level) { return -texelFetch(pyramidMap, pixel, level).x; @@ -311,7 +313,7 @@ float evalAO(in vec3 C, in vec3 n_C, in vec3 Q) { <$declareAmbientOcclusion()$> // the source occlusion texture -uniform sampler2D occlusionMap; +layout(binding=RENDER_UTILS_TEXTURE_SSAO_OCCLUSION) uniform sampler2D occlusionMap; vec2 fetchOcclusionDepthRaw(ivec2 coords, out vec3 raw) { raw = texelFetch(occlusionMap, coords, 0).xyz; diff --git a/libraries/render-utils/src/ssao_debugOcclusion.slf b/libraries/render-utils/src/ssao_debugOcclusion.slf index 6af457db67..007fd0cd7b 100644 --- a/libraries/render-utils/src/ssao_debugOcclusion.slf +++ b/libraries/render-utils/src/ssao_debugOcclusion.slf @@ -2,6 +2,8 @@ <$VERSION_HEADER$> // Generated on <$_SCRIBE_DATE$> // +// ssao_debugOcclusion.frag +// // Created by Sam Gateau on 1/1/16. // Copyright 2016 High Fidelity, Inc. // @@ -24,7 +26,7 @@ struct DebugParams{ vec4 pixelInfo; }; -uniform debugAmbientOcclusionBuffer { +layout(binding=RENDER_UTILS_BUFFER_SSAO_DEBUG_PARAMS) uniform debugAmbientOcclusionBuffer { DebugParams debugParams; }; @@ -32,7 +34,7 @@ vec2 getDebugCursorTexcoord(){ return debugParams.pixelInfo.xy; } -out vec4 outFragColor; +layout(location=0) out vec4 outFragColor; void main(void) { vec2 imageSize = getSideImageSize(getResolutionLevel()); diff --git a/libraries/render-utils/src/ssao_makeHorizontalBlur.slf b/libraries/render-utils/src/ssao_makeHorizontalBlur.slf index 7c10a2a208..94dbb2b00c 100644 --- a/libraries/render-utils/src/ssao_makeHorizontalBlur.slf +++ b/libraries/render-utils/src/ssao_makeHorizontalBlur.slf @@ -2,6 +2,8 @@ <$VERSION_HEADER$> // Generated on <$_SCRIBE_DATE$> // +// ssao_makeHorizontalBlur.frag +// // Created by Sam Gateau on 1/1/16. // Copyright 2016 High Fidelity, Inc. // @@ -15,7 +17,7 @@ const ivec2 horizontal = ivec2(1,0); <$declareBlurPass(horizontal)$> -out vec4 outFragColor; +layout(location=0) out vec4 outFragColor; void main(void) { outFragColor = vec4(getBlurredOcclusion(gl_FragCoord.xy), 1.0); diff --git a/libraries/render-utils/src/ssao_makeOcclusion.slf b/libraries/render-utils/src/ssao_makeOcclusion.slf index 4c808342c5..1b638d4270 100644 --- a/libraries/render-utils/src/ssao_makeOcclusion.slf +++ b/libraries/render-utils/src/ssao_makeOcclusion.slf @@ -2,6 +2,8 @@ <$VERSION_HEADER$> // Generated on <$_SCRIBE_DATE$> // +// ssao_makeOcclusion.frag +// // Created by Sam Gateau on 1/1/16. // Copyright 2016 High Fidelity, Inc. // @@ -17,7 +19,7 @@ <$declarePackOcclusionDepth()$> -out vec4 outFragColor; +layout(location=0) out vec4 outFragColor; void main(void) { vec2 imageSize = getSideImageSize(getResolutionLevel()); diff --git a/libraries/render-utils/src/ssao_makePyramid.slf b/libraries/render-utils/src/ssao_makePyramid.slf index 70d46fb432..c87fe1e682 100644 --- a/libraries/render-utils/src/ssao_makePyramid.slf +++ b/libraries/render-utils/src/ssao_makePyramid.slf @@ -2,6 +2,8 @@ <$VERSION_HEADER$> // Generated on <$_SCRIBE_DATE$> // +// ssao_makePyramid.frag +// // Created by Sam Gateau on 1/1/16. // Copyright 2016 High Fidelity, Inc. // @@ -12,9 +14,9 @@ <@include ssao.slh@> <$declareAmbientOcclusion()$> -uniform sampler2D depthMap; +layout(binding=0) uniform sampler2D depthMap; -out vec4 outFragColor; +layout(location=0) out vec4 outFragColor; void main(void) { float Zdb = texelFetch(depthMap, ivec2(gl_FragCoord.xy), 0).x; diff --git a/libraries/render-utils/src/ssao_makeVerticalBlur.slf b/libraries/render-utils/src/ssao_makeVerticalBlur.slf index 3325bfb79c..0b9b5c7eaf 100644 --- a/libraries/render-utils/src/ssao_makeVerticalBlur.slf +++ b/libraries/render-utils/src/ssao_makeVerticalBlur.slf @@ -2,6 +2,8 @@ <$VERSION_HEADER$> // Generated on <$_SCRIBE_DATE$> // +// ssao_makeVerticalBlur.frag +// // Created by Sam Gateau on 1/1/16. // Copyright 2016 High Fidelity, Inc. // @@ -13,7 +15,7 @@ const ivec2 vertical = ivec2(0,1); <$declareBlurPass(vertical)$> -out vec4 outFragColor; +layout(location=0) out vec4 outFragColor; void main(void) { float occlusion = getBlurredOcclusion(gl_FragCoord.xy).x; diff --git a/libraries/render-utils/src/standardDrawTexture.slf b/libraries/render-utils/src/standardDrawTexture.slf index 95ac82557c..1a8af0f71c 100644 --- a/libraries/render-utils/src/standardDrawTexture.slf +++ b/libraries/render-utils/src/standardDrawTexture.slf @@ -11,15 +11,17 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +<@include gpu/ShaderConstants.h@> + // the texture -uniform sampler2D colorMap; +layout(binding=0) uniform sampler2D colorMap; -in vec3 varPosition; -in vec3 varNormal; -in vec2 varTexCoord0; -in vec4 varColor; +layout(location=GPU_ATTR_POSITION) in vec3 varPosition; +layout(location=GPU_ATTR_NORMAL) in vec3 varNormal; +layout(location=GPU_ATTR_TEXCOORD0) in vec2 varTexCoord0; +layout(location=GPU_ATTR_COLOR) in vec4 varColor; -out vec4 outFragColor; +layout(location=0) out vec4 outFragColor; void main(void) { vec4 color = texture(colorMap, varTexCoord0); diff --git a/libraries/render-utils/src/standardDrawTextureNoBlend.slf b/libraries/render-utils/src/standardDrawTextureNoBlend.slf new file mode 100644 index 0000000000..95138d123f --- /dev/null +++ b/libraries/render-utils/src/standardDrawTextureNoBlend.slf @@ -0,0 +1,30 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// standardDrawTexture.frag +// fragment shader +// +// Created by Sam Gateau on 6/10/15. +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +<@include gpu/ShaderConstants.h@> + +// the texture +layout(binding=0) uniform sampler2D colorMap; + +layout(location=GPU_ATTR_POSITION) in vec3 varPosition; +layout(location=GPU_ATTR_NORMAL) in vec3 varNormal; +layout(location=GPU_ATTR_TEXCOORD0) in vec2 varTexCoord0; +layout(location=GPU_ATTR_COLOR) in vec4 varColor; + +layout(location=0) out vec4 outFragColor; + +void main(void) { + vec4 color = texture(colorMap, varTexCoord0); + outFragColor = color * varColor; + outFragColor.a = 1.0; +} diff --git a/libraries/render-utils/src/standardTransformPNTC.slv b/libraries/render-utils/src/standardTransformPNTC.slv index 8ec685cea0..5f933aa037 100644 --- a/libraries/render-utils/src/standardTransformPNTC.slv +++ b/libraries/render-utils/src/standardTransformPNTC.slv @@ -2,7 +2,7 @@ <$VERSION_HEADER$> // Generated on <$_SCRIBE_DATE$> // -// standardTransformPNTC.slv +// standardTransformPNTC.vert // vertex shader // // Created by Sam Gateau on 6/10/2015. @@ -17,10 +17,10 @@ <@include gpu/Transform.slh@> <$declareStandardTransform()$> -out vec3 varPosition; -out vec3 varNormal; -out vec2 varTexCoord0; -out vec4 varColor; +layout(location=GPU_ATTR_POSITION) out vec3 varPosition; +layout(location=GPU_ATTR_NORMAL) out vec3 varNormal; +layout(location=GPU_ATTR_TEXCOORD0) out vec2 varTexCoord0; +layout(location=GPU_ATTR_COLOR) out vec4 varColor; void main(void) { varTexCoord0 = inTexCoord0.st; diff --git a/libraries/render-utils/src/stencil_drawMask.slf b/libraries/render-utils/src/stencil_drawMask.slf index 3eedeecb82..5ba09a8264 100644 --- a/libraries/render-utils/src/stencil_drawMask.slf +++ b/libraries/render-utils/src/stencil_drawMask.slf @@ -2,7 +2,7 @@ <$VERSION_HEADER$> // Generated on <$_SCRIBE_DATE$> // -// stencil_drawMask.slf +// stencil_drawMask.frag // fragment shader // // Created by Sam Gateau on 5/31/17. @@ -12,7 +12,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -in vec2 varTexCoord0; +layout(location=0) in vec2 varTexCoord0; float aspectRatio = 0.95; diff --git a/libraries/render-utils/src/subsurfaceScattering_drawScattering.slf b/libraries/render-utils/src/subsurfaceScattering_drawScattering.slf index e66d881e11..00b60d6fd4 100644 --- a/libraries/render-utils/src/subsurfaceScattering_drawScattering.slf +++ b/libraries/render-utils/src/subsurfaceScattering_drawScattering.slf @@ -2,6 +2,8 @@ <$VERSION_HEADER$> // Generated on <$_SCRIBE_DATE$> // +// subsurfaceScattering_drawScattering.frag +// // Created by Sam Gateau on 6/8/16. // Copyright 2016 High Fidelity, Inc. // @@ -10,6 +12,7 @@ // +<@include gpu/ShaderConstants.h@> <@include DeferredBufferRead.slh@> <@include graphics/Light.slh@> @@ -20,10 +23,10 @@ <@include SubsurfaceScattering.slh@> <$declareSubsurfaceScatteringBRDF()$> -in vec2 varTexCoord0; -out vec4 _fragColor; +layout(location=0) in vec2 varTexCoord0; +layout(location=0) out vec4 _fragColor; -uniform vec2 uniformCursorTexcoord = vec2(0.5); +layout(location=GPU_UNIFORM_EXTRA0) uniform vec2 uniformCursorTexcoord = vec2(0.5); //uniform vec3 uniformLightVector = vec3(1.0); diff --git a/libraries/render-utils/src/subsurfaceScattering_makeLUT.slf b/libraries/render-utils/src/subsurfaceScattering_makeLUT.slf index 1cbf599558..6e10861875 100644 --- a/libraries/render-utils/src/subsurfaceScattering_makeLUT.slf +++ b/libraries/render-utils/src/subsurfaceScattering_makeLUT.slf @@ -2,6 +2,8 @@ <$VERSION_HEADER$> // Generated on <$_SCRIBE_DATE$> // +// subsurfaceScattering_makeLUT.frag +// // Created by Sam Gateau on 6/8/16. // Copyright 2016 High Fidelity, Inc. // @@ -13,8 +15,8 @@ <$declareSubsurfaceScatteringProfileSource()$> <$declareSubsurfaceScatteringIntegrate(2000)$> -in vec2 varTexCoord0; -out vec4 outFragColor; +layout(location=0) in vec2 varTexCoord0; +layout(location=0) out vec4 outFragColor; void main(void) { diff --git a/libraries/render-utils/src/subsurfaceScattering_makeProfile.slf b/libraries/render-utils/src/subsurfaceScattering_makeProfile.slf index ea4a864448..31122dcb1f 100644 --- a/libraries/render-utils/src/subsurfaceScattering_makeProfile.slf +++ b/libraries/render-utils/src/subsurfaceScattering_makeProfile.slf @@ -2,6 +2,8 @@ <$VERSION_HEADER$> // Generated on <$_SCRIBE_DATE$> // +// subsurfaceScattering_makeProfile.frag +// // Created by Sam Gateau on 6/27/16. // Copyright 2016 High Fidelity, Inc. // @@ -12,8 +14,8 @@ <@include SubsurfaceScattering.slh@> <$declareSubsurfaceScatteringGenerateProfileMap()$> -in vec2 varTexCoord0; -out vec4 outFragColor; +layout(location=0) in vec2 varTexCoord0; +layout(location=0) out vec4 outFragColor; void main(void) { outFragColor = vec4(generateProfile(varTexCoord0.xy), 1.0); diff --git a/libraries/render-utils/src/subsurfaceScattering_makeSpecularBeckmann.slf b/libraries/render-utils/src/subsurfaceScattering_makeSpecularBeckmann.slf index f10d287c35..59e73100dd 100644 --- a/libraries/render-utils/src/subsurfaceScattering_makeSpecularBeckmann.slf +++ b/libraries/render-utils/src/subsurfaceScattering_makeSpecularBeckmann.slf @@ -2,6 +2,8 @@ <$VERSION_HEADER$> // Generated on <$_SCRIBE_DATE$> // +// subsurfaceScattering_makeSpecularBeckmann.frag +// // Created by Sam Gateau on 6/30/16. // Copyright 2016 High Fidelity, Inc. // @@ -11,8 +13,8 @@ -in vec2 varTexCoord0; -out vec4 outFragColor; +layout(location=0) in vec2 varTexCoord0; +layout(location=0) out vec4 outFragColor; float specularBeckmann(float ndoth, float roughness) { float alpha = acos(ndoth); diff --git a/libraries/render-utils/src/surfaceGeometry_copyDepth.slf b/libraries/render-utils/src/surfaceGeometry_copyDepth.slf index 9db8cdbb01..7eb097224e 100644 --- a/libraries/render-utils/src/surfaceGeometry_copyDepth.slf +++ b/libraries/render-utils/src/surfaceGeometry_copyDepth.slf @@ -2,6 +2,8 @@ <$VERSION_HEADER$> // Generated on <$_SCRIBE_DATE$> // +// surfaceGeometry_copyDepth.frag +// // Created by Olivier Prat on 08/08/17. // Copyright 2017 High Fidelity, Inc. // @@ -9,9 +11,9 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -uniform sampler2D depthMap; +layout(binding=0) uniform sampler2D depthMap; -out vec4 outFragColor; +layout(location=0) out vec4 outFragColor; void main(void) { float Zdb = texelFetch(depthMap, ivec2(gl_FragCoord.xy), 0).x; diff --git a/libraries/render-utils/src/surfaceGeometry_downsampleDepthNormal.slf b/libraries/render-utils/src/surfaceGeometry_downsampleDepthNormal.slf index 82895d4684..34e78ea4ff 100644 --- a/libraries/render-utils/src/surfaceGeometry_downsampleDepthNormal.slf +++ b/libraries/render-utils/src/surfaceGeometry_downsampleDepthNormal.slf @@ -2,6 +2,8 @@ <$VERSION_HEADER$> // Generated on <$_SCRIBE_DATE$> // +// surfaceGeometry_downsampleDepthNormal.frag +// // Created by Sam Gateau on 6/3/16. // Copyright 2016 High Fidelity, Inc. // @@ -11,14 +13,15 @@ <@include gpu/PackedNormal.slh@> +<@include render-utils/ShaderConstants.h@> -uniform sampler2D linearDepthMap; -uniform sampler2D normalMap; +layout(binding=RENDER_UTILS_TEXTURE_SG_DEPTH) uniform sampler2D linearDepthMap; +layout(binding=RENDER_UTILS_TEXTURE_SG_NORMAL) uniform sampler2D normalMap; -in vec2 varTexCoord0; +layout(location=0) in vec2 varTexCoord0; -layout(location = 0) out vec4 outLinearDepth; -layout(location = 1) out vec4 outNormal; +layout(location=0) out vec4 outLinearDepth; +layout(location=1) out vec4 outNormal; void main(void) { // Gather 2 by 2 quads from texture and downsample diff --git a/libraries/render-utils/src/surfaceGeometry_makeCurvature.slf b/libraries/render-utils/src/surfaceGeometry_makeCurvature.slf index ecbc60b648..b49bd618da 100644 --- a/libraries/render-utils/src/surfaceGeometry_makeCurvature.slf +++ b/libraries/render-utils/src/surfaceGeometry_makeCurvature.slf @@ -2,6 +2,8 @@ <$VERSION_HEADER$> // Generated on <$_SCRIBE_DATE$> // +// surfaceGeometry_makeCurvature.frag +// // Created by Sam Gateau on 6/3/16. // Copyright 2016 High Fidelity, Inc. // @@ -10,6 +12,8 @@ // <@include DeferredTransform.slh@> +<@include render-utils/ShaderConstants.h@> + <$declareDeferredFrameTransform()$> <@include gpu/PackedNormal.slh@> @@ -21,7 +25,7 @@ struct SurfaceGeometryParams { vec4 curvatureInfo; }; -uniform surfaceGeometryParamsBuffer { +layout(binding= RENDER_UTILS_BUFFER_SG_PARAMS) uniform surfaceGeometryParamsBuffer { SurfaceGeometryParams params; }; @@ -42,7 +46,8 @@ bool isFullResolution() { } -uniform sampler2D linearDepthMap; +layout(binding=RENDER_UTILS_TEXTURE_SG_DEPTH) uniform sampler2D linearDepthMap; + float getZEye(ivec2 pixel) { return -texelFetch(linearDepthMap, pixel, 0).x; } @@ -54,7 +59,7 @@ vec2 sideToFrameTexcoord(vec2 side, vec2 texcoordPos) { return vec2((texcoordPos.x + side.x) * side.y, texcoordPos.y); } -uniform sampler2D normalMap; +layout(binding=RENDER_UTILS_TEXTURE_SG_NORMAL) uniform sampler2D normalMap; vec3 getRawNormal(vec2 texcoord) { return texture(normalMap, texcoord).xyz; @@ -79,8 +84,8 @@ float getEyeDepthDiff(vec2 texcoord, vec2 delta) { -in vec2 varTexCoord0; -out vec4 outFragColor; +layout(location=0) in vec2 varTexCoord0; +layout(location=0) out vec4 outFragColor; void main(void) { // Pixel being shaded diff --git a/libraries/render-utils/src/surfaceGeometry_makeLinearDepth.slf b/libraries/render-utils/src/surfaceGeometry_makeLinearDepth.slf index d512f613bc..116f3b7686 100644 --- a/libraries/render-utils/src/surfaceGeometry_makeLinearDepth.slf +++ b/libraries/render-utils/src/surfaceGeometry_makeLinearDepth.slf @@ -2,6 +2,8 @@ <$VERSION_HEADER$> // Generated on <$_SCRIBE_DATE$> // +// surfaceGeometry_makeLinearDepth.frag +// // Created by Sam Gateau on 6/3/16. // Copyright 2016 High Fidelity, Inc. // @@ -9,13 +11,14 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +<@include render-utils/ShaderConstants.h@> + <@include DeferredTransform.slh@> <$declareDeferredFrameTransform()$> +layout(binding=RENDER_UTILS_TEXTURE_SG_DEPTH) uniform sampler2D depthMap; -uniform sampler2D depthMap; - -out vec4 outFragColor; +layout(location=0) out vec4 outFragColor; void main(void) { float Zdb = texelFetch(depthMap, ivec2(gl_FragCoord.xy), 0).x; diff --git a/libraries/render-utils/src/taa.slf b/libraries/render-utils/src/taa.slf index 8d172871d4..a2b58d3050 100644 --- a/libraries/render-utils/src/taa.slf +++ b/libraries/render-utils/src/taa.slf @@ -15,8 +15,8 @@ <@include taa.slh@> -in vec2 varTexCoord0; -layout(location = 0) out vec4 outFragColor; +layout(location=0) in vec2 varTexCoord0; +layout(location=0) out vec4 outFragColor; void main() { vec2 fragUV = varTexCoord0; diff --git a/libraries/render-utils/src/taa.slh b/libraries/render-utils/src/taa.slh index 3ca5b10d57..26ffe55263 100644 --- a/libraries/render-utils/src/taa.slh +++ b/libraries/render-utils/src/taa.slh @@ -13,13 +13,14 @@ <@include DeferredTransform.slh@> <$declareDeferredFrameTransform()$> +<@include render-utils/ShaderConstants.h@> <@include gpu/Color.slh@> -uniform sampler2D depthMap; -uniform sampler2D sourceMap; -uniform sampler2D historyMap; -uniform sampler2D velocityMap; -uniform sampler2D nextMap; +layout(binding=RENDER_UTILS_TEXTURE_TAA_HISTORY) uniform sampler2D historyMap; +layout(binding=RENDER_UTILS_TEXTURE_TAA_SOURCE) uniform sampler2D sourceMap; +layout(binding=RENDER_UTILS_TEXTURE_TAA_VELOCITY) uniform sampler2D velocityMap; +layout(binding=RENDER_UTILS_TEXTURE_TAA_DEPTH) uniform sampler2D depthMap; +layout(binding=RENDER_UTILS_TEXTURE_TAA_NEXT) uniform sampler2D nextMap; struct TAAParams { @@ -32,7 +33,7 @@ struct TAAParams vec4 regionInfo; }; -layout(std140) uniform taaParamsBuffer { +layout(std140, binding=RENDER_UTILS_BUFFER_TAA_PARAMS) uniform taaParamsBuffer { TAAParams params; }; @@ -518,4 +519,5 @@ vec3 taa_evalFXAA(vec2 fragUV) { } else { return rgbB; } -} \ No newline at end of file +} + diff --git a/libraries/render-utils/src/taa_blend.slf b/libraries/render-utils/src/taa_blend.slf index aca934ca71..d2e23b590f 100644 --- a/libraries/render-utils/src/taa_blend.slf +++ b/libraries/render-utils/src/taa_blend.slf @@ -14,8 +14,8 @@ <@include taa.slh@> -in vec2 varTexCoord0; -layout(location = 0) out vec4 outFragColor; +layout(location=0) in vec2 varTexCoord0; +layout(location=0) out vec4 outFragColor; void main(void) { vec3 nextColor = texture(nextMap, varTexCoord0).xyz; diff --git a/libraries/render-utils/src/text/Font.cpp b/libraries/render-utils/src/text/Font.cpp index cd171db855..aca6ddb79f 100644 --- a/libraries/render-utils/src/text/Font.cpp +++ b/libraries/render-utils/src/text/Font.cpp @@ -6,11 +6,9 @@ #include #include +#include -#include "sdf_text3D_vert.h" -#include "sdf_text3D_frag.h" -#include "sdf_text3D_transparent_frag.h" - +#include "../render-utils/ShaderConstants.h" #include "../RenderUtilsLogging.h" #include "FontFamilies.h" #include "../StencilMaskPass.h" @@ -223,20 +221,8 @@ void Font::setupGPU() { // Setup render pipeline { - auto vertexShader = sdf_text3D_vert::getShader(); - auto pixelShader = sdf_text3D_frag::getShader(); - auto pixelShaderTransparent = sdf_text3D_transparent_frag::getShader(); - gpu::ShaderPointer program = gpu::Shader::createProgram(vertexShader, pixelShader); - gpu::ShaderPointer programTransparent = gpu::Shader::createProgram(vertexShader, pixelShaderTransparent); - - gpu::Shader::BindingSet slotBindings; - gpu::Shader::makeProgram(*program, slotBindings); - gpu::Shader::makeProgram(*programTransparent, slotBindings); - - _fontLoc = program->getTextures().findLocation("Font"); - _outlineLoc = program->getUniforms().findLocation("Outline"); - _colorLoc = program->getUniforms().findLocation("Color"); - + gpu::ShaderPointer program = gpu::Shader::createProgram(shader::render_utils::program::sdf_text3D); + gpu::ShaderPointer programTransparent = gpu::Shader::createProgram(shader::render_utils::program::sdf_text3D_transparent); auto state = std::make_shared(); state->setCullMode(gpu::State::CULL_BACK); state->setDepthTest(true, true, gpu::LESS_EQUAL); @@ -368,19 +354,11 @@ void Font::drawString(gpu::Batch& batch, float x, float y, const QString& str, c setupGPU(); batch.setPipeline(((*color).a < 1.0f || layered) ? _transparentPipeline : _pipeline); - if (_fontLoc >= 0) { - batch.setResourceTexture(_fontLoc, _texture); - } - if (_outlineLoc >= 0) { - batch._glUniform1i(_outlineLoc, (effectType == OUTLINE_EFFECT)); - } - + batch.setResourceTexture(render_utils::slot::texture::TextFont, _texture); + batch._glUniform1i(render_utils::slot::uniform::TextOutline, (effectType == OUTLINE_EFFECT)); // need the gamma corrected color here glm::vec4 lrgba = ColorUtils::sRGBToLinearVec4(*color); - if (_colorLoc >= 0) { - batch._glUniform4fv(_colorLoc, 1, (const float*)&lrgba); - } - + batch._glUniform4fv(render_utils::slot::uniform::TextColor, 1, (const float*)&lrgba); batch.setInputFormat(_format); batch.setInputBuffer(0, _verticesBuffer, 0, _format->getChannels().at(0)._stride); batch.setIndexBuffer(gpu::UINT16, _indicesBuffer, 0); diff --git a/libraries/render-utils/src/text/Font.h b/libraries/render-utils/src/text/Font.h index a41f720f15..2fa2b65fa5 100644 --- a/libraries/render-utils/src/text/Font.h +++ b/libraries/render-utils/src/text/Font.h @@ -72,10 +72,6 @@ private: unsigned int _numVertices = 0; unsigned int _numIndices = 0; - int _fontLoc = -1; - int _outlineLoc = -1; - int _colorLoc = -1; - // last string render characteristics QString _lastStringRendered; glm::vec2 _lastBounds; diff --git a/libraries/render-utils/src/toneMapping.slf b/libraries/render-utils/src/toneMapping.slf index d6504b5bff..8d89e54a1b 100644 --- a/libraries/render-utils/src/toneMapping.slf +++ b/libraries/render-utils/src/toneMapping.slf @@ -2,6 +2,8 @@ <$VERSION_HEADER$> // Generated on Sat Oct 24 09:34:37 2015 // +// toneMapping.frag +// // Draw texture 0 fetched at texcoord.xy // // Created by Sam Gateau on 6/22/2015 @@ -11,6 +13,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +<@include render-utils/ShaderConstants.h@> + struct ToneMappingParams { vec4 _exp_2powExp_s0_s1; ivec4 _toneCurve_s0_s1_s2; @@ -22,7 +26,7 @@ const int ToneCurveGamma22 = 1; const int ToneCurveReinhard = 2; const int ToneCurveFilmic = 3; -uniform toneMappingParamsBuffer { +layout(binding=RENDER_UTILS_BUFFER_TM_PARAMS) uniform toneMappingParamsBuffer { ToneMappingParams params; }; float getTwoPowExposure() { @@ -32,10 +36,10 @@ int getToneCurve() { return params._toneCurve_s0_s1_s2.x; } -uniform sampler2D colorMap; +layout(binding=RENDER_UTILS_TEXTURE_TM_COLOR) uniform sampler2D colorMap; -in vec2 varTexCoord0; -out vec4 outFragColor; +layout(location=0) in vec2 varTexCoord0; +layout(location=0) out vec4 outFragColor; void main(void) { vec4 fragColorRaw = texture(colorMap, varTexCoord0); diff --git a/libraries/render-utils/src/velocityBuffer_cameraMotion.slf b/libraries/render-utils/src/velocityBuffer_cameraMotion.slf index 548feb87dc..6932766d63 100644 --- a/libraries/render-utils/src/velocityBuffer_cameraMotion.slf +++ b/libraries/render-utils/src/velocityBuffer_cameraMotion.slf @@ -2,6 +2,8 @@ <$VERSION_HEADER$> // Generated on <$_SCRIBE_DATE$> // +// velocityBuffer_cameraMotion.frag +// // Created by Sam Gateau on 6/3/16. // Copyright 2016 High Fidelity, Inc. // @@ -12,10 +14,10 @@ <@include DeferredTransform.slh@> <$declareDeferredFrameTransform()$> -in vec2 varTexCoord0; -out vec4 outFragColor; +layout(location=0) in vec2 varTexCoord0; +layout(location=0) out vec4 outFragColor; -uniform sampler2D depthMap; +layout(binding=RENDER_UTILS_TEXTURE_TAA_DEPTH) uniform sampler2D depthMap; void main(void) { diff --git a/libraries/render-utils/src/zone_drawAmbient.slf b/libraries/render-utils/src/zone_drawAmbient.slf index 3407fe8467..e560d91308 100644 --- a/libraries/render-utils/src/zone_drawAmbient.slf +++ b/libraries/render-utils/src/zone_drawAmbient.slf @@ -19,8 +19,8 @@ <$declareLightingAmbient(_SCRIBE_NULL, 1, _SCRIBE_NULL, _SCRIBE_NULL)$> -in vec2 varTexCoord0; -out vec4 _fragColor; +layout(location=0) in vec2 varTexCoord0; +layout(location=0) out vec4 _fragColor; void main(void) { diff --git a/libraries/render-utils/src/zone_drawKeyLight.slf b/libraries/render-utils/src/zone_drawKeyLight.slf index 5f0f0265d3..99e9357cb0 100644 --- a/libraries/render-utils/src/zone_drawKeyLight.slf +++ b/libraries/render-utils/src/zone_drawKeyLight.slf @@ -18,8 +18,8 @@ <@include LightDirectional.slh@> <$declareLightingDirectional(_SCRIBE_NULL)$> -in vec2 varTexCoord0; -out vec4 _fragColor; +layout(location=0) in vec2 varTexCoord0; +layout(location=0) out vec4 _fragColor; void main(void) { diff --git a/libraries/render-utils/src/zone_drawSkybox.slf b/libraries/render-utils/src/zone_drawSkybox.slf index fd6976365e..77de75a305 100644 --- a/libraries/render-utils/src/zone_drawSkybox.slf +++ b/libraries/render-utils/src/zone_drawSkybox.slf @@ -9,19 +9,21 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // <@include zone_draw.slh@> +<@include render-utils/ShaderConstants.h@> -uniform samplerCube skyboxMap; +// FIXME use declareSkyboxMap from LightAmbient.slh? +layout(binding=RENDER_UTILS_TEXTURE_SKYBOX) uniform samplerCube skyboxMap; struct Skybox { vec4 color; }; -uniform skyboxBuffer { +layout(binding=RENDER_UTILS_BUFFER_DEBUG_SKYBOX) uniform skyboxBuffer { Skybox skybox; }; -in vec2 varTexCoord0; -out vec4 _fragColor; +layout(location=0) in vec2 varTexCoord0; +layout(location=0) out vec4 _fragColor; void main(void) { <$evalGlobeWidget()$> diff --git a/libraries/render/CMakeLists.txt b/libraries/render/CMakeLists.txt index 2a888a5b18..e3aa4bd491 100644 --- a/libraries/render/CMakeLists.txt +++ b/libraries/render/CMakeLists.txt @@ -1,8 +1,7 @@ set(TARGET_NAME render) -AUTOSCRIBE_SHADER_LIB(gpu graphics) setup_hifi_library() # render needs octree only for getAccuracyAngle(float, int) -link_hifi_libraries(shared task ktx gpu graphics octree) +link_hifi_libraries(shared task ktx gpu shaders graphics octree) target_nsight() diff --git a/libraries/render/src/render/Args.h b/libraries/render/src/render/Args.h index 627d4f17b2..dd061c0092 100644 --- a/libraries/render/src/render/Args.h +++ b/libraries/render/src/render/Args.h @@ -73,6 +73,7 @@ namespace render { Args(const gpu::ContextPointer& context, float sizeScale = 1.0f, int boundaryLevelAdjust = 0, + float lodAngleHalfTan = 0.1f, RenderMode renderMode = DEFAULT_RENDER_MODE, DisplayMode displayMode = MONO, DebugFlags debugFlags = RENDER_DEBUG_NONE, @@ -80,6 +81,8 @@ namespace render { _context(context), _sizeScale(sizeScale), _boundaryLevelAdjust(boundaryLevelAdjust), + _lodAngleHalfTan(lodAngleHalfTan), + _lodAngleHalfTanSq(lodAngleHalfTan * lodAngleHalfTan), _renderMode(renderMode), _displayMode(displayMode), _debugFlags(debugFlags), @@ -105,8 +108,12 @@ namespace render { std::stack _viewFrustums; glm::ivec4 _viewport { 0.0f, 0.0f, 1.0f, 1.0f }; glm::vec3 _boomOffset { 0.0f, 0.0f, 1.0f }; + float _sizeScale { 1.0f }; int _boundaryLevelAdjust { 0 }; + float _lodAngleHalfTan{ 0.1f }; + float _lodAngleHalfTanSq{ _lodAngleHalfTan * _lodAngleHalfTan }; + RenderMode _renderMode { DEFAULT_RENDER_MODE }; DisplayMode _displayMode { MONO }; DebugFlags _debugFlags { RENDER_DEBUG_NONE }; diff --git a/libraries/render/src/render/BlurTask.cpp b/libraries/render/src/render/BlurTask.cpp index 896482eb2a..edbfbdaff0 100644 --- a/libraries/render/src/render/BlurTask.cpp +++ b/libraries/render/src/render/BlurTask.cpp @@ -11,13 +11,7 @@ #include "BlurTask.h" #include -#include - -#include "blurGaussianV_frag.h" -#include "blurGaussianH_frag.h" - -#include "blurGaussianDepthAwareV_frag.h" -#include "blurGaussianDepthAwareH_frag.h" +#include using namespace render; @@ -201,23 +195,13 @@ bool BlurInOutResource::updateResources(const gpu::FramebufferPointer& sourceFra return true; } -BlurGaussian::BlurGaussian(bool generateOutputFramebuffer, unsigned int downsampleFactor) : - _inOutResources(generateOutputFramebuffer, downsampleFactor) -{ +BlurGaussian::BlurGaussian() { _parameters = std::make_shared(); } gpu::PipelinePointer BlurGaussian::getBlurVPipeline() { if (!_blurVPipeline) { - auto vs = gpu::StandardShaderLib::getDrawUnitQuadTexcoordVS(); - auto ps = blurGaussianV_frag::getShader(); - gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); - - gpu::Shader::BindingSet slotBindings; - slotBindings.insert(gpu::Shader::Binding(std::string("blurParamsBuffer"), BlurTask_ParamsSlot)); - slotBindings.insert(gpu::Shader::Binding(std::string("sourceMap"), BlurTask_SourceSlot)); - gpu::Shader::makeProgram(*program, slotBindings); - + gpu::ShaderPointer program = gpu::Shader::createProgram(shader::render::program::blurGaussianV); gpu::StatePointer state = gpu::StatePointer(new gpu::State()); // Stencil test the curvature pass for objects pixels only, not the background @@ -231,15 +215,7 @@ gpu::PipelinePointer BlurGaussian::getBlurVPipeline() { gpu::PipelinePointer BlurGaussian::getBlurHPipeline() { if (!_blurHPipeline) { - auto vs = gpu::StandardShaderLib::getDrawUnitQuadTexcoordVS(); - auto ps = blurGaussianH_frag::getShader(); - gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); - - gpu::Shader::BindingSet slotBindings; - slotBindings.insert(gpu::Shader::Binding(std::string("blurParamsBuffer"), BlurTask_ParamsSlot)); - slotBindings.insert(gpu::Shader::Binding(std::string("sourceMap"), BlurTask_SourceSlot)); - gpu::Shader::makeProgram(*program, slotBindings); - + gpu::ShaderPointer program = gpu::Shader::createProgram(shader::render::program::blurGaussianH); gpu::StatePointer state = gpu::StatePointer(new gpu::State()); // Stencil test the curvature pass for objects pixels only, not the background @@ -265,12 +241,17 @@ void BlurGaussian::configure(const Config& config) { } -void BlurGaussian::run(const RenderContextPointer& renderContext, const gpu::FramebufferPointer& sourceFramebuffer, gpu::FramebufferPointer& blurredFramebuffer) { +void BlurGaussian::run(const RenderContextPointer& renderContext, const Inputs& inputs, gpu::FramebufferPointer& blurredFramebuffer) { assert(renderContext->args); assert(renderContext->args->hasViewFrustum()); RenderArgs* args = renderContext->args; + const auto sourceFramebuffer = inputs.get0(); + _inOutResources._generateOutputFramebuffer = inputs.get1(); + _inOutResources._downsampleFactor = inputs.get2(); + _parameters->setFilterGaussianTaps(inputs.get3(), inputs.get4()); + BlurInOutResource::Resources blurringResources; if (!_inOutResources.updateResources(sourceFramebuffer, blurringResources)) { // early exit if no valid blurring resources @@ -323,16 +304,7 @@ BlurGaussianDepthAware::BlurGaussianDepthAware(bool generateOutputFramebuffer, c gpu::PipelinePointer BlurGaussianDepthAware::getBlurVPipeline() { if (!_blurVPipeline) { - auto vs = gpu::StandardShaderLib::getDrawUnitQuadTexcoordVS(); - auto ps = blurGaussianDepthAwareV_frag::getShader(); - gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); - - gpu::Shader::BindingSet slotBindings; - slotBindings.insert(gpu::Shader::Binding(std::string("blurParamsBuffer"), BlurTask_ParamsSlot)); - slotBindings.insert(gpu::Shader::Binding(std::string("sourceMap"), BlurTask_SourceSlot)); - slotBindings.insert(gpu::Shader::Binding(std::string("depthMap"), BlurTask_DepthSlot)); - gpu::Shader::makeProgram(*program, slotBindings); - + gpu::ShaderPointer program = gpu::Shader::createProgram(shader::render::program::blurGaussianDepthAwareV); gpu::StatePointer state = gpu::StatePointer(new gpu::State()); // Stencil test the curvature pass for objects pixels only, not the background @@ -346,16 +318,7 @@ gpu::PipelinePointer BlurGaussianDepthAware::getBlurVPipeline() { gpu::PipelinePointer BlurGaussianDepthAware::getBlurHPipeline() { if (!_blurHPipeline) { - auto vs = gpu::StandardShaderLib::getDrawUnitQuadTexcoordVS(); - auto ps = blurGaussianDepthAwareH_frag::getShader(); - gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); - - gpu::Shader::BindingSet slotBindings; - slotBindings.insert(gpu::Shader::Binding(std::string("blurParamsBuffer"), BlurTask_ParamsSlot)); - slotBindings.insert(gpu::Shader::Binding(std::string("sourceMap"), BlurTask_SourceSlot)); - slotBindings.insert(gpu::Shader::Binding(std::string("depthMap"), BlurTask_DepthSlot)); - gpu::Shader::makeProgram(*program, slotBindings); - + gpu::ShaderPointer program = gpu::Shader::createProgram(shader::render::program::blurGaussianDepthAwareH); gpu::StatePointer state = gpu::StatePointer(new gpu::State()); // Stencil test the curvature pass for objects pixels only, not the background diff --git a/libraries/render/src/render/BlurTask.h b/libraries/render/src/render/BlurTask.h index e8d268dc63..cb11570f5c 100644 --- a/libraries/render/src/render/BlurTask.h +++ b/libraries/render/src/render/BlurTask.h @@ -72,6 +72,7 @@ using BlurParamsPointer = std::shared_ptr; class BlurInOutResource { public: + BlurInOutResource() {} BlurInOutResource(bool generateOutputFramebuffer, unsigned int downsampleFactor); struct Resources { @@ -113,13 +114,14 @@ protected: class BlurGaussian { public: + using Inputs = VaryingSet5; using Config = BlurGaussianConfig; - using JobModel = Job::ModelIO; + using JobModel = Job::ModelIO; - BlurGaussian(bool generateOutputFramebuffer = false, unsigned int downsampleFactor = 1U); + BlurGaussian(); void configure(const Config& config); - void run(const RenderContextPointer& renderContext, const gpu::FramebufferPointer& sourceFramebuffer, gpu::FramebufferPointer& blurredFramebuffer); + void run(const RenderContextPointer& renderContext, const Inputs& inputs, gpu::FramebufferPointer& blurredFramebuffer); BlurParamsPointer getParameters() const { return _parameters; } diff --git a/libraries/render/src/render/BlurTask.slh b/libraries/render/src/render/BlurTask.slh index 37f29496bd..c07e71688a 100644 --- a/libraries/render/src/render/BlurTask.slh +++ b/libraries/render/src/render/BlurTask.slh @@ -21,7 +21,7 @@ struct BlurParameters { vec2 taps[BLUR_MAX_NUM_TAPS]; }; -uniform blurParamsBuffer { +layout(binding=0) uniform blurParamsBuffer { BlurParameters parameters; }; @@ -76,7 +76,7 @@ float getPosLinearDepthFar() { <$declareBlurUniforms()$> -uniform sampler2D sourceMap; +layout(binding=0) uniform sampler2D sourceMap; vec4 pixelShaderGaussian(vec2 texcoord, vec2 direction, vec2 pixelStep) { texcoord = evalTexcoordTransformed(texcoord); @@ -112,8 +112,8 @@ vec4 pixelShaderGaussian(vec2 texcoord, vec2 direction, vec2 pixelStep) { <$declareBlurUniforms()$> -uniform sampler2D sourceMap; -uniform sampler2D depthMap; +layout(binding=0) uniform sampler2D sourceMap; +layout(binding=1) uniform sampler2D depthMap; vec4 pixelShaderGaussianDepthAware(vec2 texcoord, vec2 direction, vec2 pixelStep) { texcoord = evalTexcoordTransformed(texcoord); diff --git a/libraries/render/src/render/CullTask.cpp b/libraries/render/src/render/CullTask.cpp index 8cfe7683ce..b2930032a3 100644 --- a/libraries/render/src/render/CullTask.cpp +++ b/libraries/render/src/render/CullTask.cpp @@ -19,60 +19,50 @@ using namespace render; -// Culling Frustum / solidAngle test helper class -struct Test { - CullFunctor _functor; - RenderArgs* _args; - RenderDetails::Item& _renderDetails; - ViewFrustumPointer _antiFrustum; - glm::vec3 _eyePos; - float _squareTanAlpha; +CullTest::CullTest(CullFunctor& functor, RenderArgs* pargs, RenderDetails::Item& renderDetails, ViewFrustumPointer antiFrustum) : + _functor(functor), + _args(pargs), + _renderDetails(renderDetails), + _antiFrustum(antiFrustum) { + // FIXME: Keep this code here even though we don't use it yet + /*_eyePos = _args->getViewFrustum().getPosition(); + float a = glm::degrees(Octree::getPerspectiveAccuracyAngle(_args->_sizeScale, _args->_boundaryLevelAdjust)); + auto angle = std::min(glm::radians(45.0f), a); // no worse than 45 degrees + angle = std::max(glm::radians(1.0f / 60.0f), a); // no better than 1 minute of degree + auto tanAlpha = tan(angle); + _squareTanAlpha = (float)(tanAlpha * tanAlpha); + */ +} - Test(CullFunctor& functor, RenderArgs* pargs, RenderDetails::Item& renderDetails, ViewFrustumPointer antiFrustum = nullptr) : - _functor(functor), - _args(pargs), - _renderDetails(renderDetails), - _antiFrustum(antiFrustum) { - // FIXME: Keep this code here even though we don't use it yet - /*_eyePos = _args->getViewFrustum().getPosition(); - float a = glm::degrees(Octree::getPerspectiveAccuracyAngle(_args->_sizeScale, _args->_boundaryLevelAdjust)); - auto angle = std::min(glm::radians(45.0f), a); // no worse than 45 degrees - angle = std::max(glm::radians(1.0f / 60.0f), a); // no better than 1 minute of degree - auto tanAlpha = tan(angle); - _squareTanAlpha = (float)(tanAlpha * tanAlpha); - */ +bool CullTest::frustumTest(const AABox& bound) { + if (!_args->getViewFrustum().boxIntersectsFrustum(bound)) { + _renderDetails._outOfView++; + return false; } + return true; +} - bool frustumTest(const AABox& bound) { - if (!_args->getViewFrustum().boxIntersectsFrustum(bound)) { - _renderDetails._outOfView++; - return false; - } - return true; +bool CullTest::antiFrustumTest(const AABox& bound) { + assert(_antiFrustum); + if (_antiFrustum->boxInsideFrustum(bound)) { + _renderDetails._outOfView++; + return false; } + return true; +} - bool antiFrustumTest(const AABox& bound) { - assert(_antiFrustum); - if (_antiFrustum->boxInsideFrustum(bound)) { - _renderDetails._outOfView++; - return false; - } - return true; +bool CullTest::solidAngleTest(const AABox& bound) { + // FIXME: Keep this code here even though we don't use it yet + //auto eyeToPoint = bound.calcCenter() - _eyePos; + //auto boundSize = bound.getDimensions(); + //float test = (glm::dot(boundSize, boundSize) / glm::dot(eyeToPoint, eyeToPoint)) - squareTanAlpha; + //if (test < 0.0f) { + if (!_functor(_args, bound)) { + _renderDetails._tooSmall++; + return false; } - - bool solidAngleTest(const AABox& bound) { - // FIXME: Keep this code here even though we don't use it yet - //auto eyeToPoint = bound.calcCenter() - _eyePos; - //auto boundSize = bound.getDimensions(); - //float test = (glm::dot(boundSize, boundSize) / glm::dot(eyeToPoint, eyeToPoint)) - squareTanAlpha; - //if (test < 0.0f) { - if (!_functor(_args, bound)) { - _renderDetails._tooSmall++; - return false; - } - return true; - } -}; + return true; +} void render::cullItems(const RenderContextPointer& renderContext, const CullFunctor& cullFunctor, RenderDetails::Item& details, const ItemBounds& inItems, ItemBounds& outItems) { @@ -165,7 +155,7 @@ void FetchSpatialTree::run(const RenderContextPointer& renderContext, const Inpu // Octree selection! float threshold = 0.0f; if (queryFrustum.isPerspective()) { - threshold = getPerspectiveAccuracyAngle(args->_sizeScale, args->_boundaryLevelAdjust); + threshold = args->_lodAngleHalfTan; if (frustumResolution.y > 0) { threshold = glm::max(queryFrustum.getFieldOfView() / frustumResolution.y, threshold); } @@ -205,7 +195,7 @@ void CullSpatialSelection::run(const RenderContextPointer& renderContext, args->pushViewFrustum(_frozenFrustum); // replace the true view frustum by the frozen one } - Test test(_cullFunctor, args, details); + CullTest test(_cullFunctor, args, details); // Now we have a selection of items to render outItems.clear(); @@ -382,7 +372,7 @@ void CullShapeBounds::run(const RenderContextPointer& renderContext, const Input if (!cullFilter.selectsNothing() || !boundsFilter.selectsNothing()) { auto& details = args->_details.edit(_detailType); - Test test(_cullFunctor, args, details, antiFrustum); + CullTest test(_cullFunctor, args, details, antiFrustum); auto scene = args->_scene; for (auto& inItems : inShapes) { @@ -455,7 +445,7 @@ void ApplyCullFunctorOnItemBounds::run(const RenderContextPointer& renderContext } } -void FetchSpatialSelection::run(const RenderContextPointer& renderContext, +void FilterSpatialSelection::run(const RenderContextPointer& renderContext, const Inputs& inputs, ItemBounds& outItems) { assert(renderContext->args); auto& scene = renderContext->_scene; diff --git a/libraries/render/src/render/CullTask.h b/libraries/render/src/render/CullTask.h index 47abe8a960..99ca7abe6c 100644 --- a/libraries/render/src/render/CullTask.h +++ b/libraries/render/src/render/CullTask.h @@ -22,6 +22,22 @@ namespace render { void cullItems(const RenderContextPointer& renderContext, const CullFunctor& cullFunctor, RenderDetails::Item& details, const ItemBounds& inItems, ItemBounds& outItems); + // Culling Frustum / solidAngle test helper class + struct CullTest { + CullFunctor _functor; + RenderArgs* _args; + RenderDetails::Item& _renderDetails; + ViewFrustumPointer _antiFrustum; + glm::vec3 _eyePos; + float _squareTanAlpha; + + CullTest(CullFunctor& functor, RenderArgs* pargs, RenderDetails::Item& renderDetails, ViewFrustumPointer antiFrustum = nullptr); + + bool frustumTest(const AABox& bound); + bool antiFrustumTest(const AABox& bound); + bool solidAngleTest(const AABox& bound); + }; + class FetchNonspatialItems { public: using JobModel = Job::ModelIO; @@ -131,12 +147,12 @@ namespace render { }; - class FetchSpatialSelection { + class FilterSpatialSelection { public: using Inputs = render::VaryingSet2; - using JobModel = Job::ModelIO; + using JobModel = Job::ModelIO; - FetchSpatialSelection() {} + FilterSpatialSelection() {} void run(const RenderContextPointer& renderContext, const Inputs& inputs, ItemBounds& outItems); }; diff --git a/libraries/render/src/render/DrawSceneOctree.cpp b/libraries/render/src/render/DrawSceneOctree.cpp index 433851eec2..2cb8a5d8ef 100644 --- a/libraries/render/src/render/DrawSceneOctree.cpp +++ b/libraries/render/src/render/DrawSceneOctree.cpp @@ -18,31 +18,15 @@ #include #include -#include +#include #include "Args.h" -#include "drawCellBounds_vert.h" -#include "drawCellBounds_frag.h" -#include "drawLODReticle_frag.h" - -#include "drawItemBounds_vert.h" -#include "drawItemBounds_frag.h" - using namespace render; - const gpu::PipelinePointer DrawSceneOctree::getDrawCellBoundsPipeline() { if (!_drawCellBoundsPipeline) { - auto vs = drawCellBounds_vert::getShader(); - auto ps = drawCellBounds_frag::getShader(); - gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); - - gpu::Shader::BindingSet slotBindings; - gpu::Shader::makeProgram(*program, slotBindings); - - _drawCellLocationLoc = program->getUniforms().findLocation("inCellLocation"); - + gpu::ShaderPointer program = gpu::Shader::createProgram(shader::render::program::drawCellBounds); auto state = std::make_shared(); state->setDepthTest(true, false, gpu::LESS_EQUAL); @@ -58,15 +42,7 @@ const gpu::PipelinePointer DrawSceneOctree::getDrawCellBoundsPipeline() { const gpu::PipelinePointer DrawSceneOctree::getDrawLODReticlePipeline() { if (!_drawLODReticlePipeline) { - auto vs = gpu::StandardShaderLib::getDrawTransformUnitQuadVS(); - auto ps = drawLODReticle_frag::getShader(); - gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); - - gpu::Shader::BindingSet slotBindings; - gpu::Shader::makeProgram(*program, slotBindings); - - // _drawCellLocationLoc = program->getUniforms().findLocation("inCellLocation"); - + gpu::ShaderPointer program = gpu::Shader::createProgram(shader::render::program::drawLODReticle); auto state = std::make_shared(); // Blend on transparent @@ -93,7 +69,6 @@ void DrawSceneOctree::run(const RenderContextPointer& renderContext, const ItemS std::static_pointer_cast(renderContext->jobConfig)->numAllocatedCells = (int)scene->getSpatialTree().getNumAllocatedCells(); std::static_pointer_cast(renderContext->jobConfig)->numFreeCells = (int)scene->getSpatialTree().getNumFreeCells(); - gpu::doInBatch("DrawSceneOctree::run", args->_context, [&](gpu::Batch& batch) { glm::mat4 projMat; Transform viewMat; @@ -108,44 +83,30 @@ void DrawSceneOctree::run(const RenderContextPointer& renderContext, const ItemS // bind the one gpu::Pipeline we need batch.setPipeline(getDrawCellBoundsPipeline()); - if (_showVisibleCells) { - - for (const auto& cellID : inSelection.cellSelection.insideCells) { + auto drawCellBounds = [this, &scene, &batch](const std::vector& cells) { + for (const auto& cellID : cells) { auto cell = scene->getSpatialTree().getConcreteCell(cellID); auto cellLoc = cell.getlocation(); glm::ivec4 cellLocation(cellLoc.pos.x, cellLoc.pos.y, cellLoc.pos.z, cellLoc.depth); - bool doDraw = true; - if (cell.isBrickEmpty() || !cell.hasBrick()) { + bool empty = cell.isBrickEmpty() || !cell.hasBrick(); + if (empty) { if (!_showEmptyCells) { - doDraw = false; + continue; } - cellLocation.w *= -1; + cellLocation.w *= -1.0; + } else if (!empty && !_showVisibleCells) { + continue; } - if (doDraw) { - batch._glUniform4iv(_drawCellLocationLoc, 1, ((const int*)(&cellLocation))); - batch.draw(gpu::LINES, 24, 0); - } - } - for (const auto& cellID : inSelection.cellSelection.partialCells) { - auto cell = scene->getSpatialTree().getConcreteCell(cellID); - auto cellLoc = cell.getlocation(); - glm::ivec4 cellLocation(cellLoc.pos.x, cellLoc.pos.y, cellLoc.pos.z, cellLoc.depth); - - bool doDraw = true; - if (cell.isBrickEmpty() || !cell.hasBrick()) { - if (!_showEmptyCells) { - doDraw = false; - } - cellLocation.w *= -1; - } - if (doDraw) { - batch._glUniform4iv(_drawCellLocationLoc, 1, ((const int*)(&cellLocation))); - batch.draw(gpu::LINES, 24, 0); - } + batch._glUniform4iv(gpu::slot::uniform::Extra0, 1, ((const int*)(&cellLocation))); + batch.draw(gpu::LINES, 24, 0); } - } + }; + + drawCellBounds(inSelection.cellSelection.insideCells); + drawCellBounds(inSelection.cellSelection.partialCells); + // Draw the LOD Reticle { float angle = glm::degrees(getPerspectiveAccuracyAngle(args->_sizeScale, args->_boundaryLevelAdjust)); @@ -162,17 +123,7 @@ void DrawSceneOctree::run(const RenderContextPointer& renderContext, const ItemS const gpu::PipelinePointer DrawItemSelection::getDrawItemBoundPipeline() { if (!_drawItemBoundPipeline) { - auto vs = drawItemBounds_vert::getShader(); - auto ps = drawItemBounds_frag::getShader(); - gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); - - gpu::Shader::BindingSet slotBindings; - gpu::Shader::makeProgram(*program, slotBindings); - - _drawItemBoundPosLoc = program->getUniforms().findLocation("inBoundPos"); - _drawItemBoundDimLoc = program->getUniforms().findLocation("inBoundDim"); - - _drawCellLocationLoc = program->getUniforms().findLocation("inCellLocation"); + gpu::ShaderPointer program = gpu::Shader::createProgram(shader::render::program::drawItemBounds); auto state = std::make_shared(); @@ -201,6 +152,19 @@ void DrawItemSelection::run(const RenderContextPointer& renderContext, const Ite RenderArgs* args = renderContext->args; auto& scene = renderContext->_scene; + if (!_boundsBufferInside) { + _boundsBufferInside = std::make_shared(sizeof(render::ItemBound)); + } + if (!_boundsBufferInsideSubcell) { + _boundsBufferInsideSubcell = std::make_shared(sizeof(render::ItemBound)); + } + if (!_boundsBufferPartial) { + _boundsBufferPartial = std::make_shared(sizeof(render::ItemBound)); + } + if (!_boundsBufferPartialSubcell) { + _boundsBufferPartialSubcell = std::make_shared(sizeof(render::ItemBound)); + } + gpu::doInBatch("DrawItemSelection::run", args->_context, [&](gpu::Batch& batch) { glm::mat4 projMat; Transform viewMat; @@ -215,63 +179,35 @@ void DrawItemSelection::run(const RenderContextPointer& renderContext, const Ite // bind the one gpu::Pipeline we need batch.setPipeline(getDrawItemBoundPipeline()); + auto drawItemBounds = [&](const render::ItemIDs itemIDs, const gpu::BufferPointer buffer) { + render::ItemBounds itemBounds; + for (const auto& itemID : itemIDs) { + auto& item = scene->getItem(itemID); + auto itemBound = item.getBound(); + if (!itemBound.isInvalid()) { + itemBounds.emplace_back(itemID, itemBound); + } + } + + if (itemBounds.size() > 0) { + buffer->setData(itemBounds.size() * sizeof(render::ItemBound), (const gpu::Byte*) itemBounds.data()); + batch.setResourceBuffer(0, buffer); + batch.draw(gpu::LINES, (gpu::uint32) itemBounds.size() * 24, 0); + } + }; + if (_showInsideItems) { - for (const auto& itemID : inSelection.insideItems) { - auto& item = scene->getItem(itemID); - auto itemBound = item.getBound(); - auto itemCell = scene->getSpatialTree().getCellLocation(item.getCell()); - glm::ivec4 cellLocation(0, 0, 0, itemCell.depth); - - batch._glUniform4iv(_drawCellLocationLoc, 1, ((const int*)(&cellLocation))); - batch._glUniform3fv(_drawItemBoundPosLoc, 1, (const float*)(&itemBound.getCorner())); - batch._glUniform3fv(_drawItemBoundDimLoc, 1, (const float*)(&itemBound.getScale())); - - batch.draw(gpu::LINES, 24, 0); - } + drawItemBounds(inSelection.insideItems, _boundsBufferInside); } - if (_showInsideSubcellItems) { - for (const auto& itemID : inSelection.insideSubcellItems) { - auto& item = scene->getItem(itemID); - auto itemBound = item.getBound(); - auto itemCell = scene->getSpatialTree().getCellLocation(item.getCell()); - glm::ivec4 cellLocation(0, 0, 1, itemCell.depth); - - batch._glUniform4iv(_drawCellLocationLoc, 1, ((const int*)(&cellLocation))); - batch._glUniform3fv(_drawItemBoundPosLoc, 1, (const float*)(&itemBound.getCorner())); - batch._glUniform3fv(_drawItemBoundDimLoc, 1, (const float*)(&itemBound.getScale())); - - batch.draw(gpu::LINES, 24, 0); - } + drawItemBounds(inSelection.insideSubcellItems, _boundsBufferInsideSubcell); } - if (_showPartialItems) { - for (const auto& itemID : inSelection.partialItems) { - auto& item = scene->getItem(itemID); - auto itemBound = item.getBound(); - auto itemCell = scene->getSpatialTree().getCellLocation(item.getCell()); - glm::ivec4 cellLocation(0, 0, 0, itemCell.depth); - - batch._glUniform4iv(_drawCellLocationLoc, 1, ((const int*)(&cellLocation))); - batch._glUniform3fv(_drawItemBoundPosLoc, 1, (const float*)(&itemBound.getCorner())); - batch._glUniform3fv(_drawItemBoundDimLoc, 1, (const float*)(&itemBound.getScale())); - - batch.draw(gpu::LINES, 24, 0); - } + drawItemBounds(inSelection.partialItems, _boundsBufferPartial); } - if (_showPartialSubcellItems) { - for (const auto& itemID : inSelection.partialSubcellItems) { - auto& item = scene->getItem(itemID); - auto itemBound = item.getBound(); - auto itemCell = scene->getSpatialTree().getCellLocation(item.getCell()); - glm::ivec4 cellLocation(0, 0, 1, itemCell.depth); - batch._glUniform4iv(_drawCellLocationLoc, 1, ((const int*)(&cellLocation))); - batch._glUniform3fv(_drawItemBoundPosLoc, 1, (const float*)(&itemBound.getCorner())); - batch._glUniform3fv(_drawItemBoundDimLoc, 1, (const float*)(&itemBound.getScale())); - - batch.draw(gpu::LINES, 24, 0); - } + drawItemBounds(inSelection.partialSubcellItems, _boundsBufferPartialSubcell); } + batch.setResourceBuffer(0, 0); }); } diff --git a/libraries/render/src/render/DrawSceneOctree.h b/libraries/render/src/render/DrawSceneOctree.h index 9c416262ca..0b2cd6f685 100644 --- a/libraries/render/src/render/DrawSceneOctree.h +++ b/libraries/render/src/render/DrawSceneOctree.h @@ -51,15 +51,8 @@ namespace render { }; class DrawSceneOctree { - - int _drawCellLocationLoc; gpu::PipelinePointer _drawCellBoundsPipeline; - gpu::BufferPointer _cells; - gpu::PipelinePointer _drawLODReticlePipeline; - - int _drawItemBoundPosLoc = -1; - int _drawItemBoundDimLoc = -1; gpu::PipelinePointer _drawItemBoundPipeline; bool _showVisibleCells; // initialized by Config @@ -112,11 +105,11 @@ namespace render { }; class DrawItemSelection { - - int _drawItemBoundPosLoc = -1; - int _drawItemBoundDimLoc = -1; - int _drawCellLocationLoc = -1; gpu::PipelinePointer _drawItemBoundPipeline; + gpu::BufferPointer _boundsBufferInside; + gpu::BufferPointer _boundsBufferInsideSubcell; + gpu::BufferPointer _boundsBufferPartial; + gpu::BufferPointer _boundsBufferPartialSubcell; bool _showInsideItems; // initialized by Config bool _showInsideSubcellItems; // initialized by Config diff --git a/libraries/render/src/render/DrawStatus.cpp b/libraries/render/src/render/DrawStatus.cpp index 496b2000a9..9b7d4ace2b 100644 --- a/libraries/render/src/render/DrawStatus.cpp +++ b/libraries/render/src/render/DrawStatus.cpp @@ -19,12 +19,9 @@ #include -#include "Args.h" +#include -#include "drawItemBounds_vert.h" -#include "drawItemBounds_frag.h" -#include "drawItemStatus_vert.h" -#include "drawItemStatus_frag.h" +#include "Args.h" using namespace render; @@ -35,16 +32,7 @@ void DrawStatusConfig::dirtyHelper() { const gpu::PipelinePointer DrawStatus::getDrawItemBoundsPipeline() { if (!_drawItemBoundsPipeline) { - auto vs = drawItemBounds_vert::getShader(); - auto ps = drawItemBounds_frag::getShader(); - gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); - - gpu::Shader::BindingSet slotBindings; - gpu::Shader::makeProgram(*program, slotBindings); - - _drawItemBoundPosLoc = program->getUniforms().findLocation("inBoundPos"); - _drawItemBoundDimLoc = program->getUniforms().findLocation("inBoundDim"); - _drawItemCellLocLoc = program->getUniforms().findLocation("inCellLocation"); + gpu::ShaderPointer program = gpu::Shader::createProgram(shader::render::program::drawItemBounds); auto state = std::make_shared(); @@ -63,18 +51,7 @@ const gpu::PipelinePointer DrawStatus::getDrawItemBoundsPipeline() { const gpu::PipelinePointer DrawStatus::getDrawItemStatusPipeline() { if (!_drawItemStatusPipeline) { - auto vs = drawItemStatus_vert::getShader(); - auto ps = drawItemStatus_frag::getShader(); - gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); - - gpu::Shader::BindingSet slotBindings; - slotBindings.insert(gpu::Shader::Binding(std::string("iconStatusMap"), 0)); - gpu::Shader::makeProgram(*program, slotBindings); - - _drawItemStatusPosLoc = program->getUniforms().findLocation("inBoundPos"); - _drawItemStatusDimLoc = program->getUniforms().findLocation("inBoundDim"); - _drawItemStatusValue0Loc = program->getUniforms().findLocation("inStatus0"); - _drawItemStatusValue1Loc = program->getUniforms().findLocation("inStatus1"); + gpu::ShaderPointer program = gpu::Shader::createProgram(shader::render::program::drawItemStatus); auto state = std::make_shared(); @@ -115,36 +92,30 @@ void DrawStatus::run(const RenderContextPointer& renderContext, const Input& inp const auto& inItems = input.get0(); const auto jitter = input.get1(); - // FIrst thing, we collect the bound and the status for all the items we want to render + // First thing, we collect the bound and the status for all the items we want to render int nbItems = 0; + render::ItemBounds itemBounds; + std::vector> itemStatus; { - _itemBounds.resize(inItems.size()); - _itemStatus.resize(inItems.size()); - _itemCells.resize(inItems.size()); - -// AABox* itemAABox = reinterpret_cast (_itemBounds->editData()); -// glm::ivec4* itemStatus = reinterpret_cast (_itemStatus->editData()); -// Octree::Location* itemCell = reinterpret_cast (_itemCells->editData()); for (size_t i = 0; i < inItems.size(); ++i) { const auto& item = inItems[i]; if (!item.bound.isInvalid()) { if (!item.bound.isNull()) { - _itemBounds[i] = item.bound; + itemBounds.emplace_back(render::ItemBound(item.id, item.bound)); } else { - _itemBounds[i].setBox(item.bound.getCorner(), 0.1f); + itemBounds.emplace_back(item.id, AABox(item.bound.getCorner(), 0.1f)); } - auto& itemScene = scene->getItem(item.id); - _itemCells[i] = scene->getSpatialTree().getCellLocation(itemScene.getCell()); auto itemStatusPointer = itemScene.getStatus(); if (itemStatusPointer) { + itemStatus.push_back(std::pair()); // Query the current status values, this is where the statusGetter lambda get called auto&& currentStatusValues = itemStatusPointer->getCurrentValues(); int valueNum = 0; for (int vec4Num = 0; vec4Num < NUM_STATUS_VEC4_PER_ITEM; vec4Num++) { - auto& value = (vec4Num ? _itemStatus[i].first : _itemStatus[i].second); + auto& value = (vec4Num ? itemStatus[nbItems].first : itemStatus[nbItems].second); value = glm::ivec4(Item::Status::Value::INVALID.getPackedData()); for (int component = 0; component < VEC4_LENGTH; component++) { valueNum = vec4Num * VEC4_LENGTH + component; @@ -154,7 +125,8 @@ void DrawStatus::run(const RenderContextPointer& renderContext, const Input& inp } } } else { - _itemStatus[i].first = _itemStatus[i].second = glm::ivec4(Item::Status::Value::INVALID.getPackedData()); + auto invalid = glm::ivec4(Item::Status::Value::INVALID.getPackedData()); + itemStatus.emplace_back(invalid, invalid); } nbItems++; } @@ -165,7 +137,11 @@ void DrawStatus::run(const RenderContextPointer& renderContext, const Input& inp return; } - // Allright, something to render let's do it + if (!_boundsBuffer) { + _boundsBuffer = std::make_shared(sizeof(render::ItemBound)); + } + + // Alright, something to render let's do it gpu::doInBatch("DrawStatus::run", args->_context, [&](gpu::Batch& batch) { glm::mat4 projMat; Transform viewMat; @@ -181,33 +157,24 @@ void DrawStatus::run(const RenderContextPointer& renderContext, const Input& inp // bind the one gpu::Pipeline we need batch.setPipeline(getDrawItemBoundsPipeline()); - //AABox* itemAABox = reinterpret_cast (_itemBounds->editData()); - //glm::ivec4* itemStatus = reinterpret_cast (_itemStatus->editData()); - //Octree::Location* itemCell = reinterpret_cast (_itemCells->editData()); - - const unsigned int VEC3_ADRESS_OFFSET = 3; + _boundsBuffer->setData(itemBounds.size() * sizeof(render::ItemBound), (const gpu::Byte*) itemBounds.data()); if (_showDisplay) { - for (int i = 0; i < nbItems; i++) { - batch._glUniform3fv(_drawItemBoundPosLoc, 1, (const float*)&(_itemBounds[i])); - batch._glUniform3fv(_drawItemBoundDimLoc, 1, ((const float*)&(_itemBounds[i])) + VEC3_ADRESS_OFFSET); - - glm::ivec4 cellLocation(_itemCells[i].pos, _itemCells[i].depth); - batch._glUniform4iv(_drawItemCellLocLoc, 1, ((const int*)(&cellLocation))); - batch.draw(gpu::LINES, 24, 0); - } + batch.setResourceBuffer(0, _boundsBuffer); + batch.draw(gpu::LINES, (gpu::uint32) itemBounds.size() * 24, 0); } + batch.setResourceBuffer(0, 0); batch.setResourceTexture(0, gpu::TextureView(getStatusIconMap(), 0)); batch.setPipeline(getDrawItemStatusPipeline()); if (_showNetwork) { - for (int i = 0; i < nbItems; i++) { - batch._glUniform3fv(_drawItemStatusPosLoc, 1, (const float*)&(_itemBounds[i])); - batch._glUniform3fv(_drawItemStatusDimLoc, 1, ((const float*)&(_itemBounds[i])) + VEC3_ADRESS_OFFSET); - batch._glUniform4iv(_drawItemStatusValue0Loc, 1, (const int*)&(_itemStatus[i].first)); - batch._glUniform4iv(_drawItemStatusValue1Loc, 1, (const int*)&(_itemStatus[i].second)); + for (size_t i = 0; i < itemBounds.size(); i++) { + batch._glUniform3fv(gpu::slot::uniform::Extra0, 1, (const float*)&itemBounds[i].bound.getCorner()); + batch._glUniform3fv(gpu::slot::uniform::Extra1, 1, ((const float*)&itemBounds[i].bound.getScale())); + batch._glUniform4iv(gpu::slot::uniform::Extra2, 1, (const int*)&(itemStatus[i].first)); + batch._glUniform4iv(gpu::slot::uniform::Extra3, 1, (const int*)&(itemStatus[i].second)); batch.draw(gpu::TRIANGLES, 24 * NUM_STATUS_VEC4_PER_ITEM, 0); } } diff --git a/libraries/render/src/render/DrawStatus.h b/libraries/render/src/render/DrawStatus.h index 9e05471cd6..96269fda4d 100644 --- a/libraries/render/src/render/DrawStatus.h +++ b/libraries/render/src/render/DrawStatus.h @@ -58,24 +58,11 @@ namespace render { bool _showDisplay; // initialized by Config bool _showNetwork; // initialized by Config - int _drawItemBoundPosLoc = -1; - int _drawItemBoundDimLoc = -1; - int _drawItemCellLocLoc = -1; - int _drawItemStatusPosLoc = -1; - int _drawItemStatusDimLoc = -1; - int _drawItemStatusValue0Loc = -1; - int _drawItemStatusValue1Loc = -1; - gpu::Stream::FormatPointer _drawItemFormat; gpu::PipelinePointer _drawItemBoundsPipeline; gpu::PipelinePointer _drawItemStatusPipeline; - std::vector _itemBounds; - std::vector> _itemStatus; - std::vector _itemCells; - //gpu::BufferPointer _itemBounds; - //gpu::BufferPointer _itemCells; - //gpu::BufferPointer _itemStatus; + gpu::BufferPointer _boundsBuffer; gpu::TexturePointer _statusIconMap; }; } diff --git a/libraries/render/src/render/DrawTask.cpp b/libraries/render/src/render/DrawTask.cpp index 3a7555f790..8fb800291e 100755 --- a/libraries/render/src/render/DrawTask.cpp +++ b/libraries/render/src/render/DrawTask.cpp @@ -18,13 +18,11 @@ #include #include #include -#include +#include +#include #include "Logging.h" -#include "drawItemBounds_vert.h" -#include "drawItemBounds_frag.h" - using namespace render; void render::renderItems(const RenderContextPointer& renderContext, const ItemBounds& inItems, int maxDrawnItems) { @@ -160,15 +158,7 @@ void DrawLight::run(const RenderContextPointer& renderContext, const ItemBounds& const gpu::PipelinePointer DrawBounds::getPipeline() { if (!_boundsPipeline) { - auto vs = drawItemBounds_vert::getShader(); - auto ps = drawItemBounds_frag::getShader(); - gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); - - gpu::Shader::BindingSet slotBindings; - gpu::Shader::makeProgram(*program, slotBindings); - - _colorLocation = program->getUniforms().findLocation("inColor"); - + gpu::ShaderPointer program = gpu::Shader::createProgram(shader::render::program::drawItemBounds); auto state = std::make_shared(); state->setDepthTest(true, false, gpu::LESS_EQUAL); state->setBlendFunction(true, @@ -212,7 +202,7 @@ void DrawBounds::run(const RenderContextPointer& renderContext, batch.setPipeline(getPipeline()); glm::vec4 color(glm::vec3(0.0f), -(float) numItems); - batch._glUniform4fv(_colorLocation, 1, (const float*)(&color)); + batch._glUniform4fv(gpu::slot::uniform::Color, 1, (const float*)(&color)); batch.setResourceBuffer(0, _drawBuffer); static const int NUM_VERTICES_PER_CUBE = 24; @@ -220,10 +210,15 @@ void DrawBounds::run(const RenderContextPointer& renderContext, }); } +gpu::Stream::FormatPointer DrawQuadVolume::_format; + DrawQuadVolume::DrawQuadVolume(const glm::vec3& color) : _color{ color } { _meshVertices = gpu::BufferView(std::make_shared(sizeof(glm::vec3) * 8, nullptr), gpu::Element::VEC3F_XYZ); - _meshStream.addBuffer(_meshVertices._buffer, _meshVertices._offset, _meshVertices._stride); + if (!_format) { + _format = std::make_shared(); + _format->setAttribute(gpu::Stream::POSITION, gpu::Stream::POSITION, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ), 0); + } } void DrawQuadVolume::configure(const Config& configuration) { @@ -253,10 +248,11 @@ void DrawQuadVolume::run(const render::RenderContextPointer& renderContext, cons batch.setProjectionTransform(projMat); batch.setViewTransform(viewMat); batch.setPipeline(getPipeline()); - batch.setIndexBuffer(indices); batch._glUniform4f(0, _color.x, _color.y, _color.z, 1.0f); - batch.setInputStream(0, _meshStream); + batch.setInputFormat(_format); + batch.setInputBuffer(gpu::Stream::POSITION, _meshVertices); + batch.setIndexBuffer(indices); batch.drawIndexed(gpu::LINES, indexCount, 0U); args->_batch = nullptr; @@ -267,14 +263,7 @@ gpu::PipelinePointer DrawQuadVolume::getPipeline() { static gpu::PipelinePointer pipeline; if (!pipeline) { - auto vs = gpu::StandardShaderLib::getDrawTransformVertexPositionVS(); - auto ps = gpu::StandardShaderLib::getDrawColorPS(); - gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); - - gpu::Shader::BindingSet slotBindings; - slotBindings.insert(gpu::Shader::Binding("color", 0)); - gpu::Shader::makeProgram(*program, slotBindings); - + gpu::ShaderPointer program = gpu::Shader::createProgram(shader::gpu::program::drawColor); gpu::StatePointer state = gpu::StatePointer(new gpu::State()); state->setDepthTest(gpu::State::DepthTest(true, false)); pipeline = gpu::Pipeline::create(program, state); diff --git a/libraries/render/src/render/DrawTask.h b/libraries/render/src/render/DrawTask.h index 6f98e3bef1..6b98fb2977 100755 --- a/libraries/render/src/render/DrawTask.h +++ b/libraries/render/src/render/DrawTask.h @@ -66,8 +66,6 @@ private: const gpu::PipelinePointer getPipeline(); gpu::PipelinePointer _boundsPipeline; gpu::BufferPointer _drawBuffer; - - int _colorLocation { -1 }; }; class DrawQuadVolumeConfig : public render::JobConfig { @@ -97,10 +95,11 @@ protected: const gpu::BufferView& indices, int indexCount); gpu::BufferView _meshVertices; - gpu::BufferStream _meshStream; glm::vec3 _color; bool _isUpdateEnabled{ true }; + static gpu::Stream::FormatPointer _format; + static gpu::PipelinePointer getPipeline(); }; diff --git a/libraries/render/src/render/Item.h b/libraries/render/src/render/Item.h index 28994d82b6..5ecfba2da8 100644 --- a/libraries/render/src/render/Item.h +++ b/libraries/render/src/render/Item.h @@ -321,6 +321,7 @@ inline QDebug operator<<(QDebug debug, const ItemFilter& me) { // Handy type to just pass the ID and the bound of an item class ItemBound { public: + ItemBound() {} ItemBound(ItemID id) : id(id) { } ItemBound(ItemID id, const AABox& bound) : id(id), bound(bound) { } diff --git a/libraries/render/src/render/ResampleTask.cpp b/libraries/render/src/render/ResampleTask.cpp index 008234b437..ed4d0ddfd0 100644 --- a/libraries/render/src/render/ResampleTask.cpp +++ b/libraries/render/src/render/ResampleTask.cpp @@ -12,8 +12,8 @@ // #include "ResampleTask.h" -#include "gpu/Context.h" -#include "gpu/StandardShaderLib.h" +#include +#include using namespace render; @@ -51,13 +51,7 @@ void HalfDownsample::run(const RenderContextPointer& renderContext, const gpu::F resampledFrameBuffer = getResampledFrameBuffer(sourceFramebuffer); if (!_pipeline) { - auto vs = gpu::StandardShaderLib::getDrawTransformUnitQuadVS(); - auto ps = gpu::StandardShaderLib::getDrawTextureOpaquePS(); - gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); - - gpu::Shader::BindingSet slotBindings; - gpu::Shader::makeProgram(*program, slotBindings); - + gpu::ShaderPointer program = gpu::Shader::createProgram(shader::gpu::program::drawTransformUnitQuadTextureOpaque); gpu::StatePointer state = gpu::StatePointer(new gpu::State()); state->setDepthTest(gpu::State::DepthTest(false, false)); _pipeline = gpu::Pipeline::create(program, state); @@ -113,13 +107,7 @@ void Upsample::run(const RenderContextPointer& renderContext, const gpu::Framebu resampledFrameBuffer = getResampledFrameBuffer(sourceFramebuffer); if (resampledFrameBuffer != sourceFramebuffer) { if (!_pipeline) { - auto vs = gpu::StandardShaderLib::getDrawTransformUnitQuadVS(); - auto ps = gpu::StandardShaderLib::getDrawTextureOpaquePS(); - gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); - - gpu::Shader::BindingSet slotBindings; - gpu::Shader::makeProgram(*program, slotBindings); - + gpu::ShaderPointer program = gpu::Shader::createProgram(shader::gpu::program::drawTransformUnitQuadTextureOpaque); gpu::StatePointer state = gpu::StatePointer(new gpu::State()); state->setDepthTest(gpu::State::DepthTest(false, false)); _pipeline = gpu::Pipeline::create(program, state); diff --git a/libraries/render/src/render/ShapePipeline.cpp b/libraries/render/src/render/ShapePipeline.cpp index 8cd04f8067..2b2accde37 100644 --- a/libraries/render/src/render/ShapePipeline.cpp +++ b/libraries/render/src/render/ShapePipeline.cpp @@ -12,12 +12,26 @@ #include "ShapePipeline.h" #include +#include +#include +#include +#include <../render-utils/src/render-utils/ShaderConstants.h> -#include "DependencyManager.h" #include "Logging.h" + using namespace render; +namespace ru { + using render_utils::slot::texture::Texture; + using render_utils::slot::buffer::Buffer; +} + +namespace gr { + using graphics::slot::texture::Texture; + using graphics::slot::buffer::Buffer; +} + ShapePipeline::CustomFactoryMap ShapePipeline::_globalCustomFactoryMap; ShapePipeline::CustomKey ShapePipeline::registerCustomShapePipelineFactory(CustomFactory factory) { @@ -71,70 +85,30 @@ void ShapePlumber::addPipeline(const Key& key, const gpu::ShaderPointer& program void ShapePlumber::addPipeline(const Filter& filter, const gpu::ShaderPointer& program, const gpu::StatePointer& state, BatchSetter batchSetter, ItemSetter itemSetter) { - ShapeKey key{ filter._flags }; - - - // don't call makeProgram on shaders that are already made. - if (program->getNumCompilationAttempts() < 1) { - gpu::Shader::BindingSet slotBindings; - slotBindings.insert(gpu::Shader::Binding(std::string("lightingModelBuffer"), Slot::BUFFER::LIGHTING_MODEL)); - slotBindings.insert(gpu::Shader::Binding(std::string("skinClusterBuffer"), Slot::BUFFER::SKINNING)); - slotBindings.insert(gpu::Shader::Binding(std::string("materialBuffer"), Slot::BUFFER::MATERIAL)); - slotBindings.insert(gpu::Shader::Binding(std::string("texMapArrayBuffer"), Slot::BUFFER::TEXMAPARRAY)); - slotBindings.insert(gpu::Shader::Binding(std::string("albedoMap"), Slot::MAP::ALBEDO)); - slotBindings.insert(gpu::Shader::Binding(std::string("roughnessMap"), Slot::MAP::ROUGHNESS)); - slotBindings.insert(gpu::Shader::Binding(std::string("normalMap"), Slot::MAP::NORMAL)); - slotBindings.insert(gpu::Shader::Binding(std::string("metallicMap"), Slot::MAP::METALLIC)); - slotBindings.insert(gpu::Shader::Binding(std::string("emissiveMap"), Slot::MAP::EMISSIVE_LIGHTMAP)); - slotBindings.insert(gpu::Shader::Binding(std::string("occlusionMap"), Slot::MAP::OCCLUSION)); - slotBindings.insert(gpu::Shader::Binding(std::string("scatteringMap"), Slot::MAP::SCATTERING)); - slotBindings.insert(gpu::Shader::Binding(std::string("keyLightBuffer"), Slot::BUFFER::KEY_LIGHT)); - slotBindings.insert(gpu::Shader::Binding(std::string("lightBuffer"), Slot::BUFFER::LIGHT_ARRAY_BUFFER)); - slotBindings.insert(gpu::Shader::Binding(std::string("lightAmbientBuffer"), Slot::BUFFER::LIGHT_AMBIENT_BUFFER)); - slotBindings.insert(gpu::Shader::Binding(std::string("skyboxMap"), Slot::MAP::LIGHT_AMBIENT_MAP)); - slotBindings.insert(gpu::Shader::Binding(std::string("fadeMaskMap"), Slot::MAP::FADE_MASK)); - slotBindings.insert(gpu::Shader::Binding(std::string("fadeParametersBuffer"), Slot::BUFFER::FADE_PARAMETERS)); - slotBindings.insert(gpu::Shader::Binding(std::string("fadeObjectParametersBuffer"), Slot::BUFFER::FADE_OBJECT_PARAMETERS)); - slotBindings.insert(gpu::Shader::Binding(std::string("hazeBuffer"), Slot::BUFFER::HAZE_MODEL)); - - if (key.isTranslucent()) { - slotBindings.insert(gpu::Shader::Binding(std::string("clusterGridBuffer"), Slot::BUFFER::LIGHT_CLUSTER_GRID_CLUSTER_GRID_SLOT)); - slotBindings.insert(gpu::Shader::Binding(std::string("clusterContentBuffer"), Slot::BUFFER::LIGHT_CLUSTER_GRID_CLUSTER_CONTENT_SLOT)); - slotBindings.insert(gpu::Shader::Binding(std::string("frustumGridBuffer"), Slot::BUFFER::LIGHT_CLUSTER_GRID_FRUSTUM_GRID_SLOT)); - } - - gpu::Shader::makeProgram(*program, slotBindings); - } - auto locations = std::make_shared(); - - locations->albedoTextureUnit = program->getTextures().findLocation("albedoMap"); - locations->roughnessTextureUnit = program->getTextures().findLocation("roughnessMap"); - locations->normalTextureUnit = program->getTextures().findLocation("normalMap"); - locations->metallicTextureUnit = program->getTextures().findLocation("metallicMap"); - locations->emissiveTextureUnit = program->getTextures().findLocation("emissiveMap"); - locations->occlusionTextureUnit = program->getTextures().findLocation("occlusionMap"); - locations->lightingModelBufferUnit = program->getUniformBuffers().findLocation("lightingModelBuffer"); - locations->skinClusterBufferUnit = program->getUniformBuffers().findLocation("skinClusterBuffer"); - locations->materialBufferUnit = program->getUniformBuffers().findLocation("materialBuffer"); - locations->texMapArrayBufferUnit = program->getUniformBuffers().findLocation("texMapArrayBuffer"); - locations->keyLightBufferUnit = program->getUniformBuffers().findLocation("keyLightBuffer"); - locations->lightBufferUnit = program->getUniformBuffers().findLocation("lightBuffer"); - locations->lightAmbientBufferUnit = program->getUniformBuffers().findLocation("lightAmbientBuffer"); - locations->lightAmbientMapUnit = program->getTextures().findLocation("skyboxMap"); - locations->fadeMaskTextureUnit = program->getTextures().findLocation("fadeMaskMap"); - locations->fadeParameterBufferUnit = program->getUniformBuffers().findLocation("fadeParametersBuffer"); - locations->fadeObjectParameterBufferUnit = program->getUniformBuffers().findLocation("fadeObjectParametersBuffer"); - locations->hazeParameterBufferUnit = program->getUniformBuffers().findLocation("hazeBuffer"); + locations->albedoTextureUnit = program->getTextures().isValid(graphics::slot::texture::MaterialAlbedo); + locations->roughnessTextureUnit = program->getTextures().isValid(graphics::slot::texture::MaterialRoughness); + locations->normalTextureUnit = program->getTextures().isValid(graphics::slot::texture::MaterialNormal); + locations->metallicTextureUnit = program->getTextures().isValid(graphics::slot::texture::MaterialMetallic); + locations->emissiveTextureUnit = program->getTextures().isValid(graphics::slot::texture::MaterialEmissiveLightmap); + locations->occlusionTextureUnit = program->getTextures().isValid(graphics::slot::texture::MaterialOcclusion); + locations->lightingModelBufferUnit = program->getUniformBuffers().isValid(render_utils::slot::buffer::LightModel); + locations->skinClusterBufferUnit = program->getUniformBuffers().isValid(graphics::slot::buffer::Skinning); + locations->materialBufferUnit = program->getUniformBuffers().isValid(graphics::slot::buffer::Material); + locations->texMapArrayBufferUnit = program->getUniformBuffers().isValid(graphics::slot::buffer::TexMapArray); + locations->keyLightBufferUnit = program->getUniformBuffers().isValid(graphics::slot::buffer::KeyLight); + locations->lightBufferUnit = program->getUniformBuffers().isValid(graphics::slot::buffer::Light); + locations->lightAmbientBufferUnit = program->getUniformBuffers().isValid(graphics::slot::buffer::AmbientLight); + locations->lightAmbientMapUnit = program->getTextures().isValid(graphics::slot::texture::Skybox); + locations->fadeMaskTextureUnit = program->getTextures().isValid(render_utils::slot::texture::FadeMask); + locations->fadeParameterBufferUnit = program->getUniformBuffers().isValid(render_utils::slot::buffer::FadeParameters); + locations->fadeObjectParameterBufferUnit = program->getUniformBuffers().isValid(render_utils::slot::buffer::FadeObjectParameters); + locations->hazeParameterBufferUnit = program->getUniformBuffers().isValid(render_utils::slot::buffer::HazeParams); if (key.isTranslucent()) { - locations->lightClusterGridBufferUnit = program->getUniformBuffers().findLocation("clusterGridBuffer"); - locations->lightClusterContentBufferUnit = program->getUniformBuffers().findLocation("clusterContentBuffer"); - locations->lightClusterFrustumBufferUnit = program->getUniformBuffers().findLocation("frustumGridBuffer"); - } else { - locations->lightClusterGridBufferUnit = -1; - locations->lightClusterContentBufferUnit = -1; - locations->lightClusterFrustumBufferUnit = -1; + locations->lightClusterGridBufferUnit = program->getUniformBuffers().isValid(render_utils::slot::buffer::LightClusterGrid); + locations->lightClusterContentBufferUnit = program->getUniformBuffers().isValid(render_utils::slot::buffer::LightClusterContent); + locations->lightClusterFrustumBufferUnit = program->getUniformBuffers().isValid(render_utils::slot::buffer::LightClusterFrustumGrid); } { diff --git a/libraries/render/src/render/ShapePipeline.h b/libraries/render/src/render/ShapePipeline.h index 10f1b757cc..bac1076dd6 100644 --- a/libraries/render/src/render/ShapePipeline.h +++ b/libraries/render/src/render/ShapePipeline.h @@ -228,62 +228,29 @@ inline QDebug operator<<(QDebug debug, const ShapeKey& key) { // Meta-information (pipeline and locations) to render a shape class ShapePipeline { public: - class Slot { - public: - enum BUFFER { - SKINNING = 0, - MATERIAL, - TEXMAPARRAY, - LIGHTING_MODEL, - KEY_LIGHT, - LIGHT_ARRAY_BUFFER, - LIGHT_AMBIENT_BUFFER, - HAZE_MODEL, - FADE_PARAMETERS, - FADE_OBJECT_PARAMETERS, - LIGHT_CLUSTER_GRID_FRUSTUM_GRID_SLOT, - LIGHT_CLUSTER_GRID_CLUSTER_GRID_SLOT, - LIGHT_CLUSTER_GRID_CLUSTER_CONTENT_SLOT, - - }; - - enum MAP { - ALBEDO = 0, - NORMAL, - METALLIC, - EMISSIVE_LIGHTMAP, - ROUGHNESS, - OCCLUSION, - SCATTERING, - - LIGHT_AMBIENT_MAP = 10, - FADE_MASK, - }; - }; - class Locations { public: - int albedoTextureUnit; - int normalTextureUnit; - int roughnessTextureUnit; - int metallicTextureUnit; - int emissiveTextureUnit; - int occlusionTextureUnit; - int lightingModelBufferUnit; - int skinClusterBufferUnit; - int materialBufferUnit; - int texMapArrayBufferUnit; - int keyLightBufferUnit; - int lightBufferUnit; - int lightAmbientBufferUnit; - int lightAmbientMapUnit; - int fadeMaskTextureUnit; - int fadeParameterBufferUnit; - int fadeObjectParameterBufferUnit; - int hazeParameterBufferUnit; - int lightClusterGridBufferUnit; - int lightClusterContentBufferUnit; - int lightClusterFrustumBufferUnit; + bool albedoTextureUnit{ false }; + bool normalTextureUnit{ false }; + bool roughnessTextureUnit{ false }; + bool metallicTextureUnit{ false }; + bool emissiveTextureUnit{ false }; + bool occlusionTextureUnit{ false }; + bool lightingModelBufferUnit{ false }; + bool skinClusterBufferUnit{ false }; + bool materialBufferUnit{ false }; + bool texMapArrayBufferUnit{ false }; + bool keyLightBufferUnit{ false }; + bool lightBufferUnit{ false }; + bool lightAmbientBufferUnit{ false }; + bool lightAmbientMapUnit{ false }; + bool fadeMaskTextureUnit{ false }; + bool fadeParameterBufferUnit{ false }; + bool fadeObjectParameterBufferUnit{ false }; + bool hazeParameterBufferUnit{ false }; + bool lightClusterGridBufferUnit{ false }; + bool lightClusterContentBufferUnit{ false }; + bool lightClusterFrustumBufferUnit{ false }; }; using LocationsPointer = std::shared_ptr; @@ -330,7 +297,7 @@ public: using Pipeline = ShapePipeline; using PipelinePointer = ShapePipelinePointer; using PipelineMap = std::unordered_map; - using Slot = Pipeline::Slot; + using Slot = int32_t; using Locations = Pipeline::Locations; using LocationsPointer = Pipeline::LocationsPointer; using BatchSetter = Pipeline::BatchSetter; diff --git a/libraries/render/src/render/SortTask.cpp b/libraries/render/src/render/SortTask.cpp index 5b53b5d403..5b4061a10f 100644 --- a/libraries/render/src/render/SortTask.cpp +++ b/libraries/render/src/render/SortTask.cpp @@ -61,9 +61,9 @@ void render::depthSortItems(const RenderContextPointer& renderContext, bool fron for (auto itemDetails : inItems) { auto item = scene->getItem(itemDetails.id); auto bound = itemDetails.bound; // item.getBound(); - float distance = args->getViewFrustum().distanceToCamera(bound.calcCenter()); + float distanceSquared = args->getViewFrustum().distanceToCameraSquared(bound.calcCenter()); - itemBoundSorts.emplace_back(ItemBoundSort(distance, distance, distance, itemDetails.id, bound)); + itemBoundSorts.emplace_back(ItemBoundSort(distanceSquared, distanceSquared, distanceSquared, itemDetails.id, bound)); } // sort against Z diff --git a/libraries/render/src/render/blurGaussianDepthAwareH.slf b/libraries/render/src/render/blurGaussianDepthAwareH.slf index aab1fe2b02..09a92909ff 100644 --- a/libraries/render/src/render/blurGaussianDepthAwareH.slf +++ b/libraries/render/src/render/blurGaussianDepthAwareH.slf @@ -2,6 +2,8 @@ <$VERSION_HEADER$> // Generated on <$_SCRIBE_DATE$> // +// blurGaussianDepthAwareH.frag +// // Created by Sam Gateau on 6/7/16. // Copyright 2016 High Fidelity, Inc. // @@ -12,9 +14,9 @@ <@include BlurTask.slh@> <$declareBlurGaussianDepthAware()$> -in vec2 varTexCoord0; +layout(location=0) in vec2 varTexCoord0; -out vec4 outFragColor; +layout(location=0) out vec4 outFragColor; void main(void) { outFragColor = pixelShaderGaussianDepthAware(varTexCoord0, vec2(1.0, 0.0), getViewportInvWidthHeight()); diff --git a/libraries/render/src/render/blurGaussianDepthAwareH.slp b/libraries/render/src/render/blurGaussianDepthAwareH.slp new file mode 100644 index 0000000000..c2c4bfbebd --- /dev/null +++ b/libraries/render/src/render/blurGaussianDepthAwareH.slp @@ -0,0 +1 @@ +VERTEX gpu::vertex::DrawUnitQuadTexcoord diff --git a/libraries/render/src/render/blurGaussianDepthAwareV.slf b/libraries/render/src/render/blurGaussianDepthAwareV.slf index 35f0d3a381..4f9bc86c01 100644 --- a/libraries/render/src/render/blurGaussianDepthAwareV.slf +++ b/libraries/render/src/render/blurGaussianDepthAwareV.slf @@ -2,6 +2,8 @@ <$VERSION_HEADER$> // Generated on <$_SCRIBE_DATE$> // +// blurGaussianDepthAwareV.frag +// // Created by Sam Gateau on 6/7/16. // Copyright 2016 High Fidelity, Inc. // @@ -12,9 +14,9 @@ <@include BlurTask.slh@> <$declareBlurGaussianDepthAware()$> -in vec2 varTexCoord0; +layout(location=0) in vec2 varTexCoord0; -out vec4 outFragColor; +layout(location=0) out vec4 outFragColor; void main(void) { outFragColor = pixelShaderGaussianDepthAware(varTexCoord0, vec2(0.0, 1.0), getViewportInvWidthHeight()); diff --git a/libraries/render/src/render/blurGaussianDepthAwareV.slp b/libraries/render/src/render/blurGaussianDepthAwareV.slp new file mode 100644 index 0000000000..c2c4bfbebd --- /dev/null +++ b/libraries/render/src/render/blurGaussianDepthAwareV.slp @@ -0,0 +1 @@ +VERTEX gpu::vertex::DrawUnitQuadTexcoord diff --git a/libraries/render/src/render/blurGaussianH.slf b/libraries/render/src/render/blurGaussianH.slf index 02cc73fe13..1c13664907 100644 --- a/libraries/render/src/render/blurGaussianH.slf +++ b/libraries/render/src/render/blurGaussianH.slf @@ -2,6 +2,8 @@ <$VERSION_HEADER$> // Generated on <$_SCRIBE_DATE$> // +// blurGaussianH.frag +// // Created by Sam Gateau on 6/7/16. // Copyright 2016 High Fidelity, Inc. // @@ -13,9 +15,9 @@ <$declareBlurGaussian()$> -in vec2 varTexCoord0; +layout(location=0) in vec2 varTexCoord0; -out vec4 outFragColor; +layout(location=0) out vec4 outFragColor; void main(void) { outFragColor = pixelShaderGaussian(varTexCoord0, vec2(1.0, 0.0), getViewportInvWidthHeight()); diff --git a/libraries/render/src/render/blurGaussianH.slp b/libraries/render/src/render/blurGaussianH.slp new file mode 100644 index 0000000000..c2c4bfbebd --- /dev/null +++ b/libraries/render/src/render/blurGaussianH.slp @@ -0,0 +1 @@ +VERTEX gpu::vertex::DrawUnitQuadTexcoord diff --git a/libraries/render/src/render/blurGaussianV.slf b/libraries/render/src/render/blurGaussianV.slf index 99beab6275..f8351f9670 100644 --- a/libraries/render/src/render/blurGaussianV.slf +++ b/libraries/render/src/render/blurGaussianV.slf @@ -2,6 +2,8 @@ <$VERSION_HEADER$> // Generated on <$_SCRIBE_DATE$> // +// blurGaussianV.frag +// // Created by Sam Gateau on 6/7/16. // Copyright 2016 High Fidelity, Inc. // @@ -12,9 +14,9 @@ <$declareBlurGaussian()$> -in vec2 varTexCoord0; +layout(location=0) in vec2 varTexCoord0; -out vec4 outFragColor; +layout(location=0) out vec4 outFragColor; void main(void) { outFragColor = pixelShaderGaussian(varTexCoord0, vec2(0.0, 1.0), getViewportInvWidthHeight()); diff --git a/libraries/render/src/render/blurGaussianV.slp b/libraries/render/src/render/blurGaussianV.slp new file mode 100644 index 0000000000..c2c4bfbebd --- /dev/null +++ b/libraries/render/src/render/blurGaussianV.slp @@ -0,0 +1 @@ +VERTEX gpu::vertex::DrawUnitQuadTexcoord diff --git a/libraries/render/src/render/drawCellBounds.slf b/libraries/render/src/render/drawCellBounds.slf index 6b9f5b96bc..410f0f5d78 100644 --- a/libraries/render/src/render/drawCellBounds.slf +++ b/libraries/render/src/render/drawCellBounds.slf @@ -1,7 +1,7 @@ <@include gpu/Config.slh@> <$VERSION_HEADER$> // Generated on <$_SCRIBE_DATE$> -// drawCellBounds.slf +// drawCellBounds.frag // fragment shader // // Created by Sam Gateau on 1/25/2016. @@ -11,8 +11,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -in vec4 varColor; -out vec4 outFragColor; +layout(location=0) in vec4 varColor; +layout(location=0) out vec4 outFragColor; void main(void) { diff --git a/libraries/render/src/render/drawCellBounds.slp b/libraries/render/src/render/drawCellBounds.slp new file mode 100644 index 0000000000..e69de29bb2 diff --git a/libraries/render/src/render/drawCellBounds.slv b/libraries/render/src/render/drawCellBounds.slv index f50331f15c..f3cc4c6411 100644 --- a/libraries/render/src/render/drawCellBounds.slv +++ b/libraries/render/src/render/drawCellBounds.slv @@ -2,7 +2,7 @@ <$VERSION_HEADER$> // Generated on <$_SCRIBE_DATE$> // -// drawCellBounds.slv +// drawCellBounds.vert // Vertex shader // // Created by Sam Gateau on 1/25/2016 @@ -20,10 +20,9 @@ <$declareColorWheel()$> <@include SceneOctree.slh@> -uniform ivec4 inCellLocation; - -out vec4 varColor; +layout(location=GPU_UNIFORM_EXTRA0) uniform ivec4 inCellLocation; +layout(location=0) out vec4 varColor; void main(void) { const vec4 UNIT_BOX[8] = vec4[8]( diff --git a/libraries/render/src/render/drawItemBounds.slf b/libraries/render/src/render/drawItemBounds.slf index 84c47d0933..90faaff05c 100644 --- a/libraries/render/src/render/drawItemBounds.slf +++ b/libraries/render/src/render/drawItemBounds.slf @@ -11,9 +11,9 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -in vec4 varColor; -in vec2 varTexcoord; -out vec4 outFragColor; +layout(location=0) in vec4 varColor; +layout(location=1) in vec2 varTexcoord; +layout(location=0) out vec4 outFragColor; void main(void) { float var = step(fract(varTexcoord.x * varTexcoord.y * 1.0), 0.5); diff --git a/libraries/render/src/render/drawItemBounds.slp b/libraries/render/src/render/drawItemBounds.slp new file mode 100644 index 0000000000..e69de29bb2 diff --git a/libraries/render/src/render/drawItemBounds.slv b/libraries/render/src/render/drawItemBounds.slv index 0bb2b795bd..8925009160 100644 --- a/libraries/render/src/render/drawItemBounds.slv +++ b/libraries/render/src/render/drawItemBounds.slv @@ -2,7 +2,7 @@ <$VERSION_HEADER$> // Generated on <$_SCRIBE_DATE$> // -// drawItemBounds.slv +// drawItemBounds.vert // vertex shader // // Created by Sam Gateau on 6/29/2015. @@ -12,14 +12,15 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +<@include gpu/ShaderConstants.h@> + <@include gpu/Transform.slh@> <$declareStandardTransform()$> <@include gpu/Color.slh@> <$declareColorWheel()$> -uniform vec4 inColor; - +layout(location=GPU_UNIFORM_COLOR) uniform vec4 inColor; struct ItemBound { vec4 id_boundPos; @@ -27,7 +28,7 @@ struct ItemBound { }; #if defined(GPU_GL410) -uniform samplerBuffer ssbo0Buffer; +layout(binding=0) uniform samplerBuffer ssbo0Buffer; ItemBound getItemBound(int i) { int offset = 2 * i; ItemBound bound; @@ -36,7 +37,7 @@ ItemBound getItemBound(int i) { return bound; } #else -layout(std140) buffer ssbo0Buffer { +layout(std140, binding=0) buffer ssbo0Buffer { ItemBound bounds[]; }; ItemBound getItemBound(int i) { @@ -45,10 +46,8 @@ ItemBound getItemBound(int i) { } #endif - - -out vec4 varColor; -out vec2 varTexcoord; +layout(location=0) out vec4 varColor; +layout(location=1) out vec2 varTexcoord; void main(void) { const vec4 UNIT_BOX[8] = vec4[8]( diff --git a/libraries/render/src/render/drawItemStatus.slf b/libraries/render/src/render/drawItemStatus.slf index 40cf450363..309a73ae15 100644 --- a/libraries/render/src/render/drawItemStatus.slf +++ b/libraries/render/src/render/drawItemStatus.slf @@ -11,11 +11,11 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -in vec4 varColor; -in vec3 varTexcoord; -out vec4 outFragColor; +layout(location=0) in vec4 varColor; +layout(location=1) in vec3 varTexcoord; +layout(location=0) out vec4 outFragColor; -uniform sampler2D _icons; +layout(binding=0) uniform sampler2D _icons; vec2 getIconTexcoord(float icon, vec2 uv) { const vec2 ICON_COORD_SIZE = vec2(0.0625, 1.0); return vec2((uv.x + icon) * ICON_COORD_SIZE.x, uv.y * ICON_COORD_SIZE.y); diff --git a/libraries/render/src/render/drawItemStatus.slp b/libraries/render/src/render/drawItemStatus.slp new file mode 100644 index 0000000000..e69de29bb2 diff --git a/libraries/render/src/render/drawItemStatus.slv b/libraries/render/src/render/drawItemStatus.slv index 792f2733c5..e92bdda248 100644 --- a/libraries/render/src/render/drawItemStatus.slv +++ b/libraries/render/src/render/drawItemStatus.slv @@ -2,7 +2,7 @@ <$VERSION_HEADER$> // Generated on <$_SCRIBE_DATE$> // -// drawItemStatus.slv +// drawItemStatus.vert // vertex shader // // Created by Sam Gateau on 6/30/2015. @@ -13,16 +13,17 @@ // <@include gpu/Transform.slh@> +<@include gpu/ShaderConstants.h@> <$declareStandardTransform()$> -out vec4 varColor; -out vec3 varTexcoord; +layout(location=0) out vec4 varColor; +layout(location=1) out vec3 varTexcoord; -uniform vec3 inBoundPos; -uniform vec3 inBoundDim; -uniform ivec4 inStatus0; -uniform ivec4 inStatus1; +layout(location=GPU_UNIFORM_EXTRA0) uniform vec3 inBoundPos; +layout(location=GPU_UNIFORM_EXTRA1) uniform vec3 inBoundDim; +layout(location=GPU_UNIFORM_EXTRA2) uniform ivec4 inStatus0; +layout(location=GPU_UNIFORM_EXTRA3) uniform ivec4 inStatus1; vec3 paintRainbow(float normalizedHue) { float v = normalizedHue * 6.f; diff --git a/libraries/render/src/render/drawLODReticle.slf b/libraries/render/src/render/drawLODReticle.slf index 68eb27b775..61d0cf32fb 100644 --- a/libraries/render/src/render/drawLODReticle.slf +++ b/libraries/render/src/render/drawLODReticle.slf @@ -11,8 +11,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -in vec2 varTexCoord0; -out vec4 outFragColor; +layout(location=0) in vec2 varTexCoord0; +layout(location=0) out vec4 outFragColor; void main(void) { vec2 circlePos = 2.0 * ( varTexCoord0.xy * 2.0 - vec2(1.0) ); diff --git a/libraries/render/src/render/drawLODReticle.slp b/libraries/render/src/render/drawLODReticle.slp new file mode 100644 index 0000000000..a5c2bb33e6 --- /dev/null +++ b/libraries/render/src/render/drawLODReticle.slp @@ -0,0 +1 @@ +VERTEX gpu::vertex::DrawTransformUnitQuad diff --git a/libraries/script-engine/src/AudioScriptingInterface.h b/libraries/script-engine/src/AudioScriptingInterface.h index 20ca977da1..1220a9b769 100644 --- a/libraries/script-engine/src/AudioScriptingInterface.h +++ b/libraries/script-engine/src/AudioScriptingInterface.h @@ -23,6 +23,7 @@ class AudioScriptingInterface : public QObject, public Dependency { Q_OBJECT SINGLETON_DEPENDENCY + // JSDoc for property is in Audio.h. Q_PROPERTY(bool isStereoInput READ isStereoInput WRITE setStereoInput NOTIFY isStereoInputChanged) public: @@ -35,91 +36,121 @@ protected: // these methods are protected to stop C++ callers from calling, but invokable from script /**jsdoc + * Starts playing — "injecting" — the content of an audio file. The sound is played globally (sent to the audio + * mixer) so that everyone hears it, unless the injectorOptions has localOnly set to + * true in which case only the client hears the sound played. No sound is played if sent to the audio mixer + * but the client is not connected to an audio mixer. The {@link AudioInjector} object returned by the function can be used + * to control the playback and get information about its current state. * @function Audio.playSound - * @param {} sound - * @param {} [injectorOptions=null] - * @returns {object} + * @param {SoundObject} sound - The content of an audio file, loaded using {@link SoundCache.getSound}. See + * {@link SoundObject} for supported formats. + * @param {AudioInjector.AudioInjectorOptions} [injectorOptions={}] - Audio injector configuration. + * @returns {AudioInjector} The audio injector that plays the audio file. + * @example Play a sound. + * var sound = SoundCache.getSound(Script.resourcesPath() + "sounds/sample.wav"); + * var injector; + * var injectorOptions = { + * position: MyAvatar.position + * }; + * + * Script.setTimeout(function () { // Give the sound time to load. + * injector = Audio.playSound(sound, injectorOptions); + * }, 1000); */ Q_INVOKABLE ScriptAudioInjector* playSound(SharedSoundPointer sound, const AudioInjectorOptions& injectorOptions = AudioInjectorOptions()); /**jsdoc + * Start playing the content of an audio file, locally (isn't sent to the audio mixer). This is the same as calling + * {@link Audio.playSound} with {@link AudioInjector.AudioInjectorOptions} localOnly set true and + * the specified position. * @function Audio.playSystemSound - * @param {} sound - * @param {} position - * @returns {object} + * @param {SoundObject} sound - The content of an audio file, loaded using {@link SoundCache.getSound}. See + * {@link SoundObject} for supported formats. + * @param {Vec3} position - The position in the domain to play the sound. + * @returns {AudioInjector} The audio injector that plays the audio file. */ // FIXME: there is no way to play a positionless sound Q_INVOKABLE ScriptAudioInjector* playSystemSound(SharedSoundPointer sound, const QVector3D& position); /**jsdoc + * Set whether or not the audio input should be used in stereo. If the audio input does not support stereo then setting a + * value of true has no effect. * @function Audio.setStereoInput - * @param {boolean} stereo + * @param {boolean} stereo - true if the audio input should be used in stereo, otherwise false. */ Q_INVOKABLE void setStereoInput(bool stereo); /**jsdoc + * Get whether or not the audio input is used in stereo. * @function Audio.isStereoInput - * @returns {boolean} + * @returns {boolean} true if the audio input is used in stereo, otherwise false. */ Q_INVOKABLE bool isStereoInput(); signals: /**jsdoc - * The client has been muted by the mixer. + * Triggered when the client is muted by the mixer because their loudness value for the noise background has reached the + * threshold set for the domain in the server settings. * @function Audio.mutedByMixer * @returns {Signal} */ void mutedByMixer(); /**jsdoc - * The entire environment has been muted by the mixer. + * Triggered when the client is muted by the mixer because they're within a certain radius (50m) of someone who requested + * the mute through Developer > Audio > Mute Environment. * @function Audio.environmentMuted * @returns {Signal} */ void environmentMuted(); /**jsdoc - * The client has received its first packet from the audio mixer. + * Triggered when the client receives its first packet from the audio mixer. * @function Audio.receivedFirstPacket * @returns {Signal} */ void receivedFirstPacket(); /**jsdoc - * The client has been disconnected from the audio mixer. + * Triggered when the client is disconnected from the audio mixer. * @function Audio.disconnected * @returns {Signal} */ void disconnected(); /**jsdoc - * The noise gate has opened. + * Triggered when the noise gate is opened: the input audio signal is no longer blocked (fully attenuated) because it has + * risen above an adaptive threshold set just above the noise floor. Only occurs if Audio.noiseReduction is + * true. * @function Audio.noiseGateOpened * @returns {Signal} */ void noiseGateOpened(); /**jsdoc - * The noise gate has closed. + * Triggered when the noise gate is closed: the input audio signal is blocked (fully attenuated) because it has fallen + * below an adaptive threshold set just above the noise floor. Only occurs if Audio.noiseReduction is + * true. * @function Audio.noiseGateClosed * @returns {Signal} */ void noiseGateClosed(); /**jsdoc - * A frame of mic input audio has been received and processed. + * Triggered when a frame of audio input is processed. * @function Audio.inputReceived - * @param {} inputSamples + * @param {Int16Array} inputSamples - The audio input processed. * @returns {Signal} */ void inputReceived(const QByteArray& inputSamples); /**jsdoc - * @function Audio.isStereoInputChanged - * @param {boolean} isStereo - * @returns {Signal} - */ + * Triggered when the input audio use changes between mono and stereo. + * @function Audio.isStereoInputChanged + * @param {boolean} isStereo - true if the input audio is stereo, otherwise false. + * @returns {Signal} + */ void isStereoInputChanged(bool isStereo); private: diff --git a/libraries/script-engine/src/RecordingScriptingInterface.cpp b/libraries/script-engine/src/RecordingScriptingInterface.cpp index 55895e31a4..cc2edfcca7 100644 --- a/libraries/script-engine/src/RecordingScriptingInterface.cpp +++ b/libraries/script-engine/src/RecordingScriptingInterface.cpp @@ -59,7 +59,7 @@ void RecordingScriptingInterface::playClip(NetworkClipLoaderPointer clipLoader, if (callback.isFunction()) { QScriptValueList args { true, url }; - callback.call(_scriptEngine->globalObject(), args); + callback.call(QScriptValue(), args); } } @@ -78,7 +78,7 @@ void RecordingScriptingInterface::loadRecording(const QString& url, QScriptValue auto weakClipLoader = clipLoader.toWeakRef(); // when clip loaded, call the callback with the URL and success boolean - connect(clipLoader.data(), &recording::NetworkClipLoader::clipLoaded, this, + connect(clipLoader.data(), &recording::NetworkClipLoader::clipLoaded, callback.engine(), [this, weakClipLoader, url, callback]() mutable { if (auto clipLoader = weakClipLoader.toStrongRef()) { @@ -92,12 +92,12 @@ void RecordingScriptingInterface::loadRecording(const QString& url, QScriptValue }); // when clip load fails, call the callback with the URL and failure boolean - connect(clipLoader.data(), &recording::NetworkClipLoader::failed, this, [this, weakClipLoader, url, callback](QNetworkReply::NetworkError error) mutable { + connect(clipLoader.data(), &recording::NetworkClipLoader::failed, callback.engine(), [this, weakClipLoader, url, callback](QNetworkReply::NetworkError error) mutable { qCDebug(scriptengine) << "Failed to load recording from" << url; if (callback.isFunction()) { QScriptValueList args { false, url }; - callback.call(_scriptEngine->currentContext()->thisObject(), args); + callback.call(QScriptValue(), args); } if (auto clipLoader = weakClipLoader.toStrongRef()) { diff --git a/libraries/script-engine/src/RecordingScriptingInterface.h b/libraries/script-engine/src/RecordingScriptingInterface.h index 29d9b31049..c4d576351f 100644 --- a/libraries/script-engine/src/RecordingScriptingInterface.h +++ b/libraries/script-engine/src/RecordingScriptingInterface.h @@ -36,8 +36,6 @@ class RecordingScriptingInterface : public QObject, public Dependency { public: RecordingScriptingInterface(); - void setScriptEngine(QSharedPointer scriptEngine) { _scriptEngine = scriptEngine; } - public slots: /**jsdoc @@ -246,7 +244,6 @@ protected: Flag _useSkeletonModel { false }; recording::ClipPointer _lastClip; - QSharedPointer _scriptEngine; QSet _clipLoaders; private: diff --git a/libraries/script-engine/src/ScriptAudioInjector.h b/libraries/script-engine/src/ScriptAudioInjector.h index 4c2871dd34..2c88d618e1 100644 --- a/libraries/script-engine/src/ScriptAudioInjector.h +++ b/libraries/script-engine/src/ScriptAudioInjector.h @@ -16,6 +16,22 @@ #include +/**jsdoc + * Plays — "injects" — the content of an audio file. Used in the {@link Audio} API. + * + * @class AudioInjector + * + * @hifi-interface + * @hifi-client-entity + * @hifi-server-entity + * @hifi-assignment-client + * + * @property {boolean} playing - true if the audio is currently playing, otherwise false. + * Read-only. + * @property {number} loudness - The loudness in the last frame of audio, range 0.01.0. + * Read-only. + * @property {AudioInjector.AudioInjectorOptions} options - Configures how the injector plays the audio. + */ class ScriptAudioInjector : public QObject { Q_OBJECT @@ -26,19 +42,103 @@ public: ScriptAudioInjector(const AudioInjectorPointer& injector); ~ScriptAudioInjector(); public slots: + + /**jsdoc + * Stop current playback, if any, and start playing from the beginning. + * @function AudioInjector.restart + */ void restart() { _injector->restart(); } + + /**jsdoc + * Stop audio playback. + * @function AudioInjector.stop + * @example Stop playing a sound before it finishes. + * var sound = SoundCache.getSound(Script.resourcesPath() + "sounds/sample.wav"); + * var injector; + * var injectorOptions = { + * position: MyAvatar.position + * }; + * + * Script.setTimeout(function () { // Give the sound time to load. + * injector = Audio.playSound(sound, injectorOptions); + * }, 1000); + * + * Script.setTimeout(function () { + * injector.stop(); + * }, 2000); + */ void stop() { _injector->stop(); } + /**jsdoc + * Get the current configuration of the audio injector. + * @function AudioInjector.getOptions + * @returns {AudioInjector.AudioInjectorOptions} Configuration of how the injector plays the audio. + */ const AudioInjectorOptions& getOptions() const { return _injector->getOptions(); } + + /**jsdoc + * Configure how the injector plays the audio. + * @function AudioInjector.setOptions + * @param {AudioInjector.AudioInjectorOptions} options - Configuration of how the injector plays the audio. + */ void setOptions(const AudioInjectorOptions& options) { _injector->setOptions(options); } + /**jsdoc + * Get the loudness of the most recent frame of audio played. + * @function AudioInjector.getLoudness + * @returns {number} The loudness of the most recent frame of audio played, range 0.01.0. + */ float getLoudness() const { return _injector->getLoudness(); } + + /**jsdoc + * Get whether or not the audio is currently playing. + * @function AudioInjector.isPlaying + * @returns {boolean} true if the audio is currently playing, otherwise false. + * @example See if a sound is playing. + * var sound = SoundCache.getSound(Script.resourcesPath() + "sounds/sample.wav"); + * var injector; + * var injectorOptions = { + * position: MyAvatar.position + * }; + * + * Script.setTimeout(function () { // Give the sound time to load. + * injector = Audio.playSound(sound, injectorOptions); + * }, 1000); + * + * Script.setTimeout(function () { + * print("Sound is playing: " + injector.isPlaying()); + * }, 2000); + */ bool isPlaying() const { return _injector->isPlaying(); } signals: + + /**jsdoc + * Triggered when the audio has finished playing. + * @function AudioInjector.finished + * @returns {Signal} + * @example Report when a sound has finished playing. + * var sound = SoundCache.getSound(Script.resourcesPath() + "sounds/sample.wav"); + * var injector; + * var injectorOptions = { + * position: MyAvatar.position + * }; + * + * Script.setTimeout(function () { // Give the sound time to load. + * injector = Audio.playSound(sound, injectorOptions); + * injector.finished.connect(function () { + * print("Finished playing sound"); + * }); + * }, 1000); + */ void finished(); protected slots: + + /**jsdoc + * Stop audio playback. (Synonym of {@link AudioInjector.stop|stop}.) + * @function AudioInjector.stopInjectorImmediately + */ void stopInjectorImmediately(); private: AudioInjectorPointer _injector; diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 99c02ba1f6..ce4ec89950 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -74,6 +74,7 @@ #include "WebSocketClass.h" #include "RecordingScriptingInterface.h" #include "ScriptEngines.h" +#include "StackTestScriptingInterface.h" #include "ModelScriptingInterface.h" @@ -175,9 +176,7 @@ ScriptEngine::ScriptEngine(Context context, const QString& scriptContents, const _timerFunctionMap(), _fileNameString(fileNameString), _arrayBufferClass(new ArrayBufferClass(this)), - _assetScriptingInterface(new AssetScriptingInterface(this)), - // don't delete `ScriptEngines` until all `ScriptEngine`s are gone - _scriptEngines(DependencyManager::get()) + _assetScriptingInterface(new AssetScriptingInterface(this)) { switch (_context) { case Context::CLIENT_SCRIPT: @@ -219,6 +218,20 @@ ScriptEngine::ScriptEngine(Context context, const QString& scriptContents, const } logException(output); }); + + if (_type == Type::ENTITY_CLIENT || _type == Type::ENTITY_SERVER) { + QObject::connect(this, &ScriptEngine::update, this, [this]() { + // process pending entity script content + if (!_contentAvailableQueue.empty()) { + EntityScriptContentAvailableMap pending; + std::swap(_contentAvailableQueue, pending); + for (auto& pair : pending) { + auto& args = pair.second; + entityScriptContentAvailable(args.entityID, args.scriptOrURL, args.contents, args.isURL, args.success, args.status); + } + } + }); + } } QString ScriptEngine::getContext() const { @@ -748,6 +761,10 @@ void ScriptEngine::init() { qScriptRegisterMetaType(this, meshesToScriptValue, meshesFromScriptValue); registerGlobalObject("UserActivityLogger", DependencyManager::get().data()); + +#if DEV_BUILD || PR_BUILD + registerGlobalObject("StackTest", new StackTestScriptingInterface(this)); +#endif } void ScriptEngine::registerValue(const QString& valueName, QScriptValue value) { @@ -2181,7 +2198,7 @@ void ScriptEngine::loadEntityScript(const EntityItemID& entityID, const QString& qCDebug(scriptengine) << "loadEntityScript.contentAvailable" << status << QUrl(url).fileName() << entityID.toString(); #endif if (!isStopping() && _entityScripts.contains(entityID)) { - entityScriptContentAvailable(entityID, url, contents, isURL, success, status); + _contentAvailableQueue[entityID] = { entityID, url, contents, isURL, success, status }; } else { #ifdef DEBUG_ENTITY_STATES qCDebug(scriptengine) << "loadEntityScript.contentAvailable -- aborting"; diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index c02a63ef3c..94b50bfd2c 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -12,6 +12,7 @@ #ifndef hifi_ScriptEngine_h #define hifi_ScriptEngine_h +#include #include #include @@ -71,6 +72,17 @@ public: //bool forceRedownload; }; +struct EntityScriptContentAvailable { + EntityItemID entityID; + QString scriptOrURL; + QString contents; + bool isURL; + bool success; + QString status; +}; + +typedef std::unordered_map EntityScriptContentAvailableMap; + typedef QList CallbackList; typedef QHash RegisteredEventHandlers; @@ -762,6 +774,7 @@ protected: QHash _entityScripts; QHash _occupiedScriptURLs; QList _deferredEntityLoads; + EntityScriptContentAvailableMap _contentAvailableQueue; bool _isThreaded { false }; QScriptEngineDebugger* _debugger { nullptr }; @@ -793,8 +806,6 @@ protected: static const QString _SETTINGS_ENABLE_EXTENDED_EXCEPTIONS; Setting::Handle _enableExtendedJSExceptions { _SETTINGS_ENABLE_EXTENDED_EXCEPTIONS, true }; - - QSharedPointer _scriptEngines; }; ScriptEnginePointer scriptEngineFactory(ScriptEngine::Context context, diff --git a/libraries/script-engine/src/ScriptEngines.cpp b/libraries/script-engine/src/ScriptEngines.cpp index d385dcca84..6393ff33ea 100644 --- a/libraries/script-engine/src/ScriptEngines.cpp +++ b/libraries/script-engine/src/ScriptEngines.cpp @@ -133,7 +133,7 @@ QUrl expandScriptUrl(const QUrl& rawScriptURL) { QObject* scriptsModel(); bool NativeScriptInitializers::registerNativeScriptInitializer(NativeScriptInitializer initializer) { - return registerScriptInitializer([=](ScriptEnginePointer engine) { + return registerScriptInitializer([initializer](ScriptEnginePointer engine) { initializer(qobject_cast(engine.data())); }); } @@ -492,8 +492,7 @@ ScriptEnginePointer ScriptEngines::loadScript(const QUrl& scriptFilename, bool i return scriptEngine; } - scriptEngine = ScriptEnginePointer(new ScriptEngine(_context, NO_SCRIPT, "about:" + scriptFilename.fileName())); - addScriptEngine(scriptEngine); + scriptEngine = scriptEngineFactory(_context, NO_SCRIPT, "about:" + scriptFilename.fileName()); scriptEngine->setUserLoaded(isUserLoaded); scriptEngine->setQuitWhenFinished(quitWhenFinished); @@ -564,10 +563,10 @@ int ScriptEngines::runScriptInitializers(ScriptEnginePointer scriptEngine) { void ScriptEngines::launchScriptEngine(ScriptEnginePointer scriptEngine) { connect(scriptEngine.data(), &ScriptEngine::finished, this, &ScriptEngines::onScriptFinished, Qt::DirectConnection); - connect(scriptEngine.data(), &ScriptEngine::loadScript, [&](const QString& scriptName, bool userLoaded) { + connect(scriptEngine.data(), &ScriptEngine::loadScript, [this](const QString& scriptName, bool userLoaded) { loadScript(scriptName, userLoaded); }); - connect(scriptEngine.data(), &ScriptEngine::reloadScript, [&](const QString& scriptName, bool userLoaded) { + connect(scriptEngine.data(), &ScriptEngine::reloadScript, [this](const QString& scriptName, bool userLoaded) { loadScript(scriptName, userLoaded, false, false, true); }); diff --git a/libraries/script-engine/src/StackTestScriptingInterface.cpp b/libraries/script-engine/src/StackTestScriptingInterface.cpp new file mode 100644 index 0000000000..432c1807f0 --- /dev/null +++ b/libraries/script-engine/src/StackTestScriptingInterface.cpp @@ -0,0 +1,31 @@ +// +// StackTestScriptingInterface.cpp +// libraries/script-engine/src +// +// Created by Clement Brisset on 7/25/18. +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "StackTestScriptingInterface.h" + +#include +#include + +Q_DECLARE_LOGGING_CATEGORY(stackTest) +Q_LOGGING_CATEGORY(stackTest, "hifi.tools.stack-test") + +void StackTestScriptingInterface::pass(QString message) { + qCInfo(stackTest) << "PASS" << qPrintable(message); +} + +void StackTestScriptingInterface::fail(QString message) { + qCInfo(stackTest) << "FAIL" << qPrintable(message); +} + +void StackTestScriptingInterface::exit(QString message) { + qCInfo(stackTest) << "COMPLETE" << qPrintable(message); + qApp->exit(); +} diff --git a/libraries/script-engine/src/StackTestScriptingInterface.h b/libraries/script-engine/src/StackTestScriptingInterface.h new file mode 100644 index 0000000000..74e3290ddb --- /dev/null +++ b/libraries/script-engine/src/StackTestScriptingInterface.h @@ -0,0 +1,31 @@ +// +// StackTestScriptingInterface.h +// libraries/script-engine/src +// +// Created by Clement Brisset on 7/25/18. +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#pragma once + +#ifndef hifi_StackTestScriptingInterface_h +#define hifi_StackTestScriptingInterface_h + +#include + +class StackTestScriptingInterface : public QObject { + Q_OBJECT + +public: + StackTestScriptingInterface(QObject* parent = nullptr) : QObject(parent) {} + + Q_INVOKABLE void pass(QString message = QString()); + Q_INVOKABLE void fail(QString message = QString()); + + Q_INVOKABLE void exit(QString message = QString()); +}; + +#endif // hifi_StackTestScriptingInterface_h diff --git a/libraries/shaders/CMakeLists.txt b/libraries/shaders/CMakeLists.txt new file mode 100644 index 0000000000..a065c635e7 --- /dev/null +++ b/libraries/shaders/CMakeLists.txt @@ -0,0 +1,16 @@ +set(TARGET_NAME shaders) +autoscribe_shader_libs(gpu graphics display-plugins procedural render render-utils entities-renderer) +setup_hifi_library(Gui) + +add_dependencies(${TARGET_NAME} compiled_shaders reflected_shaders) + +# Despite the dependency above, the autogen logic will attempt to compile the QRC before +# the compiled_shaders project is built causing an error on a clean workspace because the +# QRC references files generated by the compiled_shaders target +# To fix that we need to explicitly add every shader as a dependnecy of the autogen process +foreach(COMPILED_SHADER ${COMPILED_SHADERS}) + set_property(TARGET ${TARGET_NAME} APPEND PROPERTY AUTOGEN_TARGET_DEPENDS "${COMPILED_SHADER}") +endforeach() + +link_hifi_libraries(shared gl) + diff --git a/libraries/shaders/ShaderEnums.cpp.in b/libraries/shaders/ShaderEnums.cpp.in new file mode 100644 index 0000000000..7f4751f116 --- /dev/null +++ b/libraries/shaders/ShaderEnums.cpp.in @@ -0,0 +1,11 @@ +#include "ShaderEnums.h" + +namespace shader { + +uint32_t all_programs[] = { +@SHADER_PROGRAMS_ARRAY@ + (uint32_t)-1 +}; + +} + diff --git a/libraries/shaders/ShaderEnums.h.in b/libraries/shaders/ShaderEnums.h.in new file mode 100644 index 0000000000..153f1d6fab --- /dev/null +++ b/libraries/shaders/ShaderEnums.h.in @@ -0,0 +1,9 @@ +#include +#include + +namespace shader { + +@SHADER_ENUMS@ + +} + diff --git a/libraries/shaders/shaders.qrc.in b/libraries/shaders/shaders.qrc.in new file mode 100644 index 0000000000..6078b4a9e9 --- /dev/null +++ b/libraries/shaders/shaders.qrc.in @@ -0,0 +1,6 @@ + + + +@SHADER_QRC@ + + \ No newline at end of file diff --git a/libraries/shaders/src/shaders/Shaders.cpp b/libraries/shaders/src/shaders/Shaders.cpp new file mode 100644 index 0000000000..ac4810a896 --- /dev/null +++ b/libraries/shaders/src/shaders/Shaders.cpp @@ -0,0 +1,108 @@ +// +// Created by Bradley Austin Davis on 2018/06/02 +// Copyright 2013-2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "Shaders.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +static bool cleanShaders() { +#if defined(Q_OS_MAC) + static const bool CLEAN_SHADERS = true; +#else + static const bool CLEAN_SHADERS = ::gl::disableGl45(); + +#endif + return CLEAN_SHADERS; +} + +// Can't use the Q_INIT_RESOURCE macro inside a namespace on Mac, +// so this is done out of line +void initShaders() { + static std::once_flag once; + std::call_once(once, [] { + Q_INIT_RESOURCE(shaders); + }); +} + +static std::vector splitStringIntoLines(const std::string& s) { + std::stringstream ss(s); + std::vector result; + + std::string line; + while (std::getline(ss, line, '\n')) { + result.push_back(line); + } + return result; +} + +static std::string loadResource(const std::string& path) { + return FileUtils::readFile(path.c_str()).toStdString(); +} + +namespace shader { + +void cleanShaderSource(std::string& shaderSource) { + static const std::regex LAYOUT_REGEX{ R"REGEX(^layout\((\s*std140\s*,\s*)?(?:binding|location)\s*=\s*(?:\b\w+\b)\)\s*(?!(?:flat\s+)?(?:out|in|buffer))\b(.*)$)REGEX" }; + static const int GROUP_STD140 = 1; + static const int THE_REST_OF_THE_OWL = 2; + std::vector lines = splitStringIntoLines(shaderSource); + std::vector outLines; + std::unordered_map locationDefines; + for (const auto& line : lines) { + std::cmatch m; + if (std::regex_match(line.c_str(), m, LAYOUT_REGEX)) { + std::string outLine; + if (m[GROUP_STD140].matched) { + outLine = "layout(std140) "; + } + outLine += m[THE_REST_OF_THE_OWL].str(); + outLines.push_back(outLine); + continue; + // On mac we have to strip out all the explicit binding location layouts, + // because GL 4.1 doesn't support them + } + outLines.push_back(line); + } + std::ostringstream joined; + std::copy(outLines.begin(), outLines.end(), std::ostream_iterator(joined, "\n")); + shaderSource = joined.str(); +} + +std::string loadShaderSource(uint32_t shaderId) { + initShaders(); + auto shaderStr = loadResource(std::string(":/shaders/") + std::to_string(shaderId)); + if (cleanShaders()) { + // OSX only supports OpenGL 4.1 without ARB_shading_language_420pack or + // ARB_explicit_uniform_location or basically anything useful that's + // been released in the last 8 fucking years, so in that case we need to + // strip out all explicit locations and do a bunch of background magic to + // make the system seem like it is using the explicit locations + cleanShaderSource(shaderStr); + } + return shaderStr; +} + +std::string loadShaderReflection(uint32_t shaderId) { + initShaders(); + auto path = std::string(":/shaders/") + std::to_string(shaderId) + std::string("_reflection"); + auto json = loadResource(path); + return json; +} + +} diff --git a/libraries/shaders/src/shaders/Shaders.h b/libraries/shaders/src/shaders/Shaders.h new file mode 100644 index 0000000000..1335c1b49b --- /dev/null +++ b/libraries/shaders/src/shaders/Shaders.h @@ -0,0 +1,34 @@ +// +// Created by Bradley Austin Davis on 2018/07/09 +// Copyright 2013-2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#pragma once +#include +#include +#include +#include + +namespace shader { + +static const uint32_t INVALID_SHADER = (uint32_t)-1; +static const uint32_t INVALID_PROGRAM = (uint32_t)-1; + +extern uint32_t all_programs[]; + +std::string loadShaderSource(uint32_t shaderId); +std::string loadShaderReflection(uint32_t shaderId); + +inline uint32_t getVertexId(uint32_t programId) { + return (programId >> 16) & UINT16_MAX; +} + +inline uint32_t getFragmentId(uint32_t programId) { + return programId & UINT16_MAX; +} + +} + diff --git a/libraries/shared/src/AABox.cpp b/libraries/shared/src/AABox.cpp index cbf3c1b785..ff6c2a4e6e 100644 --- a/libraries/shared/src/AABox.cpp +++ b/libraries/shared/src/AABox.cpp @@ -79,49 +79,52 @@ void AABox::setBox(const glm::vec3& corner, const glm::vec3& scale) { glm::vec3 AABox::getFarthestVertex(const glm::vec3& normal) const { glm::vec3 result = _corner; - if (normal.x > 0.0f) { - result.x += _scale.x; - } - if (normal.y > 0.0f) { - result.y += _scale.y; - } - if (normal.z > 0.0f) { - result.z += _scale.z; - } + // This is a branchless version of: + //if (normal.x > 0.0f) { + // result.x += _scale.x; + //} + //if (normal.y > 0.0f) { + // result.y += _scale.y; + //} + //if (normal.z > 0.0f) { + // result.z += _scale.z; + //} + float blend = (float)(normal.x > 0.0f); + result.x += blend * _scale.x + (1.0f - blend) * 0.0f; + blend = (float)(normal.y > 0.0f); + result.y += blend * _scale.y + (1.0f - blend) * 0.0f; + blend = (float)(normal.z > 0.0f); + result.z += blend * _scale.z + (1.0f - blend) * 0.0f; return result; } glm::vec3 AABox::getNearestVertex(const glm::vec3& normal) const { glm::vec3 result = _corner; - - if (normal.x < 0.0f) { - result.x += _scale.x; - } - - if (normal.y < 0.0f) { - result.y += _scale.y; - } - - if (normal.z < 0.0f) { - result.z += _scale.z; - } - + // This is a branchless version of: + //if (normal.x < 0.0f) { + // result.x += _scale.x; + //} + //if (normal.y < 0.0f) { + // result.y += _scale.y; + //} + //if (normal.z < 0.0f) { + // result.z += _scale.z; + //} + float blend = (float)(normal.x < 0.0f); + result.x += blend * _scale.x + (1.0f - blend) * 0.0f; + blend = (float)(normal.y < 0.0f); + result.y += blend * _scale.y + (1.0f - blend) * 0.0f; + blend = (float)(normal.z < 0.0f); + result.z += blend * _scale.z + (1.0f - blend) * 0.0f; return result; } -// determines whether a value is within the extents -static bool isWithin(float value, float corner, float size) { - return value >= corner && value <= corner + size; -} - bool AABox::contains(const Triangle& triangle) const { return contains(triangle.v0) && contains(triangle.v1) && contains(triangle.v2); } bool AABox::contains(const glm::vec3& point) const { - return isWithin(point.x, _corner.x, _scale.x) && - isWithin(point.y, _corner.y, _scale.y) && - isWithin(point.z, _corner.z, _scale.z); + return aaBoxContains(point, _corner, _scale); } bool AABox::contains(const AABox& otherBox) const { @@ -175,30 +178,6 @@ bool AABox::expandedContains(const glm::vec3& point, float expansion) const { isWithinExpanded(point.z, _corner.z, _scale.z, expansion); } -// finds the intersection between a ray and the facing plane on one axis -static bool findIntersection(float origin, float direction, float corner, float size, float& distance) { - if (direction > EPSILON) { - distance = (corner - origin) / direction; - return true; - } else if (direction < -EPSILON) { - distance = (corner + size - origin) / direction; - return true; - } - return false; -} - -// finds the intersection between a ray and the inside facing plane on one axis -static bool findInsideOutIntersection(float origin, float direction, float corner, float size, float& distance) { - if (direction > EPSILON) { - distance = -1.0f * (origin - (corner + size)) / direction; - return true; - } else if (direction < -EPSILON) { - distance = -1.0f * (origin - corner) / direction; - return true; - } - return false; -} - bool AABox::expandedIntersectsSegment(const glm::vec3& start, const glm::vec3& end, float expansion) const { // handle the trivial cases where the expanded box contains the start or end if (expandedContains(start, expansion) || expandedContains(end, expansion)) { @@ -223,68 +202,14 @@ bool AABox::expandedIntersectsSegment(const glm::vec3& start, const glm::vec3& e isWithin(start.x + axisDistance*direction.x, expandedCorner.x, expandedSize.x)); } -bool AABox::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, - BoxFace& face, glm::vec3& surfaceNormal) const { - // handle the trivial case where the box contains the origin - if (contains(origin)) { - // We still want to calculate the distance from the origin to the inside out plane - float axisDistance; - if ((findInsideOutIntersection(origin.x, direction.x, _corner.x, _scale.x, axisDistance) && axisDistance >= 0 && - isWithin(origin.y + axisDistance*direction.y, _corner.y, _scale.y) && - isWithin(origin.z + axisDistance*direction.z, _corner.z, _scale.z))) { - distance = axisDistance; - face = direction.x > 0 ? MAX_X_FACE : MIN_X_FACE; - surfaceNormal = glm::vec3(direction.x > 0 ? 1.0f : -1.0f, 0.0f, 0.0f); - return true; - } - if ((findInsideOutIntersection(origin.y, direction.y, _corner.y, _scale.y, axisDistance) && axisDistance >= 0 && - isWithin(origin.x + axisDistance*direction.x, _corner.x, _scale.x) && - isWithin(origin.z + axisDistance*direction.z, _corner.z, _scale.z))) { - distance = axisDistance; - face = direction.y > 0 ? MAX_Y_FACE : MIN_Y_FACE; - surfaceNormal = glm::vec3(0.0f, direction.y > 0 ? 1.0f : -1.0f, 0.0f); - return true; - } - if ((findInsideOutIntersection(origin.z, direction.z, _corner.z, _scale.z, axisDistance) && axisDistance >= 0 && - isWithin(origin.y + axisDistance*direction.y, _corner.y, _scale.y) && - isWithin(origin.x + axisDistance*direction.x, _corner.x, _scale.x))) { - distance = axisDistance; - face = direction.z > 0 ? MAX_Z_FACE : MIN_Z_FACE; - surfaceNormal = glm::vec3(0.0f, 0.0f, direction.z > 0 ? 1.0f : -1.0f); - return true; - } - // This case is unexpected, but mimics the previous behavior for inside out intersections - distance = 0; - return true; - } +bool AABox::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, const glm::vec3& invDirection, + float& distance, BoxFace& face, glm::vec3& surfaceNormal) const { + return findRayAABoxIntersection(origin, direction, invDirection, _corner, _scale, distance, face, surfaceNormal); +} - // check each axis - float axisDistance; - if ((findIntersection(origin.x, direction.x, _corner.x, _scale.x, axisDistance) && axisDistance >= 0 && - isWithin(origin.y + axisDistance*direction.y, _corner.y, _scale.y) && - isWithin(origin.z + axisDistance*direction.z, _corner.z, _scale.z))) { - distance = axisDistance; - face = direction.x > 0 ? MIN_X_FACE : MAX_X_FACE; - surfaceNormal = glm::vec3(direction.x > 0 ? -1.0f : 1.0f, 0.0f, 0.0f); - return true; - } - if ((findIntersection(origin.y, direction.y, _corner.y, _scale.y, axisDistance) && axisDistance >= 0 && - isWithin(origin.x + axisDistance*direction.x, _corner.x, _scale.x) && - isWithin(origin.z + axisDistance*direction.z, _corner.z, _scale.z))) { - distance = axisDistance; - face = direction.y > 0 ? MIN_Y_FACE : MAX_Y_FACE; - surfaceNormal = glm::vec3(0.0f, direction.y > 0 ? -1.0f : 1.0f, 0.0f); - return true; - } - if ((findIntersection(origin.z, direction.z, _corner.z, _scale.z, axisDistance) && axisDistance >= 0 && - isWithin(origin.y + axisDistance*direction.y, _corner.y, _scale.y) && - isWithin(origin.x + axisDistance*direction.x, _corner.x, _scale.x))) { - distance = axisDistance; - face = direction.z > 0 ? MIN_Z_FACE : MAX_Z_FACE; - surfaceNormal = glm::vec3(0.0f, 0.0f, direction.z > 0 ? -1.0f : 1.0f); - return true; - } - return false; +bool AABox::findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal) const { + return findParabolaAABoxIntersection(origin, velocity, acceleration, _corner, _scale, parabolicDistance, face, surfaceNormal); } bool AABox::rayHitsBoundingSphere(const glm::vec3& origin, const glm::vec3& direction) const { @@ -296,6 +221,29 @@ bool AABox::rayHitsBoundingSphere(const glm::vec3& origin, const glm::vec3& dire || (glm::abs(distance) > 0.0f && glm::distance2(distance * direction, localCenter) < radiusSquared)); } +bool AABox::parabolaPlaneIntersectsBoundingSphere(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, const glm::vec3& normal) const { + glm::vec3 localCenter = calcCenter() - origin; + const float ONE_OVER_TWO_SQUARED = 0.25f; + float radiusSquared = ONE_OVER_TWO_SQUARED * glm::length2(_scale); + + // origin is inside the sphere + if (glm::length2(localCenter) < radiusSquared) { + return true; + } + + if (glm::length2(acceleration) < EPSILON) { + // Handle the degenerate case where acceleration == (0, 0, 0) + return rayHitsBoundingSphere(origin, glm::normalize(velocity)); + } else { + // Project vector from plane to sphere center onto the normal + float distance = glm::dot(localCenter, normal); + if (distance * distance < radiusSquared) { + return true; + } + } + return false; +} + bool AABox::touchesSphere(const glm::vec3& center, float radius) const { // Avro's algorithm from this paper: http://www.mrtc.mdh.se/projects/3Dgraphics/paperF.pdf glm::vec3 e = glm::max(_corner - center, Vectors::ZERO) + glm::max(center - _corner - _scale, Vectors::ZERO); @@ -521,28 +469,6 @@ AABox AABox::clamp(float min, float max) const { return AABox(clampedCorner, clampedScale); } -AABox& AABox::operator += (const glm::vec3& point) { - - if (isInvalid()) { - _corner = glm::min(_corner, point); - } else { - glm::vec3 maximum(_corner + _scale); - _corner = glm::min(_corner, point); - maximum = glm::max(maximum, point); - _scale = maximum - _corner; - } - - return (*this); -} - -AABox& AABox::operator += (const AABox& box) { - if (!box.isInvalid()) { - (*this) += box._corner; - (*this) += box.calcTopFarLeft(); - } - return (*this); -} - void AABox::embiggen(float scale) { _corner += scale * (-0.5f * _scale); _scale *= scale; diff --git a/libraries/shared/src/AABox.h b/libraries/shared/src/AABox.h index cf79cf9d04..f41bb8a814 100644 --- a/libraries/shared/src/AABox.h +++ b/libraries/shared/src/AABox.h @@ -69,9 +69,12 @@ public: bool expandedContains(const glm::vec3& point, float expansion) const; bool expandedIntersectsSegment(const glm::vec3& start, const glm::vec3& end, float expansion) const; - bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, - BoxFace& face, glm::vec3& surfaceNormal) const; + bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, const glm::vec3& invDirection, float& distance, + BoxFace& face, glm::vec3& surfaceNormal) const; + bool findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal) const; bool rayHitsBoundingSphere(const glm::vec3& origin, const glm::vec3& direction) const; + bool parabolaPlaneIntersectsBoundingSphere(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, const glm::vec3& normal) const; bool touchesSphere(const glm::vec3& center, float radius) const; // fast but may generate false positives bool touchesAAEllipsoid(const glm::vec3& center, const glm::vec3& radials) const; bool findSpherePenetration(const glm::vec3& center, float radius, glm::vec3& penetration) const; @@ -82,8 +85,23 @@ public: AABox clamp(const glm::vec3& min, const glm::vec3& max) const; AABox clamp(float min, float max) const; - AABox& operator += (const glm::vec3& point); - AABox& operator += (const AABox& box); + inline AABox& operator+=(const glm::vec3& point) { + bool valid = !isInvalid(); + glm::vec3 maximum = glm::max(_corner + _scale, point); + _corner = glm::min(_corner, point); + if (valid) { + _scale = maximum - _corner; + } + return (*this); + } + + inline AABox& operator+=(const AABox& box) { + if (!box.isInvalid()) { + (*this) += box._corner; + (*this) += box.calcTopFarLeft(); + } + return (*this); + } // Translate the AABox just moving the corner void translate(const glm::vec3& translation) { _corner += translation; } @@ -111,7 +129,7 @@ public: static const glm::vec3 INFINITY_VECTOR; - bool isInvalid() const { return _corner == INFINITY_VECTOR; } + bool isInvalid() const { return _corner.x == std::numeric_limits::infinity(); } void clear() { _corner = INFINITY_VECTOR; _scale = glm::vec3(0.0f); } @@ -136,6 +154,9 @@ private: static BoxFace getOppositeFace(BoxFace face); + void checkPossibleParabolicIntersection(float t, int i, float& minDistance, + const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, bool& hit) const; + glm::vec3 _corner; glm::vec3 _scale; }; diff --git a/libraries/shared/src/AACube.cpp b/libraries/shared/src/AACube.cpp index 7dd2f8cb5b..af2e531917 100644 --- a/libraries/shared/src/AACube.cpp +++ b/libraries/shared/src/AACube.cpp @@ -110,15 +110,8 @@ glm::vec3 AACube::getNearestVertex(const glm::vec3& normal) const { return result; } -// determines whether a value is within the extents -static bool isWithin(float value, float corner, float size) { - return value >= corner && value <= corner + size; -} - bool AACube::contains(const glm::vec3& point) const { - return isWithin(point.x, _corner.x, _scale) && - isWithin(point.y, _corner.y, _scale) && - isWithin(point.z, _corner.z, _scale); + return aaBoxContains(point, _corner, glm::vec3(_scale)); } bool AACube::contains(const AACube& otherCube) const { @@ -170,30 +163,6 @@ bool AACube::expandedContains(const glm::vec3& point, float expansion) const { isWithinExpanded(point.z, _corner.z, _scale, expansion); } -// finds the intersection between a ray and the facing plane on one axis -static bool findIntersection(float origin, float direction, float corner, float size, float& distance) { - if (direction > EPSILON) { - distance = (corner - origin) / direction; - return true; - } else if (direction < -EPSILON) { - distance = (corner + size - origin) / direction; - return true; - } - return false; -} - -// finds the intersection between a ray and the inside facing plane on one axis -static bool findInsideOutIntersection(float origin, float direction, float corner, float size, float& distance) { - if (direction > EPSILON) { - distance = -1.0f * (origin - (corner + size)) / direction; - return true; - } else if (direction < -EPSILON) { - distance = -1.0f * (origin - corner) / direction; - return true; - } - return false; -} - bool AACube::expandedIntersectsSegment(const glm::vec3& start, const glm::vec3& end, float expansion) const { // handle the trivial cases where the expanded box contains the start or end if (expandedContains(start, expansion) || expandedContains(end, expansion)) { @@ -218,69 +187,14 @@ bool AACube::expandedIntersectsSegment(const glm::vec3& start, const glm::vec3& isWithin(start.x + axisDistance*direction.x, expandedCorner.x, expandedSize.x)); } -bool AACube::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, - BoxFace& face, glm::vec3& surfaceNormal) const { - // handle the trivial case where the box contains the origin - if (contains(origin)) { +bool AACube::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, const glm::vec3& invDirection, + float& distance, BoxFace& face, glm::vec3& surfaceNormal) const { + return findRayAABoxIntersection(origin, direction, invDirection, _corner, glm::vec3(_scale), distance, face, surfaceNormal); +} - // We still want to calculate the distance from the origin to the inside out plane - float axisDistance; - if ((findInsideOutIntersection(origin.x, direction.x, _corner.x, _scale, axisDistance) && axisDistance >= 0 && - isWithin(origin.y + axisDistance*direction.y, _corner.y, _scale) && - isWithin(origin.z + axisDistance*direction.z, _corner.z, _scale))) { - distance = axisDistance; - face = direction.x > 0 ? MAX_X_FACE : MIN_X_FACE; - surfaceNormal = glm::vec3(direction.x > 0 ? 1.0f : -1.0f, 0.0f, 0.0f); - return true; - } - if ((findInsideOutIntersection(origin.y, direction.y, _corner.y, _scale, axisDistance) && axisDistance >= 0 && - isWithin(origin.x + axisDistance*direction.x, _corner.x, _scale) && - isWithin(origin.z + axisDistance*direction.z, _corner.z, _scale))) { - distance = axisDistance; - face = direction.y > 0 ? MAX_Y_FACE : MIN_Y_FACE; - surfaceNormal = glm::vec3(0.0f, direction.y > 0 ? 1.0f : -1.0f, 0.0f); - return true; - } - if ((findInsideOutIntersection(origin.z, direction.z, _corner.z, _scale, axisDistance) && axisDistance >= 0 && - isWithin(origin.y + axisDistance*direction.y, _corner.y, _scale) && - isWithin(origin.x + axisDistance*direction.x, _corner.x, _scale))) { - distance = axisDistance; - face = direction.z > 0 ? MAX_Z_FACE : MIN_Z_FACE; - surfaceNormal = glm::vec3(0.0f, 0.0f, direction.z > 0 ? 1.0f : -1.0f); - return true; - } - // This case is unexpected, but mimics the previous behavior for inside out intersections - distance = 0; - return true; - } - - // check each axis - float axisDistance; - if ((findIntersection(origin.x, direction.x, _corner.x, _scale, axisDistance) && axisDistance >= 0 && - isWithin(origin.y + axisDistance*direction.y, _corner.y, _scale) && - isWithin(origin.z + axisDistance*direction.z, _corner.z, _scale))) { - distance = axisDistance; - face = direction.x > 0 ? MIN_X_FACE : MAX_X_FACE; - surfaceNormal = glm::vec3(direction.x > 0 ? -1.0f : 1.0f, 0.0f, 0.0f); - return true; - } - if ((findIntersection(origin.y, direction.y, _corner.y, _scale, axisDistance) && axisDistance >= 0 && - isWithin(origin.x + axisDistance*direction.x, _corner.x, _scale) && - isWithin(origin.z + axisDistance*direction.z, _corner.z, _scale))) { - distance = axisDistance; - face = direction.y > 0 ? MIN_Y_FACE : MAX_Y_FACE; - surfaceNormal = glm::vec3(0.0f, direction.y > 0 ? -1.0f : 1.0f, 0.0f); - return true; - } - if ((findIntersection(origin.z, direction.z, _corner.z, _scale, axisDistance) && axisDistance >= 0 && - isWithin(origin.y + axisDistance*direction.y, _corner.y, _scale) && - isWithin(origin.x + axisDistance*direction.x, _corner.x, _scale))) { - distance = axisDistance; - face = direction.z > 0 ? MIN_Z_FACE : MAX_Z_FACE; - surfaceNormal = glm::vec3(0.0f, 0.0f, direction.z > 0 ? -1.0f : 1.0f); - return true; - } - return false; +bool AACube::findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal) const { + return findParabolaAABoxIntersection(origin, velocity, acceleration, _corner, glm::vec3(_scale), parabolicDistance, face, surfaceNormal); } bool AACube::touchesSphere(const glm::vec3& center, float radius) const { diff --git a/libraries/shared/src/AACube.h b/libraries/shared/src/AACube.h index 87a38cb304..66b29e3185 100644 --- a/libraries/shared/src/AACube.h +++ b/libraries/shared/src/AACube.h @@ -56,8 +56,10 @@ public: bool touches(const AABox& otherBox) const; bool expandedContains(const glm::vec3& point, float expansion) const; bool expandedIntersectsSegment(const glm::vec3& start, const glm::vec3& end, float expansion) const; - bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, - BoxFace& face, glm::vec3& surfaceNormal) const; + bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, const glm::vec3& invDirection, + float& distance, BoxFace& face, glm::vec3& surfaceNormal) const; + bool findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal) const; bool touchesSphere(const glm::vec3& center, float radius) const; bool findSpherePenetration(const glm::vec3& center, float radius, glm::vec3& penetration) const; bool findCapsulePenetration(const glm::vec3& start, const glm::vec3& end, float radius, glm::vec3& penetration) const; diff --git a/libraries/shared/src/AvatarConstants.h b/libraries/shared/src/AvatarConstants.h index 3dc344dc6f..986d39e94d 100644 --- a/libraries/shared/src/AvatarConstants.h +++ b/libraries/shared/src/AvatarConstants.h @@ -23,10 +23,10 @@ const float DEFAULT_AVATAR_EYE_HEIGHT = DEFAULT_AVATAR_HEIGHT - DEFAULT_AVATAR_E const float DEFAULT_AVATAR_SUPPORT_BASE_LEFT = -0.25f; const float DEFAULT_AVATAR_SUPPORT_BASE_RIGHT = 0.25f; const float DEFAULT_AVATAR_SUPPORT_BASE_FRONT = -0.20f; -const float DEFAULT_AVATAR_SUPPORT_BASE_BACK = 0.10f; +const float DEFAULT_AVATAR_SUPPORT_BASE_BACK = 0.12f; const float DEFAULT_AVATAR_LATERAL_STEPPING_THRESHOLD = 0.10f; const float DEFAULT_AVATAR_ANTERIOR_STEPPING_THRESHOLD = 0.04f; -const float DEFAULT_AVATAR_POSTERIOR_STEPPING_THRESHOLD = 0.07f; +const float DEFAULT_AVATAR_POSTERIOR_STEPPING_THRESHOLD = 0.05f; const float DEFAULT_AVATAR_HEAD_ANGULAR_VELOCITY_STEPPING_THRESHOLD = 0.3f; const float DEFAULT_AVATAR_MODE_HEIGHT_STEPPING_THRESHOLD = -0.02f; const float DEFAULT_HANDS_VELOCITY_DIRECTION_STEPPING_THRESHOLD = 0.4f; @@ -34,7 +34,7 @@ const float DEFAULT_HANDS_ANGULAR_VELOCITY_STEPPING_THRESHOLD = 3.3f; const float DEFAULT_HEAD_VELOCITY_STEPPING_THRESHOLD = 0.18f; const float DEFAULT_HEAD_PITCH_STEPPING_TOLERANCE = 7.0f; const float DEFAULT_HEAD_ROLL_STEPPING_TOLERANCE = 7.0f; -const float DEFAULT_AVATAR_SPINE_STRETCH_LIMIT = 0.07f; +const float DEFAULT_AVATAR_SPINE_STRETCH_LIMIT = 0.04f; const float DEFAULT_AVATAR_FORWARD_DAMPENING_FACTOR = 0.5f; const float DEFAULT_AVATAR_LATERAL_DAMPENING_FACTOR = 2.0f; const float DEFAULT_AVATAR_HIPS_MASS = 40.0f; @@ -44,7 +44,7 @@ const float DEFAULT_AVATAR_RIGHTHAND_MASS = 2.0f; // Used when avatar is missing joints... (avatar space) const glm::quat DEFAULT_AVATAR_MIDDLE_EYE_ROT { Quaternions::Y_180 }; -const glm::vec3 DEFAULT_AVATAR_MIDDLE_EYE_POS { 0.0f, 0.6f, 0.0f }; +const glm::vec3 DEFAULT_AVATAR_HEAD_TO_MIDDLE_EYE_OFFSET = { 0.0f, 0.06f, -0.09f }; const glm::vec3 DEFAULT_AVATAR_HEAD_POS { 0.0f, 0.53f, 0.0f }; const glm::quat DEFAULT_AVATAR_HEAD_ROT { Quaternions::Y_180 }; const glm::vec3 DEFAULT_AVATAR_RIGHTARM_POS { -0.134824f, 0.396348f, -0.0515777f }; @@ -66,6 +66,7 @@ const glm::vec3 DEFAULT_AVATAR_RIGHTFOOT_POS { 0.08f, -0.96f, 0.029f }; const glm::quat DEFAULT_AVATAR_RIGHTFOOT_ROT { -0.4016716778278351f, 0.9154615998268127f, 0.0053307069465518f, 0.023696165531873703f }; const float DEFAULT_AVATAR_MAX_WALKING_SPEED = 2.6f; // meters / second +const float DEFAULT_AVATAR_MAX_WALKING_BACKWARD_SPEED = 2.2f; // meters / second const float DEFAULT_AVATAR_MAX_FLYING_SPEED = 30.0f; // meters / second const float DEFAULT_AVATAR_GRAVITY = -5.0f; // meters / second^2 diff --git a/libraries/shared/src/BoxBase.cpp b/libraries/shared/src/BoxBase.cpp new file mode 100644 index 0000000000..0b790dc2b0 --- /dev/null +++ b/libraries/shared/src/BoxBase.cpp @@ -0,0 +1,46 @@ +// +// Created by Sam Gondelman on 7/20/18 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "BoxBase.h" + +QString boxFaceToString(BoxFace face) { + switch (face) { + case MIN_X_FACE: + return "MIN_X_FACE"; + case MAX_X_FACE: + return "MAX_X_FACE"; + case MIN_Y_FACE: + return "MIN_Y_FACE"; + case MAX_Y_FACE: + return "MAX_Y_FACE"; + case MIN_Z_FACE: + return "MIN_Z_FACE"; + case MAX_Z_FACE: + return "MAX_Z_FACE"; + default: + return "UNKNOWN_FACE"; + } +} + +BoxFace boxFaceFromString(const QString& face) { + if (face == "MIN_X_FACE") { + return MIN_X_FACE; + } else if (face == "MAX_X_FACE") { + return MAX_X_FACE; + } else if (face == "MIN_Y_FACE") { + return MIN_Y_FACE; + } else if (face == "MAX_Y_FACE") { + return MAX_Y_FACE; + } else if (face == "MIN_Z_FACE") { + return MIN_Z_FACE; + } else if (face == "MAX_Z_FACE") { + return MAX_Z_FACE; + } else { + return UNKNOWN_FACE; + } +} \ No newline at end of file diff --git a/libraries/shared/src/BoxBase.h b/libraries/shared/src/BoxBase.h index 7f1dd4d34c..9bc2115d9e 100644 --- a/libraries/shared/src/BoxBase.h +++ b/libraries/shared/src/BoxBase.h @@ -16,7 +16,26 @@ #define hifi_BoxBase_h #include +#include +/**jsdoc +*

A BoxFace specifies the face of an axis-aligned (AA) box. +* +* +* +* +* +* +* +* +* +* +* +* +* +*
ValueDescription
"MIN_X_FACE"The minimum x-axis face.
"MAX_X_FACE"The maximum x-axis face.
"MIN_Y_FACE"The minimum y-axis face.
"MAX_Y_FACE"The maximum y-axis face.
"MIN_Z_FACE"The minimum z-axis face.
"MAX_Z_FACE"The maximum z-axis face.
"UNKNOWN_FACE"Unknown value.
+* @typedef {string} BoxFace +*/ enum BoxFace { MIN_X_FACE, MAX_X_FACE, @@ -27,6 +46,9 @@ enum BoxFace { UNKNOWN_FACE }; +QString boxFaceToString(BoxFace face); +BoxFace boxFaceFromString(const QString& face); + enum BoxVertex { BOTTOM_LEFT_NEAR = 0, BOTTOM_RIGHT_NEAR = 1, diff --git a/libraries/shared/src/DependencyManager.h b/libraries/shared/src/DependencyManager.h index e6fc7ce96b..da877f7b3b 100644 --- a/libraries/shared/src/DependencyManager.h +++ b/libraries/shared/src/DependencyManager.h @@ -139,7 +139,13 @@ QSharedPointer DependencyManager::set(Args&&... args) { template void DependencyManager::destroy() { static size_t hashCode = manager().getHashCode(); - manager().safeGet(hashCode).clear(); + QSharedPointer& shared = manager().safeGet(hashCode); + QWeakPointer weak = shared; + shared.clear(); + // Check that the dependency was actually destroyed. If it wasn't, it was improperly captured somewhere + if (weak.lock()) { + qWarning() << "DependencyManager::destroy():" << typeid(T).name() << "was not properly destroyed!"; + } } template diff --git a/libraries/shared/src/GLMHelpers.cpp b/libraries/shared/src/GLMHelpers.cpp index 4be8ad0e41..d324e5af10 100644 --- a/libraries/shared/src/GLMHelpers.cpp +++ b/libraries/shared/src/GLMHelpers.cpp @@ -294,7 +294,19 @@ glm::vec3 safeEulerAngles(const glm::quat& q) { // Helper function returns the positive angle (in radians) between two 3D vectors float angleBetween(const glm::vec3& v1, const glm::vec3& v2) { - return acosf((glm::dot(v1, v2)) / (glm::length(v1) * glm::length(v2))); + float lengthFactor = glm::length(v1) * glm::length(v2); + + if (lengthFactor < EPSILON) { + qWarning() << "DANGER: don't supply zero-length vec3's as arguments"; + } + + float cosAngle = glm::dot(v1, v2) / lengthFactor; + // If v1 and v2 are colinear, then floating point rounding errors might cause + // cosAngle to be slightly higher than 1 or slightly lower than -1 + // which is are values for which acos is not defined and result in a NaN + // So we clamp the value to insure the value is in the correct range + cosAngle = glm::clamp(cosAngle, -1.0f, 1.0f); + return acosf(cosAngle); } // Helper function return the rotation from the first vector onto the second diff --git a/libraries/shared/src/GLMHelpers.h b/libraries/shared/src/GLMHelpers.h index 7e6ef4cb28..96219ea48c 100644 --- a/libraries/shared/src/GLMHelpers.h +++ b/libraries/shared/src/GLMHelpers.h @@ -316,4 +316,17 @@ inline void glm_mat4u_mul(const glm::mat4& m1, const glm::mat4& m2, glm::mat4& r #endif } +// convert float to int, using round-to-nearest-even (undefined on overflow) +inline int fastLrintf(float x) { +#if GLM_ARCH & GLM_ARCH_SSE2_BIT + return _mm_cvt_ss2si(_mm_set_ss(x)); +#else + // return lrintf(x); + static_assert(std::numeric_limits::is_iec559, "Requires IEEE-754 double precision format"); + union { double d; int64_t i; } bits = { (double)x }; + bits.d += (3ULL << 51); + return (int)bits.i; +#endif +} + #endif // hifi_GLMHelpers_h diff --git a/libraries/shared/src/GeometryUtil.cpp b/libraries/shared/src/GeometryUtil.cpp index 0742a5625b..3f2f7cd7fb 100644 --- a/libraries/shared/src/GeometryUtil.cpp +++ b/libraries/shared/src/GeometryUtil.cpp @@ -1,4 +1,4 @@ -// +// // GeometryUtil.cpp // libraries/shared/src // @@ -15,7 +15,10 @@ #include #include #include +#include +#include #include +#include "glm/gtc/matrix_transform.hpp" #include "NumericalConstants.h" #include "GLMHelpers.h" @@ -187,6 +190,68 @@ glm::vec3 addPenetrations(const glm::vec3& currentPenetration, const glm::vec3& newPenetration - (currentDirection * directionalComponent); } +// finds the intersection between a ray and the facing plane on one axis +bool findIntersection(float origin, float direction, float corner, float size, float& distance) { + if (direction > EPSILON) { + distance = (corner - origin) / direction; + return true; + } else if (direction < -EPSILON) { + distance = (corner + size - origin) / direction; + return true; + } + return false; +} + +// finds the intersection between a ray and the inside facing plane on one axis +bool findInsideOutIntersection(float origin, float direction, float corner, float size, float& distance) { + if (direction > EPSILON) { + distance = -1.0f * (origin - (corner + size)) / direction; + return true; + } else if (direction < -EPSILON) { + distance = -1.0f * (origin - corner) / direction; + return true; + } + return false; +} + +// https://tavianator.com/fast-branchless-raybounding-box-intersections/ +bool findRayAABoxIntersection(const glm::vec3& origin, const glm::vec3& direction, const glm::vec3& invDirection, + const glm::vec3& corner, const glm::vec3& scale, float& distance, BoxFace& face, glm::vec3& surfaceNormal) { + float t1, t2, newTmin, newTmax, tmin = -INFINITY, tmax = INFINITY; + int minAxis = -1, maxAxis = -1; + + for (int i = 0; i < 3; ++i) { + t1 = (corner[i] - origin[i]) * invDirection[i]; + t2 = (corner[i] + scale[i] - origin[i]) * invDirection[i]; + + newTmin = glm::min(t1, t2); + newTmax = glm::max(t1, t2); + + minAxis = newTmin > tmin ? i : minAxis; + tmin = glm::max(tmin, newTmin); + maxAxis = newTmax < tmax ? i : maxAxis; + tmax = glm::min(tmax, newTmax); + } + + if (tmax >= glm::max(tmin, 0.0f)) { + if (tmin < 0.0f) { + distance = tmax; + bool positiveDirection = direction[maxAxis] > 0.0f; + surfaceNormal = glm::vec3(0.0f); + surfaceNormal[maxAxis] = positiveDirection ? -1.0f : 1.0f; + face = positiveDirection ? BoxFace(2 * maxAxis + 1) : BoxFace(2 * maxAxis); + } else { + distance = tmin; + bool positiveDirection = direction[minAxis] > 0.0f; + surfaceNormal = glm::vec3(0.0f); + surfaceNormal[minAxis] = positiveDirection ? -1.0f : 1.0f; + face = positiveDirection ? BoxFace(2 * minAxis) : BoxFace(2 * minAxis + 1); + } + return true; + } + return false; +} + bool findRaySphereIntersection(const glm::vec3& origin, const glm::vec3& direction, const glm::vec3& center, float radius, float& distance) { glm::vec3 relativeOrigin = origin - center; @@ -195,12 +260,13 @@ bool findRaySphereIntersection(const glm::vec3& origin, const glm::vec3& directi distance = 0.0f; return true; // starts inside the sphere } - float b = glm::dot(direction, relativeOrigin); - float radicand = b * b - c; + float b = 2.0f * glm::dot(direction, relativeOrigin); + float a = glm::dot(direction, direction); + float radicand = b * b - 4.0f * a * c; if (radicand < 0.0f) { return false; // doesn't hit the sphere } - float t = -b - sqrtf(radicand); + float t = 0.5f * (-b - sqrtf(radicand)) / a; if (t < 0.0f) { return false; // doesn't hit the sphere } @@ -300,24 +366,34 @@ Triangle Triangle::operator*(const glm::mat4& transform) const { }; } +// https://en.wikipedia.org/wiki/M%C3%B6ller%E2%80%93Trumbore_intersection_algorithm bool findRayTriangleIntersection(const glm::vec3& origin, const glm::vec3& direction, const glm::vec3& v0, const glm::vec3& v1, const glm::vec3& v2, float& distance, bool allowBackface) { - glm::vec3 firstSide = v0 - v1; - glm::vec3 secondSide = v2 - v1; - glm::vec3 normal = glm::cross(secondSide, firstSide); - float dividend = glm::dot(normal, v1) - glm::dot(origin, normal); - if (!allowBackface && dividend > 0.0f) { - return false; // origin below plane - } - float divisor = glm::dot(normal, direction); - if (divisor >= 0.0f) { + glm::vec3 firstSide = v1 - v0; + glm::vec3 secondSide = v2 - v0; + glm::vec3 P = glm::cross(direction, secondSide); + float det = glm::dot(firstSide, P); + if (!allowBackface && det < EPSILON) { + return false; + } else if (fabsf(det) < EPSILON) { return false; } - float t = dividend / divisor; - glm::vec3 point = origin + direction * t; - if (glm::dot(normal, glm::cross(point - v1, firstSide)) > 0.0f && - glm::dot(normal, glm::cross(secondSide, point - v1)) > 0.0f && - glm::dot(normal, glm::cross(point - v0, v2 - v0)) > 0.0f) { + + float invDet = 1.0f / det; + glm::vec3 T = origin - v0; + float u = glm::dot(T, P) * invDet; + if (u < 0.0f || u > 1.0f) { + return false; + } + + glm::vec3 Q = glm::cross(T, firstSide); + float v = glm::dot(direction, Q) * invDet; + if (v < 0.0f || u + v > 1.0f) { + return false; + } + + float t = glm::dot(secondSide, Q) * invDet; + if (t > EPSILON) { distance = t; return true; } @@ -711,6 +787,658 @@ bool findRayRectangleIntersection(const glm::vec3& origin, const glm::vec3& dire return false; } +// determines whether a value is within the extents +bool isWithin(float value, float corner, float size) { + return value >= corner && value <= corner + size; +} + +bool aaBoxContains(const glm::vec3& point, const glm::vec3& corner, const glm::vec3& scale) { + return isWithin(point.x, corner.x, scale.x) && + isWithin(point.y, corner.y, scale.y) && + isWithin(point.z, corner.z, scale.z); +} + +void checkPossibleParabolicIntersectionWithZPlane(float t, float& minDistance, + const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, const glm::vec2& corner, const glm::vec2& scale) { + if (t < minDistance && t > 0.0f && + isWithin(origin.x + velocity.x * t + 0.5f * acceleration.x * t * t, corner.x, scale.x) && + isWithin(origin.y + velocity.y * t + 0.5f * acceleration.y * t * t, corner.y, scale.y)) { + minDistance = t; + } +} + +// Intersect with the plane z = 0 and make sure the intersection is within dimensions +bool findParabolaRectangleIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + const glm::vec2& dimensions, float& parabolicDistance) { + glm::vec2 localCorner = -0.5f * dimensions; + + float minDistance = FLT_MAX; + if (fabsf(acceleration.z) < EPSILON) { + if (fabsf(velocity.z) > EPSILON) { + // Handle the degenerate case where we only have a line in the z-axis + float possibleDistance = -origin.z / velocity.z; + checkPossibleParabolicIntersectionWithZPlane(possibleDistance, minDistance, origin, velocity, acceleration, localCorner, dimensions); + } + } else { + float a = 0.5f * acceleration.z; + float b = velocity.z; + float c = origin.z; + glm::vec2 possibleDistances = { FLT_MAX, FLT_MAX }; + if (computeRealQuadraticRoots(a, b, c, possibleDistances)) { + for (int i = 0; i < 2; i++) { + checkPossibleParabolicIntersectionWithZPlane(possibleDistances[i], minDistance, origin, velocity, acceleration, localCorner, dimensions); + } + } + } + if (minDistance < FLT_MAX) { + parabolicDistance = minDistance; + return true; + } + return false; +} + +bool findParabolaSphereIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + const glm::vec3& center, float radius, float& parabolicDistance) { + glm::vec3 localCenter = center - origin; + float radiusSquared = radius * radius; + + float accelerationLength = glm::length(acceleration); + float minDistance = FLT_MAX; + + if (accelerationLength < EPSILON) { + // Handle the degenerate case where acceleration == (0, 0, 0) + glm::vec3 offset = origin - center; + float a = glm::dot(velocity, velocity); + float b = 2.0f * glm::dot(velocity, offset); + float c = glm::dot(offset, offset) - radius * radius; + glm::vec2 possibleDistances(FLT_MAX); + if (computeRealQuadraticRoots(a, b, c, possibleDistances)) { + for (int i = 0; i < 2; i++) { + if (possibleDistances[i] < minDistance && possibleDistances[i] > 0.0f) { + minDistance = possibleDistances[i]; + } + } + } + } else { + glm::vec3 vectorOnPlane = velocity; + if (fabsf(glm::dot(glm::normalize(velocity), glm::normalize(acceleration))) > 1.0f - EPSILON) { + // Handle the degenerate case where velocity is parallel to acceleration + // We pick t = 1 and calculate a second point on the plane + vectorOnPlane = velocity + 0.5f * acceleration; + } + // Get the normal of the plane, the cross product of two vectors on the plane + glm::vec3 normal = glm::normalize(glm::cross(vectorOnPlane, acceleration)); + + // Project vector from plane to sphere center onto the normal + float distance = glm::dot(localCenter, normal); + // Exit early if the sphere doesn't intersect the plane defined by the parabola + if (fabsf(distance) > radius) { + return false; + } + + glm::vec3 circleCenter = center - distance * normal; + float circleRadius = sqrtf(radiusSquared - distance * distance); + glm::vec3 q = glm::normalize(acceleration); + glm::vec3 p = glm::cross(normal, q); + + float a1 = accelerationLength * 0.5f; + float b1 = glm::dot(velocity, q); + float c1 = glm::dot(origin - circleCenter, q); + float a2 = glm::dot(velocity, p); + float b2 = glm::dot(origin - circleCenter, p); + + float a = a1 * a1; + float b = 2.0f * a1 * b1; + float c = 2.0f * a1 * c1 + b1 * b1 + a2 * a2; + float d = 2.0f * b1 * c1 + 2.0f * a2 * b2; + float e = c1 * c1 + b2 * b2 - circleRadius * circleRadius; + + glm::vec4 possibleDistances(FLT_MAX); + if (computeRealQuarticRoots(a, b, c, d, e, possibleDistances)) { + for (int i = 0; i < 4; i++) { + if (possibleDistances[i] < minDistance && possibleDistances[i] > 0.0f) { + minDistance = possibleDistances[i]; + } + } + } + } + + if (minDistance < FLT_MAX) { + parabolicDistance = minDistance; + return true; + } + return false; +} + +void checkPossibleParabolicIntersectionWithTriangle(float t, float& minDistance, + const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + const glm::vec3& localVelocity, const glm::vec3& localAcceleration, const glm::vec3& normal, + const glm::vec3& v0, const glm::vec3& v1, const glm::vec3& v2, bool allowBackface) { + // Check if we're hitting the backface in the rotated coordinate space + float localIntersectionVelocityZ = localVelocity.z + localAcceleration.z * t; + if (!allowBackface && localIntersectionVelocityZ < 0.0f) { + return; + } + + // Check that the point is within all three sides + glm::vec3 point = origin + velocity * t + 0.5f * acceleration * t * t; + if (t < minDistance && t > 0.0f && + glm::dot(normal, glm::cross(point - v1, v0 - v1)) > 0.0f && + glm::dot(normal, glm::cross(v2 - v1, point - v1)) > 0.0f && + glm::dot(normal, glm::cross(point - v0, v2 - v0)) > 0.0f) { + minDistance = t; + } +} + +bool findParabolaTriangleIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + const glm::vec3& v0, const glm::vec3& v1, const glm::vec3& v2, float& parabolicDistance, bool allowBackface) { + glm::vec3 normal = glm::normalize(glm::cross(v2 - v1, v0 - v1)); + + // We transform the parabola and triangle so that the triangle is in the plane z = 0, with v0 at the origin + glm::quat inverseRot; + // Note: OpenGL view matrix is already the inverse of our camera matrix + // if the direction is nearly aligned with the Y axis, then use the X axis for 'up' + const float MAX_ABS_Y_COMPONENT = 0.9999991f; + if (fabsf(normal.y) > MAX_ABS_Y_COMPONENT) { + inverseRot = glm::quat_cast(glm::lookAt(glm::vec3(0.0f), normal, Vectors::UNIT_X)); + } else { + inverseRot = glm::quat_cast(glm::lookAt(glm::vec3(0.0f), normal, Vectors::UNIT_Y)); + } + + glm::vec3 localOrigin = inverseRot * (origin - v0); + glm::vec3 localVelocity = inverseRot * velocity; + glm::vec3 localAcceleration = inverseRot * acceleration; + + float minDistance = FLT_MAX; + if (fabsf(localAcceleration.z) < EPSILON) { + if (fabsf(localVelocity.z) > EPSILON) { + float possibleDistance = -localOrigin.z / localVelocity.z; + checkPossibleParabolicIntersectionWithTriangle(possibleDistance, minDistance, origin, velocity, acceleration, + localVelocity, localAcceleration, normal, v0, v1, v2, allowBackface); + } + } else { + float a = 0.5f * localAcceleration.z; + float b = localVelocity.z; + float c = localOrigin.z; + glm::vec2 possibleDistances = { FLT_MAX, FLT_MAX }; + if (computeRealQuadraticRoots(a, b, c, possibleDistances)) { + for (int i = 0; i < 2; i++) { + checkPossibleParabolicIntersectionWithTriangle(possibleDistances[i], minDistance, origin, velocity, acceleration, + localVelocity, localAcceleration, normal, v0, v1, v2, allowBackface); + } + } + } + if (minDistance < FLT_MAX) { + parabolicDistance = minDistance; + return true; + } + return false; +} + +bool findParabolaCapsuleIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + const glm::vec3& start, const glm::vec3& end, float radius, const glm::quat& rotation, float& parabolicDistance) { + if (start == end) { + return findParabolaSphereIntersection(origin, velocity, acceleration, start, radius, parabolicDistance); // handle degenerate case + } + if (glm::distance2(origin, start) < radius * radius) { // inside start sphere + float startDistance; + bool intersectsStart = findParabolaSphereIntersection(origin, velocity, acceleration, start, radius, startDistance); + if (glm::distance2(origin, end) < radius * radius) { // also inside end sphere + float endDistance; + bool intersectsEnd = findParabolaSphereIntersection(origin, velocity, acceleration, end, radius, endDistance); + if (endDistance < startDistance) { + parabolicDistance = endDistance; + return intersectsEnd; + } + } + parabolicDistance = startDistance; + return intersectsStart; + } else if (glm::distance2(origin, end) < radius * radius) { // inside end sphere (and not start sphere) + return findParabolaSphereIntersection(origin, velocity, acceleration, end, radius, parabolicDistance); + } + + // We are either inside the middle of the capsule or outside it completely + // Either way, we need to check all three parts of the capsule and find the closest intersection + glm::vec3 results(FLT_MAX); + findParabolaSphereIntersection(origin, velocity, acceleration, start, radius, results[0]); + findParabolaSphereIntersection(origin, velocity, acceleration, end, radius, results[1]); + + // We rotate the infinite cylinder to be aligned with the y-axis and then cap the values at the end + glm::quat inverseRot = glm::inverse(rotation); + glm::vec3 localOrigin = inverseRot * (origin - start); + glm::vec3 localVelocity = inverseRot * velocity; + glm::vec3 localAcceleration = inverseRot * acceleration; + float capsuleLength = glm::length(end - start); + + const float MIN_ACCELERATION_PRODUCT = 0.00001f; + if (fabsf(localAcceleration.x * localAcceleration.z) < MIN_ACCELERATION_PRODUCT) { + // Handle the degenerate case where we only have a line in the XZ plane + float a = localVelocity.x * localVelocity.x + localVelocity.z * localVelocity.z; + float b = 2.0f * (localVelocity.x * localOrigin.x + localVelocity.z * localOrigin.z); + float c = localOrigin.x * localOrigin.x + localOrigin.z * localOrigin.z - radius * radius; + glm::vec2 possibleDistances = { FLT_MAX, FLT_MAX }; + if (computeRealQuadraticRoots(a, b, c, possibleDistances)) { + for (int i = 0; i < 2; i++) { + if (possibleDistances[i] < results[2] && possibleDistances[i] > 0.0f) { + float y = localOrigin.y + localVelocity.y * possibleDistances[i] + 0.5f * localAcceleration.y * possibleDistances[i] * possibleDistances[i]; + if (y > 0.0f && y < capsuleLength) { + results[2] = possibleDistances[i]; + } + } + } + } + } else { + float a = 0.25f * (localAcceleration.x * localAcceleration.x + localAcceleration.z * localAcceleration.z); + float b = localVelocity.x * localAcceleration.x + localVelocity.z * localAcceleration.z; + float c = localOrigin.x * localAcceleration.x + localOrigin.z * localAcceleration.z + localVelocity.x * localVelocity.x + localVelocity.z * localVelocity.z; + float d = 2.0f * (localOrigin.x * localVelocity.x + localOrigin.z * localVelocity.z); + float e = localOrigin.x * localOrigin.x + localOrigin.z * localOrigin.z - radius * radius; + glm::vec4 possibleDistances(FLT_MAX); + if (computeRealQuarticRoots(a, b, c, d, e, possibleDistances)) { + for (int i = 0; i < 4; i++) { + if (possibleDistances[i] < results[2] && possibleDistances[i] > 0.0f) { + float y = localOrigin.y + localVelocity.y * possibleDistances[i] + 0.5f * localAcceleration.y * possibleDistances[i] * possibleDistances[i]; + if (y > 0.0f && y < capsuleLength) { + results[2] = possibleDistances[i]; + } + } + } + } + } + + float minDistance = FLT_MAX; + for (int i = 0; i < 3; i++) { + minDistance = glm::min(minDistance, results[i]); + } + parabolicDistance = minDistance; + return minDistance != FLT_MAX; +} + +void checkPossibleParabolicIntersection(float t, int i, float& minDistance, const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + const glm::vec3& corner, const glm::vec3& scale, bool& hit) { + if (t < minDistance && t > 0.0f && + isWithin(origin[(i + 1) % 3] + velocity[(i + 1) % 3] * t + 0.5f * acceleration[(i + 1) % 3] * t * t, corner[(i + 1) % 3], scale[(i + 1) % 3]) && + isWithin(origin[(i + 2) % 3] + velocity[(i + 2) % 3] * t + 0.5f * acceleration[(i + 2) % 3] * t * t, corner[(i + 2) % 3], scale[(i + 2) % 3])) { + minDistance = t; + hit = true; + } +} + +inline float parabolaVelocityAtT(float velocity, float acceleration, float t) { + return velocity + acceleration * t; +} + +bool findParabolaAABoxIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + const glm::vec3& corner, const glm::vec3& scale, float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal) { + float minDistance = FLT_MAX; + BoxFace minFace = UNKNOWN_FACE; + glm::vec3 minNormal; + glm::vec2 possibleDistances; + float a, b, c; + + // Solve the intersection for each face of the cube. As we go, keep track of the smallest, positive, real distance + // that is within the bounds of the other two dimensions + for (int i = 0; i < 3; i++) { + if (fabsf(acceleration[i]) < EPSILON) { + // Handle the degenerate case where we only have a line in this axis + if (origin[i] < corner[i]) { + { // min + if (velocity[i] > 0.0f) { + float possibleDistance = (corner[i] - origin[i]) / velocity[i]; + bool hit = false; + checkPossibleParabolicIntersection(possibleDistance, i, minDistance, origin, velocity, acceleration, corner, scale, hit); + if (hit) { + minFace = BoxFace(2 * i); + minNormal = glm::vec3(0.0f); + minNormal[i] = -1.0f; + } + } + } + } else if (origin[i] > corner[i] + scale[i]) { + { // max + if (velocity[i] < 0.0f) { + float possibleDistance = (corner[i] + scale[i] - origin[i]) / velocity[i]; + bool hit = false; + checkPossibleParabolicIntersection(possibleDistance, i, minDistance, origin, velocity, acceleration, corner, scale, hit); + if (hit) { + minFace = BoxFace(2 * i + 1); + minNormal = glm::vec3(0.0f); + minNormal[i] = 1.0f; + } + } + } + } else { + { // min + if (velocity[i] < 0.0f) { + float possibleDistance = (corner[i] - origin[i]) / velocity[i]; + bool hit = false; + checkPossibleParabolicIntersection(possibleDistance, i, minDistance, origin, velocity, acceleration, corner, scale, hit); + if (hit) { + minFace = BoxFace(2 * i + 1); + minNormal = glm::vec3(0.0f); + minNormal[i] = 1.0f; + } + } + } + { // max + if (velocity[i] > 0.0f) { + float possibleDistance = (corner[i] + scale[i] - origin[i]) / velocity[i]; + bool hit = false; + checkPossibleParabolicIntersection(possibleDistance, i, minDistance, origin, velocity, acceleration, corner, scale, hit); + if (hit) { + minFace = BoxFace(2 * i); + minNormal = glm::vec3(0.0f); + minNormal[i] = -1.0f; + } + } + } + } + } else { + a = 0.5f * acceleration[i]; + b = velocity[i]; + if (origin[i] < corner[i]) { + // If we're below corner, we have the following cases: + // - within bounds on other axes + // - if +velocity or +acceleration + // - can only hit MIN_FACE with -normal + // - else + // - if +acceleration + // - can only hit MIN_FACE with -normal + // - else if +velocity + // - can hit MIN_FACE with -normal iff velocity at intersection is + + // - else can hit MAX_FACE with +normal iff velocity at intersection is - + if (origin[(i + 1) % 3] > corner[(i + 1) % 3] && origin[(i + 1) % 3] < corner[(i + 1) % 3] + scale[(i + 1) % 3] && + origin[(i + 2) % 3] > corner[(i + 2) % 3] && origin[(i + 2) % 3] < corner[(i + 2) % 3] + scale[(i + 2) % 3]) { + if (velocity[i] > 0.0f || acceleration[i] > 0.0f) { + { // min + c = origin[i] - corner[i]; + possibleDistances = { FLT_MAX, FLT_MAX }; + if (computeRealQuadraticRoots(a, b, c, possibleDistances)) { + bool hit = false; + for (int j = 0; j < 2; j++) { + checkPossibleParabolicIntersection(possibleDistances[j], i, minDistance, origin, velocity, acceleration, corner, scale, hit); + } + if (hit) { + minFace = BoxFace(2 * i); + minNormal = glm::vec3(0.0f); + minNormal[i] = -1.0f; + } + } + } + } + } else { + if (acceleration[i] > 0.0f) { + { // min + c = origin[i] - corner[i]; + possibleDistances = { FLT_MAX, FLT_MAX }; + if (computeRealQuadraticRoots(a, b, c, possibleDistances)) { + bool hit = false; + for (int j = 0; j < 2; j++) { + checkPossibleParabolicIntersection(possibleDistances[j], i, minDistance, origin, velocity, acceleration, corner, scale, hit); + } + if (hit) { + minFace = BoxFace(2 * i); + minNormal = glm::vec3(0.0f); + minNormal[i] = -1.0f; + } + } + } + } else if (velocity[i] > 0.0f) { + bool hit = false; + { // min + c = origin[i] - corner[i]; + possibleDistances = { FLT_MAX, FLT_MAX }; + if (computeRealQuadraticRoots(a, b, c, possibleDistances)) { + for (int j = 0; j < 2; j++) { + if (parabolaVelocityAtT(velocity[i], acceleration[i], possibleDistances[j]) > 0.0f) { + checkPossibleParabolicIntersection(possibleDistances[j], i, minDistance, origin, velocity, acceleration, corner, scale, hit); + } + } + if (hit) { + minFace = BoxFace(2 * i); + minNormal = glm::vec3(0.0f); + minNormal[i] = -1.0f; + } + } + } + if (!hit) { // max + c = origin[i] - (corner[i] + scale[i]); + possibleDistances = { FLT_MAX, FLT_MAX }; + if (computeRealQuadraticRoots(a, b, c, possibleDistances)) { + for (int j = 0; j < 2; j++) { + if (parabolaVelocityAtT(velocity[i], acceleration[i], possibleDistances[j]) < 0.0f) { + checkPossibleParabolicIntersection(possibleDistances[j], i, minDistance, origin, velocity, acceleration, corner, scale, hit); + } + } + if (hit) { + minFace = BoxFace(2 * i + 1); + minNormal = glm::vec3(0.0f); + minNormal[i] = 1.0f; + } + } + } + } + } + } else if (origin[i] > corner[i] + scale[i]) { + // If we're above corner + scale, we have the following cases: + // - within bounds on other axes + // - if -velocity or -acceleration + // - can only hit MAX_FACE with +normal + // - else + // - if -acceleration + // - can only hit MAX_FACE with +normal + // - else if -velocity + // - can hit MAX_FACE with +normal iff velocity at intersection is - + // - else can hit MIN_FACE with -normal iff velocity at intersection is + + if (origin[(i + 1) % 3] > corner[(i + 1) % 3] && origin[(i + 1) % 3] < corner[(i + 1) % 3] + scale[(i + 1) % 3] && + origin[(i + 2) % 3] > corner[(i + 2) % 3] && origin[(i + 2) % 3] < corner[(i + 2) % 3] + scale[(i + 2) % 3]) { + if (velocity[i] < 0.0f || acceleration[i] < 0.0f) { + { // max + c = origin[i] - (corner[i] + scale[i]); + possibleDistances = { FLT_MAX, FLT_MAX }; + if (computeRealQuadraticRoots(a, b, c, possibleDistances)) { + bool hit = false; + for (int j = 0; j < 2; j++) { + checkPossibleParabolicIntersection(possibleDistances[j], i, minDistance, origin, velocity, acceleration, corner, scale, hit); + } + if (hit) { + minFace = BoxFace(2 * i + 1); + minNormal = glm::vec3(0.0f); + minNormal[i] = 1.0f; + } + } + } + } + } else { + if (acceleration[i] < 0.0f) { + { // max + c = origin[i] - (corner[i] + scale[i]); + possibleDistances = { FLT_MAX, FLT_MAX }; + if (computeRealQuadraticRoots(a, b, c, possibleDistances)) { + bool hit = false; + for (int j = 0; j < 2; j++) { + checkPossibleParabolicIntersection(possibleDistances[j], i, minDistance, origin, velocity, acceleration, corner, scale, hit); + } + if (hit) { + minFace = BoxFace(2 * i + 1); + minNormal = glm::vec3(0.0f); + minNormal[i] = 1.0f; + } + } + } + } else if (velocity[i] < 0.0f) { + bool hit = false; + { // max + c = origin[i] - (corner[i] + scale[i]); + possibleDistances = { FLT_MAX, FLT_MAX }; + if (computeRealQuadraticRoots(a, b, c, possibleDistances)) { + for (int j = 0; j < 2; j++) { + if (parabolaVelocityAtT(velocity[i], acceleration[i], possibleDistances[j]) < 0.0f) { + checkPossibleParabolicIntersection(possibleDistances[j], i, minDistance, origin, velocity, acceleration, corner, scale, hit); + } + } + if (hit) { + minFace = BoxFace(2 * i + 1); + minNormal = glm::vec3(0.0f); + minNormal[i] = 1.0f; + } + } + } + if (!hit) { // min + c = origin[i] - corner[i]; + possibleDistances = { FLT_MAX, FLT_MAX }; + if (computeRealQuadraticRoots(a, b, c, possibleDistances)) { + for (int j = 0; j < 2; j++) { + if (parabolaVelocityAtT(velocity[i], acceleration[i], possibleDistances[j]) > 0.0f) { + checkPossibleParabolicIntersection(possibleDistances[j], i, minDistance, origin, velocity, acceleration, corner, scale, hit); + } + } + if (hit) { + minFace = BoxFace(2 * i); + minNormal = glm::vec3(0.0f); + minNormal[i] = -1.0f; + } + } + } + } + } + } else { + // If we're between corner and corner + scale, we have the following cases: + // - within bounds on other axes + // - if -velocity and -acceleration + // - can only hit MIN_FACE with +normal + // - else if +velocity and +acceleration + // - can only hit MAX_FACE with -normal + // - else + // - can hit MIN_FACE with +normal iff velocity at intersection is - + // - can hit MAX_FACE with -normal iff velocity at intersection is + + // - else + // - if -velocity and +acceleration + // - can hit MIN_FACE with -normal iff velocity at intersection is + + // - else if +velocity and -acceleration + // - can hit MAX_FACE with +normal iff velocity at intersection is - + if (origin[(i + 1) % 3] > corner[(i + 1) % 3] && origin[(i + 1) % 3] < corner[(i + 1) % 3] + scale[(i + 1) % 3] && + origin[(i + 2) % 3] > corner[(i + 2) % 3] && origin[(i + 2) % 3] < corner[(i + 2) % 3] + scale[(i + 2) % 3]) { + if (velocity[i] < 0.0f && acceleration[i] < 0.0f) { + { // min + c = origin[i] - corner[i]; + possibleDistances = { FLT_MAX, FLT_MAX }; + if (computeRealQuadraticRoots(a, b, c, possibleDistances)) { + bool hit = false; + for (int j = 0; j < 2; j++) { + checkPossibleParabolicIntersection(possibleDistances[j], i, minDistance, origin, velocity, acceleration, corner, scale, hit); + } + if (hit) { + minFace = BoxFace(2 * i); + minNormal = glm::vec3(0.0f); + minNormal[i] = 1.0f; + } + } + } + } else if (velocity[i] > 0.0f && acceleration[i] > 0.0f) { + { // max + c = origin[i] - (corner[i] + scale[i]); + possibleDistances = { FLT_MAX, FLT_MAX }; + if (computeRealQuadraticRoots(a, b, c, possibleDistances)) { + bool hit = false; + for (int j = 0; j < 2; j++) { + checkPossibleParabolicIntersection(possibleDistances[j], i, minDistance, origin, velocity, acceleration, corner, scale, hit); + } + if (hit) { + minFace = BoxFace(2 * i + 1); + minNormal = glm::vec3(0.0f); + minNormal[i] = -1.0f; + } + } + } + } else { + { // min + c = origin[i] - corner[i]; + possibleDistances = { FLT_MAX, FLT_MAX }; + if (computeRealQuadraticRoots(a, b, c, possibleDistances)) { + bool hit = false; + for (int j = 0; j < 2; j++) { + if (parabolaVelocityAtT(velocity[i], acceleration[i], possibleDistances[j]) < 0.0f) { + checkPossibleParabolicIntersection(possibleDistances[j], i, minDistance, origin, velocity, acceleration, corner, scale, hit); + } + } + if (hit) { + minFace = BoxFace(2 * i); + minNormal = glm::vec3(0.0f); + minNormal[i] = 1.0f; + } + } + } + { // max + c = origin[i] - (corner[i] + scale[i]); + possibleDistances = { FLT_MAX, FLT_MAX }; + if (computeRealQuadraticRoots(a, b, c, possibleDistances)) { + bool hit = false; + for (int j = 0; j < 2; j++) { + if (parabolaVelocityAtT(velocity[i], acceleration[i], possibleDistances[j]) > 0.0f) { + checkPossibleParabolicIntersection(possibleDistances[j], i, minDistance, origin, velocity, acceleration, corner, scale, hit); + } + } + if (hit) { + minFace = BoxFace(2 * i + 1); + minNormal = glm::vec3(0.0f); + minNormal[i] = -1.0f; + } + } + } + } + } else { + if (velocity[i] < 0.0f && acceleration[i] > 0.0f) { + { // min + c = origin[i] - corner[i]; + possibleDistances = { FLT_MAX, FLT_MAX }; + if (computeRealQuadraticRoots(a, b, c, possibleDistances)) { + bool hit = false; + for (int j = 0; j < 2; j++) { + if (parabolaVelocityAtT(velocity[i], acceleration[i], possibleDistances[j]) > 0.0f) { + checkPossibleParabolicIntersection(possibleDistances[j], i, minDistance, origin, velocity, acceleration, corner, scale, hit); + } + } + if (hit) { + minFace = BoxFace(2 * i); + minNormal = glm::vec3(0.0f); + minNormal[i] = -1.0f; + } + } + } + } else if (velocity[i] > 0.0f && acceleration[i] < 0.0f) { + { // max + c = origin[i] - (corner[i] + scale[i]); + possibleDistances = { FLT_MAX, FLT_MAX }; + if (computeRealQuadraticRoots(a, b, c, possibleDistances)) { + bool hit = false; + for (int j = 0; j < 2; j++) { + if (parabolaVelocityAtT(velocity[i], acceleration[i], possibleDistances[j]) < 0.0f) { + checkPossibleParabolicIntersection(possibleDistances[j], i, minDistance, origin, velocity, acceleration, corner, scale, hit); + } + } + if (hit) { + minFace = BoxFace(2 * i + 1); + minNormal = glm::vec3(0.0f); + minNormal[i] = 1.0f; + } + } + } + } + } + } + } + } + + if (minDistance < FLT_MAX) { + parabolicDistance = minDistance; + face = minFace; + surfaceNormal = minNormal; + return true; + } + return false; +} + void swingTwistDecomposition(const glm::quat& rotation, const glm::vec3& direction, glm::quat& swing, @@ -941,3 +1669,142 @@ void generateBoundryLinesForDop14(const std::vector& dots, const glm::vec } } } + +bool computeRealQuadraticRoots(float a, float b, float c, glm::vec2& roots) { + float discriminant = b * b - 4.0f * a * c; + if (discriminant < 0.0f) { + return false; + } else if (discriminant == 0.0f) { + roots.x = (-b + sqrtf(discriminant)) / (2.0f * a); + } else { + float discriminantRoot = sqrtf(discriminant); + roots.x = (-b + discriminantRoot) / (2.0f * a); + roots.y = (-b - discriminantRoot) / (2.0f * a); + } + return true; +} + +// The following functions provide an analytical solution to a quartic equation, adapted from the solution here: https://github.com/sasamil/Quartic +unsigned int solveP3(float* x, float a, float b, float c) { + float a2 = a * a; + float q = (a2 - 3.0f * b) / 9.0f; + float r = (a * (2.0f * a2 - 9.0f * b) + 27.0f * c) / 54.0f; + float r2 = r * r; + float q3 = q * q * q; + float A, B; + if (r2 < q3) { + float t = r / sqrtf(q3); + t = glm::clamp(t, -1.0f, 1.0f); + t = acosf(t); + a /= 3.0f; + q = -2.0f * sqrtf(q); + x[0] = q * cosf(t / 3.0f) - a; + x[1] = q * cosf((t + 2.0f * (float)M_PI) / 3.0f) - a; + x[2] = q * cosf((t - 2.0f * (float)M_PI) / 3.0f) - a; + return 3; + } else { + A = -powf(fabsf(r) + sqrtf(r2 - q3), 1.0f / 3.0f); + if (r < 0) { + A = -A; + } + B = (A == 0.0f ? 0.0f : q / A); + + a /= 3.0f; + x[0] = (A + B) - a; + x[1] = -0.5f * (A + B) - a; + x[2] = 0.5f * sqrtf(3.0f) * (A - B); + if (fabsf(x[2]) < EPSILON) { + x[2] = x[1]; + return 2; + } + + return 1; + } +} + +bool solve_quartic(float a, float b, float c, float d, glm::vec4& roots) { + float a3 = -b; + float b3 = a * c - 4.0f *d; + float c3 = -a * a * d - c * c + 4.0f * b * d; + + float px3[3]; + unsigned int iZeroes = solveP3(px3, a3, b3, c3); + + float q1, q2, p1, p2, D, sqD, y; + + y = px3[0]; + if (iZeroes != 1) { + if (fabsf(px3[1]) > fabsf(y)) { + y = px3[1]; + } + if (fabsf(px3[2]) > fabsf(y)) { + y = px3[2]; + } + } + + D = y * y - 4.0f * d; + if (fabsf(D) < EPSILON) { + q1 = q2 = 0.5f * y; + D = a * a - 4.0f * (b - y); + if (fabsf(D) < EPSILON) { + p1 = p2 = 0.5f * a; + } else { + sqD = sqrtf(D); + p1 = 0.5f * (a + sqD); + p2 = 0.5f * (a - sqD); + } + } else { + sqD = sqrtf(D); + q1 = 0.5f * (y + sqD); + q2 = 0.5f * (y - sqD); + p1 = (a * q1 - c) / (q1 - q2); + p2 = (c - a * q2) / (q1 - q2); + } + + std::complex x1, x2, x3, x4; + D = p1 * p1 - 4.0f * q1; + if (D < 0.0f) { + x1.real(-0.5f * p1); + x1.imag(0.5f * sqrtf(-D)); + x2 = std::conj(x1); + } else { + sqD = sqrtf(D); + x1.real(0.5f * (-p1 + sqD)); + x2.real(0.5f * (-p1 - sqD)); + } + + D = p2 * p2 - 4.0f * q2; + if (D < 0.0f) { + x3.real(-0.5f * p2); + x3.imag(0.5f * sqrtf(-D)); + x4 = std::conj(x3); + } else { + sqD = sqrtf(D); + x3.real(0.5f * (-p2 + sqD)); + x4.real(0.5f * (-p2 - sqD)); + } + + bool hasRealRoot = false; + if (fabsf(x1.imag()) < EPSILON) { + roots.x = x1.real(); + hasRealRoot = true; + } + if (fabsf(x2.imag()) < EPSILON) { + roots.y = x2.real(); + hasRealRoot = true; + } + if (fabsf(x3.imag()) < EPSILON) { + roots.z = x3.real(); + hasRealRoot = true; + } + if (fabsf(x4.imag()) < EPSILON) { + roots.w = x4.real(); + hasRealRoot = true; + } + + return hasRealRoot; +} + +bool computeRealQuarticRoots(float a, float b, float c, float d, float e, glm::vec4& roots) { + return solve_quartic(b / a, c / a, d / a, e / a, roots); +} \ No newline at end of file diff --git a/libraries/shared/src/GeometryUtil.h b/libraries/shared/src/GeometryUtil.h index 4832616fbd..8ec75f71bd 100644 --- a/libraries/shared/src/GeometryUtil.h +++ b/libraries/shared/src/GeometryUtil.h @@ -14,6 +14,7 @@ #include #include +#include "BoxBase.h" class Plane; @@ -73,6 +74,11 @@ bool findCapsulePlanePenetration(const glm::vec3& capsuleStart, const glm::vec3& glm::vec3 addPenetrations(const glm::vec3& currentPenetration, const glm::vec3& newPenetration); +bool findIntersection(float origin, float direction, float corner, float size, float& distance); +bool findInsideOutIntersection(float origin, float direction, float corner, float size, float& distance); +bool findRayAABoxIntersection(const glm::vec3& origin, const glm::vec3& direction, const glm::vec3& invDirection, + const glm::vec3& corner, const glm::vec3& scale, float& distance, BoxFace& face, glm::vec3& surfaceNormal); + bool findRaySphereIntersection(const glm::vec3& origin, const glm::vec3& direction, const glm::vec3& center, float radius, float& distance); @@ -88,6 +94,21 @@ bool findRayRectangleIntersection(const glm::vec3& origin, const glm::vec3& dire bool findRayTriangleIntersection(const glm::vec3& origin, const glm::vec3& direction, const glm::vec3& v0, const glm::vec3& v1, const glm::vec3& v2, float& distance, bool allowBackface = false); +bool findParabolaRectangleIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + const glm::vec2& dimensions, float& parabolicDistance); + +bool findParabolaSphereIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + const glm::vec3& center, float radius, float& distance); + +bool findParabolaTriangleIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + const glm::vec3& v0, const glm::vec3& v1, const glm::vec3& v2, float& parabolicDistance, bool allowBackface = false); + +bool findParabolaCapsuleIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + const glm::vec3& start, const glm::vec3& end, float radius, const glm::quat& rotation, float& parabolicDistance); + +bool findParabolaAABoxIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + const glm::vec3& corner, const glm::vec3& scale, float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal); + /// \brief decomposes rotation into its components such that: rotation = swing * twist /// \param rotation[in] rotation to decompose /// \param direction[in] normalized axis about which the twist happens (typically original direction before rotation applied) @@ -112,6 +133,11 @@ inline bool findRayTriangleIntersection(const glm::vec3& origin, const glm::vec3 return findRayTriangleIntersection(origin, direction, triangle.v0, triangle.v1, triangle.v2, distance, allowBackface); } +inline bool findParabolaTriangleIntersection(const glm::vec3& origin, const glm::vec3& velocity, + const glm::vec3& acceleration, const Triangle& triangle, float& parabolicDistance, bool allowBackface = false) { + return findParabolaTriangleIntersection(origin, velocity, acceleration, triangle.v0, triangle.v1, triangle.v2, parabolicDistance, allowBackface); +} + int clipTriangleWithPlane(const Triangle& triangle, const Plane& plane, Triangle* clippedTriangles, int maxClippedTriangleCount); int clipTriangleWithPlanes(const Triangle& triangle, const Plane* planes, int planeCount, Triangle* clippedTriangles, int maxClippedTriangleCount); @@ -178,4 +204,20 @@ bool findIntersectionOfThreePlanes(const glm::vec4& planeA, const glm::vec4& pla void generateBoundryLinesForDop14(const std::vector& dots, const glm::vec3& center, std::vector& linesOut); +bool computeRealQuadraticRoots(float a, float b, float c, glm::vec2& roots); + +unsigned int solveP3(float *x, float a, float b, float c); +bool solve_quartic(float a, float b, float c, float d, glm::vec4& roots); +bool computeRealQuarticRoots(float a, float b, float c, float d, float e, glm::vec4& roots); + +bool isWithin(float value, float corner, float size); +bool aaBoxContains(const glm::vec3& point, const glm::vec3& corner, const glm::vec3& scale); + +void checkPossibleParabolicIntersectionWithZPlane(float t, float& minDistance, + const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, const glm::vec2& corner, const glm::vec2& scale); +void checkPossibleParabolicIntersectionWithTriangle(float t, float& minDistance, + const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + const glm::vec3& localVelocity, const glm::vec3& localAcceleration, const glm::vec3& normal, + const glm::vec3& v0, const glm::vec3& v1, const glm::vec3& v2, bool allowBackface); + #endif // hifi_GeometryUtil_h diff --git a/libraries/shared/src/HifiConfigVariantMap.cpp b/libraries/shared/src/HifiConfigVariantMap.cpp index 59f660feee..91ec056f1a 100644 --- a/libraries/shared/src/HifiConfigVariantMap.cpp +++ b/libraries/shared/src/HifiConfigVariantMap.cpp @@ -91,58 +91,7 @@ QVariantMap HifiConfigVariantMap::mergeCLParametersWithJSONConfig(const QStringL return mergedMap; } -void HifiConfigVariantMap::loadConfig(const QStringList& argumentList) { - // load the user config - const QString USER_CONFIG_FILE_OPTION = "--user-config"; - static const QString USER_CONFIG_FILE_NAME = "config.json"; - - int userConfigIndex = argumentList.indexOf(USER_CONFIG_FILE_OPTION); - if (userConfigIndex != -1) { - _userConfigFilename = argumentList[userConfigIndex + 1]; - } else { - // we weren't passed a user config path - _userConfigFilename = PathUtils::getAppDataFilePath(USER_CONFIG_FILE_NAME); - - // as of 1/19/2016 this path was moved so we attempt a migration for first run post migration here - - // figure out what the old path was - - // if our build version is "dev" we should migrate from a different organization folder - - auto oldConfigFilename = QString("%1/%2/%3/%4").arg(QStandardPaths::writableLocation(QStandardPaths::ConfigLocation), - QCoreApplication::organizationName(), - QCoreApplication::applicationName(), - USER_CONFIG_FILE_NAME); - - oldConfigFilename = oldConfigFilename.replace("High Fidelity - dev", "High Fidelity"); - - - // check if there's already a config file at the new path - QFile newConfigFile { _userConfigFilename }; - if (!newConfigFile.exists()) { - - QFile oldConfigFile { oldConfigFilename }; - - if (oldConfigFile.exists()) { - // we have the old file and not the new file - time to copy the file - - // make the destination directory if it doesn't exist - auto dataDirectory = PathUtils::getAppDataPath(); - if (QDir().mkpath(dataDirectory)) { - if (oldConfigFile.copy(_userConfigFilename)) { - qCDebug(shared) << "Migrated config file from" << oldConfigFilename << "to" << _userConfigFilename; - } else { - qCWarning(shared) << "Could not copy previous config file from" << oldConfigFilename << "to" << _userConfigFilename - << "- please try to copy manually and restart."; - } - } else { - qCWarning(shared) << "Could not create application data directory" << dataDirectory << "- unable to migrate previous config file."; - } - } - } - - } - +void HifiConfigVariantMap::loadConfig() { loadMapFromJSONFile(_userConfig, _userConfigFilename); } diff --git a/libraries/shared/src/HifiConfigVariantMap.h b/libraries/shared/src/HifiConfigVariantMap.h index ee248ec3d2..5725ae8f34 100644 --- a/libraries/shared/src/HifiConfigVariantMap.h +++ b/libraries/shared/src/HifiConfigVariantMap.h @@ -21,7 +21,7 @@ class HifiConfigVariantMap { public: static QVariantMap mergeCLParametersWithJSONConfig(const QStringList& argumentList); - void loadConfig(const QStringList& argumentList); + void loadConfig(); const QVariant value(const QString& key) const { return _userConfig.value(key); } QVariant* valueForKeyPath(const QString& keyPath, bool shouldCreateIfMissing = false) @@ -30,6 +30,7 @@ public: QVariantMap& getConfig() { return _userConfig; } const QString& getUserConfigFilename() const { return _userConfigFilename; } + void setUserConfigFilename(const QString& filename) { _userConfigFilename = filename; } private: QString _userConfigFilename; diff --git a/libraries/shared/src/LogHandler.cpp b/libraries/shared/src/LogHandler.cpp index 45cf01510d..65651373be 100644 --- a/libraries/shared/src/LogHandler.cpp +++ b/libraries/shared/src/LogHandler.cpp @@ -33,17 +33,12 @@ LogHandler& LogHandler::getInstance() { } LogHandler::LogHandler() { - // when the log handler is first setup we should print our timezone - QString timezoneString = "Time zone: " + QDateTime::currentDateTime().toString("t"); - printMessage(LogMsgType::LogInfo, QMessageLogContext(), timezoneString); - // make sure we setup the repeated message flusher, but do it on the LogHandler thread QMetaObject::invokeMethod(this, "setupRepeatedMessageFlusher"); } LogHandler::~LogHandler() { flushRepeatedMessages(); - printMessage(LogMsgType::LogDebug, QMessageLogContext(), "LogHandler shutdown."); } const char* stringForLogType(LogMsgType msgType) { diff --git a/libraries/shared/src/NestableTransformNode.cpp b/libraries/shared/src/NestableTransformNode.cpp new file mode 100644 index 0000000000..17456d69ce --- /dev/null +++ b/libraries/shared/src/NestableTransformNode.cpp @@ -0,0 +1,31 @@ +// +// Created by Sabrina Shanman 8/14/2018 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "NestableTransformNode.h" + +NestableTransformNode::NestableTransformNode(SpatiallyNestableWeakPointer spatiallyNestable, int jointIndex) : + _spatiallyNestable(spatiallyNestable), + _jointIndex(jointIndex) +{ +} + +Transform NestableTransformNode::getTransform() { + auto nestable = _spatiallyNestable.lock(); + if (!nestable) { + return Transform(); + } + + bool success; + Transform jointWorldTransform = nestable->getTransform(_jointIndex, success); + + if (success) { + return jointWorldTransform; + } else { + return Transform(); + } +} \ No newline at end of file diff --git a/libraries/shared/src/NestableTransformNode.h b/libraries/shared/src/NestableTransformNode.h new file mode 100644 index 0000000000..131de9e786 --- /dev/null +++ b/libraries/shared/src/NestableTransformNode.h @@ -0,0 +1,25 @@ +// +// Created by Sabrina Shanman 8/14/2018 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#ifndef hifi_NestableTransformNode_h +#define hifi_NestableTransformNode_h + +#include "TransformNode.h" + +#include "SpatiallyNestable.h" + +class NestableTransformNode : public TransformNode { +public: + NestableTransformNode(SpatiallyNestableWeakPointer spatiallyNestable, int jointIndex); + Transform getTransform() override; + +protected: + SpatiallyNestableWeakPointer _spatiallyNestable; + int _jointIndex; +}; + +#endif // hifi_NestableTransformNode_h \ No newline at end of file diff --git a/libraries/shared/src/NumericalConstants.h b/libraries/shared/src/NumericalConstants.h index 4c24a73226..8377c48960 100644 --- a/libraries/shared/src/NumericalConstants.h +++ b/libraries/shared/src/NumericalConstants.h @@ -36,6 +36,7 @@ const float METERS_PER_MILLIMETER = 0.001f; const float MILLIMETERS_PER_METER = 1000.0f; const quint64 NSECS_PER_USEC = 1000; const quint64 NSECS_PER_MSEC = 1000000; +const quint64 NSECS_PER_SECOND = 1e9; const quint64 USECS_PER_MSEC = 1000; const quint64 MSECS_PER_SECOND = 1000; const quint64 USECS_PER_SECOND = USECS_PER_MSEC * MSECS_PER_SECOND; diff --git a/libraries/shared/src/PathUtils.cpp b/libraries/shared/src/PathUtils.cpp index 7fc94db6af..e66121f159 100644 --- a/libraries/shared/src/PathUtils.cpp +++ b/libraries/shared/src/PathUtils.cpp @@ -90,7 +90,6 @@ const QString& PathUtils::resourcesPath() { staticResourcePath = projectRootPath() + "/interface/resources/"; } #endif - qDebug() << "Resource path resolved to " << staticResourcePath; }); return staticResourcePath; } @@ -105,7 +104,6 @@ const QString& PathUtils::resourcesUrl() { staticResourcePath = QUrl::fromLocalFile(projectRootPath() + "/interface/resources/").toString(); } #endif - qDebug() << "Resource url resolved to " << staticResourcePath; }); return staticResourcePath; } diff --git a/libraries/shared/src/PerfStat.cpp b/libraries/shared/src/PerfStat.cpp index c3bc44b7d3..2fc1fb3c9b 100644 --- a/libraries/shared/src/PerfStat.cpp +++ b/libraries/shared/src/PerfStat.cpp @@ -80,16 +80,19 @@ void PerformanceTimerRecord::tallyResult(const quint64& now) { // ---------------------------------------------------------------------------- std::atomic PerformanceTimer::_isActive(false); +std::mutex PerformanceTimer::_mutex; QHash PerformanceTimer::_fullNames; QMap PerformanceTimer::_records; - PerformanceTimer::PerformanceTimer(const QString& name) { if (_isActive) { _name = name; - QString& fullName = _fullNames[QThread::currentThread()]; - fullName.append("/"); - fullName.append(_name); + { + std::lock_guard guard(_mutex); + QString& fullName = _fullNames[QThread::currentThread()]; + fullName.append("/"); + fullName.append(_name); + } _start = usecTimestampNow(); } } @@ -97,6 +100,7 @@ PerformanceTimer::PerformanceTimer(const QString& name) { PerformanceTimer::~PerformanceTimer() { if (_isActive && _start != 0) { quint64 elapsedUsec = (usecTimestampNow() - _start); + std::lock_guard guard(_mutex); QString& fullName = _fullNames[QThread::currentThread()]; PerformanceTimerRecord& namedRecord = _records[fullName]; namedRecord.accumulateResult(elapsedUsec); @@ -111,11 +115,13 @@ bool PerformanceTimer::isActive() { // static QString PerformanceTimer::getContextName() { + std::lock_guard guard(_mutex); return _fullNames[QThread::currentThread()]; } // static void PerformanceTimer::addTimerRecord(const QString& fullName, quint64 elapsedUsec) { + std::lock_guard guard(_mutex); PerformanceTimerRecord& namedRecord = _records[fullName]; namedRecord.accumulateResult(elapsedUsec); } @@ -125,6 +131,7 @@ void PerformanceTimer::setActive(bool active) { if (active != _isActive) { _isActive.store(active); if (!active) { + std::lock_guard guard(_mutex); _fullNames.clear(); _records.clear(); } @@ -133,8 +140,15 @@ void PerformanceTimer::setActive(bool active) { } } +// static +QMap PerformanceTimer::getAllTimerRecords() { + std::lock_guard guard(_mutex); + return _records; +}; + // static void PerformanceTimer::tallyAllTimerRecords() { + std::lock_guard guard(_mutex); QMap::iterator recordsItr = _records.begin(); QMap::const_iterator recordsEnd = _records.end(); quint64 now = usecTimestampNow(); @@ -150,6 +164,7 @@ void PerformanceTimer::tallyAllTimerRecords() { } void PerformanceTimer::dumpAllTimerRecords() { + std::lock_guard guard(_mutex); QMapIterator i(_records); while (i.hasNext()) { i.next(); diff --git a/libraries/shared/src/PerfStat.h b/libraries/shared/src/PerfStat.h index b09cb38808..f9ff6960b0 100644 --- a/libraries/shared/src/PerfStat.h +++ b/libraries/shared/src/PerfStat.h @@ -84,8 +84,7 @@ public: static QString getContextName(); static void addTimerRecord(const QString& fullName, quint64 elapsedUsec); - static const PerformanceTimerRecord& getTimerRecord(const QString& name) { return _records[name]; }; - static const QMap& getAllTimerRecords() { return _records; }; + static QMap getAllTimerRecords(); static void tallyAllTimerRecords(); static void dumpAllTimerRecords(); @@ -93,6 +92,8 @@ private: quint64 _start = 0; QString _name; static std::atomic _isActive; + + static std::mutex _mutex; // used to guard multi-threaded access to _fullNames and _records static QHash _fullNames; static QMap _records; }; diff --git a/libraries/shared/src/PhysicsCollisionGroups.h b/libraries/shared/src/PhysicsCollisionGroups.h index 9d99ec3532..cae3918a3f 100644 --- a/libraries/shared/src/PhysicsCollisionGroups.h +++ b/libraries/shared/src/PhysicsCollisionGroups.h @@ -59,7 +59,11 @@ const int32_t BULLET_COLLISION_MASK_KINEMATIC = BULLET_COLLISION_MASK_STATIC; // MY_AVATAR does not collide with itself const int32_t BULLET_COLLISION_MASK_MY_AVATAR = ~(BULLET_COLLISION_GROUP_COLLISIONLESS | BULLET_COLLISION_GROUP_MY_AVATAR); -const int32_t BULLET_COLLISION_MASK_OTHER_AVATAR = BULLET_COLLISION_MASK_DEFAULT; +// OTHER_AVATARs are dynamic, but are slammed to whatever the avatar-mixer says, which means +// their motion can't actually be affected by the local physics simulation -- we rely on the remote simulation +// to move its avatar around correctly and to communicate its motion through the avatar-mixer. +// Therefore, they only need to collide against things that can be affected by their motion: dynamic and MyAvatar +const int32_t BULLET_COLLISION_MASK_OTHER_AVATAR = BULLET_COLLISION_GROUP_DYNAMIC | BULLET_COLLISION_GROUP_MY_AVATAR; // COLLISIONLESS gets an empty mask. const int32_t BULLET_COLLISION_MASK_COLLISIONLESS = 0; @@ -100,6 +104,7 @@ const uint16_t ENTITY_COLLISION_MASK_DEFAULT = USER_COLLISION_GROUP_OTHER_AVATAR; const uint16_t USER_COLLISION_MASK_AVATARS = USER_COLLISION_GROUP_MY_AVATAR | USER_COLLISION_GROUP_OTHER_AVATAR; +const uint16_t USER_COLLISION_MASK_ENTITIES = USER_COLLISION_GROUP_STATIC | USER_COLLISION_GROUP_DYNAMIC | USER_COLLISION_GROUP_KINEMATIC; const int32_t NUM_USER_COLLISION_GROUPS = 5; diff --git a/libraries/shared/src/PrioritySortUtil.h b/libraries/shared/src/PrioritySortUtil.h index 34ec074d45..8ded047212 100644 --- a/libraries/shared/src/PrioritySortUtil.h +++ b/libraries/shared/src/PrioritySortUtil.h @@ -12,7 +12,6 @@ #define hifi_PrioritySortUtil_h #include -#include #include "NumericalConstants.h" #include "shared/ConicalViewFrustum.h" @@ -75,7 +74,6 @@ namespace PrioritySortUtil { void setPriority(float priority) { _priority = priority; } float getPriority() const { return _priority; } - bool operator<(const Sortable& other) const { return _priority < other._priority; } private: float _priority { 0.0f }; }; @@ -97,14 +95,18 @@ namespace PrioritySortUtil { _ageWeight = ageWeight; } - size_t size() const { return _queue.size(); } + size_t size() const { return _vector.size(); } void push(T thing) { thing.setPriority(computePriority(thing)); - _queue.push(thing); + _vector.push_back(thing); + } + void reserve(size_t num) { + _vector.reserve(num); + } + const std::vector& getSortedVector() { + std::sort(_vector.begin(), _vector.end(), [](const T& left, const T& right) { return left.getPriority() > right.getPriority(); }); + return _vector; } - const T& top() const { return _queue.top(); } - void pop() { return _queue.pop(); } - bool empty() const { return _queue.empty(); } private: @@ -153,7 +155,7 @@ namespace PrioritySortUtil { } ConicalViewFrustums _views; - std::priority_queue _queue; + std::vector _vector; float _angularWeight { DEFAULT_ANGULAR_COEF }; float _centerWeight { DEFAULT_CENTER_COEF }; float _ageWeight { DEFAULT_AGE_COEF }; diff --git a/libraries/shared/src/RegisteredMetaTypes.h b/libraries/shared/src/RegisteredMetaTypes.h index 467d6374a5..02f5fa570c 100644 --- a/libraries/shared/src/RegisteredMetaTypes.h +++ b/libraries/shared/src/RegisteredMetaTypes.h @@ -20,8 +20,10 @@ #include #include "AACube.h" +#include "ShapeInfo.h" #include "SharedUtil.h" #include "shared/Bilateral.h" +#include "Transform.h" class QColor; class QUrl; @@ -219,6 +221,158 @@ public: } }; +/**jsdoc +* A PickParabola defines a parabola with a starting point, intitial velocity, and acceleration. +* +* @typedef {object} PickParabola +* @property {Vec3} origin - The starting position of the PickParabola. +* @property {Vec3} velocity - The starting velocity of the parabola. +* @property {Vec3} acceleration - The acceleration that the parabola experiences. +*/ +class PickParabola : public MathPick { +public: + PickParabola() : origin(NAN), velocity(NAN), acceleration(NAN) { } + PickParabola(const QVariantMap& pickVariant) : origin(vec3FromVariant(pickVariant["origin"])), velocity(vec3FromVariant(pickVariant["velocity"])), acceleration(vec3FromVariant(pickVariant["acceleration"])) {} + PickParabola(const glm::vec3& origin, const glm::vec3 velocity, const glm::vec3 acceleration) : origin(origin), velocity(velocity), acceleration(acceleration) {} + glm::vec3 origin; + glm::vec3 velocity; + glm::vec3 acceleration; + + operator bool() const override { + return !(glm::any(glm::isnan(origin)) || glm::any(glm::isnan(velocity)) || glm::any(glm::isnan(acceleration))); + } + bool operator==(const PickParabola& other) const { + return (origin == other.origin && velocity == other.velocity && acceleration == other.acceleration); + } + QVariantMap toVariantMap() const override { + QVariantMap pickParabola; + pickParabola["origin"] = vec3toVariant(origin); + pickParabola["velocity"] = vec3toVariant(velocity); + pickParabola["acceleration"] = vec3toVariant(acceleration); + return pickParabola; + } +}; + +// TODO: Add "loaded" to CollisionRegion jsdoc once model collision picks are supported. + +/**jsdoc +* A CollisionRegion defines a volume for checking collisions in the physics simulation. + +* @typedef {object} CollisionRegion +* @property {Shape} shape - The information about the collision region's size and shape. +* @property {Vec3} position - The position of the collision region, relative to a parent if defined. +* @property {Quat} orientation - The orientation of the collision region, relative to a parent if defined. +* @property {float} threshold - The approximate minimum penetration depth for a test object to be considered in contact with the collision region. +* @property {Uuid} parentID - The ID of the parent, either an avatar, an entity, or an overlay. +* @property {number} parentJointIndex - The joint of the parent to parent to, for example, the joints on the model of an avatar. (default = 0, no joint) +* @property {string} joint - If "Mouse," parents the pick to the mouse. If "Avatar," parents the pick to MyAvatar's head. Otherwise, parents to the joint of the given name on MyAvatar. +*/ +class CollisionRegion : public MathPick { +public: + CollisionRegion() { } + + CollisionRegion(const CollisionRegion& collisionRegion) : + loaded(collisionRegion.loaded), + modelURL(collisionRegion.modelURL), + shapeInfo(std::make_shared()), + transform(collisionRegion.transform), + threshold(collisionRegion.threshold) + { + shapeInfo->setParams(collisionRegion.shapeInfo->getType(), collisionRegion.shapeInfo->getHalfExtents(), collisionRegion.modelURL.toString()); + } + + CollisionRegion(const QVariantMap& pickVariant) { + // "loaded" is not deserialized here because there is no way to know if the shape is actually loaded + if (pickVariant["shape"].isValid()) { + auto shape = pickVariant["shape"].toMap(); + if (!shape.empty()) { + ShapeType shapeType = SHAPE_TYPE_NONE; + if (shape["shapeType"].isValid()) { + shapeType = ShapeInfo::getShapeTypeForName(shape["shapeType"].toString()); + } + if (shapeType >= SHAPE_TYPE_COMPOUND && shapeType <= SHAPE_TYPE_STATIC_MESH && shape["modelURL"].isValid()) { + QString newURL = shape["modelURL"].toString(); + modelURL.setUrl(newURL); + } else { + modelURL.setUrl(""); + } + + if (shape["dimensions"].isValid()) { + transform.setScale(vec3FromVariant(shape["dimensions"])); + } + + shapeInfo->setParams(shapeType, transform.getScale() / 2.0f, modelURL.toString()); + } + } + + if (pickVariant["threshold"].isValid()) { + threshold = glm::max(0.0f, pickVariant["threshold"].toFloat()); + } + + if (pickVariant["position"].isValid()) { + transform.setTranslation(vec3FromVariant(pickVariant["position"])); + } + if (pickVariant["orientation"].isValid()) { + transform.setRotation(quatFromVariant(pickVariant["orientation"])); + } + } + + QVariantMap toVariantMap() const override { + QVariantMap collisionRegion; + + QVariantMap shape; + shape["shapeType"] = ShapeInfo::getNameForShapeType(shapeInfo->getType()); + shape["modelURL"] = modelURL.toString(); + shape["dimensions"] = vec3toVariant(transform.getScale()); + + collisionRegion["shape"] = shape; + collisionRegion["loaded"] = loaded; + + collisionRegion["threshold"] = threshold; + + collisionRegion["position"] = vec3toVariant(transform.getTranslation()); + collisionRegion["orientation"] = quatToVariant(transform.getRotation()); + + return collisionRegion; + } + + operator bool() const override { + return !std::isnan(threshold) && + !(glm::any(glm::isnan(transform.getTranslation())) || + glm::any(glm::isnan(transform.getRotation())) || + shapeInfo->getType() == SHAPE_TYPE_NONE); + } + + bool operator==(const CollisionRegion& other) const { + return loaded == other.loaded && + threshold == other.threshold && + glm::all(glm::equal(transform.getTranslation(), other.transform.getTranslation())) && + glm::all(glm::equal(transform.getRotation(), other.transform.getRotation())) && + glm::all(glm::equal(transform.getScale(), other.transform.getScale())) && + shapeInfo->getType() == other.shapeInfo->getType() && + modelURL == other.modelURL; + } + + bool shouldComputeShapeInfo() const { + if (!(shapeInfo->getType() == SHAPE_TYPE_HULL || + (shapeInfo->getType() >= SHAPE_TYPE_COMPOUND && + shapeInfo->getType() <= SHAPE_TYPE_STATIC_MESH) + )) { + return false; + } + + return !shapeInfo->getPointCollection().size(); + } + + // We can't load the model here because it would create a circular dependency, so we delegate that responsibility to the owning CollisionPick + bool loaded { false }; + QUrl modelURL; + + // We can't compute the shapeInfo here without loading the model first, so we delegate that responsibility to the owning CollisionPick + std::shared_ptr shapeInfo = std::make_shared(); + Transform transform; + float threshold; +}; namespace std { inline void hash_combine(std::size_t& seed) { } @@ -255,6 +409,15 @@ namespace std { } }; + template <> + struct hash { + size_t operator()(const Transform& a) const { + size_t result = 0; + hash_combine(result, a.getTranslation(), a.getRotation(), a.getScale()); + return result; + } + }; + template <> struct hash { size_t operator()(const PickRay& a) const { @@ -273,6 +436,24 @@ namespace std { } }; + template <> + struct hash { + size_t operator()(const PickParabola& a) const { + size_t result = 0; + hash_combine(result, a.origin, a.velocity, a.acceleration); + return result; + } + }; + + template <> + struct hash { + size_t operator()(const CollisionRegion& a) const { + size_t result = 0; + hash_combine(result, a.transform, (int)a.shapeInfo->getType(), qHash(a.modelURL)); + return result; + } + }; + template <> struct hash { size_t operator()(const QString& a) const { diff --git a/libraries/shared/src/ShapeInfo.cpp b/libraries/shared/src/ShapeInfo.cpp index 968292da87..152e305bf2 100644 --- a/libraries/shared/src/ShapeInfo.cpp +++ b/libraries/shared/src/ShapeInfo.cpp @@ -68,12 +68,23 @@ const float MIN_HALF_EXTENT = 0.005f; // 0.5 cm QString ShapeInfo::getNameForShapeType(ShapeType type) { if (((int)type <= 0) || ((int)type >= (int)SHAPETYPE_NAME_COUNT)) { - type = (ShapeType)0; + type = SHAPE_TYPE_NONE; } return shapeTypeNames[(int)type]; } +ShapeType ShapeInfo::getShapeTypeForName(QString string) { + for (int i = 0; i < (int)SHAPETYPE_NAME_COUNT; i++) { + auto name = shapeTypeNames[i]; + if (name == string) { + return (ShapeType)i; + } + } + + return SHAPE_TYPE_NONE; +} + void ShapeInfo::clear() { _url.clear(); _pointCollection.clear(); diff --git a/libraries/shared/src/ShapeInfo.h b/libraries/shared/src/ShapeInfo.h index 069241e29d..5020e492cf 100644 --- a/libraries/shared/src/ShapeInfo.h +++ b/libraries/shared/src/ShapeInfo.h @@ -59,6 +59,7 @@ public: using TriangleIndices = QVector; static QString getNameForShapeType(ShapeType type); + static ShapeType getShapeTypeForName(QString string); void clear(); diff --git a/libraries/shared/src/SpatiallyNestable.cpp b/libraries/shared/src/SpatiallyNestable.cpp index ccb275ffc9..4b8768704a 100644 --- a/libraries/shared/src/SpatiallyNestable.cpp +++ b/libraries/shared/src/SpatiallyNestable.cpp @@ -22,8 +22,8 @@ const float defaultAACubeSize = 1.0f; const int MAX_PARENTING_CHAIN_SIZE = 30; SpatiallyNestable::SpatiallyNestable(NestableType nestableType, QUuid id) : - _nestableType(nestableType), _id(id), + _nestableType(nestableType), _transform() { // set flags in _transform _transform.setTranslation(glm::vec3(0.0f)); diff --git a/libraries/shared/src/SpatiallyNestable.h b/libraries/shared/src/SpatiallyNestable.h index 361f0aaf17..a50e687a9b 100644 --- a/libraries/shared/src/SpatiallyNestable.h +++ b/libraries/shared/src/SpatiallyNestable.h @@ -212,7 +212,6 @@ public: virtual void parentDeleted() { } // called on children of a deleted parent protected: - const NestableType _nestableType; // EntityItem or an AvatarData QUuid _id; mutable SpatiallyNestableWeakPointer _parent; @@ -232,6 +231,8 @@ protected: quint64 _rotationChanged { 0 }; private: + SpatiallyNestable() = delete; + const NestableType _nestableType; // EntityItem or an AvatarData QUuid _parentID; // what is this thing's transform relative to? quint16 _parentJointIndex { INVALID_JOINT_INDEX }; // which joint of the parent is this relative to? diff --git a/libraries/shared/src/ThreadSafeValueCache.h b/libraries/shared/src/ThreadSafeValueCache.h index 37a1258aa1..192300abbb 100644 --- a/libraries/shared/src/ThreadSafeValueCache.h +++ b/libraries/shared/src/ThreadSafeValueCache.h @@ -32,15 +32,30 @@ public: return _value; } + // returns atomic copy of the cached value and indicates validity + T get(bool& valid) const { + std::lock_guard guard(_mutex); + valid = _valid; + return _value; + } + // will reflect copy of value into the cache. void set(const T& v) { std::lock_guard guard(_mutex); _value = v; + _valid = true; + } + + // indicate that the value is not longer valid + void invalidate() { + std::lock_guard guard(_mutex); + _valid = false; } private: mutable std::mutex _mutex; T _value; + bool _valid { false }; // no copies ThreadSafeValueCache(const ThreadSafeValueCache&) = delete; diff --git a/libraries/shared/src/TransformNode.h b/libraries/shared/src/TransformNode.h new file mode 100644 index 0000000000..73223a8cee --- /dev/null +++ b/libraries/shared/src/TransformNode.h @@ -0,0 +1,18 @@ +// +// Created by Sabrina Shanman 8/14/2018 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#ifndef hifi_TransformNode_h +#define hifi_TransformNode_h + +#include "Transform.h" + +class TransformNode { +public: + virtual Transform getTransform() = 0; +}; + +#endif // hifi_TransformNode_h \ No newline at end of file diff --git a/libraries/shared/src/TriangleSet.cpp b/libraries/shared/src/TriangleSet.cpp index d7f685f8d3..02a8d458a9 100644 --- a/libraries/shared/src/TriangleSet.cpp +++ b/libraries/shared/src/TriangleSet.cpp @@ -13,6 +13,8 @@ #include "GLMHelpers.h" +#include + void TriangleSet::insert(const Triangle& t) { _isBalanced = false; @@ -27,28 +29,7 @@ void TriangleSet::clear() { _bounds.clear(); _isBalanced = false; - _triangleOctree.clear(); -} - -bool TriangleSet::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - float& distance, BoxFace& face, Triangle& triangle, bool precision, bool allowBackface) { - - // reset our distance to be the max possible, lower level tests will store best distance here - distance = std::numeric_limits::max(); - - if (!_isBalanced) { - balanceOctree(); - } - - int trianglesTouched = 0; - auto result = _triangleOctree.findRayIntersection(origin, direction, distance, face, triangle, precision, trianglesTouched, allowBackface); - - #if WANT_DEBUGGING - if (precision) { - qDebug() << "trianglesTouched :" << trianglesTouched << "out of:" << _triangleOctree._population << "_triangles.size:" << _triangles.size(); - } - #endif - return result; + _triangleTree.clear(); } bool TriangleSet::convexHullContains(const glm::vec3& point) const { @@ -72,131 +53,122 @@ void TriangleSet::debugDump() { qDebug() << __FUNCTION__; qDebug() << "bounds:" << getBounds(); qDebug() << "triangles:" << size() << "at top level...."; - qDebug() << "----- _triangleOctree -----"; - _triangleOctree.debugDump(); + qDebug() << "----- _triangleTree -----"; + _triangleTree.debugDump(); } -void TriangleSet::balanceOctree() { - _triangleOctree.reset(_bounds, 0); +void TriangleSet::balanceTree() { + _triangleTree.reset(_bounds); // insert all the triangles for (size_t i = 0; i < _triangles.size(); i++) { - _triangleOctree.insert(i); + _triangleTree.insert(i); } _isBalanced = true; - #if WANT_DEBUGGING +#if WANT_DEBUGGING debugDump(); - #endif +#endif } +// With an octree: 8 ^ MAX_DEPTH = 4096 leaves +//static const int MAX_DEPTH = 4; +// With a k-d tree: 2 ^ MAX_DEPTH = 4096 leaves +static const int MAX_DEPTH = 12; -// Determine of the given ray (origin/direction) in model space intersects with any triangles -// in the set. If an intersection occurs, the distance and surface normal will be provided. -bool TriangleSet::TriangleOctreeCell::findRayIntersectionInternal(const glm::vec3& origin, const glm::vec3& direction, - float& distance, BoxFace& face, Triangle& triangle, bool precision, int& trianglesTouched, bool allowBackface) { - - bool intersectedSomething = false; - float boxDistance = distance; - float bestDistance = distance; - glm::vec3 surfaceNormal; - - if (_bounds.findRayIntersection(origin, direction, boxDistance, face, surfaceNormal)) { - - // if our bounding box intersects at a distance greater than the current known - // best distance, and our origin isn't inside the boounds, then we can safely - // not check any of our triangles - if (boxDistance > bestDistance && !_bounds.contains(origin)) { - return false; - } - - if (precision) { - for (const auto& triangleIndex : _triangleIndices) { - const auto& thisTriangle = _allTriangles[triangleIndex]; - float thisTriangleDistance; - trianglesTouched++; - if (findRayTriangleIntersection(origin, direction, thisTriangle, thisTriangleDistance, allowBackface)) { - if (thisTriangleDistance < bestDistance) { - bestDistance = thisTriangleDistance; - intersectedSomething = true; - triangle = thisTriangle; - distance = bestDistance; - } - } - } - } else { - intersectedSomething = true; - distance = boxDistance; - } - } - - return intersectedSomething; -} - -static const int MAX_DEPTH = 4; // for now -static const int MAX_CHILDREN = 8; - -TriangleSet::TriangleOctreeCell::TriangleOctreeCell(std::vector& allTriangles, const AABox& bounds, int depth) : +TriangleSet::TriangleTreeCell::TriangleTreeCell(std::vector& allTriangles, const AABox& bounds, int depth) : _allTriangles(allTriangles) { reset(bounds, depth); } -void TriangleSet::TriangleOctreeCell::clear() { +void TriangleSet::TriangleTreeCell::clear() { _population = 0; _triangleIndices.clear(); _bounds.clear(); - _children.clear(); + _children.first.reset(); + _children.second.reset(); } -void TriangleSet::TriangleOctreeCell::reset(const AABox& bounds, int depth) { +void TriangleSet::TriangleTreeCell::reset(const AABox& bounds, int depth) { clear(); _bounds = bounds; _depth = depth; } -void TriangleSet::TriangleOctreeCell::debugDump() { +void TriangleSet::TriangleTreeCell::debugDump() { qDebug() << __FUNCTION__; - qDebug() << "bounds:" << getBounds(); - qDebug() << "depth:" << _depth; - qDebug() << "population:" << _population << "this level or below" + qDebug() << " bounds:" << getBounds(); + qDebug() << " depth:" << _depth; + qDebug() << " population:" << _population << "this level or below" << " ---- triangleIndices:" << _triangleIndices.size() << "in this cell"; - qDebug() << "child cells:" << _children.size(); + int numChildren = 0; + if (_children.first) { + numChildren++; + } else if (_children.second) { + numChildren++; + } + qDebug() << "child cells:" << numChildren; if (_depth < MAX_DEPTH) { - int childNum = 0; - for (auto& child : _children) { - qDebug() << "child:" << childNum; - child.second.debugDump(); - childNum++; + if (_children.first) { + qDebug() << "child: 0"; + _children.first->debugDump(); + } + if (_children.second) { + qDebug() << "child: 1"; + _children.second->debugDump(); } } } -void TriangleSet::TriangleOctreeCell::insert(size_t triangleIndex) { - const Triangle& triangle = _allTriangles[triangleIndex]; +std::pair TriangleSet::TriangleTreeCell::getTriangleTreeCellChildBounds() { + std::pair toReturn; + int axis = 0; + // find biggest axis + glm::vec3 dimensions = _bounds.getDimensions(); + for (int i = 0; i < 3; i++) { + if (dimensions[i] >= dimensions[(i + 1) % 3] && dimensions[i] >= dimensions[(i + 2) % 3]) { + axis = i; + break; + } + } + + // The new boxes are half the size in the largest dimension + glm::vec3 newDimensions = dimensions; + newDimensions[axis] *= 0.5f; + toReturn.first.setBox(_bounds.getCorner(), newDimensions); + glm::vec3 offset = glm::vec3(0.0f); + offset[axis] = newDimensions[axis]; + toReturn.second.setBox(_bounds.getCorner() + offset, newDimensions); + return toReturn; +} + +void TriangleSet::TriangleTreeCell::insert(size_t triangleIndex) { _population++; + // if we're not yet at the max depth, then check which child the triangle fits in if (_depth < MAX_DEPTH) { + const Triangle& triangle = _allTriangles[triangleIndex]; + auto childBounds = getTriangleTreeCellChildBounds(); - for (int child = 0; child < MAX_CHILDREN; child++) { - AABox childBounds = getBounds().getOctreeChild((AABox::OctreeChild)child); - - + auto insertOperator = [&](const AABox& childBound, std::shared_ptr& child) { // if the child AABox would contain the triangle... - if (childBounds.contains(triangle)) { + if (childBound.contains(triangle)) { // if the child cell doesn't yet exist, create it... - if (_children.find((AABox::OctreeChild)child) == _children.end()) { - _children.insert( - std::pair - ((AABox::OctreeChild)child, TriangleOctreeCell(_allTriangles, childBounds, _depth + 1))); + if (!child) { + child = std::make_shared(_allTriangles, childBound, _depth + 1); } // insert the triangleIndex in the child cell - _children.at((AABox::OctreeChild)child).insert(triangleIndex); - return; + child->insert(triangleIndex); + return true; } + return false; + }; + if (insertOperator(childBounds.first, _children.first) || insertOperator(childBounds.second, _children.second)) { + return; } } // either we're at max depth, or the triangle doesn't fit in one of our @@ -204,62 +176,147 @@ void TriangleSet::TriangleOctreeCell::insert(size_t triangleIndex) { _triangleIndices.push_back(triangleIndex); } -bool TriangleSet::TriangleOctreeCell::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - float& distance, BoxFace& face, Triangle& triangle, bool precision, int& trianglesTouched, - bool allowBackface) { +bool TriangleSet::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, const glm::vec3& invDirection, float& distance, + BoxFace& face, Triangle& triangle, bool precision, bool allowBackface) { + if (!_isBalanced) { + balanceTree(); + } + float localDistance = distance; + int trianglesTouched = 0; + bool hit = _triangleTree.findRayIntersection(origin, direction, invDirection, localDistance, face, triangle, precision, trianglesTouched, allowBackface); + if (hit) { + distance = localDistance; + } + +#if WANT_DEBUGGING + if (precision) { + qDebug() << "trianglesTouched :" << trianglesTouched << "out of:" << _triangleTree._population << "_triangles.size:" << _triangles.size(); + } +#endif + return hit; +} + +// Determine of the given ray (origin/direction) in model space intersects with any triangles +// in the set. If an intersection occurs, the distance and surface normal will be provided. +bool TriangleSet::TriangleTreeCell::findRayIntersectionInternal(const glm::vec3& origin, const glm::vec3& direction, + float& distance, BoxFace& face, Triangle& triangle, bool precision, + int& trianglesTouched, bool allowBackface) { + bool intersectedSomething = false; + float bestDistance = FLT_MAX; + Triangle bestTriangle; + + if (precision) { + for (const auto& triangleIndex : _triangleIndices) { + const auto& thisTriangle = _allTriangles[triangleIndex]; + float thisTriangleDistance; + trianglesTouched++; + if (findRayTriangleIntersection(origin, direction, thisTriangle, thisTriangleDistance, allowBackface)) { + if (thisTriangleDistance < bestDistance) { + bestDistance = thisTriangleDistance; + bestTriangle = thisTriangle; + intersectedSomething = true; + } + } + } + } else { + intersectedSomething = true; + bestDistance = distance; + } + + if (intersectedSomething) { + distance = bestDistance; + triangle = bestTriangle; + } + + return intersectedSomething; +} + +bool TriangleSet::TriangleTreeCell::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, const glm::vec3& invDirection, + float& distance, BoxFace& face, Triangle& triangle, bool precision, int& trianglesTouched, + bool allowBackface) { if (_population < 1) { return false; // no triangles below here, so we can't intersect } - float bestLocalDistance = distance; + float bestLocalDistance = FLT_MAX; BoxFace bestLocalFace; Triangle bestLocalTriangle; - glm::vec3 bestLocalNormal; bool intersects = false; - // if the ray intersects our bounding box, then continue - if (getBounds().findRayIntersection(origin, direction, bestLocalDistance, bestLocalFace, bestLocalNormal)) { - - // if the intersection with our bounding box, is greater than the current best distance (the distance passed in) - // then we know that none of our triangles can represent a better intersection and we can return - - if (bestLocalDistance > distance) { - return false; + // Check our local triangle set first + // The distance passed in here is the distance to our bounding box. If !precision, that distance is used + { + float internalDistance = distance; + BoxFace internalFace; + Triangle internalTriangle; + if (findRayIntersectionInternal(origin, direction, internalDistance, internalFace, internalTriangle, precision, trianglesTouched, allowBackface)) { + bestLocalDistance = internalDistance; + bestLocalFace = internalFace; + bestLocalTriangle = internalTriangle; + intersects = true; } + } - bestLocalDistance = distance; + // if we're not yet at the max depth, then check our children + if (_depth < MAX_DEPTH) { + std::list sortedTriangleCells; + auto sortingOperator = [&](std::shared_ptr& child) { + if (child) { + float priority = FLT_MAX; + if (child->getBounds().contains(origin)) { + priority = 0.0f; + } else { + float childBoundDistance = FLT_MAX; + BoxFace childBoundFace; + glm::vec3 childBoundNormal; + if (child->getBounds().findRayIntersection(origin, direction, invDirection, childBoundDistance, childBoundFace, childBoundNormal)) { + // We only need to add this cell if it's closer than the local triangle set intersection (if there was one) + if (childBoundDistance < bestLocalDistance) { + priority = childBoundDistance; + } + } + } - float childDistance = distance; - BoxFace childFace; - Triangle childTriangle; - - // if we're not yet at the max depth, then check which child the triangle fits in - if (_depth < MAX_DEPTH) { - for (auto& child : _children) { - // check each child, if there's an intersection, it will return some distance that we need - // to compare against the other results, because there might be multiple intersections and - // we will always choose the best (shortest) intersection - if (child.second.findRayIntersection(origin, direction, childDistance, childFace, childTriangle, precision, trianglesTouched)) { - if (childDistance < bestLocalDistance) { - bestLocalDistance = childDistance; - bestLocalFace = childFace; - bestLocalTriangle = childTriangle; - intersects = true; + if (priority < FLT_MAX) { + if (sortedTriangleCells.size() > 0 && priority < sortedTriangleCells.front().first) { + sortedTriangleCells.emplace_front(priority, child); + } else { + sortedTriangleCells.emplace_back(priority, child); } } } - } - // also check our local triangle set - if (findRayIntersectionInternal(origin, direction, childDistance, childFace, childTriangle, precision, trianglesTouched, allowBackface)) { - if (childDistance < bestLocalDistance) { - bestLocalDistance = childDistance; - bestLocalFace = childFace; - bestLocalTriangle = childTriangle; - intersects = true; + }; + sortingOperator(_children.first); + sortingOperator(_children.second); + + for (auto it = sortedTriangleCells.begin(); it != sortedTriangleCells.end(); ++it) { + const SortedTriangleCell& sortedTriangleCell = *it; + float childDistance = sortedTriangleCell.first; + // We can exit once childDistance > bestLocalDistance + if (childDistance > bestLocalDistance) { + break; + } + // If we're inside the child cell and !precision, we need the actual distance to the cell bounds + if (!precision && childDistance < EPSILON) { + BoxFace childBoundFace; + glm::vec3 childBoundNormal; + sortedTriangleCell.second->getBounds().findRayIntersection(origin, direction, invDirection, childDistance, childBoundFace, childBoundNormal); + } + BoxFace childFace; + Triangle childTriangle; + if (sortedTriangleCell.second->findRayIntersection(origin, direction, invDirection, childDistance, childFace, childTriangle, precision, trianglesTouched)) { + if (childDistance < bestLocalDistance) { + bestLocalDistance = childDistance; + bestLocalFace = childFace; + bestLocalTriangle = childTriangle; + intersects = true; + break; + } } } } + if (intersects) { distance = bestLocalDistance; face = bestLocalFace; @@ -267,3 +324,152 @@ bool TriangleSet::TriangleOctreeCell::findRayIntersection(const glm::vec3& origi } return intersects; } + +bool TriangleSet::findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + float& parabolicDistance, BoxFace& face, Triangle& triangle, bool precision, bool allowBackface) { + if (!_isBalanced) { + balanceTree(); + } + + float localDistance = parabolicDistance; + int trianglesTouched = 0; + bool hit = _triangleTree.findParabolaIntersection(origin, velocity, acceleration, localDistance, face, triangle, precision, trianglesTouched, allowBackface); + if (hit) { + parabolicDistance = localDistance; + } + +#if WANT_DEBUGGING + if (precision) { + qDebug() << "trianglesTouched :" << trianglesTouched << "out of:" << _triangleTree._population << "_triangles.size:" << _triangles.size(); + } +#endif + return hit; +} + +bool TriangleSet::TriangleTreeCell::findParabolaIntersectionInternal(const glm::vec3& origin, const glm::vec3& velocity, + const glm::vec3& acceleration, float& parabolicDistance, + BoxFace& face, Triangle& triangle, bool precision, + int& trianglesTouched, bool allowBackface) { + bool intersectedSomething = false; + float bestDistance = FLT_MAX; + Triangle bestTriangle; + + if (precision) { + for (const auto& triangleIndex : _triangleIndices) { + const auto& thisTriangle = _allTriangles[triangleIndex]; + float thisTriangleDistance; + trianglesTouched++; + if (findParabolaTriangleIntersection(origin, velocity, acceleration, thisTriangle, thisTriangleDistance, allowBackface)) { + if (thisTriangleDistance < bestDistance) { + bestDistance = thisTriangleDistance; + bestTriangle = thisTriangle; + intersectedSomething = true; + } + } + } + } else { + intersectedSomething = true; + bestDistance = parabolicDistance; + } + + if (intersectedSomething) { + parabolicDistance = bestDistance; + triangle = bestTriangle; + } + + return intersectedSomething; +} + +bool TriangleSet::TriangleTreeCell::findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, + const glm::vec3& acceleration, float& parabolicDistance, + BoxFace& face, Triangle& triangle, bool precision, + int& trianglesTouched, bool allowBackface) { + if (_population < 1) { + return false; // no triangles below here, so we can't intersect + } + + float bestLocalDistance = FLT_MAX; + BoxFace bestLocalFace; + Triangle bestLocalTriangle; + bool intersects = false; + + // Check our local triangle set first + // The distance passed in here is the distance to our bounding box. If !precision, that distance is used + { + float internalDistance = parabolicDistance; + BoxFace internalFace; + Triangle internalTriangle; + if (findParabolaIntersectionInternal(origin, velocity, acceleration, internalDistance, internalFace, internalTriangle, precision, trianglesTouched, allowBackface)) { + bestLocalDistance = internalDistance; + bestLocalFace = internalFace; + bestLocalTriangle = internalTriangle; + intersects = true; + } + } + + // if we're not yet at the max depth, then check our children + if (_depth < MAX_DEPTH) { + std::list sortedTriangleCells; + auto sortingOperator = [&](std::shared_ptr& child) { + if (child) { + float priority = FLT_MAX; + if (child->getBounds().contains(origin)) { + priority = 0.0f; + } else { + float childBoundDistance = FLT_MAX; + BoxFace childBoundFace; + glm::vec3 childBoundNormal; + if (child->getBounds().findParabolaIntersection(origin, velocity, acceleration, childBoundDistance, childBoundFace, childBoundNormal)) { + // We only need to add this cell if it's closer than the local triangle set intersection (if there was one) + if (childBoundDistance < bestLocalDistance) { + priority = childBoundDistance; + } + } + } + + if (priority < FLT_MAX) { + if (sortedTriangleCells.size() > 0 && priority < sortedTriangleCells.front().first) { + sortedTriangleCells.emplace_front(priority, child); + } else { + sortedTriangleCells.emplace_back(priority, child); + } + } + } + }; + sortingOperator(_children.first); + sortingOperator(_children.second); + + for (auto it = sortedTriangleCells.begin(); it != sortedTriangleCells.end(); ++it) { + const SortedTriangleCell& sortedTriangleCell = *it; + float childDistance = sortedTriangleCell.first; + // We can exit once childDistance > bestLocalDistance + if (childDistance > bestLocalDistance) { + break; + } + // If we're inside the child cell and !precision, we need the actual distance to the cell bounds + if (!precision && childDistance < EPSILON) { + BoxFace childBoundFace; + glm::vec3 childBoundNormal; + sortedTriangleCell.second->getBounds().findParabolaIntersection(origin, velocity, acceleration, childDistance, childBoundFace, childBoundNormal); + } + BoxFace childFace; + Triangle childTriangle; + if (sortedTriangleCell.second->findParabolaIntersection(origin, velocity, acceleration, childDistance, childFace, childTriangle, precision, trianglesTouched)) { + if (childDistance < bestLocalDistance) { + bestLocalDistance = childDistance; + bestLocalFace = childFace; + bestLocalTriangle = childTriangle; + intersects = true; + break; + } + } + } + } + + if (intersects) { + parabolicDistance = bestLocalDistance; + face = bestLocalFace; + triangle = bestLocalTriangle; + } + return intersects; +} \ No newline at end of file diff --git a/libraries/shared/src/TriangleSet.h b/libraries/shared/src/TriangleSet.h index 2853d0f68e..3417b36b4a 100644 --- a/libraries/shared/src/TriangleSet.h +++ b/libraries/shared/src/TriangleSet.h @@ -12,72 +12,74 @@ #pragma once #include +#include #include "AABox.h" #include "GeometryUtil.h" class TriangleSet { - class TriangleOctreeCell { + class TriangleTreeCell { public: - TriangleOctreeCell(std::vector& allTriangles) : - _allTriangles(allTriangles) - { } + TriangleTreeCell(std::vector& allTriangles) : _allTriangles(allTriangles) {} + TriangleTreeCell(std::vector& allTriangles, const AABox& bounds, int depth); void insert(size_t triangleIndex); void reset(const AABox& bounds, int depth = 0); void clear(); - bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, + bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, const glm::vec3& invDirection, float& distance, BoxFace& face, Triangle& triangle, bool precision, int& trianglesTouched, bool allowBackface = false); + bool findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + float& parabolicDistance, BoxFace& face, Triangle& triangle, bool precision, int& trianglesTouched, + bool allowBackface = false); const AABox& getBounds() const { return _bounds; } void debugDump(); protected: - TriangleOctreeCell(std::vector& allTriangles, const AABox& bounds, int depth); - // checks our internal list of triangles bool findRayIntersectionInternal(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face, Triangle& triangle, bool precision, int& trianglesTouched, bool allowBackface = false); + bool findParabolaIntersectionInternal(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + float& parabolicDistance, BoxFace& face, Triangle& triangle, bool precision, int& trianglesTouched, + bool allowBackface = false); + + std::pair getTriangleTreeCellChildBounds(); std::vector& _allTriangles; - std::map _children; - int _depth{ 0 }; - int _population{ 0 }; + std::pair, std::shared_ptr> _children; + int _depth { 0 }; + int _population { 0 }; AABox _bounds; std::vector _triangleIndices; friend class TriangleSet; }; + using SortedTriangleCell = std::pair>; + public: - TriangleSet() : - _triangleOctree(_triangles) - {} + TriangleSet() : _triangleTree(_triangles) {} void debugDump(); void insert(const Triangle& t); - bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, + bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, const glm::vec3& invDirection, float& distance, BoxFace& face, Triangle& triangle, bool precision, bool allowBackface = false); + bool findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + float& parabolicDistance, BoxFace& face, Triangle& triangle, bool precision, bool allowBackface = false); - void balanceOctree(); + void balanceTree(); void reserve(size_t size) { _triangles.reserve(size); } // reserve space in the datastructure for size number of triangles size_t size() const { return _triangles.size(); } void clear(); - // Determine if the given ray (origin/direction) in model space intersects with any triangles in the set. If an - // intersection occurs, the distance and surface normal will be provided. - // note: this might side-effect internal structures - bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - float& distance, BoxFace& face, Triangle& triangle, bool precision, int& trianglesTouched); - // Determine if a point is "inside" all the triangles of a convex hull. It is the responsibility of the caller to // determine that the triangle set is indeed a convex hull. If the triangles added to this set are not in fact a // convex hull, the result of this method is meaningless and undetermined. @@ -85,9 +87,8 @@ public: const AABox& getBounds() const { return _bounds; } protected: - - bool _isBalanced{ false }; + bool _isBalanced { false }; std::vector _triangles; - TriangleOctreeCell _triangleOctree; + TriangleTreeCell _triangleTree; AABox _bounds; }; diff --git a/libraries/shared/src/ViewFrustum.cpp b/libraries/shared/src/ViewFrustum.cpp index 3e03c13fa4..e925ef960d 100644 --- a/libraries/shared/src/ViewFrustum.cpp +++ b/libraries/shared/src/ViewFrustum.cpp @@ -654,10 +654,10 @@ const ViewFrustum::Corners ViewFrustum::getCorners(const float depth) const { }; } -float ViewFrustum::distanceToCamera(const glm::vec3& point) const { +float ViewFrustum::distanceToCameraSquared(const glm::vec3& point) const { glm::vec3 temp = getPosition() - point; - float distanceToPoint = sqrtf(glm::dot(temp, temp)); - return distanceToPoint; + float distanceToPointSquared = glm::dot(temp, temp); + return distanceToPointSquared; } void ViewFrustum::evalProjectionMatrix(glm::mat4& proj) const { diff --git a/libraries/shared/src/ViewFrustum.h b/libraries/shared/src/ViewFrustum.h index eada65468d..9c80538e60 100644 --- a/libraries/shared/src/ViewFrustum.h +++ b/libraries/shared/src/ViewFrustum.h @@ -127,7 +127,8 @@ public: bool getProjectedRect(const AABox& box, glm::vec2& bottomLeft, glm::vec2& topRight) const; void getFurthestPointFromCamera(const AACube& box, glm::vec3& furthestPoint) const; - float distanceToCamera(const glm::vec3& point) const; + float distanceToCameraSquared(const glm::vec3& point) const; + float distanceToCamera(const glm::vec3& point) const { return sqrtf(distanceToCameraSquared(point)); } void evalProjectionMatrix(glm::mat4& proj) const; diff --git a/libraries/shared/src/shared/ConicalViewFrustum.cpp b/libraries/shared/src/shared/ConicalViewFrustum.cpp index 2ef096e3a8..78f4f7d1bc 100644 --- a/libraries/shared/src/shared/ConicalViewFrustum.cpp +++ b/libraries/shared/src/shared/ConicalViewFrustum.cpp @@ -69,7 +69,7 @@ bool ConicalViewFrustum::intersects(const AABox& box) const { return intersects(position, distance, radius); } -bool ConicalViewFrustum::getAngularSize(const AACube& cube) const { +float ConicalViewFrustum::getAngularSize(const AACube& cube) const { auto radius = 0.5f * SQRT_THREE * cube.getScale(); // radius of bounding sphere auto position = cube.calcCenter() - _position; // position of bounding sphere in view-frame float distance = glm::length(position); @@ -77,7 +77,7 @@ bool ConicalViewFrustum::getAngularSize(const AACube& cube) const { return getAngularSize(distance, radius); } -bool ConicalViewFrustum::getAngularSize(const AABox& box) const { +float ConicalViewFrustum::getAngularSize(const AABox& box) const { auto radius = 0.5f * glm::length(box.getScale()); // radius of bounding sphere auto position = box.calcCenter() - _position; // position of bounding sphere in view-frame float distance = glm::length(position); @@ -107,7 +107,7 @@ bool ConicalViewFrustum::intersects(const glm::vec3& relativePosition, float dis sqrtf(distance * distance - radius * radius) * _cosAngle - radius * _sinAngle; } -bool ConicalViewFrustum::getAngularSize(float distance, float radius) const { +float ConicalViewFrustum::getAngularSize(float distance, float radius) const { const float AVOID_DIVIDE_BY_ZERO = 0.001f; float angularSize = radius / (distance + AVOID_DIVIDE_BY_ZERO); return angularSize; @@ -144,3 +144,8 @@ int ConicalViewFrustum::deserialize(const unsigned char* sourceBuffer) { return sourceBuffer - startPosition; } + +void ConicalViewFrustum::setSimpleRadius(float radius) { + _radius = radius; + _farClip = radius / 2.0f; +} diff --git a/libraries/shared/src/shared/ConicalViewFrustum.h b/libraries/shared/src/shared/ConicalViewFrustum.h index dc372d560e..6a2cc53f03 100644 --- a/libraries/shared/src/shared/ConicalViewFrustum.h +++ b/libraries/shared/src/shared/ConicalViewFrustum.h @@ -45,15 +45,18 @@ public: bool intersects(const AACube& cube) const; bool intersects(const AABox& box) const; - bool getAngularSize(const AACube& cube) const; - bool getAngularSize(const AABox& box) const; + float getAngularSize(const AACube& cube) const; + float getAngularSize(const AABox& box) const; bool intersects(const glm::vec3& relativePosition, float distance, float radius) const; - bool getAngularSize(float distance, float radius) const; + float getAngularSize(float distance, float radius) const; int serialize(unsigned char* destinationBuffer) const; int deserialize(const unsigned char* sourceBuffer); + // Just test for within radius. + void setSimpleRadius(float radius); + private: glm::vec3 _position { 0.0f, 0.0f, 0.0f }; glm::vec3 _direction { 0.0f, 0.0f, 1.0f }; diff --git a/libraries/shared/src/shared/FileUtils.cpp b/libraries/shared/src/shared/FileUtils.cpp index 9bdd65bf11..0709a53602 100644 --- a/libraries/shared/src/shared/FileUtils.cpp +++ b/libraries/shared/src/shared/FileUtils.cpp @@ -156,10 +156,12 @@ bool FileUtils::canCreateFile(const QString& fullPath) { qDebug(shared) << "unable to overwrite file '" << fullPath << "'"; return false; } - QDir dir(fileInfo.absolutePath()); + + QString absolutePath = fileInfo.absolutePath(); + QDir dir(absolutePath); if (!dir.exists()) { - if (!dir.mkpath(fullPath)) { - qDebug(shared) << "unable to create dir '" << dir.absolutePath() << "'"; + if (!dir.mkpath(absolutePath)) { + qDebug(shared) << "unable to create dir '" << absolutePath << "'"; return false; } } diff --git a/libraries/shared/src/shared/GlobalAppProperties.cpp b/libraries/shared/src/shared/GlobalAppProperties.cpp index 6c9f3f9601..54e50da3ea 100644 --- a/libraries/shared/src/shared/GlobalAppProperties.cpp +++ b/libraries/shared/src/shared/GlobalAppProperties.cpp @@ -21,7 +21,6 @@ namespace hifi { namespace properties { namespace gl { const char* BACKEND = "com.highfidelity.gl.backend"; - const char* MAKE_PROGRAM_CALLBACK = "com.highfidelity.gl.makeProgram"; const char* PRIMARY_CONTEXT = "com.highfidelity.gl.primaryContext"; } diff --git a/libraries/test-utils/src/test-utils/Utils.cpp b/libraries/test-utils/src/test-utils/Utils.cpp new file mode 100644 index 0000000000..16df34bb8b --- /dev/null +++ b/libraries/test-utils/src/test-utils/Utils.cpp @@ -0,0 +1,40 @@ +// +// Created by Bradley Austin Davis on 2018/05/13 +// Copyright 2013-2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "Utils.h" + +#include +#include +#include + +#if defined(Q_OS_WIN) +#include +#endif + + +#include "FileDownloader.h" + +static QtMessageHandler originalHandler; + +static void messageHandler(QtMsgType type, const QMessageLogContext& context, const QString& message) { +#if defined(Q_OS_WIN) + OutputDebugStringA(message.toStdString().c_str()); + OutputDebugStringA("\n"); +#endif + originalHandler(type, context, message); +} + + +void installTestMessageHandler() { + originalHandler = qInstallMessageHandler(messageHandler); +} + +bool downloadFile(const QString& file, const std::function handler) { + FileDownloader(file, handler).waitForDownload(); + return true; +} \ No newline at end of file diff --git a/libraries/test-utils/src/test-utils/Utils.h b/libraries/test-utils/src/test-utils/Utils.h new file mode 100644 index 0000000000..27ed1f8207 --- /dev/null +++ b/libraries/test-utils/src/test-utils/Utils.h @@ -0,0 +1,17 @@ +// +// Created by Bradley Austin Davis on 2018/05/13 +// Copyright 2013-2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#pragma once + +class QByteArray; +class QString; + +#include + +void installTestMessageHandler(); + +bool downloadFile(const QString& url, const std::function handler); \ No newline at end of file diff --git a/libraries/ui/src/InteractiveWindow.cpp b/libraries/ui/src/InteractiveWindow.cpp index 5078fcb602..6c7f2d503f 100644 --- a/libraries/ui/src/InteractiveWindow.cpp +++ b/libraries/ui/src/InteractiveWindow.cpp @@ -13,12 +13,19 @@ #include #include +#include +#include #include #include #include "OffscreenUi.h" #include "shared/QtHelpers.h" +#include "MainWindow.h" + +#ifdef Q_OS_WIN +#include +#endif static auto CONTENT_WINDOW_QML = QUrl("InteractiveWindow.qml"); @@ -87,6 +94,12 @@ InteractiveWindow::InteractiveWindow(const QString& sourceUrl, const QVariantMap connect(object, SIGNAL(windowClosed()), this, SIGNAL(closed()), Qt::QueuedConnection); connect(object, SIGNAL(selfDestruct()), this, SLOT(close()), Qt::QueuedConnection); +#ifdef Q_OS_WIN + connect(object, SIGNAL(nativeWindowChanged()), this, SLOT(parentNativeWindowToMainWindow()), Qt::QueuedConnection); + connect(object, SIGNAL(interactiveWindowVisibleChanged()), this, SLOT(parentNativeWindowToMainWindow()), Qt::QueuedConnection); + connect(object, SIGNAL(presentationModeChanged()), this, SLOT(parentNativeWindowToMainWindow()), Qt::QueuedConnection); +#endif + QUrl sourceURL{ sourceUrl }; // If the passed URL doesn't correspond to a known scheme, assume it's a local file path if (!KNOWN_SCHEMES.contains(sourceURL.scheme(), Qt::CaseInsensitive)) { @@ -279,6 +292,24 @@ int InteractiveWindow::getPresentationMode() const { return _qmlWindow->property(PRESENTATION_MODE_PROPERTY).toInt(); } +#ifdef Q_OS_WIN +void InteractiveWindow::parentNativeWindowToMainWindow() { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "parentNativeWindowToMainWindow"); + return; + } + if (_qmlWindow.isNull()) { + return; + } + const auto nativeWindowProperty = _qmlWindow->property("nativeWindow"); + if (nativeWindowProperty.isNull() || !nativeWindowProperty.isValid()) { + return; + } + const auto nativeWindow = qvariant_cast(nativeWindowProperty); + SetWindowLongPtr((HWND)nativeWindow->winId(), GWLP_HWNDPARENT, (LONG)MainWindow::findMainWindow()->winId()); +} +#endif + void InteractiveWindow::setPresentationMode(int presentationMode) { if (QThread::currentThread() != thread()) { QMetaObject::invokeMethod(this, "setPresentationMode", Q_ARG(int, presentationMode)); diff --git a/libraries/ui/src/InteractiveWindow.h b/libraries/ui/src/InteractiveWindow.h index bf832550b5..f456b32e8d 100644 --- a/libraries/ui/src/InteractiveWindow.h +++ b/libraries/ui/src/InteractiveWindow.h @@ -84,6 +84,10 @@ private: Q_INVOKABLE void setPresentationMode(int presentationMode); Q_INVOKABLE int getPresentationMode() const; +#ifdef Q_OS_WIN + Q_INVOKABLE void parentNativeWindowToMainWindow(); +#endif + public slots: /**jsdoc diff --git a/libraries/ui/src/MainWindow.cpp b/libraries/ui/src/MainWindow.cpp index f9fc71e417..680433b2f9 100644 --- a/libraries/ui/src/MainWindow.cpp +++ b/libraries/ui/src/MainWindow.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include "ui/Logging.h" @@ -39,6 +40,18 @@ MainWindow::~MainWindow() { qCDebug(uiLogging) << "Destroying main window"; } +QWindow* MainWindow::findMainWindow() { + auto windows = qApp->topLevelWindows(); + QWindow* result = nullptr; + for (const auto& window : windows) { + if (window->objectName().contains("MainWindow")) { + result = window; + break; + } + } + return result; +} + void MainWindow::restoreGeometry() { // Did not use setGeometry() on purpose, // see http://doc.qt.io/qt-5/qsettings.html#restoring-the-state-of-a-gui-application diff --git a/libraries/ui/src/MainWindow.h b/libraries/ui/src/MainWindow.h index 75421340a2..fbd48e5eb1 100644 --- a/libraries/ui/src/MainWindow.h +++ b/libraries/ui/src/MainWindow.h @@ -21,6 +21,8 @@ class MainWindow : public QMainWindow { public: explicit MainWindow(QWidget* parent = NULL); ~MainWindow(); + + static QWindow* findMainWindow(); public slots: void restoreGeometry(); diff --git a/libraries/ui/src/OffscreenUi.cpp b/libraries/ui/src/OffscreenUi.cpp index 25f0652496..d82cfbbf3f 100644 --- a/libraries/ui/src/OffscreenUi.cpp +++ b/libraries/ui/src/OffscreenUi.cpp @@ -29,6 +29,7 @@ #include "ui/Logging.h" #include +#include "MainWindow.h" /**jsdoc * @namespace OffscreenFlags @@ -96,10 +97,13 @@ static OffscreenFlags* offscreenFlags { nullptr }; // so I think it's OK for the time being. bool OffscreenUi::shouldSwallowShortcut(QEvent* event) { Q_ASSERT(event->type() == QEvent::ShortcutOverride); - QObject* focusObject = getWindow()->focusObject(); - if (focusObject != getWindow() && focusObject != getRootItem()) { - event->accept(); - return true; + auto window = getWindow(); + if (window) { + QObject* focusObject = getWindow()->focusObject(); + if (focusObject != getWindow() && focusObject != getRootItem()) { + event->accept(); + return true; + } } return false; } @@ -646,20 +650,7 @@ public: } private: - - static QWindow* findMainWindow() { - auto windows = qApp->topLevelWindows(); - QWindow* result = nullptr; - for (auto window : windows) { - if (window->objectName().contains("MainWindow")) { - result = window; - break; - } - } - return result; - } - - QWindow* const _mainWindow { findMainWindow() }; + QWindow* const _mainWindow { MainWindow::findMainWindow() }; QWindow* _hackWindow { nullptr }; }; diff --git a/libraries/ui/src/QmlFragmentClass.cpp b/libraries/ui/src/QmlFragmentClass.cpp index c4cac73b2d..13e3527ded 100644 --- a/libraries/ui/src/QmlFragmentClass.cpp +++ b/libraries/ui/src/QmlFragmentClass.cpp @@ -20,10 +20,10 @@ std::mutex QmlFragmentClass::_mutex; std::map QmlFragmentClass::_fragments; -QmlFragmentClass::QmlFragmentClass(QString id) : qml(id) { } +QmlFragmentClass::QmlFragmentClass(bool restricted, QString id) : QmlWindowClass(restricted), qml(id) { } // Method called by Qt scripts to create a new bottom menu bar in Android -QScriptValue QmlFragmentClass::constructor(QScriptContext* context, QScriptEngine* engine) { +QScriptValue QmlFragmentClass::internal_constructor(QScriptContext* context, QScriptEngine* engine, bool restricted) { std::lock_guard guard(_mutex); auto qml = context->argument(0).toVariant().toMap().value("qml"); @@ -41,7 +41,7 @@ QScriptValue QmlFragmentClass::constructor(QScriptContext* context, QScriptEngin auto properties = parseArguments(context); auto offscreenUi = DependencyManager::get(); - QmlFragmentClass* retVal = new QmlFragmentClass(qml.toString()); + QmlFragmentClass* retVal = new QmlFragmentClass(restricted, qml.toString()); Q_ASSERT(retVal); if (QThread::currentThread() != qApp->thread()) { retVal->moveToThread(qApp->thread()); diff --git a/libraries/ui/src/QmlFragmentClass.h b/libraries/ui/src/QmlFragmentClass.h index 8a8d0e1732..ea80b2bd13 100644 --- a/libraries/ui/src/QmlFragmentClass.h +++ b/libraries/ui/src/QmlFragmentClass.h @@ -13,9 +13,19 @@ class QmlFragmentClass : public QmlWindowClass { Q_OBJECT + +private: + static QScriptValue internal_constructor(QScriptContext* context, QScriptEngine* engine, bool restricted); public: - QmlFragmentClass(QString id); - static QScriptValue constructor(QScriptContext* context, QScriptEngine* engine); + static QScriptValue constructor(QScriptContext* context, QScriptEngine* engine) { + return internal_constructor(context, engine, false); + } + + static QScriptValue restricted_constructor(QScriptContext* context, QScriptEngine* engine ){ + return internal_constructor(context, engine, true); + } + + QmlFragmentClass(bool restricted, QString id); /**jsdoc * Creates a new button, adds it to this and returns it. diff --git a/libraries/ui/src/QmlWebWindowClass.cpp b/libraries/ui/src/QmlWebWindowClass.cpp index 44a0af7787..282161497a 100644 --- a/libraries/ui/src/QmlWebWindowClass.cpp +++ b/libraries/ui/src/QmlWebWindowClass.cpp @@ -20,10 +20,10 @@ static const char* const URL_PROPERTY = "source"; static const char* const SCRIPT_PROPERTY = "scriptUrl"; // Method called by Qt scripts to create a new web window in the overlay -QScriptValue QmlWebWindowClass::constructor(QScriptContext* context, QScriptEngine* engine) { +QScriptValue QmlWebWindowClass::internal_constructor(QScriptContext* context, QScriptEngine* engine, bool restricted) { auto properties = parseArguments(context); auto offscreenUi = DependencyManager::get(); - QmlWebWindowClass* retVal = new QmlWebWindowClass(); + QmlWebWindowClass* retVal = new QmlWebWindowClass(restricted); Q_ASSERT(retVal); if (QThread::currentThread() != qApp->thread()) { retVal->moveToThread(qApp->thread()); diff --git a/libraries/ui/src/QmlWebWindowClass.h b/libraries/ui/src/QmlWebWindowClass.h index 8cf77e4286..e3aea22e3d 100644 --- a/libraries/ui/src/QmlWebWindowClass.h +++ b/libraries/ui/src/QmlWebWindowClass.h @@ -57,8 +57,18 @@ class QmlWebWindowClass : public QmlWindowClass { Q_OBJECT Q_PROPERTY(QString url READ getURL CONSTANT) +private: + static QScriptValue internal_constructor(QScriptContext* context, QScriptEngine* engine, bool restricted); public: - static QScriptValue constructor(QScriptContext* context, QScriptEngine* engine); + QmlWebWindowClass(bool restricted) : QmlWindowClass(restricted) {} + + static QScriptValue constructor(QScriptContext* context, QScriptEngine* engine) { + return internal_constructor(context, engine, false); + } + + static QScriptValue restricted_constructor(QScriptContext* context, QScriptEngine* engine ){ + return internal_constructor(context, engine, true); + } public slots: diff --git a/libraries/ui/src/QmlWindowClass.cpp b/libraries/ui/src/QmlWindowClass.cpp index 64fa27c8c6..0182e3adc3 100644 --- a/libraries/ui/src/QmlWindowClass.cpp +++ b/libraries/ui/src/QmlWindowClass.cpp @@ -25,6 +25,8 @@ #include #include "OffscreenUi.h" +#include "ui/types/HFWebEngineProfile.h" +#include "ui/types/FileTypeProfile.h" static const char* const SOURCE_PROPERTY = "source"; static const char* const TITLE_PROPERTY = "title"; @@ -68,10 +70,10 @@ QVariantMap QmlWindowClass::parseArguments(QScriptContext* context) { // Method called by Qt scripts to create a new web window in the overlay -QScriptValue QmlWindowClass::constructor(QScriptContext* context, QScriptEngine* engine) { +QScriptValue QmlWindowClass::internal_constructor(QScriptContext* context, QScriptEngine* engine, bool restricted) { auto properties = parseArguments(context); auto offscreenUi = DependencyManager::get(); - QmlWindowClass* retVal = new QmlWindowClass(); + QmlWindowClass* retVal = new QmlWindowClass(restricted); Q_ASSERT(retVal); if (QThread::currentThread() != qApp->thread()) { retVal->moveToThread(qApp->thread()); @@ -83,7 +85,7 @@ QScriptValue QmlWindowClass::constructor(QScriptContext* context, QScriptEngine* return engine->newQObject(retVal); } -QmlWindowClass::QmlWindowClass() { +QmlWindowClass::QmlWindowClass(bool restricted) : _restricted(restricted) { } @@ -99,8 +101,7 @@ void QmlWindowClass::initQml(QVariantMap properties) { auto offscreenUi = DependencyManager::get(); _source = properties[SOURCE_PROPERTY].toString(); - // Build the event bridge and wrapper on the main thread - offscreenUi->loadInNewContext(qmlSource(), [&](QQmlContext* context, QObject* object) { + auto objectInitLambda = [&](QQmlContext* context, QObject* object) { _qmlWindow = object; context->setContextProperty(EVENT_BRIDGE_PROPERTY, this); context->engine()->setObjectOwnership(this, QQmlEngine::CppOwnership); @@ -128,7 +129,24 @@ void QmlWindowClass::initQml(QVariantMap properties) { if (metaObject->indexOfSignal("moved") >= 0) connect(_qmlWindow, SIGNAL(moved(QVector2D)), this, SLOT(hasMoved(QVector2D)), Qt::QueuedConnection); connect(_qmlWindow, SIGNAL(windowClosed()), this, SLOT(hasClosed()), Qt::QueuedConnection); - }); + }; + + auto contextInitLambda = [&](QQmlContext* context) { +#if !defined(Q_OS_ANDROID) + // If the restricted flag is on, override the FileTypeProfile and HFWebEngineProfile objects in the + // QML surface root context with local ones + qDebug() << "Context initialization lambda"; + if (_restricted) { + qDebug() << "Restricting web content"; + ContextAwareProfile::restrictContext(context); + FileTypeProfile::registerWithContext(context); + HFWebEngineProfile::registerWithContext(context); + } +#endif + }; + + // Build the event bridge and wrapper on the main thread + offscreenUi->loadInNewContext(qmlSource(), objectInitLambda, contextInitLambda); Q_ASSERT(_qmlWindow); Q_ASSERT(dynamic_cast(_qmlWindow.data())); diff --git a/libraries/ui/src/QmlWindowClass.h b/libraries/ui/src/QmlWindowClass.h index 2b01c028ea..18ee1fedd5 100644 --- a/libraries/ui/src/QmlWindowClass.h +++ b/libraries/ui/src/QmlWindowClass.h @@ -38,9 +38,18 @@ class QmlWindowClass : public QObject { Q_PROPERTY(glm::vec2 size READ getSize WRITE setSize NOTIFY sizeChanged) Q_PROPERTY(bool visible READ isVisible WRITE setVisible NOTIFY visibleChanged) +private: + static QScriptValue internal_constructor(QScriptContext* context, QScriptEngine* engine, bool restricted); public: - static QScriptValue constructor(QScriptContext* context, QScriptEngine* engine); - QmlWindowClass(); + static QScriptValue constructor(QScriptContext* context, QScriptEngine* engine) { + return internal_constructor(context, engine, false); + } + + static QScriptValue restricted_constructor(QScriptContext* context, QScriptEngine* engine ){ + return internal_constructor(context, engine, true); + } + + QmlWindowClass(bool restricted); ~QmlWindowClass(); /**jsdoc @@ -51,6 +60,8 @@ public: QQuickItem* asQuickItem() const; + + public slots: /**jsdoc @@ -250,10 +261,12 @@ protected: QPointer _qmlWindow; QString _source; + const bool _restricted; private: // QmlWindow content may include WebView requiring EventBridge. void setKeyboardRaised(QObject* object, bool raised, bool numeric = false); + }; #endif diff --git a/libraries/ui/src/ui/OffscreenQmlSurface.cpp b/libraries/ui/src/ui/OffscreenQmlSurface.cpp index 48e778c063..74098f69c7 100644 --- a/libraries/ui/src/ui/OffscreenQmlSurface.cpp +++ b/libraries/ui/src/ui/OffscreenQmlSurface.cpp @@ -265,19 +265,6 @@ void OffscreenQmlSurface::initializeEngine(QQmlEngine* engine) { if (!javaScriptToInject.isEmpty()) { rootContext->setContextProperty("eventBridgeJavaScriptToInject", QVariant(javaScriptToInject)); } -#if !defined(Q_OS_ANDROID) - rootContext->setContextProperty("FileTypeProfile", new FileTypeProfile(rootContext)); - rootContext->setContextProperty("HFWebEngineProfile", new HFWebEngineProfile(rootContext)); - { - PROFILE_RANGE(startup, "FileTypeProfile"); - rootContext->setContextProperty("FileTypeProfile", new FileTypeProfile(rootContext)); - } - { - PROFILE_RANGE(startup, "HFWebEngineProfile"); - rootContext->setContextProperty("HFWebEngineProfile", new HFWebEngineProfile(rootContext)); - - } -#endif rootContext->setContextProperty("Paths", DependencyManager::get().data()); rootContext->setContextProperty("Tablet", DependencyManager::get().data()); rootContext->setContextProperty("Toolbars", DependencyManager::get().data()); @@ -300,6 +287,17 @@ void OffscreenQmlSurface::onRootContextCreated(QQmlContext* qmlContext) { // FIXME Compatibility mechanism for existing HTML and JS that uses eventBridgeWrapper // Find a way to flag older scripts using this mechanism and wanr that this is deprecated qmlContext->setContextProperty("eventBridgeWrapper", new EventBridgeWrapper(this, qmlContext)); +#if !defined(Q_OS_ANDROID) + { + PROFILE_RANGE(startup, "FileTypeProfile"); + FileTypeProfile::registerWithContext(qmlContext); + } + { + PROFILE_RANGE(startup, "HFWebEngineProfile"); + HFWebEngineProfile::registerWithContext(qmlContext); + + } +#endif } QQmlContext* OffscreenQmlSurface::contextForUrl(const QUrl& qmlSource, QQuickItem* parent, bool forceNewContext) { @@ -331,8 +329,7 @@ void OffscreenQmlSurface::onRootCreated() { getSurfaceContext()->setContextProperty("offscreenWindow", QVariant::fromValue(getWindow())); // Connect with the audio client and listen for audio device changes - auto audioIO = DependencyManager::get(); - connect(audioIO.data(), &AudioClient::deviceChanged, this, [&](QAudio::Mode mode, const QAudioDeviceInfo& device) { + connect(DependencyManager::get().data(), &AudioClient::deviceChanged, this, [this](QAudio::Mode mode, const QAudioDeviceInfo& device) { if (mode == QAudio::Mode::AudioOutput) { QMetaObject::invokeMethod(this, "changeAudioOutputDevice", Qt::QueuedConnection, Q_ARG(QString, device.deviceName())); } @@ -426,11 +423,17 @@ PointerEvent::EventType OffscreenQmlSurface::choosePointerEventType(QEvent::Type } void OffscreenQmlSurface::hoverBeginEvent(const PointerEvent& event, class QTouchDevice& device) { +#if defined(DISABLE_QML) + return; +#endif handlePointerEvent(event, device); _activeTouchPoints[event.getID()].hovering = true; } void OffscreenQmlSurface::hoverEndEvent(const PointerEvent& event, class QTouchDevice& device) { +#if defined(DISABLE_QML) + return; +#endif _activeTouchPoints[event.getID()].hovering = false; // Send a fake mouse move event if // - the event told us to @@ -444,6 +447,10 @@ void OffscreenQmlSurface::hoverEndEvent(const PointerEvent& event, class QTouchD } bool OffscreenQmlSurface::handlePointerEvent(const PointerEvent& event, class QTouchDevice& device, bool release) { +#if defined(DISABLE_QML) + return false; +#endif + // Ignore mouse interaction if we're paused if (!getRootItem() || isPaused()) { return false; diff --git a/libraries/ui/src/ui/OffscreenQmlSurfaceCache.cpp b/libraries/ui/src/ui/OffscreenQmlSurfaceCache.cpp index 51fe11fdc7..6911c34e2a 100644 --- a/libraries/ui/src/ui/OffscreenQmlSurfaceCache.cpp +++ b/libraries/ui/src/ui/OffscreenQmlSurfaceCache.cpp @@ -46,8 +46,18 @@ void OffscreenQmlSurfaceCache::release(const QString& rootSource, const QSharedP QSharedPointer OffscreenQmlSurfaceCache::buildSurface(const QString& rootSource) { auto surface = QSharedPointer(new OffscreenQmlSurface()); + + QObject::connect(surface.data(), &hifi::qml::OffscreenSurface::rootContextCreated, [this, rootSource](QQmlContext* surfaceContext) { + if (_onRootContextCreated) { + _onRootContextCreated(rootSource, surfaceContext); + } + }); + surface->load(rootSource); surface->resize(QSize(100, 100)); return surface; } +void OffscreenQmlSurfaceCache::setOnRootContextCreated(const std::function& onRootContextCreated) { + _onRootContextCreated = onRootContextCreated; +} \ No newline at end of file diff --git a/libraries/ui/src/ui/OffscreenQmlSurfaceCache.h b/libraries/ui/src/ui/OffscreenQmlSurfaceCache.h index 02382a791b..6dc8b79ca3 100644 --- a/libraries/ui/src/ui/OffscreenQmlSurfaceCache.h +++ b/libraries/ui/src/ui/OffscreenQmlSurfaceCache.h @@ -13,6 +13,7 @@ #include +class QQmlContext; class OffscreenQmlSurface; class OffscreenQmlSurfaceCache : public Dependency { @@ -25,10 +26,12 @@ public: QSharedPointer acquire(const QString& rootSource); void release(const QString& rootSource, const QSharedPointer& surface); void reserve(const QString& rootSource, int count = 1); + void setOnRootContextCreated(const std::function & onRootContextCreated); private: QSharedPointer buildSurface(const QString& rootSource); QHash>> _cache; + std::function _onRootContextCreated; }; #endif diff --git a/libraries/ui/src/ui/TabletScriptingInterface.cpp b/libraries/ui/src/ui/TabletScriptingInterface.cpp index 6f00e046af..1081f8c4e7 100644 --- a/libraries/ui/src/ui/TabletScriptingInterface.cpp +++ b/libraries/ui/src/ui/TabletScriptingInterface.cpp @@ -140,8 +140,7 @@ int TabletButtonsProxyModel::buttonIndex(const QString &uuid) { return -1; } -void TabletButtonsProxyModel::setPageIndex(int pageIndex) -{ +void TabletButtonsProxyModel::setPageIndex(int pageIndex) { if (_pageIndex == pageIndex) return; @@ -334,6 +333,8 @@ static const char* VRMENU_SOURCE_URL = "hifi/tablet/TabletMenu.qml"; class TabletRootWindow : public QmlWindowClass { virtual QString qmlSource() const override { return "hifi/tablet/WindowRoot.qml"; } +public: + TabletRootWindow() : QmlWindowClass(false) {} }; TabletProxy::TabletProxy(QObject* parent, const QString& name) : QObject(parent), _name(name) { @@ -463,6 +464,9 @@ void TabletProxy::onTabletShown() { _showRunningScripts = false; pushOntoStack("hifi/dialogs/TabletRunningScripts.qml"); } + if (_currentPathLoaded == TABLET_HOME_SOURCE_URL) { + loadHomeScreen(true); + } } } diff --git a/libraries/ui/src/ui/types/ContextAwareProfile.cpp b/libraries/ui/src/ui/types/ContextAwareProfile.cpp new file mode 100644 index 0000000000..98cc94ec10 --- /dev/null +++ b/libraries/ui/src/ui/types/ContextAwareProfile.cpp @@ -0,0 +1,32 @@ +// +// FileTypeProfile.cpp +// interface/src/networking +// +// Created by Kunal Gosar on 2017-03-10. +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "ContextAwareProfile.h" + +#if !defined(Q_OS_ANDROID) + +#include + +static const QString RESTRICTED_FLAG_PROPERTY = "RestrictFileAccess"; + +ContextAwareProfile::ContextAwareProfile(QQmlContext* parent) : + QQuickWebEngineProfile(parent), _context(parent) { } + + +void ContextAwareProfile::restrictContext(QQmlContext* context) { + context->setContextProperty(RESTRICTED_FLAG_PROPERTY, true); +} + +bool ContextAwareProfile::isRestricted(QQmlContext* context) { + return context->contextProperty(RESTRICTED_FLAG_PROPERTY).toBool(); +} + +#endif \ No newline at end of file diff --git a/libraries/ui/src/ui/types/ContextAwareProfile.h b/libraries/ui/src/ui/types/ContextAwareProfile.h new file mode 100644 index 0000000000..8fa5b98878 --- /dev/null +++ b/libraries/ui/src/ui/types/ContextAwareProfile.h @@ -0,0 +1,42 @@ +// +// Created by Bradley Austin Davis on 2018/07/27 +// Copyright 2013-2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#pragma once + +#ifndef hifi_ContextAwareProfile_h +#define hifi_ContextAwareProfile_h + +#include + +#if !defined(Q_OS_ANDROID) +#include +#include + +class QQmlContext; + +class ContextAwareProfile : public QQuickWebEngineProfile { +public: + static void restrictContext(QQmlContext* context); + static bool isRestricted(QQmlContext* context); + QQmlContext* getContext() const { return _context; } +protected: + + class RequestInterceptor : public QWebEngineUrlRequestInterceptor { + public: + RequestInterceptor(ContextAwareProfile* parent) : QWebEngineUrlRequestInterceptor(parent), _profile(parent) {} + QQmlContext* getContext() const { return _profile->getContext(); } + protected: + ContextAwareProfile* _profile; + }; + + ContextAwareProfile(QQmlContext* parent); + QQmlContext* _context; +}; +#endif + +#endif // hifi_FileTypeProfile_h diff --git a/libraries/ui/src/ui/types/FileTypeProfile.cpp b/libraries/ui/src/ui/types/FileTypeProfile.cpp index 90a2c6ba18..073460903e 100644 --- a/libraries/ui/src/ui/types/FileTypeProfile.cpp +++ b/libraries/ui/src/ui/types/FileTypeProfile.cpp @@ -11,18 +11,31 @@ #include "FileTypeProfile.h" -#include "FileTypeRequestInterceptor.h" +#include + +#include "RequestFilters.h" #if !defined(Q_OS_ANDROID) static const QString QML_WEB_ENGINE_STORAGE_NAME = "qmlWebEngine"; -FileTypeProfile::FileTypeProfile(QObject* parent) : - QQuickWebEngineProfile(parent) +FileTypeProfile::FileTypeProfile(QQmlContext* parent) : + ContextAwareProfile(parent) { static const QString WEB_ENGINE_USER_AGENT = "Chrome/48.0 (HighFidelityInterface)"; setHttpUserAgent(WEB_ENGINE_USER_AGENT); - auto requestInterceptor = new FileTypeRequestInterceptor(this); + auto requestInterceptor = new RequestInterceptor(this); setRequestInterceptor(requestInterceptor); } + +void FileTypeProfile::RequestInterceptor::interceptRequest(QWebEngineUrlRequestInfo& info) { + RequestFilters::interceptHFWebEngineRequest(info, getContext()); + RequestFilters::interceptFileType(info, getContext()); +} + +void FileTypeProfile::registerWithContext(QQmlContext* context) { + context->setContextProperty("FileTypeProfile", new FileTypeProfile(context)); +} + + #endif \ No newline at end of file diff --git a/libraries/ui/src/ui/types/FileTypeProfile.h b/libraries/ui/src/ui/types/FileTypeProfile.h index c7d07cd822..7ddfdd0aed 100644 --- a/libraries/ui/src/ui/types/FileTypeProfile.h +++ b/libraries/ui/src/ui/types/FileTypeProfile.h @@ -17,12 +17,23 @@ #include #if !defined(Q_OS_ANDROID) -#include +#include "ContextAwareProfile.h" + +class FileTypeProfile : public ContextAwareProfile { + using Parent = ContextAwareProfile; -class FileTypeProfile : public QQuickWebEngineProfile { public: - FileTypeProfile(QObject* parent = Q_NULLPTR); + static void registerWithContext(QQmlContext* parent); + +protected: + FileTypeProfile(QQmlContext* parent); + class RequestInterceptor : public Parent::RequestInterceptor { + public: + RequestInterceptor(ContextAwareProfile* parent) : Parent::RequestInterceptor(parent) {} + void interceptRequest(QWebEngineUrlRequestInfo& info) override; + }; }; + #endif #endif // hifi_FileTypeProfile_h diff --git a/libraries/ui/src/ui/types/FileTypeRequestInterceptor.cpp b/libraries/ui/src/ui/types/FileTypeRequestInterceptor.cpp deleted file mode 100644 index 25866ad395..0000000000 --- a/libraries/ui/src/ui/types/FileTypeRequestInterceptor.cpp +++ /dev/null @@ -1,25 +0,0 @@ -// -// FileTypeRequestInterceptor.cpp -// interface/src/networking -// -// Created by Kunal Gosar on 2017-03-10. -// Copyright 2017 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#include "FileTypeRequestInterceptor.h" - -#include - -#include "RequestFilters.h" - -#if !defined(Q_OS_ANDROID) - -void FileTypeRequestInterceptor::interceptRequest(QWebEngineUrlRequestInfo& info) { - RequestFilters::interceptHFWebEngineRequest(info); - RequestFilters::interceptFileType(info); -} - -#endif \ No newline at end of file diff --git a/libraries/ui/src/ui/types/FileTypeRequestInterceptor.h b/libraries/ui/src/ui/types/FileTypeRequestInterceptor.h deleted file mode 100644 index b8a01a53fa..0000000000 --- a/libraries/ui/src/ui/types/FileTypeRequestInterceptor.h +++ /dev/null @@ -1,30 +0,0 @@ -// -// FileTypeRequestInterceptor.h -// interface/src/networking -// -// Created by Kunal Gosar on 2017-03-10. -// Copyright 2017 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#pragma once - -#ifndef hifi_FileTypeRequestInterceptor_h -#define hifi_FileTypeRequestInterceptor_h - -#include - -#if !defined(Q_OS_ANDROID) -#include - -class FileTypeRequestInterceptor : public QWebEngineUrlRequestInterceptor { -public: - FileTypeRequestInterceptor(QObject* parent) : QWebEngineUrlRequestInterceptor(parent) {}; - - virtual void interceptRequest(QWebEngineUrlRequestInfo& info) override; -}; -#endif - -#endif // hifi_FileTypeRequestInterceptor_h diff --git a/libraries/ui/src/ui/types/HFTabletWebEngineProfile.h b/libraries/ui/src/ui/types/HFTabletWebEngineProfile.h deleted file mode 100644 index 406cb1a19a..0000000000 --- a/libraries/ui/src/ui/types/HFTabletWebEngineProfile.h +++ /dev/null @@ -1,23 +0,0 @@ -// -// HFTabletWebEngineProfile.h -// interface/src/networking -// -// Created by Dante Ruiz on 2017-03-31. -// Copyright 2017 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - - -#ifndef hifi_HFTabletWebEngineProfile_h -#define hifi_HFTabletWebEngineProfile_h - -#include - -class HFTabletWebEngineProfile : public QQuickWebEngineProfile { -public: - HFTabletWebEngineProfile(QObject* parent = Q_NULLPTR); -}; - -#endif // hifi_HFTabletWebEngineProfile_h diff --git a/libraries/ui/src/ui/types/HFTabletWebEngineRequestInterceptor.h b/libraries/ui/src/ui/types/HFTabletWebEngineRequestInterceptor.h deleted file mode 100644 index 8be2974782..0000000000 --- a/libraries/ui/src/ui/types/HFTabletWebEngineRequestInterceptor.h +++ /dev/null @@ -1,30 +0,0 @@ -// -// HFTabletWebEngineRequestInterceptor.h -// interface/src/networking -// -// Created by Dante Ruiz on 2017-3-31. -// Copyright 2017 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#ifndef hifi_HFTabletWebEngineRequestInterceptor_h -#define hifi_HFTabletWebEngineRequestInterceptor_h -#if !defined(Q_OS_ANDROID) -#include - -#include - -class HFTabletWebEngineRequestInterceptor - : public QWebEngineUrlRequestInterceptor -{ -public: - HFTabletWebEngineRequestInterceptor(QObject* parent) - : QWebEngineUrlRequestInterceptor(parent) - {}; - virtual void interceptRequest(QWebEngineUrlRequestInfo& info) override; -}; -#endif - -#endif // hifi_HFWebEngineRequestInterceptor_h diff --git a/libraries/ui/src/ui/types/HFWebEngineProfile.cpp b/libraries/ui/src/ui/types/HFWebEngineProfile.cpp index 381bdb10bd..ef1d009f09 100644 --- a/libraries/ui/src/ui/types/HFWebEngineProfile.cpp +++ b/libraries/ui/src/ui/types/HFWebEngineProfile.cpp @@ -11,20 +11,28 @@ #include "HFWebEngineProfile.h" -#include "HFWebEngineRequestInterceptor.h" +#include + +#include "RequestFilters.h" #if !defined(Q_OS_ANDROID) static const QString QML_WEB_ENGINE_STORAGE_NAME = "qmlWebEngine"; -HFWebEngineProfile::HFWebEngineProfile(QObject* parent) : - QQuickWebEngineProfile(parent) +HFWebEngineProfile::HFWebEngineProfile(QQmlContext* parent) : Parent(parent) { setStorageName(QML_WEB_ENGINE_STORAGE_NAME); // we use the HFWebEngineRequestInterceptor to make sure that web requests are authenticated for the interface user - auto requestInterceptor = new HFWebEngineRequestInterceptor(this); - setRequestInterceptor(requestInterceptor); + setRequestInterceptor(new RequestInterceptor(this)); } -#endif \ No newline at end of file +void HFWebEngineProfile::RequestInterceptor::interceptRequest(QWebEngineUrlRequestInfo& info) { + RequestFilters::interceptHFWebEngineRequest(info, getContext()); +} + +void HFWebEngineProfile::registerWithContext(QQmlContext* context) { + context->setContextProperty("HFWebEngineProfile", new HFWebEngineProfile(context)); +} + +#endif diff --git a/libraries/ui/src/ui/types/HFWebEngineProfile.h b/libraries/ui/src/ui/types/HFWebEngineProfile.h index 30da489c92..6b84ad6f80 100644 --- a/libraries/ui/src/ui/types/HFWebEngineProfile.h +++ b/libraries/ui/src/ui/types/HFWebEngineProfile.h @@ -14,15 +14,24 @@ #ifndef hifi_HFWebEngineProfile_h #define hifi_HFWebEngineProfile_h -#include +#include "ContextAwareProfile.h" #if !defined(Q_OS_ANDROID) -#include -class HFWebEngineProfile : public QQuickWebEngineProfile { +class HFWebEngineProfile : public ContextAwareProfile { + using Parent = ContextAwareProfile; public: - HFWebEngineProfile(QObject* parent = Q_NULLPTR); + static void registerWithContext(QQmlContext* parent); + +protected: + HFWebEngineProfile(QQmlContext* parent); + class RequestInterceptor : public Parent::RequestInterceptor { + public: + RequestInterceptor(ContextAwareProfile* parent) : Parent::RequestInterceptor(parent) {} + void interceptRequest(QWebEngineUrlRequestInfo& info) override; + }; }; + #endif #endif // hifi_HFWebEngineProfile_h diff --git a/libraries/ui/src/ui/types/HFWebEngineRequestInterceptor.cpp b/libraries/ui/src/ui/types/HFWebEngineRequestInterceptor.cpp deleted file mode 100644 index 5a11c32efa..0000000000 --- a/libraries/ui/src/ui/types/HFWebEngineRequestInterceptor.cpp +++ /dev/null @@ -1,25 +0,0 @@ -// -// HFWebEngineRequestInterceptor.cpp -// interface/src/networking -// -// Created by Stephen Birarda on 2016-10-14. -// Copyright 2016 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#include "HFWebEngineRequestInterceptor.h" - -#include - -#include "AccountManager.h" -#include "RequestFilters.h" - -#if !defined(Q_OS_ANDROID) - -void HFWebEngineRequestInterceptor::interceptRequest(QWebEngineUrlRequestInfo& info) { - RequestFilters::interceptHFWebEngineRequest(info); -} - -#endif \ No newline at end of file diff --git a/libraries/ui/src/ui/types/HFWebEngineRequestInterceptor.h b/libraries/ui/src/ui/types/HFWebEngineRequestInterceptor.h deleted file mode 100644 index b5521a106e..0000000000 --- a/libraries/ui/src/ui/types/HFWebEngineRequestInterceptor.h +++ /dev/null @@ -1,30 +0,0 @@ -// -// HFWebEngineRequestInterceptor.h -// interface/src/networking -// -// Created by Stephen Birarda on 2016-10-14. -// Copyright 2016 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#pragma once - -#ifndef hifi_HFWebEngineRequestInterceptor_h -#define hifi_HFWebEngineRequestInterceptor_h - -#include - -#if !defined(Q_OS_ANDROID) -#include - -class HFWebEngineRequestInterceptor : public QWebEngineUrlRequestInterceptor { -public: - HFWebEngineRequestInterceptor(QObject* parent) : QWebEngineUrlRequestInterceptor(parent) {}; - - virtual void interceptRequest(QWebEngineUrlRequestInfo& info) override; -}; -#endif - -#endif // hifi_HFWebEngineRequestInterceptor_h diff --git a/libraries/ui/src/ui/types/RequestFilters.cpp b/libraries/ui/src/ui/types/RequestFilters.cpp index 4cd51c6d98..7f192d6e52 100644 --- a/libraries/ui/src/ui/types/RequestFilters.cpp +++ b/libraries/ui/src/ui/types/RequestFilters.cpp @@ -10,12 +10,15 @@ // #include "RequestFilters.h" -#include "NetworkingConstants.h" #include -#include +#include -#include "AccountManager.h" +#include +#include +#include + +#include "ContextAwareProfile.h" #if !defined(Q_OS_ANDROID) @@ -42,9 +45,29 @@ namespace { return filename.endsWith(".json", Qt::CaseInsensitive); } + bool blockLocalFiles(QWebEngineUrlRequestInfo& info) { + auto requestUrl = info.requestUrl(); + if (!requestUrl.isLocalFile()) { + // Not a local file, do not block + return false; + } + + // We can potentially add whitelisting logic or development environment variables that + // will allow people to override this setting on a per-client basis here. + QString targetFilePath = QFileInfo(requestUrl.toLocalFile()).canonicalFilePath(); + + // If we get here, we've determined it's a local file and we have no reason not to block it + qWarning() << "Blocking web access to local file path" << targetFilePath; + info.block(true); + return true; + } } -void RequestFilters::interceptHFWebEngineRequest(QWebEngineUrlRequestInfo& info) { +void RequestFilters::interceptHFWebEngineRequest(QWebEngineUrlRequestInfo& info, QQmlContext* context) { + if (ContextAwareProfile::isRestricted(context) && blockLocalFiles(info)) { + return; + } + // check if this is a request to a highfidelity URL bool isAuthable = isAuthableHighFidelityURL(info.requestUrl()); if (isAuthable) { @@ -71,7 +94,7 @@ void RequestFilters::interceptHFWebEngineRequest(QWebEngineUrlRequestInfo& info) info.setHttpHeader(USER_AGENT.toLocal8Bit(), tokenString.toLocal8Bit()); } -void RequestFilters::interceptFileType(QWebEngineUrlRequestInfo& info) { +void RequestFilters::interceptFileType(QWebEngineUrlRequestInfo& info, QQmlContext* context) { QString filename = info.requestUrl().fileName(); if (isScript(filename) || isJSON(filename)) { static const QString CONTENT_HEADER = "Accept"; @@ -79,4 +102,4 @@ void RequestFilters::interceptFileType(QWebEngineUrlRequestInfo& info) { info.setHttpHeader(CONTENT_HEADER.toLocal8Bit(), TYPE_VALUE.toLocal8Bit()); } } -#endif \ No newline at end of file +#endif diff --git a/libraries/ui/src/ui/types/RequestFilters.h b/libraries/ui/src/ui/types/RequestFilters.h index ccab6a6ee3..8fde94a1b4 100644 --- a/libraries/ui/src/ui/types/RequestFilters.h +++ b/libraries/ui/src/ui/types/RequestFilters.h @@ -20,10 +20,12 @@ #include #include +class QQmlContext; + class RequestFilters : public QObject { public: - static void interceptHFWebEngineRequest(QWebEngineUrlRequestInfo& info); - static void interceptFileType(QWebEngineUrlRequestInfo& info); + static void interceptHFWebEngineRequest(QWebEngineUrlRequestInfo& info, QQmlContext* context); + static void interceptFileType(QWebEngineUrlRequestInfo& info, QQmlContext* context); }; #endif diff --git a/libraries/workload/src/workload/Space.h b/libraries/workload/src/workload/Space.h index 310955f4c6..7dcb2217f7 100644 --- a/libraries/workload/src/workload/Space.h +++ b/libraries/workload/src/workload/Space.h @@ -70,7 +70,8 @@ private: using SpacePointer = std::shared_ptr; using Changes = std::vector; using IndexVectors = std::vector; -using Timings = std::vector; +using Timing_ns = std::chrono::nanoseconds; +using Timings = std::vector; } // namespace workload diff --git a/libraries/workload/src/workload/ViewTask.cpp b/libraries/workload/src/workload/ViewTask.cpp index 4b77733c52..9d0c693149 100644 --- a/libraries/workload/src/workload/ViewTask.cpp +++ b/libraries/workload/src/workload/ViewTask.cpp @@ -91,23 +91,60 @@ void ControlViews::run(const workload::WorkloadContextPointer& runContext, const regulateViews(outViews, inTimings); } - // Export the timings for debuging - if (inTimings.size()) { - _dataExport.timings[workload::Region::R1] = std::chrono::duration(inTimings[0]).count(); - _dataExport.timings[workload::Region::R2] = _dataExport.timings[workload::Region::R1]; - _dataExport.timings[workload::Region::R3] = std::chrono::duration(inTimings[1]).count(); + // Export the ranges for debuging + bool doExport = false; + if (outViews.size()) { + _dataExport.ranges[workload::Region::R1] = outViews[0].regionBackFronts[workload::Region::R1]; + _dataExport.ranges[workload::Region::R2] = outViews[0].regionBackFronts[workload::Region::R2]; + _dataExport.ranges[workload::Region::R3] = outViews[0].regionBackFronts[workload::Region::R3]; + doExport = true; + } + // Export the ranges and timings for debuging + if (inTimings.size()) { + // NOTE for reference: + // inTimings[0] = prePhysics entities + // inTimings[1] = prePhysics avatars + // inTimings[2] = stepPhysics + // inTimings[3] = postPhysics + // inTimings[4] = non-physical kinematics + // inTimings[5] = game loop + _dataExport.timings[workload::Region::R1] = std::chrono::duration(inTimings[2] + inTimings[3]).count(); + _dataExport.timings[workload::Region::R2] = _dataExport.timings[workload::Region::R1]; + _dataExport.timings[workload::Region::R3] = std::chrono::duration(inTimings[4]).count(); + doExport = true; + } + + if (doExport) { auto config = std::static_pointer_cast(runContext->jobConfig); config->dataExport = _dataExport; config->emitDirty(); } } -glm::vec2 Regulator::run(const Timing_ns& regulationDuration, const Timing_ns& measured, const glm::vec2& current) { - // Regulate next value based on current moving toward the goal budget - float error_ms = std::chrono::duration(_budget - measured).count(); - float coef = glm::clamp(error_ms / std::chrono::duration(regulationDuration).count(), -1.0f, 1.0f); - return current * (1.0f + coef * (error_ms < 0.0f ? _relativeStepDown : _relativeStepUp)); +glm::vec2 Regulator::run(const Timing_ns& deltaTime, const Timing_ns& measuredTime, const glm::vec2& currentFrontBack) { + // measure signal: average and noise + const float FILTER_TIMESCALE = 0.5f * (float)NSECS_PER_SECOND; + float del = deltaTime.count() / FILTER_TIMESCALE; + if (del > 1.0f) { + del = 1.0f; // clamp for stability + } + _measuredTimeAverage = (1.0f - del) * _measuredTimeAverage + del * measuredTime.count(); + float diff = measuredTime.count() - _measuredTimeAverage; + _measuredTimeNoiseSquared = (1.0f - del) * _measuredTimeNoiseSquared + del * diff * diff; + float noise = sqrtf(_measuredTimeNoiseSquared); + + // check budget + float offsetFromTarget = _budget.count() - _measuredTimeAverage; + if (fabsf(offsetFromTarget) < noise) { + // budget is within the noise --> do nothing + return currentFrontBack; + } + + // compute response + glm::vec2 stepDelta = offsetFromTarget < 0.0f ? -_relativeStepDown : _relativeStepUp; + stepDelta *= glm::min(1.0f, (fabsf(offsetFromTarget) - noise) / noise); // ease out of "do nothing" + return currentFrontBack * (1.0f + stepDelta); } glm::vec2 Regulator::clamp(const glm::vec2& backFront) const { @@ -116,24 +153,26 @@ glm::vec2 Regulator::clamp(const glm::vec2& backFront) const { } void ControlViews::regulateViews(workload::Views& outViews, const workload::Timings& timings) { - for (auto& outView : outViews) { for (int32_t r = 0; r < workload::Region::NUM_VIEW_REGIONS; r++) { outView.regionBackFronts[r] = regionBackFronts[r]; } } - auto loopDuration = std::chrono::nanoseconds{ std::chrono::milliseconds(16) }; - regionBackFronts[workload::Region::R1] = regionRegulators[workload::Region::R1].run(loopDuration, timings[0], regionBackFronts[workload::Region::R1]); - regionBackFronts[workload::Region::R2] = regionRegulators[workload::Region::R2].run(loopDuration, timings[0], regionBackFronts[workload::Region::R2]); - regionBackFronts[workload::Region::R3] = regionRegulators[workload::Region::R3].run(loopDuration, timings[1], regionBackFronts[workload::Region::R3]); + // Note: for reference: + // timings[0] = prePhysics entities + // timings[1] = prePhysics avatars + // timings[2] = stepPhysics + // timings[3] = postPhysics + // timings[4] = non-physical kinematics + // timings[5] = game loop + + auto loopDuration = timings[5]; + regionBackFronts[workload::Region::R1] = regionRegulators[workload::Region::R1].run(loopDuration, timings[2] + timings[3], regionBackFronts[workload::Region::R1]); + regionBackFronts[workload::Region::R2] = regionRegulators[workload::Region::R2].run(loopDuration, timings[2] + timings[3], regionBackFronts[workload::Region::R2]); + regionBackFronts[workload::Region::R3] = regionRegulators[workload::Region::R3].run(loopDuration, timings[4], regionBackFronts[workload::Region::R3]); enforceRegionContainment(); - - _dataExport.ranges[workload::Region::R1] = regionBackFronts[workload::Region::R1]; - _dataExport.ranges[workload::Region::R2] = regionBackFronts[workload::Region::R2]; - _dataExport.ranges[workload::Region::R3] = regionBackFronts[workload::Region::R3]; - for (auto& outView : outViews) { outView.regionBackFronts[workload::Region::R1] = regionBackFronts[workload::Region::R1]; outView.regionBackFronts[workload::Region::R2] = regionBackFronts[workload::Region::R2]; diff --git a/libraries/workload/src/workload/ViewTask.h b/libraries/workload/src/workload/ViewTask.h index 867f22d534..207bc04276 100644 --- a/libraries/workload/src/workload/ViewTask.h +++ b/libraries/workload/src/workload/ViewTask.h @@ -32,13 +32,13 @@ namespace workload { }; const std::vector MAX_VIEW_BACK_FRONTS = { - { 100.0f, 200.0f }, - { 150.0f, 300.0f }, - { 250.0f, 500.0f } + { 100.0f, 1600.0f }, + { 150.0f, 10000.0f }, + { 250.0f, 16000.0f } }; - const float RELATIVE_STEP_DOWN = 0.05f; - const float RELATIVE_STEP_UP = 0.04f; + const float RELATIVE_STEP_DOWN = 0.11f; + const float RELATIVE_STEP_UP = 0.09f; class SetupViewsConfig : public Job::Config{ Q_OBJECT @@ -192,7 +192,7 @@ namespace workload { struct Data { - bool regulateViewRanges{ false }; + bool regulateViewRanges{ true }; // regulation is ON by default } data; struct DataExport { @@ -212,20 +212,31 @@ namespace workload { }; struct Regulator { - using Timing_ns = std::chrono::nanoseconds; - Timing_ns _budget{ std::chrono::milliseconds(2) }; glm::vec2 _minRange{ MIN_VIEW_BACK_FRONTS[0] }; glm::vec2 _maxRange{ MAX_VIEW_BACK_FRONTS[0] }; - glm::vec2 _relativeStepDown{ RELATIVE_STEP_DOWN }; glm::vec2 _relativeStepUp{ RELATIVE_STEP_UP }; - + Timing_ns _budget{ std::chrono::milliseconds(2) }; + float _measuredTimeAverage { 0.0f }; + float _measuredTimeNoiseSquared { 0.0f }; Regulator() {} - Regulator(const Timing_ns& budget_ns, const glm::vec2& minRange, const glm::vec2& maxRange, const glm::vec2& relativeStepDown, const glm::vec2& relativeStepUp) : - _budget(budget_ns), _minRange(minRange), _maxRange(maxRange), _relativeStepDown(relativeStepDown), _relativeStepUp(relativeStepUp) {} + Regulator(const Timing_ns& budget_ns, + const glm::vec2& minRange, + const glm::vec2& maxRange, + const glm::vec2& relativeStepDown, + const glm::vec2& relativeStepUp) : + _minRange(minRange), + _maxRange(maxRange), + _relativeStepDown(relativeStepDown), + _relativeStepUp(relativeStepUp), + _budget(budget_ns), + _measuredTimeAverage(budget_ns.count()), + _measuredTimeNoiseSquared(0.0f) + {} - glm::vec2 run(const Timing_ns& regulationDuration, const Timing_ns& measured, const glm::vec2& current); + void setBudget(const Timing_ns& budget) { _budget = budget; } + glm::vec2 run(const Timing_ns& deltaTime, const Timing_ns& measuredTime, const glm::vec2& currentFrontBack); glm::vec2 clamp(const glm::vec2& backFront) const; }; diff --git a/plugins/oculus/src/OculusBaseDisplayPlugin.cpp b/plugins/oculus/src/OculusBaseDisplayPlugin.cpp index 5aa1e45943..36790d1b50 100644 --- a/plugins/oculus/src/OculusBaseDisplayPlugin.cpp +++ b/plugins/oculus/src/OculusBaseDisplayPlugin.cpp @@ -36,6 +36,10 @@ bool OculusBaseDisplayPlugin::beginFrameRender(uint32_t frameIndex) { if (ovr::reorientRequested(status)) { emit resetSensorsRequested(); } + if (ovr::hmdMounted(status) != _hmdMounted) { + _hmdMounted = !_hmdMounted; + emit hmdMountedChanged(); + } _currentRenderFrameInfo = FrameInfo(); _currentRenderFrameInfo.sensorSampleTime = ovr_GetTimeInSeconds(); diff --git a/plugins/oculus/src/OculusBaseDisplayPlugin.h b/plugins/oculus/src/OculusBaseDisplayPlugin.h index d70d14dc28..244c06ecf5 100644 --- a/plugins/oculus/src/OculusBaseDisplayPlugin.h +++ b/plugins/oculus/src/OculusBaseDisplayPlugin.h @@ -44,4 +44,5 @@ protected: ovrLayerEyeFov _sceneLayer; ovrViewScaleDesc _viewScaleDesc; // ovrLayerEyeFovDepth _depthLayer; + bool _hmdMounted { false }; }; diff --git a/plugins/openvr/src/OpenVrDisplayPlugin.cpp b/plugins/openvr/src/OpenVrDisplayPlugin.cpp index 5e4079cbcf..fae2144caf 100644 --- a/plugins/openvr/src/OpenVrDisplayPlugin.cpp +++ b/plugins/openvr/src/OpenVrDisplayPlugin.cpp @@ -23,7 +23,6 @@ #include #include - #include #include #include @@ -36,7 +35,7 @@ Q_DECLARE_LOGGING_CATEGORY(displayplugins) -const char* OpenVrThreadedSubmit { "OpenVR Threaded Submit" }; // this probably shouldn't be hardcoded here +const char* OpenVrThreadedSubmit{ "OpenVR Threaded Submit" }; // this probably shouldn't be hardcoded here PoseData _nextRenderPoseData; PoseData _nextSimPoseData; @@ -44,8 +43,8 @@ PoseData _nextSimPoseData; #define MIN_CORES_FOR_NORMAL_RENDER 5 bool forceInterleavedReprojection = (QThread::idealThreadCount() < MIN_CORES_FOR_NORMAL_RENDER); -static std::array VR_EYES { { vr::Eye_Left, vr::Eye_Right } }; -bool _openVrDisplayActive { false }; +static std::array VR_EYES{ { vr::Eye_Left, vr::Eye_Right } }; +bool _openVrDisplayActive{ false }; // Flip y-axis since GL UV coords are backwards. static vr::VRTextureBounds_t OPENVR_TEXTURE_BOUNDS_LEFT{ 0, 0, 0.5f, 1 }; static vr::VRTextureBounds_t OPENVR_TEXTURE_BOUNDS_RIGHT{ 0.5f, 0, 1, 1 }; @@ -164,9 +163,7 @@ public: friend class OpenVrDisplayPlugin; std::shared_ptr _canvas; - OpenVrSubmitThread(OpenVrDisplayPlugin& plugin) : _plugin(plugin) { - setObjectName("OpenVR Submit Thread"); - } + OpenVrSubmitThread(OpenVrDisplayPlugin& plugin) : _plugin(plugin) { setObjectName("OpenVR Submit Thread"); } void updateSource() { _plugin.withNonPresentThreadLock([&] { @@ -190,18 +187,18 @@ public: }); } - GLuint _program { 0 }; + GLuint _program{ 0 }; void updateProgram() { if (!_program) { std::string vsSource = HMD_REPROJECTION_VERT; std::string fsSource = HMD_REPROJECTION_FRAG; - GLuint vertexShader { 0 }, fragmentShader { 0 }; + GLuint vertexShader{ 0 }, fragmentShader{ 0 }; std::string error; - ::gl::CachedShader binary; ::gl::compileShader(GL_VERTEX_SHADER, vsSource, vertexShader, error); ::gl::compileShader(GL_FRAGMENT_SHADER, fsSource, fragmentShader, error); - _program = ::gl::compileProgram({ { vertexShader, fragmentShader } }, error, binary); + _program = ::gl::buildProgram({ { vertexShader, fragmentShader } }); + ::gl::linkProgram(_program, error); glDeleteShader(vertexShader); glDeleteShader(fragmentShader); qDebug() << "Rebuild proigram"; @@ -211,14 +208,13 @@ public: #define COLOR_BUFFER_COUNT 4 void run() override { - - GLuint _framebuffer { 0 }; + GLuint _framebuffer{ 0 }; std::array _colors; - size_t currentColorBuffer { 0 }; - size_t globalColorBufferCount { 0 }; - GLuint _uniformBuffer { 0 }; - GLuint _vao { 0 }; - GLuint _depth { 0 }; + size_t currentColorBuffer{ 0 }; + size_t globalColorBufferCount{ 0 }; + GLuint _uniformBuffer{ 0 }; + GLuint _vao{ 0 }; + GLuint _depth{ 0 }; Reprojection _reprojection; QThread::currentThread()->setPriority(QThread::Priority::TimeCriticalPriority); @@ -229,7 +225,6 @@ public: glCreateVertexArrays(1, &_vao); glBindVertexArray(_vao); - glCreateFramebuffers(1, &_framebuffer); { glCreateRenderbuffers(1, &_depth); @@ -253,7 +248,6 @@ public: continue; } - updateProgram(); { auto presentRotation = glm::mat3(_nextRender.poses[0]); @@ -281,13 +275,15 @@ public: static const vr::VRTextureBounds_t leftBounds{ 0, 0, 0.5f, 1 }; static const vr::VRTextureBounds_t rightBounds{ 0.5f, 0, 1, 1 }; - vr::Texture_t texture{ (void*)(uintptr_t)_colors[currentColorBuffer], vr::TextureType_OpenGL, vr::ColorSpace_Auto }; + vr::Texture_t texture{ (void*)(uintptr_t)_colors[currentColorBuffer], vr::TextureType_OpenGL, + vr::ColorSpace_Auto }; vr::VRCompositor()->Submit(vr::Eye_Left, &texture, &leftBounds); vr::VRCompositor()->Submit(vr::Eye_Right, &texture, &rightBounds); _plugin._presentRate.increment(); PoseData nextRender, nextSim; nextRender.frameIndex = _plugin.presentCount(); - vr::VRCompositor()->WaitGetPoses(nextRender.vrPoses, vr::k_unMaxTrackedDeviceCount, nextSim.vrPoses, vr::k_unMaxTrackedDeviceCount); + vr::VRCompositor()->WaitGetPoses(nextRender.vrPoses, vr::k_unMaxTrackedDeviceCount, nextSim.vrPoses, + vr::k_unMaxTrackedDeviceCount); // Copy invalid poses in nextSim from nextRender for (uint32_t i = 0; i < vr::k_unMaxTrackedDeviceCount; ++i) { @@ -297,9 +293,7 @@ public: } mat4 sensorResetMat; - _plugin.withNonPresentThreadLock([&] { - sensorResetMat = _plugin._sensorResetMat; - }); + _plugin.withNonPresentThreadLock([&] { sensorResetMat = _plugin._sensorResetMat; }); nextRender.update(sensorResetMat); nextSim.update(sensorResetMat); @@ -327,16 +321,12 @@ public: _canvas->doneCurrent(); } - void update(const CompositeInfo& newCompositeInfo) { - _queue.push(newCompositeInfo); - } + void update(const CompositeInfo& newCompositeInfo) { _queue.push(newCompositeInfo); } void waitForPresent() { auto lastCount = _presentCount.load(); Lock lock(_plugin._presentMutex); - _presented.wait(lock, [&]()->bool { - return _presentCount.load() > lastCount; - }); + _presented.wait(lock, [&]() -> bool { return _presentCount.load() > lastCount; }); _nextSimPoseData = _nextSim; _nextRenderPoseData = _nextRender; } @@ -345,9 +335,9 @@ public: CompositeInfo::Queue _queue; PoseData _nextRender, _nextSim; - bool _quit { false }; - GLuint _currentTexture { 0 }; - std::atomic _presentCount { 0 }; + bool _quit{ false }; + GLuint _currentTexture{ 0 }; + std::atomic _presentCount{ 0 }; Condition _presented; OpenVrDisplayPlugin& _plugin; }; @@ -451,7 +441,7 @@ bool OpenVrDisplayPlugin::internalActivate() { _openVrDisplayActive = true; _system->GetRecommendedRenderTargetSize(&_renderTargetSize.x, &_renderTargetSize.y); - // Recommended render target size is per-eye, so double the X size for + // Recommended render target size is per-eye, so double the X size for // left + right eyes _renderTargetSize.x *= 2; @@ -468,7 +458,6 @@ bool OpenVrDisplayPlugin::internalActivate() { if (forceInterleavedReprojection) { vr::VRCompositor()->ForceInterleavedReprojectionOn(true); } - // set up default sensor space such that the UI overlay will align with the front of the room. auto chaperone = vr::VRChaperone(); @@ -482,9 +471,9 @@ bool OpenVrDisplayPlugin::internalActivate() { glm::vec3 uiPos(0.0f, UI_HEIGHT, UI_RADIUS - (0.5f * zSize) - UI_Z_OFFSET); _sensorResetMat = glm::inverse(createMatFromQuatAndPos(glm::quat(), uiPos)); } else { - #if DEV_BUILD - qDebug() << "OpenVR: error could not get chaperone pointer"; - #endif +#if DEV_BUILD + qDebug() << "OpenVR: error could not get chaperone pointer"; +#endif } if (_threadedSubmit) { @@ -523,7 +512,9 @@ void OpenVrDisplayPlugin::customizeContext() { _compositeInfos[0].texture = _compositeFramebuffer->getRenderBuffer(0); for (size_t i = 0; i < COMPOSITING_BUFFER_SIZE; ++i) { if (0 != i) { - _compositeInfos[i].texture = gpu::Texture::createRenderBuffer(gpu::Element::COLOR_RGBA_32, _renderTargetSize.x, _renderTargetSize.y, gpu::Texture::SINGLE_MIP, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_POINT)); + _compositeInfos[i].texture = gpu::Texture::createRenderBuffer(gpu::Element::COLOR_RGBA_32, _renderTargetSize.x, + _renderTargetSize.y, gpu::Texture::SINGLE_MIP, + gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_POINT)); } _compositeInfos[i].textureID = getGLBackend()->getTextureID(_compositeInfos[i].texture); } @@ -544,9 +535,7 @@ void OpenVrDisplayPlugin::uncustomizeContext() { void OpenVrDisplayPlugin::resetSensors() { glm::mat4 m; - withNonPresentThreadLock([&] { - m = toGlm(_nextSimPoseData.vrPoses[0].mDeviceToAbsoluteTracking); - }); + withNonPresentThreadLock([&] { m = toGlm(_nextSimPoseData.vrPoses[0].mDeviceToAbsoluteTracking); }); _sensorResetMat = glm::inverse(cancelOutRollAndPitch(m)); } @@ -567,9 +556,7 @@ bool OpenVrDisplayPlugin::beginFrameRender(uint32_t frameIndex) { _currentRenderFrameInfo = FrameInfo(); PoseData nextSimPoseData; - withNonPresentThreadLock([&] { - nextSimPoseData = _nextSimPoseData; - }); + withNonPresentThreadLock([&] { nextSimPoseData = _nextSimPoseData; }); // HACK: when interface is launched and steam vr is NOT running, openvr will return bad HMD poses for a few frames // To workaround this, filter out any hmd poses that are obviously bad, i.e. beneath the floor. @@ -582,10 +569,11 @@ bool OpenVrDisplayPlugin::beginFrameRender(uint32_t frameIndex) { _lastGoodHMDPose = nextSimPoseData.vrPoses[vr::k_unTrackedDeviceIndex_Hmd].mDeviceToAbsoluteTracking; } - vr::TrackedDeviceIndex_t handIndices[2] { vr::k_unTrackedDeviceIndexInvalid, vr::k_unTrackedDeviceIndexInvalid }; + vr::TrackedDeviceIndex_t handIndices[2]{ vr::k_unTrackedDeviceIndexInvalid, vr::k_unTrackedDeviceIndexInvalid }; { - vr::TrackedDeviceIndex_t controllerIndices[2] ; - auto trackedCount = _system->GetSortedTrackedDeviceIndicesOfClass(vr::TrackedDeviceClass_Controller, controllerIndices, 2); + vr::TrackedDeviceIndex_t controllerIndices[2]; + auto trackedCount = + _system->GetSortedTrackedDeviceIndicesOfClass(vr::TrackedDeviceClass_Controller, controllerIndices, 2); // Find the left and right hand controllers, if they exist for (uint32_t i = 0; i < std::min(trackedCount, 2); ++i) { if (nextSimPoseData.vrPoses[i].bPoseIsValid) { @@ -614,13 +602,12 @@ bool OpenVrDisplayPlugin::beginFrameRender(uint32_t frameIndex) { const vec3& angularVelocity = nextSimPoseData.angularVelocities[deviceIndex]; auto correctedPose = openVrControllerPoseToHandPose(i == 0, mat, linearVelocity, angularVelocity); static const glm::quat HAND_TO_LASER_ROTATION = glm::rotation(Vectors::UNIT_Z, Vectors::UNIT_NEG_Y); - handPoses[i] = glm::translate(glm::mat4(), correctedPose.translation) * glm::mat4_cast(correctedPose.rotation * HAND_TO_LASER_ROTATION); + handPoses[i] = glm::translate(glm::mat4(), correctedPose.translation) * + glm::mat4_cast(correctedPose.rotation * HAND_TO_LASER_ROTATION); } } - withNonPresentThreadLock([&] { - _frameInfos[frameIndex] = _currentRenderFrameInfo; - }); + withNonPresentThreadLock([&] { _frameInfos[frameIndex] = _currentRenderFrameInfo; }); return Parent::beginFrameRender(frameIndex); } @@ -652,9 +639,7 @@ void OpenVrDisplayPlugin::compositeLayers() { if (!newComposite.textureID) { newComposite.textureID = getGLBackend()->getTextureID(newComposite.texture); } - withPresentThreadLock([&] { - _submitThread->update(newComposite); - }); + withPresentThreadLock([&] { _submitThread->update(newComposite); }); } } @@ -665,7 +650,7 @@ void OpenVrDisplayPlugin::hmdPresent() { _submitThread->waitForPresent(); } else { GLuint glTexId = getGLBackend()->getTextureID(_compositeFramebuffer->getRenderBuffer(0)); - vr::Texture_t vrTexture { (void*)(uintptr_t)glTexId, vr::TextureType_OpenGL, vr::ColorSpace_Auto }; + vr::Texture_t vrTexture{ (void*)(uintptr_t)glTexId, vr::TextureType_OpenGL, vr::ColorSpace_Auto }; vr::VRCompositor()->Submit(vr::Eye_Left, &vrTexture, &OPENVR_TEXTURE_BOUNDS_LEFT); vr::VRCompositor()->Submit(vr::Eye_Right, &vrTexture, &OPENVR_TEXTURE_BOUNDS_RIGHT); vr::VRCompositor()->PostPresentHandoff(); @@ -687,19 +672,20 @@ void OpenVrDisplayPlugin::postPreview() { _hmdActivityLevel = _system->GetTrackedDeviceActivityLevel(vr::k_unTrackedDeviceIndex_Hmd); if (!_threadedSubmit) { - vr::VRCompositor()->WaitGetPoses(nextRender.vrPoses, vr::k_unMaxTrackedDeviceCount, nextSim.vrPoses, vr::k_unMaxTrackedDeviceCount); + vr::VRCompositor()->WaitGetPoses(nextRender.vrPoses, vr::k_unMaxTrackedDeviceCount, nextSim.vrPoses, + vr::k_unMaxTrackedDeviceCount); glm::mat4 resetMat; - withPresentThreadLock([&] { - resetMat = _sensorResetMat; - }); + withPresentThreadLock([&] { resetMat = _sensorResetMat; }); nextRender.update(resetMat); nextSim.update(resetMat); - withPresentThreadLock([&] { - _nextSimPoseData = nextSim; - }); + withPresentThreadLock([&] { _nextSimPoseData = nextSim; }); _nextRenderPoseData = nextRender; + } + if (isHmdMounted() != _hmdMounted) { + _hmdMounted = !_hmdMounted; + emit hmdMountedChanged(); } } @@ -711,7 +697,7 @@ void OpenVrDisplayPlugin::updatePresentPose() { _currentPresentFrameInfo.presentPose = _nextRenderPoseData.poses[vr::k_unTrackedDeviceIndex_Hmd]; } -bool OpenVrDisplayPlugin::suppressKeyboard() { +bool OpenVrDisplayPlugin::suppressKeyboard() { if (isOpenVrKeyboardShown()) { return false; } @@ -732,10 +718,10 @@ void OpenVrDisplayPlugin::unsuppressKeyboard() { } bool OpenVrDisplayPlugin::isKeyboardVisible() { - return isOpenVrKeyboardShown(); + return isOpenVrKeyboardShown(); } -int OpenVrDisplayPlugin::getRequiredThreadCount() const { +int OpenVrDisplayPlugin::getRequiredThreadCount() const { return Parent::getRequiredThreadCount() + (_threadedSubmit ? 1 : 0); } @@ -764,4 +750,3 @@ QString OpenVrDisplayPlugin::getPreferredAudioOutDevice() const { } return device; } - diff --git a/plugins/openvr/src/OpenVrDisplayPlugin.h b/plugins/openvr/src/OpenVrDisplayPlugin.h index 15a434341d..add35d6383 100644 --- a/plugins/openvr/src/OpenVrDisplayPlugin.h +++ b/plugins/openvr/src/OpenVrDisplayPlugin.h @@ -90,4 +90,6 @@ private: friend class OpenVrSubmitThread; bool _asyncReprojectionActive { false }; + + bool _hmdMounted { false }; }; diff --git a/scripts/developer/tests/agentAPITest.js b/scripts/developer/tests/agentAPITest.js new file mode 100644 index 0000000000..b7d21efbdf --- /dev/null +++ b/scripts/developer/tests/agentAPITest.js @@ -0,0 +1,55 @@ +// agentAPITest.js +// scripts/developer/tests +// +// Created by Thijs Wenker on 7/23/18. +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html + +var SOUND_DATA = { url: "http://hifi-content.s3.amazonaws.com/howard/sounds/piano1.wav" }; + +// getSound function from crowd-agent.js +function getSound(data, callback) { // callback(sound) when downloaded (which may be immediate). + var sound = SoundCache.getSound(data.url); + if (sound.downloaded) { + return callback(sound); + } + function onDownloaded() { + sound.ready.disconnect(onDownloaded); + callback(sound); + } + sound.ready.connect(onDownloaded); +} + + +function agentAPITest() { + console.warn('Agent.isAvatar =', Agent.isAvatar); + + Agent.isAvatar = true; + console.warn('Agent.isAvatar =', Agent.isAvatar); + + console.warn('Agent.isListeningToAudioStream =', Agent.isListeningToAudioStream); + + Agent.isListeningToAudioStream = true; + console.warn('Agent.isListeningToAudioStream =', Agent.isListeningToAudioStream); + + console.warn('Agent.isNoiseGateEnabled =', Agent.isNoiseGateEnabled); + + Agent.isNoiseGateEnabled = true; + console.warn('Agent.isNoiseGateEnabled =', Agent.isNoiseGateEnabled); + console.warn('Agent.lastReceivedAudioLoudness =', Agent.lastReceivedAudioLoudness); + console.warn('Agent.sessionUUID =', Agent.sessionUUID); + + getSound(SOUND_DATA, function (sound) { + console.warn('Agent.isPlayingAvatarSound =', Agent.isPlayingAvatarSound); + Agent.playAvatarSound(sound); + console.warn('Agent.isPlayingAvatarSound =', Agent.isPlayingAvatarSound); + }); +} + +if (Script.context === "agent") { + agentAPITest(); +} else { + console.error('This script should be run as agent script. EXITING.'); +} diff --git a/scripts/developer/utilities/lib/plotperf/PlotPerf.qml b/scripts/developer/utilities/lib/plotperf/PlotPerf.qml index 916c9cae55..f239cc010a 100644 --- a/scripts/developer/utilities/lib/plotperf/PlotPerf.qml +++ b/scripts/developer/utilities/lib/plotperf/PlotPerf.qml @@ -48,6 +48,11 @@ Item { property var valueMax : 1 + property var valueMin : 0 + + property var displayMinAt0 : true + property var _displayMaxValue : 1 + property var _displayMinValue : 0 property var _values property var tick : 0 @@ -71,7 +76,9 @@ Item { value: value, fromBinding: isBinding, valueMax: 1, + valueMin: 0, numSamplesConstantMax: 0, + numSamplesConstantMin: 0, valueHistory: new Array(), label: (plot["label"] !== undefined ? plot["label"] : ""), color: (plot["color"] !== undefined ? plot["color"] : "white"), @@ -90,6 +97,11 @@ Item { _values[i].valueMax *= 0.25 // Fast reduce the max value as we click } } + function resetMin() { + for (var i = 0; i < _values.length; i++) { + _values[i].valueMin *= 0.25 // Fast reduce the min value as we click + } + } function pullFreshValues() { // Wait until values are created to begin pulling @@ -99,6 +111,7 @@ Item { tick++; var currentValueMax = 0 + var currentValueMin = 0 for (var i = 0; i < _values.length; i++) { var currentVal = (+_values[i].object[_values[i].value]) * _values[i].scale; @@ -112,26 +125,47 @@ Item { _values[i].valueMax *= 0.99 _values[i].numSamplesConstantMax = 0 } + if (lostValue <= _values[i].valueMin) { + _values[i].valueMin *= 0.99 + _values[i].numSamplesConstantMin = 0 + } } if (_values[i].valueMax < currentVal) { _values[i].valueMax = currentVal; _values[i].numSamplesConstantMax = 0 } + if (_values[i].valueMin > currentVal) { + _values[i].valueMin = currentVal; + _values[i].numSamplesConstantMin = 0 + } if (_values[i].numSamplesConstantMax > VALUE_HISTORY_SIZE) { _values[i].numSamplesConstantMax = 0 _values[i].valueMax *= 0.95 // lower slowly the current max if no new above max since a while } - + if (_values[i].numSamplesConstantMin > VALUE_HISTORY_SIZE) { + _values[i].numSamplesConstantMin = 0 + _values[i].valueMin *= 0.95 // lower slowly the current min if no new above min since a while + } + if (currentValueMax < _values[i].valueMax) { currentValueMax = _values[i].valueMax } + if (currentValueMin > _values[i].valueMin) { + currentValueMin = _values[i].valueMin + } } if ((valueMax < currentValueMax) || (tick % VALUE_HISTORY_SIZE == 0)) { valueMax = currentValueMax; } + if ((valueMin > currentValueMin) || (tick % VALUE_HISTORY_SIZE == 0)) { + valueMin = currentValueMin; + } + _displayMaxValue = valueMax; + _displayMinValue = ( displayMinAt0 ? 0 : valueMin ) + mycanvas.requestPaint() } @@ -152,10 +186,10 @@ Item { } function pixelFromVal(val, valScale) { - return lineHeight + (height - lineHeight) * (1 - (0.9) * val / valueMax); + return lineHeight + (height - lineHeight) * (1 - (0.99) * (val - _displayMinValue) / (_displayMaxValue - _displayMinValue)); } function valueFromPixel(pixY) { - return ((pixY - lineHeight) / (height - lineHeight) - 1) * valueMax / (-0.9); + return _displayMinValue + (((pixY - lineHeight) / (height - lineHeight) - 1) * (_displayMaxValue - _displayMinValue) / (-0.99)); } function plotValueHistory(ctx, valHistory, color) { var widthStep= width / (valHistory.length - 1); @@ -183,8 +217,10 @@ Item { function displayTitle(ctx, text, maxVal) { ctx.fillStyle = "grey"; ctx.textAlign = "right"; - ctx.fillText(displayValue(valueFromPixel(lineHeight), root.valueUnit), width, lineHeight); + ctx.fillText("max " + displayValue(_displayMaxValue, root.valueUnit), width, pixelFromVal(_displayMaxValue)); + ctx.fillText("min " + displayValue(_displayMinValue, root.valueUnit), width, pixelFromVal(_displayMinValue)); + ctx.fillStyle = "white"; ctx.textAlign = "left"; ctx.fillText(text, 0, lineHeight); @@ -193,15 +229,37 @@ Item { ctx.fillStyle = Qt.rgba(0, 0, 0, root.backgroundOpacity); ctx.fillRect(0, 0, width, height); - ctx.strokeStyle= "grey"; + /* ctx.strokeStyle= "grey"; ctx.lineWidth="2"; ctx.beginPath(); ctx.moveTo(0, lineHeight + 1); - ctx.lineTo(width, lineHeight + 1); + ctx.lineTo(width, lineHeight + 1); ctx.moveTo(0, height); ctx.lineTo(width, height); + ctx.stroke();*/ + } + + function displayMaxZeroMin(ctx) { + var maxY = pixelFromVal(_displayMaxValue); + + ctx.strokeStyle= "LightSlateGray"; + ctx.lineWidth="1"; + ctx.beginPath(); + ctx.moveTo(0, maxY); + ctx.lineTo(width, maxY); ctx.stroke(); + + if (_displayMinValue != 0) { + var zeroY = pixelFromVal(0); + var minY = pixelFromVal(_displayMinValue); + ctx.beginPath(); + ctx.moveTo(0, zeroY); + ctx.lineTo(width, zeroY); + ctx.moveTo(0, minY); + ctx.lineTo(width, minY); + ctx.stroke(); + } } var ctx = getContext("2d"); @@ -215,7 +273,9 @@ Item { displayValueLegend(ctx, _values[i], i) } - displayTitle(ctx, title, valueMax) + displayMaxZeroMin(ctx); + + displayTitle(ctx, title, _displayMaxValue) } } @@ -225,6 +285,7 @@ Item { onClicked: { resetMax(); + resetMin(); } } } diff --git a/scripts/developer/utilities/render/bloom.qml b/scripts/developer/utilities/render/bloom.qml index 52090348d9..705a9826d6 100644 --- a/scripts/developer/utilities/render/bloom.qml +++ b/scripts/developer/utilities/render/bloom.qml @@ -14,20 +14,11 @@ import "configSlider" Item { id: root - property var config: Render.getConfig("RenderMainView.Bloom") - property var configThreshold: Render.getConfig("RenderMainView.BloomThreshold") property var configDebug: Render.getConfig("RenderMainView.DebugBloom") Column { spacing: 8 - CheckBox { - text: "Enable" - checked: root.config["enabled"] - onCheckedChanged: { - root.config["enabled"] = checked; - } - } GroupBox { title: "Debug" Row { @@ -88,35 +79,5 @@ Item { } } } - ConfigSlider { - label: "Intensity" - integral: false - config: root.config - property: "intensity" - max: 1.0 - min: 0.0 - width: 280 - height:38 - } - ConfigSlider { - label: "Size" - integral: false - config: root.config - property: "size" - max: 1.0 - min: 0.0 - width: 280 - height:38 - } - ConfigSlider { - label: "Threshold" - integral: false - config: root.configThreshold - property: "threshold" - max: 2.0 - min: 0.0 - width: 280 - height:38 - } } } diff --git a/scripts/developer/utilities/render/configSlider/RichSlider.qml b/scripts/developer/utilities/render/configSlider/RichSlider.qml new file mode 100644 index 0000000000..01b14f3d48 --- /dev/null +++ b/scripts/developer/utilities/render/configSlider/RichSlider.qml @@ -0,0 +1,81 @@ +// +// RichSlider.qml +// +// Created by Zach Pomerantz on 2/8/2016 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.7 +import QtQuick.Controls 1.4 as Original +import QtQuick.Controls.Styles 1.4 + +import "qrc:///qml/styles-uit" +import "qrc:///qml/controls-uit" as HifiControls + + +Item { + HifiConstants { id: hifi } + id: root + + anchors.left: parent.left + anchors.right: parent.right + height: 24 + + function defaultGet() { return 0 } + function defaultSet(value) { } + + property var labelAreaWidthScale: 0.5 + property bool integral: false + property var numDigits: 2 + + property var valueVarSetter: defaultSet + property alias valueVar : sliderControl.value + + property alias min: sliderControl.minimumValue + property alias max: sliderControl.maximumValue + + property alias label: labelControl.text + property bool showLabel: true + + property bool showValue: true + + + + signal valueChanged(real value) + + Component.onCompleted: { + } + + HifiControls.Label { + id: labelControl + text: root.label + enabled: root.showLabel + anchors.left: root.left + width: root.width * root.labelAreaWidthScale + anchors.verticalCenter: root.verticalCenter + } + + HifiControls.Slider { + id: sliderControl + stepSize: root.integral ? 1.0 : 0.0 + anchors.left: labelControl.right + anchors.right: root.right + anchors.rightMargin: 0 + anchors.top: root.top + anchors.topMargin: 0 + onValueChanged: { root.valueVarSetter(value) } + } + + HifiControls.Label { + id: labelValue + enabled: root.showValue + text: sliderControl.value.toFixed(root.integral ? 0 : root.numDigits) + anchors.right: labelControl.right + anchors.rightMargin: 5 + anchors.verticalCenter: root.verticalCenter + } + +} diff --git a/scripts/developer/utilities/render/configSlider/qmldir b/scripts/developer/utilities/render/configSlider/qmldir index 6680ec9638..479f786b22 100644 --- a/scripts/developer/utilities/render/configSlider/qmldir +++ b/scripts/developer/utilities/render/configSlider/qmldir @@ -1 +1,2 @@ -ConfigSlider 1.0 ConfigSlider.qml \ No newline at end of file +ConfigSlider 1.0 ConfigSlider.qml +RichSlider 1.0 RichSlider.qml \ No newline at end of file diff --git a/scripts/developer/utilities/render/culling.qml b/scripts/developer/utilities/render/culling.qml index 2ce3cc1dea..801cb5b573 100644 --- a/scripts/developer/utilities/render/culling.qml +++ b/scripts/developer/utilities/render/culling.qml @@ -19,7 +19,7 @@ Column { Component.onCompleted: { sceneOctree.enabled = true; - itemSelection.enabled = true; + itemSelection.enabled = true; sceneOctree.showVisibleCells = false; sceneOctree.showEmptyCells = false; itemSelection.showInsideItems = false; @@ -29,9 +29,9 @@ Column { } Component.onDestruction: { sceneOctree.enabled = false; - itemSelection.enabled = false; + itemSelection.enabled = false; Render.getConfig("RenderMainView.FetchSceneSelection").freezeFrustum = false; - Render.getConfig("RenderMainView.CullSceneSelection").freezeFrustum = false; + Render.getConfig("RenderMainView.CullSceneSelection").freezeFrustum = false; } GroupBox { @@ -44,7 +44,7 @@ Column { CheckBox { text: "Freeze Culling Frustum" checked: false - onCheckedChanged: { + onCheckedChanged: { Render.getConfig("RenderMainView.FetchSceneSelection").freezeFrustum = checked; Render.getConfig("RenderMainView.CullSceneSelection").freezeFrustum = checked; } @@ -88,15 +88,19 @@ Column { text: "Partial Sub-cell Items" checked: false onCheckedChanged: { root.itemSelection.showPartialSubcellItems = checked } - } + } } } - } + } GroupBox { title: "Render Items" + anchors.left: parent.left; + anchors.right: parent.right; Column{ + anchors.left: parent.left; + anchors.right: parent.right; Repeater { model: [ "Opaque:RenderMainView.DrawOpaqueDeferred", "Transparent:RenderMainView.DrawTransparentDeferred", "Light:RenderMainView.DrawLight", "Opaque Overlays:RenderMainView.DrawOverlay3DOpaque", "Transparent Overlays:RenderMainView.DrawOverlay3DTransparent" ] diff --git a/scripts/developer/utilities/render/debugBloom.js b/scripts/developer/utilities/render/debugBloom.js index 3a508d351c..39629ab0ce 100644 --- a/scripts/developer/utilities/render/debugBloom.js +++ b/scripts/developer/utilities/render/debugBloom.js @@ -15,6 +15,6 @@ var window = new OverlayWindow({ title: 'Bloom', source: qml, width: 285, - height: 210, + height: 40, }); window.closed.connect(function() { Script.stop(); }); \ No newline at end of file diff --git a/scripts/developer/utilities/render/debugShadow.js b/scripts/developer/utilities/render/debugShadow.js index 1f1d00e6b4..1ff59a316a 100644 --- a/scripts/developer/utilities/render/debugShadow.js +++ b/scripts/developer/utilities/render/debugShadow.js @@ -1,3 +1,5 @@ +"use strict"; + // // debugShadow.js // developer/utilities/render @@ -9,12 +11,71 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -// Set up the qml ui -var qml = Script.resolvePath('shadow.qml'); -var window = new OverlayWindow({ - title: 'Shadow Debug', - source: qml, - width: 250, - height: 300 -}); -window.closed.connect(function() { Script.stop(); }); \ No newline at end of file +(function() { + var TABLET_BUTTON_NAME = "Shadow"; + var QMLAPP_URL = Script.resolvePath("./shadow.qml"); + + + var onLuciScreen = false; + + function onClicked() { + if (onLuciScreen) { + tablet.gotoHomeScreen(); + } else { + tablet.loadQMLSource(QMLAPP_URL); + } + } + + var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + var button = tablet.addButton({ + text: TABLET_BUTTON_NAME, + sortOrder: 1 + }); + + var hasEventBridge = false; + + function wireEventBridge(on) { + if (!tablet) { + print("Warning in wireEventBridge(): 'tablet' undefined!"); + return; + } + if (on) { + if (!hasEventBridge) { + tablet.fromQml.connect(fromQml); + hasEventBridge = true; + } + } else { + if (hasEventBridge) { + tablet.fromQml.disconnect(fromQml); + hasEventBridge = false; + } + } + } + + function onScreenChanged(type, url) { + if (url === QMLAPP_URL) { + onLuciScreen = true; + } else { + onLuciScreen = false; + } + + button.editProperties({isActive: onLuciScreen}); + wireEventBridge(onLuciScreen); + } + + function fromQml(message) { + } + + button.clicked.connect(onClicked); + tablet.screenChanged.connect(onScreenChanged); + + Script.scriptEnding.connect(function () { + if (onLuciScreen) { + tablet.gotoHomeScreen(); + } + button.clicked.disconnect(onClicked); + tablet.screenChanged.disconnect(onScreenChanged); + tablet.removeButton(button); + }); + +}()); \ No newline at end of file diff --git a/scripts/developer/utilities/render/lod.js b/scripts/developer/utilities/render/lod.js index 307e509d39..f3e4208034 100644 --- a/scripts/developer/utilities/render/lod.js +++ b/scripts/developer/utilities/render/lod.js @@ -16,15 +16,9 @@ var ICON_URL = Script.resolvePath("../../../system/assets/images/lod-i.svg"); var ACTIVE_ICON_URL = Script.resolvePath("../../../system/assets/images/lod-a.svg"); - var onScreen = false; + var onTablet = false; // set this to true to use the tablet, false use a floating window - function onClicked() { - if (onScreen) { - tablet.gotoHomeScreen(); - } else { - tablet.loadQMLSource(QMLAPP_URL); - } - } + var onAppScreen = false; var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); var button = tablet.addButton({ @@ -35,6 +29,50 @@ var hasEventBridge = false; + var onScreen = false; + var window; + + function onClicked() { + if (onTablet) { + if (onAppScreen) { + tablet.gotoHomeScreen(); + } else { + tablet.loadQMLSource(QMLAPP_URL); + } + } else { + if (onScreen) { + killWindow() + } else { + createWindow() + } + } + } + + function createWindow() { + var qml = Script.resolvePath(QMLAPP_URL); + window = Desktop.createWindow(Script.resolvePath(QMLAPP_URL), { + title: TABLET_BUTTON_NAME, + flags: Desktop.ALWAYS_ON_TOP, + presentationMode: Desktop.PresentationMode.NATIVE, + size: {x: 400, y: 600} + }); + window.closed.connect(killWindow); + window.fromQml.connect(fromQml); + onScreen = true + button.editProperties({isActive: true}); + } + + function killWindow() { + if (window !== undefined) { + window.closed.disconnect(killWindow); + window.fromQml.disconnect(fromQml); + window.close() + window = undefined + } + onScreen = false + button.editProperties({isActive: false}) + } + function wireEventBridge(on) { if (!tablet) { print("Warning in wireEventBridge(): 'tablet' undefined!"); @@ -54,23 +92,38 @@ } function onScreenChanged(type, url) { - onScreen = (url === QMLAPP_URL); - button.editProperties({isActive: onScreen}); - wireEventBridge(onScreen); - } - - function fromQml(message) { + if (onTablet) { + onAppScreen = (url === QMLAPP_URL); + + button.editProperties({isActive: onAppScreen}); + wireEventBridge(onAppScreen); + } } button.clicked.connect(onClicked); tablet.screenChanged.connect(onScreenChanged); Script.scriptEnding.connect(function () { - if (onScreen) { + killWindow() + if (onAppScreen) { tablet.gotoHomeScreen(); } button.clicked.disconnect(onClicked); tablet.screenChanged.disconnect(onScreenChanged); tablet.removeButton(button); }); + + function fromQml(message) { + } + + function sendToQml(message) { + if (onTablet) { + tablet.sendToQml(message); + } else { + if (window) { + window.sendToQml(message); + } + } + } + }()); diff --git a/scripts/developer/utilities/render/lod.qml b/scripts/developer/utilities/render/lod.qml index d7b9f1cd57..889d8db836 100644 --- a/scripts/developer/utilities/render/lod.qml +++ b/scripts/developer/utilities/render/lod.qml @@ -10,20 +10,174 @@ // import QtQuick 2.5 import QtQuick.Controls 1.4 + +import "qrc:///qml/styles-uit" +import "qrc:///qml/controls-uit" as HifiControls + import "../lib/plotperf" +import "configSlider" Item { id: lodIU anchors.fill:parent + Component.onCompleted: { + Render.getConfig("RenderMainView.DrawSceneOctree").showVisibleCells = false + Render.getConfig("RenderMainView.DrawSceneOctree").showEmptyCells = false + } + + Component.onDestruction: { + Render.getConfig("RenderMainView.DrawSceneOctree").enabled = false + } + + Column { + id: topHeader + spacing: 8 + anchors.right: parent.right + anchors.left: parent.left + + HifiControls.CheckBox { + boxSize: 20 + text: "Show LOD Reticule" + checked: Render.getConfig("RenderMainView.DrawSceneOctree").enabled + onCheckedChanged: { Render.getConfig("RenderMainView.DrawSceneOctree").enabled = checked } + } + + RichSlider { + showLabel: true + showValue: false + label: "World Quality" + valueVar: LODManager["worldDetailQuality"] + valueVarSetter: (function (v) { LODManager["worldDetailQuality"] = v }) + max: 0.75 + min: 0.25 + integral: false + + anchors.left: parent.left + anchors.right: parent.right + } + + Row { + HifiControls.CheckBox { + id: autoLOD + boxSize: 20 + text: "Auto LOD" + checked: LODManager.automaticLODAdjust + onCheckedChanged: { LODManager.automaticLODAdjust = (checked) } + } + HifiControls.CheckBox { + id: showLODRegulatorDetails + visible: LODManager.automaticLODAdjust + boxSize: 20 + text: "Show LOD Details" + } + } + + RichSlider { + visible: !LODManager.automaticLODAdjust + showLabel: true + label: "LOD Angle [deg]" + valueVar: LODManager["lodAngleDeg"] + valueVarSetter: (function (v) { LODManager["lodAngleDeg"] = v }) + max: 90.0 + min: 0.5 + integral: false + + anchors.left: parent.left + anchors.right: parent.right + } + Column { + id: lodRegulatorDetails + visible: LODManager.automaticLODAdjust && showLODRegulatorDetails.checked + anchors.left: parent.left + anchors.right: parent.right + RichSlider { + visible: lodRegulatorDetails.visible + showLabel: true + label: "LOD Kp" + valueVar: LODManager["pidKp"] + valueVarSetter: (function (v) { LODManager["pidKp"] = v }) + max: 2.0 + min: 0.0 + integral: false + numDigits: 3 + + anchors.left: parent.left + anchors.right: parent.right + } + RichSlider { + visible: false && lodRegulatorDetails.visible + showLabel: true + label: "LOD Ki" + valueVar: LODManager["pidKi"] + valueVarSetter: (function (v) { LODManager["pidKi"] = v }) + max: 0.1 + min: 0.0 + integral: false + numDigits: 8 + + anchors.left: parent.left + anchors.right: parent.right + } + RichSlider { + visible: false && lodRegulatorDetails.visible + showLabel: true + label: "LOD Kd" + valueVar: LODManager["pidKd"] + valueVarSetter: (function (v) { LODManager["pidKd"] = v }) + max: 10.0 + min: 0.0 + integral: false + numDigits: 3 + + anchors.left: parent.left + anchors.right: parent.right + } + RichSlider { + visible: lodRegulatorDetails.visible + showLabel: true + label: "LOD Kv" + valueVar: LODManager["pidKv"] + valueVarSetter: (function (v) { LODManager["pidKv"] = v }) + max: 2.0 + min: 0.0 + integral: false + + anchors.left: parent.left + anchors.right: parent.right + } + RichSlider { + visible: lodRegulatorDetails.visible + showLabel: true + label: "LOD Smooth Scale" + valueVar: LODManager["smoothScale"] + valueVarSetter: (function (v) { LODManager["smoothScale"] = v }) + max: 20.0 + min: 1.0 + integral: true + + anchors.left: parent.left + anchors.right: parent.right + } + } + } + Column { id: stats - spacing: 8 - anchors.fill:parent + spacing: 4 + anchors.right: parent.right + anchors.left: parent.left + anchors.top: topHeader.bottom + anchors.bottom: parent.bottom function evalEvenHeight() { // Why do we have to do that manually ? cannot seem to find a qml / anchor / layout mode that does that ? - return (height - spacing * (children.length - 1)) / children.length + var numPlots = (children.length + (lodRegulatorDetails.visible ? 1 : 0) - 2) + return (height - topLine.height - bottomLine.height - spacing * (numPlots - 1)) / (numPlots) + } + + Separator { + id: topLine } PlotPerf { @@ -38,6 +192,11 @@ Item { label: "present", color: "#FFFF00" }, + { + prop: "batchTime", + label: "batch", + color: "#00FF00" + }, { prop: "engineRunTime", label: "engineRun", @@ -58,35 +217,70 @@ Item { valueUnit: "Hz" plots: [ { - prop: "lodIncreaseFPS", - label: "LOD++", + prop: "lodTargetFPS", + label: "target", color: "#66FF66" }, { - prop: "fps", + prop: "nowRenderFPS", label: "FPS", - color: "#FFFFFF" + color: "#FFFF55" }, { - prop: "lodDecreaseFPS", - label: "LOD--", - color: "#FF6666" - } - ] - } - PlotPerf { - title: "LOD" - height: parent.evalEvenHeight() - object: LODManager - valueScale: 0.1 - valueUnit: "" - plots: [ - { - prop: "lodLevel", - label: "LOD", + prop: "smoothRenderFPS", + label: "Smooth FPS", color: "#9999FF" } ] } + PlotPerf { + title: "LOD Angle" + height: parent.evalEvenHeight() + object: LODManager + valueScale: 1.0 + valueUnit: "deg" + plots: [ + { + prop: "lodAngleDeg", + label: "LOD Angle", + color: "#9999FF" + } + ] + } + PlotPerf { + // visible: lodRegulatorDetails.visible + title: "PID Output" + height: parent.evalEvenHeight() + object: LODManager + valueScale: 1.0 + valueUnit: "deg" + valueNumDigits: 1 + displayMinAt0: false + plots: [ + { + prop: "pidOp", + label: "Op", + color: "#9999FF" + }, + { + prop: "pidOi", + label: "Oi", + color: "#FFFFFF" + }, + { + prop: "pidOd", + label: "Od", + color: "#FF6666" + }, + { + prop: "pidO", + label: "Output", + color: "#66FF66" + } + ] + } + Separator { + id: bottomLine + } } } diff --git a/scripts/developer/utilities/render/shadow.qml b/scripts/developer/utilities/render/shadow.qml index 3400dcd847..464fe00eb9 100644 --- a/scripts/developer/utilities/render/shadow.qml +++ b/scripts/developer/utilities/render/shadow.qml @@ -8,21 +8,31 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html // -import QtQuick 2.5 +import QtQuick 2.7 import QtQuick.Controls 1.4 +import QtQuick.Layouts 1.3 + import "qrc:///qml/styles-uit" import "qrc:///qml/controls-uit" as HifiControls -import "configSlider" + +import "configSlider" + +Rectangle { + id: root; + + HifiConstants { id: hifi; } + color: hifi.colors.baseGray; -Column { - id: root - spacing: 8 property var viewConfig: Render.getConfig("RenderMainView.DrawViewFrustum"); property var shadowConfig : Render.getConfig("RenderMainView.ShadowSetup"); property var shadow0Config: Render.getConfig("RenderMainView.DrawShadowFrustum0"); property var shadow1Config: Render.getConfig("RenderMainView.DrawShadowFrustum1"); property var shadow2Config: Render.getConfig("RenderMainView.DrawShadowFrustum2"); property var shadow3Config: Render.getConfig("RenderMainView.DrawShadowFrustum3"); + property var shadowBBox0Config: Render.getConfig("RenderMainView.DrawShadowBBox0"); + property var shadowBBox1Config: Render.getConfig("RenderMainView.DrawShadowBBox1"); + property var shadowBBox2Config: Render.getConfig("RenderMainView.DrawShadowBBox2"); + property var shadowBBox3Config: Render.getConfig("RenderMainView.DrawShadowBBox3"); Component.onCompleted: { viewConfig.enabled = true; @@ -30,6 +40,10 @@ Column { shadow1Config.enabled = true; shadow2Config.enabled = true; shadow3Config.enabled = true; + shadowBBox0Config.enabled = true; + shadowBBox1Config.enabled = true; + shadowBBox2Config.enabled = true; + shadowBBox3Config.enabled = true; } Component.onDestruction: { viewConfig.enabled = false; @@ -41,108 +55,103 @@ Column { shadow1Config.isFrozen = false; shadow2Config.isFrozen = false; shadow3Config.isFrozen = false; - shadow0BoundConfig.isFrozen = false; - shadow1BoundConfig.isFrozen = false; - shadow2BoundConfig.isFrozen = false; - shadow3BoundConfig.isFrozen = false; + + shadowBBox0Config.enabled = false; + shadowBBox1Config.enabled = false; + shadowBBox2Config.enabled = false; + shadowBBox3Config.enabled = false; + shadowBBox0Config.isFrozen = false; + shadowBBox1Config.isFrozen = false; + shadowBBox2Config.isFrozen = false; + shadowBBox3Config.isFrozen = false; } - CheckBox { - text: "Freeze Frustums" - checked: false - onCheckedChanged: { - viewConfig.isFrozen = checked; - shadow0Config.isFrozen = checked; - shadow1Config.isFrozen = checked; - shadow2Config.isFrozen = checked; - shadow3Config.isFrozen = checked; - shadow0BoundConfig.isFrozen = checked; - shadow1BoundConfig.isFrozen = checked; - shadow2BoundConfig.isFrozen = checked; - shadow3BoundConfig.isFrozen = checked; - } - } - Row { - spacing: 8 - Label { - text: "View" - color: "yellow" - font.italic: true - } - Label { - text: "Shadow" - color: "blue" - font.italic: true - } - Label { - text: "Items" - color: "magenta" - font.italic: true - } - } - ConfigSlider { - label: qsTr("Cascade 0 constant bias") - integral: false - config: shadowConfig - property: "constantBias0" - max: 1.0 - min: 0.0 - } - ConfigSlider { - label: qsTr("Cascade 1 constant bias") - integral: false - config: shadowConfig - property: "constantBias1" - max: 1.0 - min: 0.0 - } - ConfigSlider { - label: qsTr("Cascade 2 constant bias") - integral: false - config: shadowConfig - property: "constantBias2" - max: 1.0 - min: 0.0 - } - ConfigSlider { - label: qsTr("Cascade 3 constant bias") - integral: false - config: shadowConfig - property: "constantBias3" - max: 1.0 - min: 0.0 - } + ColumnLayout { + spacing: 20 + anchors.left: parent.left + anchors.right: parent.right + anchors.margins: hifi.dimensions.contentMargin.x - ConfigSlider { - label: qsTr("Cascade 0 slope bias") - integral: false - config: shadowConfig - property: "slopeBias0" - max: 1.0 - min: 0.0 - } - ConfigSlider { - label: qsTr("Cascade 1 slope bias") - integral: false - config: shadowConfig - property: "slopeBias1" - max: 1.0 - min: 0.0 - } - ConfigSlider { - label: qsTr("Cascade 2 slope bias") - integral: false - config: shadowConfig - property: "slopeBias2" - max: 1.0 - min: 0.0 - } - ConfigSlider { - label: qsTr("Cascade 3 slope bias") - integral: false - config: shadowConfig - property: "slopeBias3" - max: 1.0 - min: 0.0 + RowLayout { + spacing: 20 + Layout.fillWidth: true + + HifiControls.CheckBox { + boxSize: 20 + text: "Freeze" + checked: false + onCheckedChanged: { + viewConfig.isFrozen = checked; + shadow0Config.isFrozen = checked; + shadow1Config.isFrozen = checked; + shadow2Config.isFrozen = checked; + shadow3Config.isFrozen = checked; + + shadowBBox0Config.isFrozen = checked; + shadowBBox1Config.isFrozen = checked; + shadowBBox2Config.isFrozen = checked; + shadowBBox3Config.isFrozen = checked; + } + } + HifiControls.Label { + text: "View" + color: "yellow" + font.italic: true + } + HifiControls.Label { + text: "Shadow" + color: "blue" + font.italic: true + } + HifiControls.Label { + text: "AABB" + color: "red" + font.italic: true + } + } + Repeater { + model: [ + "0", "1", "2", "3" + ] + ColumnLayout { + spacing: 8 + anchors.left: parent.left + anchors.right: parent.right + + HifiControls.Separator { + anchors.left: parent.left + anchors.right: parent.right + } + HifiControls.CheckBox { + text: "Cascade "+modelData + boxSize: 20 + checked: Render.getConfig("RenderMainView.DrawShadowFrustum"+modelData) + onCheckedChanged: { + Render.getConfig("RenderMainView.DrawShadowFrustum"+modelData).enabled = checked; + Render.getConfig("RenderMainView.DrawShadowBBox"+modelData).enabled = checked; + } + } + ConfigSlider { + label: qsTr("Constant bias") + integral: false + config: shadowConfig + property: "constantBias"+modelData + max: 1.0 + min: 0.0 + height: 38 + width:250 + } + ConfigSlider { + label: qsTr("Slope bias") + integral: false + config: shadowConfig + property: "slopeBias"+modelData + max: 1.0 + min: 0.0 + height: 38 + width: 250 + } + } + } } } diff --git a/scripts/developer/utilities/workload/workload.js b/scripts/developer/utilities/workload/workload.js index d74eb4e6d5..bdd33dcc5a 100644 --- a/scripts/developer/utilities/workload/workload.js +++ b/scripts/developer/utilities/workload/workload.js @@ -16,16 +16,9 @@ var ICON_URL = Script.resolvePath("../../../system/assets/images/luci-i.svg"); var ACTIVE_ICON_URL = Script.resolvePath("../../../system/assets/images/luci-a.svg"); - - var onAppScreen = false; + var onTablet = false; // set this to true to use the tablet, false use a floating window - function onClicked() { - if (onAppScreen) { - tablet.gotoHomeScreen(); - } else { - tablet.loadQMLSource(QMLAPP_URL); - } - } + var onAppScreen = false; var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); var button = tablet.addButton({ @@ -36,42 +29,90 @@ var hasEventBridge = false; - function wireEventBridge(on) { - if (!tablet) { - print("Warning in wireEventBridge(): 'tablet' undefined!"); - return; - } - if (on) { - if (!hasEventBridge) { - tablet.fromQml.connect(fromQml); - hasEventBridge = true; + var onScreen = false; + var window; + + function onClicked() { + if (onTablet) { + if (onAppScreen) { + tablet.gotoHomeScreen(); + } else { + tablet.loadQMLSource(QMLAPP_URL); } } else { - if (hasEventBridge) { - tablet.fromQml.disconnect(fromQml); - hasEventBridge = false; + if (onScreen) { + killWindow() + } else { + createWindow() + } + } + } + + function createWindow() { + var qml = Script.resolvePath(QMLAPP_URL); + window = Desktop.createWindow(Script.resolvePath(QMLAPP_URL), { + title: 'Workload Inspector', + flags: Desktop.ALWAYS_ON_TOP, + presentationMode: Desktop.PresentationMode.NATIVE, + size: {x: 400, y: 600} + }); + // window.setPosition(200, 50); + window.closed.connect(killWindow); + window.fromQml.connect(fromQml); + onScreen = true + button.editProperties({isActive: true}); + } + + function killWindow() { + if (window !== undefined) { + window.closed.disconnect(killWindow); + window.fromQml.disconnect(fromQml); + window.close() + window = undefined + } + onScreen = false + button.editProperties({isActive: false}) + } + + function wireEventBridge(on) { + if (onTablet) { + if (!tablet) { + print("Warning in wireEventBridge(): 'tablet' undefined!"); + return; + } + if (on) { + if (!hasEventBridge) { + tablet.fromQml.connect(fromQml); + hasEventBridge = true; + } + } else { + if (hasEventBridge) { + tablet.fromQml.disconnect(fromQml); + hasEventBridge = false; + } } } } function onScreenChanged(type, url) { - if (url === QMLAPP_URL) { - onAppScreen = true; - } else { - onAppScreen = false; + if (onTablet) { + if (url === QMLAPP_URL) { + onAppScreen = true; + } else { + onAppScreen = false; + } + + button.editProperties({isActive: onAppScreen}); + wireEventBridge(onAppScreen); } - - button.editProperties({isActive: onAppScreen}); - wireEventBridge(onAppScreen); } - function fromQml(message) { - } - + button.clicked.connect(onClicked); tablet.screenChanged.connect(onScreenChanged); Script.scriptEnding.connect(function () { + killWindow() if (onAppScreen) { tablet.gotoHomeScreen(); } @@ -84,6 +125,7 @@ Script.include("./test_physics_scene.js") function fromQml(message) { + print("fromQml: " + JSON.stringify(message)) switch (message.method) { case "createScene": createScene(); @@ -113,8 +155,14 @@ } function sendToQml(message) { - tablet.sendToQml(message); + if (onTablet) { + tablet.sendToQml(message); + } else { + if (window) { + window.sendToQml(message); + } + } } updateGridInQML() -}()); \ No newline at end of file +}()); diff --git a/scripts/developer/utilities/workload/workloadInspector.qml b/scripts/developer/utilities/workload/workloadInspector.qml index 8076f5c1c2..2eaa9d8133 100644 --- a/scripts/developer/utilities/workload/workloadInspector.qml +++ b/scripts/developer/utilities/workload/workloadInspector.qml @@ -160,9 +160,9 @@ Rectangle { } Repeater { model: [ - "r1Front:500:1.0", - "r2Front:500:1.0", - "r3Front:500:1.0" + "r1Front:16000:1.0", + "r2Front:16000:1.0", + "r3Front:16000:1.0" ] ConfigSlider { showLabel: false @@ -259,18 +259,71 @@ Rectangle { } ] } + /* PlotPerf { + title: "Ranges" + height: 100 + object: stats.controlViews + valueScale: 1.0 + valueUnit: "m" + plots: [ + { + prop: "r3RangeFront", + label: "R3 F", + color: "#FF0000" + }, + { + prop: "r3RangeBack", + label: "R3 B", + color: "#EF0000" + }, + { + prop: "r2RangeFront", + label: "R2 F", + color: "orange" + }, + { + prop: "r2RangeBack", + label: "R2 B", + color: "magenta" + }, + { + prop: "r1RangeFront", + label: "R1 F", + color: "#00FF00" + }, + { + prop: "r1RangeBack", + label: "R1 B", + color: "#00EF00" + }, + ] + }*/ Separator {} HifiControls.Label { - text: "Numbers:"; + text: "Ranges & Numbers:"; } - HifiControls.Label { - text: "R1= " + Workload.getConfig("regionState")["numR1"]; - } - HifiControls.Label { - text: "R2= " + Workload.getConfig("regionState")["numR2"]; - } - HifiControls.Label { - text: "R3= " + Workload.getConfig("regionState")["numR3"]; + Repeater { + model: [ + "green:R1:numR1:r1RangeBack:r1RangeFront", + "orange:R2:numR2:r2RangeBack:r2RangeFront", + "red:R3:numR3:r3RangeBack:r3RangeFront" + ] + RowLayout { + anchors.left: parent.left + anchors.right: parent.right + HifiControls.Label { + text: modelData.split(":")[1] + " : " + Workload.getConfig("regionState")[modelData.split(":")[2]] ; + color: modelData.split(":")[0] + } + HifiControls.Label { + text: Workload.getConfig("controlViews")[modelData.split(":")[3]].toFixed(0) ; + color: modelData.split(":")[0] + } + HifiControls.Label { + text: Workload.getConfig("controlViews")[modelData.split(":")[4]].toFixed(0) ; + color: modelData.split(":")[0] + } + } } Separator {} diff --git a/scripts/modules/appUi.js b/scripts/modules/appUi.js index db81af3755..6d2986768a 100644 --- a/scripts/modules/appUi.js +++ b/scripts/modules/appUi.js @@ -87,12 +87,18 @@ function AppUi(properties) { defaultButton('activeButton', 'a.svg'); defaultButton('normalMessagesButton', 'i-msg.svg'); defaultButton('activeMessagesButton', 'a-msg.svg'); - that.button = that.tablet.addButton({ + var buttonOptions = { icon: that.normalButton, activeIcon: that.activeButton, - text: that.buttonName, - sortOrder: that.sortOrder - }); + text: that.buttonName + }; + // `TabletScriptingInterface` looks for the presence of a `sortOrder` key. + // What it SHOULD do is look to see if the value inside that key is defined. + // To get around the current code, we do this instead. + if (that.sortOrder) { + buttonOptions.sortOrder = that.sortOrder; + } + that.button = that.tablet.addButton(buttonOptions); that.ignore = function ignore() { }; // Handlers @@ -126,6 +132,7 @@ function AppUi(properties) { // (Although injected javascript still has to use JSON.stringify/JSON.parse.) that.sendToHtml = function (messageObject) { that.tablet.emitScriptEvent(JSON.stringify(messageObject)); }; that.fromHtml = function (messageString) { that.onMessage(JSON.parse(messageString)); }; + that.sendMessage = that.ignore; that.wireEventBridge = function wireEventBridge(on) { // Uniquivocally sets that.sendMessage(messageObject) to do the right thing. // Sets hasEventBridge and wires onMessage to eventSignal as appropriate, IFF onMessage defined. diff --git a/scripts/system/avatarapp.js b/scripts/system/avatarapp.js index f692128fa3..10ccb66d96 100644 --- a/scripts/system/avatarapp.js +++ b/scripts/system/avatarapp.js @@ -20,7 +20,6 @@ Script.include("/~/system/libraries/controllers.js"); // constants from AvatarBookmarks.h var ENTRY_AVATAR_URL = "avatarUrl"; -var ENTRY_AVATAR_ATTACHMENTS = "attachments"; var ENTRY_AVATAR_ENTITIES = "avatarEntites"; var ENTRY_AVATAR_SCALE = "avatarScale"; var ENTRY_VERSION = "version"; @@ -29,13 +28,25 @@ function executeLater(callback) { Script.setTimeout(callback, 300); } -function getMyAvatarWearables() { - var wearablesArray = MyAvatar.getAvatarEntitiesVariant(); +var INVALID_JOINT_INDEX = -1 +function isWearable(avatarEntity) { + return avatarEntity.properties.visible === true && (avatarEntity.properties.parentJointIndex !== INVALID_JOINT_INDEX || avatarEntity.properties.relayParentJoints === true) && + (avatarEntity.properties.parentID === MyAvatar.sessionUUID || avatarEntity.properties.parentID === MyAvatar.SELF_ID); +} - for(var i = 0; i < wearablesArray.length; ++i) { - var wearable = wearablesArray[i]; - var localRotation = wearable.properties.localRotation; - wearable.properties.localRotationAngles = Quat.safeEulerAngles(localRotation) +function getMyAvatarWearables() { + var entitiesArray = MyAvatar.getAvatarEntitiesVariant(); + var wearablesArray = []; + + for (var i = 0; i < entitiesArray.length; ++i) { + var entity = entitiesArray[i]; + if (!isWearable(entity)) { + continue; + } + + var localRotation = entity.properties.localRotation; + entity.properties.localRotationAngles = Quat.safeEulerAngles(localRotation) + wearablesArray.push(entity); } return wearablesArray; @@ -45,7 +56,6 @@ function getMyAvatar() { var avatar = {} avatar[ENTRY_AVATAR_URL] = MyAvatar.skeletonModelURL; avatar[ENTRY_AVATAR_SCALE] = MyAvatar.getAvatarScale(); - avatar[ENTRY_AVATAR_ATTACHMENTS] = MyAvatar.getAttachmentsVariant(); avatar[ENTRY_AVATAR_ENTITIES] = getMyAvatarWearables(); return avatar; } @@ -55,16 +65,20 @@ function getMyAvatarSettings() { dominantHand: MyAvatar.getDominantHand(), collisionsEnabled : MyAvatar.getCollisionsEnabled(), collisionSoundUrl : MyAvatar.collisionSoundURL, - animGraphUrl : MyAvatar.getAnimGraphUrl(), + animGraphUrl: MyAvatar.getAnimGraphUrl(), + animGraphOverrideUrl : MyAvatar.getAnimGraphOverrideUrl(), } } -function updateAvatarWearables(avatar, bookmarkAvatarName) { +function updateAvatarWearables(avatar, bookmarkAvatarName, callback) { executeLater(function() { var wearables = getMyAvatarWearables(); avatar[ENTRY_AVATAR_ENTITIES] = wearables; sendToQml({'method' : 'wearablesUpdated', 'wearables' : wearables, 'avatarName' : bookmarkAvatarName}) + + if(callback) + callback(); }); } @@ -130,9 +144,14 @@ function onNewCollisionSoundUrl(url) { } function onAnimGraphUrlChanged(url) { - if(currentAvatarSettings.animGraphUrl !== url) { + if (currentAvatarSettings.animGraphUrl !== url) { currentAvatarSettings.animGraphUrl = url; - sendToQml({'method' : 'settingChanged', 'name' : 'animGraphUrl', 'value' : url}) + sendToQml({ 'method': 'settingChanged', 'name': 'animGraphUrl', 'value': currentAvatarSettings.animGraphUrl }) + + if (currentAvatarSettings.animGraphOverrideUrl !== MyAvatar.getAnimGraphOverrideUrl()) { + currentAvatarSettings.animGraphOverrideUrl = MyAvatar.getAnimGraphOverrideUrl(); + sendToQml({ 'method': 'settingChanged', 'name': 'animGraphOverrideUrl', 'value': currentAvatarSettings.animGraphOverrideUrl }) + } } } @@ -159,13 +178,12 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See for(var bookmarkName in message.data.bookmarks) { var bookmark = message.data.bookmarks[bookmarkName]; - if (!bookmark.avatarEntites) { // ensure avatarEntites always exist - bookmark.avatarEntites = []; - } - bookmark.avatarEntites.forEach(function(avatarEntity) { - avatarEntity.properties.localRotationAngles = Quat.safeEulerAngles(avatarEntity.properties.localRotation) - }) + if (bookmark.avatarEntites) { + bookmark.avatarEntites.forEach(function(avatarEntity) { + avatarEntity.properties.localRotationAngles = Quat.safeEulerAngles(avatarEntity.properties.localRotation); + }); + } } sendToQml(message) @@ -218,6 +236,32 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See Messages.messageReceived.disconnect(handleWearableMessages); Messages.unsubscribe('Hifi-Object-Manipulation'); break; + case 'addWearable': + + var joints = MyAvatar.getJointNames(); + var hipsIndex = -1; + + for(var i = 0; i < joints.length; ++i) { + if(joints[i] === 'Hips') { + hipsIndex = i; + break; + } + } + + var properties = { + name: "Custom wearable", + type: "Model", + modelURL: message.url, + parentID: MyAvatar.sessionUUID, + relayParentJoints: false, + parentJointIndex: hipsIndex + }; + + var entityID = Entities.addEntity(properties, true); + updateAvatarWearables(currentAvatar, message.avatarName, function() { + onSelectedEntity(entityID); + }); + break; case 'selectWearable': ensureWearableSelected(message.entityID); break; @@ -271,7 +315,7 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See MyAvatar.setDominantHand(message.settings.dominantHand); MyAvatar.setCollisionsEnabled(message.settings.collisionsEnabled); MyAvatar.collisionSoundURL = message.settings.collisionSoundUrl; - MyAvatar.setAnimGraphUrl(message.settings.animGraphUrl); + MyAvatar.setAnimGraphOverrideUrl(message.settings.animGraphOverrideUrl); settings = getMyAvatarSettings(); break; @@ -444,10 +488,6 @@ startup(); var isWired = false; function off() { - if (isWired) { // It is not ok to disconnect these twice, hence guard. - isWired = false; - } - if(adjustWearables.opened) { adjustWearables.setOpened(false); ensureWearableSelected(null); @@ -457,16 +497,20 @@ function off() { Messages.unsubscribe('Hifi-Object-Manipulation'); } - AvatarBookmarks.bookmarkLoaded.disconnect(onBookmarkLoaded); - AvatarBookmarks.bookmarkDeleted.disconnect(onBookmarkDeleted); - AvatarBookmarks.bookmarkAdded.disconnect(onBookmarkAdded); + if (isWired) { // It is not ok to disconnect these twice, hence guard. + isWired = false; - MyAvatar.skeletonModelURLChanged.disconnect(onSkeletonModelURLChanged); - MyAvatar.dominantHandChanged.disconnect(onDominantHandChanged); - MyAvatar.collisionsEnabledChanged.disconnect(onCollisionsEnabledChanged); - MyAvatar.newCollisionSoundURL.disconnect(onNewCollisionSoundUrl); - MyAvatar.animGraphUrlChanged.disconnect(onAnimGraphUrlChanged); - MyAvatar.targetScaleChanged.disconnect(onTargetScaleChanged); + AvatarBookmarks.bookmarkLoaded.disconnect(onBookmarkLoaded); + AvatarBookmarks.bookmarkDeleted.disconnect(onBookmarkDeleted); + AvatarBookmarks.bookmarkAdded.disconnect(onBookmarkAdded); + + MyAvatar.skeletonModelURLChanged.disconnect(onSkeletonModelURLChanged); + MyAvatar.dominantHandChanged.disconnect(onDominantHandChanged); + MyAvatar.collisionsEnabledChanged.disconnect(onCollisionsEnabledChanged); + MyAvatar.newCollisionSoundURL.disconnect(onNewCollisionSoundUrl); + MyAvatar.animGraphUrlChanged.disconnect(onAnimGraphUrlChanged); + MyAvatar.targetScaleChanged.disconnect(onTargetScaleChanged); + } } function on() { diff --git a/scripts/system/commerce/wallet.js b/scripts/system/commerce/wallet.js index ac25269e41..5939b36438 100644 --- a/scripts/system/commerce/wallet.js +++ b/scripts/system/commerce/wallet.js @@ -406,6 +406,13 @@ sendMoneyRecipient = null; } + function onUsernameChanged() { + if (Account.username !== Settings.getValue("wallet/savedUsername")) { + Settings.setValue("wallet/autoLogout", false); + Settings.setValue("wallet/savedUsername", ""); + } + } + // Function Name: fromQml() // // Description: @@ -581,6 +588,7 @@ var tablet = null; var walletEnabled = Settings.getValue("commerce", true); function startup() { + GlobalServices.myUsernameChanged.connect(onUsernameChanged); if (walletEnabled) { tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); button = tablet.addButton({ @@ -612,6 +620,7 @@ removeOverlays(); } function shutdown() { + GlobalServices.myUsernameChanged.disconnect(onUsernameChanged); button.clicked.disconnect(onButtonClicked); tablet.removeButton(button); deleteSendMoneyParticleEffect(); diff --git a/scripts/system/controllers/controllerDispatcher.js b/scripts/system/controllers/controllerDispatcher.js index 4002fd297b..7a916392b9 100644 --- a/scripts/system/controllers/controllerDispatcher.js +++ b/scripts/system/controllers/controllerDispatcher.js @@ -12,7 +12,8 @@ LEFT_HAND, RIGHT_HAND, NEAR_GRAB_PICK_RADIUS, DEFAULT_SEARCH_SPHERE_DISTANCE, DISPATCHER_PROPERTIES, getGrabPointSphereOffset, HMD, MyAvatar, Messages, findHandChildEntities, Picks, PickType, Pointers, COLORS_GRAB_SEARCHING_HALF_SQUEEZE COLORS_GRAB_SEARCHING_FULL_SQUEEZE, COLORS_GRAB_DISTANCE_HOLD, TRIGGER_ON_VALUE, PointerManager, print - Selection, DISPATCHER_HOVERING_LIST, DISPATCHER_HOVERING_STYLE + getGrabPointSphereOffset, HMD, MyAvatar, Messages, findHandChildEntities, Picks, PickType, Pointers, + PointerManager, print, Selection, DISPATCHER_HOVERING_LIST, DISPATCHER_HOVERING_STYLE */ controllerDispatcherPlugins = {}; diff --git a/scripts/system/controllers/controllerModules/farActionGrabEntity.js b/scripts/system/controllers/controllerModules/farActionGrabEntity.js index e4563fda14..5e798ed680 100644 --- a/scripts/system/controllers/controllerModules/farActionGrabEntity.js +++ b/scripts/system/controllers/controllerModules/farActionGrabEntity.js @@ -14,7 +14,8 @@ PICK_MAX_DISTANCE, COLORS_GRAB_SEARCHING_HALF_SQUEEZE, COLORS_GRAB_SEARCHING_FULL_SQUEEZE, COLORS_GRAB_DISTANCE_HOLD, DEFAULT_SEARCH_SPHERE_DISTANCE, TRIGGER_OFF_VALUE, TRIGGER_ON_VALUE, ZERO_VEC, ensureDynamic, getControllerWorldLocation, projectOntoEntityXYPlane, ContextOverlay, HMD, Reticle, Overlays, isPointingAtUI - Picks, makeLaserLockInfo Xform, makeLaserParams, AddressManager, getEntityParents, Selection, DISPATCHER_HOVERING_LIST + Picks, makeLaserLockInfo Xform, makeLaserParams, AddressManager, getEntityParents, Selection, DISPATCHER_HOVERING_LIST, + worldPositionToRegistrationFrameMatrix */ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); @@ -593,18 +594,9 @@ Script.include("/~/system/libraries/Xform.js"); this.calculateOffset = function(controllerData) { if (this.distanceHolding || this.distanceRotating) { - var targetProps = Entities.getEntityProperties(this.targetObject.entityID, [ - "position", - "rotation" - ]); - var zeroVector = { x: 0, y: 0, z:0, w: 0 }; - var intersection = controllerData.rayPicks[this.hand].intersection; - var intersectionMat = new Xform(zeroVector, intersection); - var modelMat = new Xform(targetProps.rotation, targetProps.position); - var modelMatInv = modelMat.inv(); - var xformMat = Xform.mul(modelMatInv, intersectionMat); - var offsetMat = Mat4.createFromRotAndTrans(xformMat.rot, xformMat.pos); - return offsetMat; + var targetProps = Entities.getEntityProperties(this.targetObject.entityID, + [ "position", "rotation", "registrationPoint", "dimensions" ]); + return worldPositionToRegistrationFrameMatrix(targetProps, controllerData.rayPicks[this.hand].intersection); } return undefined; }; diff --git a/scripts/system/controllers/controllerModules/farActionGrabEntityDynOnly.js b/scripts/system/controllers/controllerModules/farActionGrabEntityDynOnly.js new file mode 100644 index 0000000000..78abcb9b20 --- /dev/null +++ b/scripts/system/controllers/controllerModules/farActionGrabEntityDynOnly.js @@ -0,0 +1,594 @@ +"use strict"; + +// farActionGrabEntity.js +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html + +/* jslint bitwise: true */ + +/* global Script, Controller, RIGHT_HAND, LEFT_HAND, Mat4, MyAvatar, Vec3, Camera, Quat, getEnabledModuleByName, + makeRunningValues, Entities, enableDispatcherModule, disableDispatcherModule, entityIsGrabbable, + makeDispatcherModuleParameters, MSECS_PER_SEC, HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, TRIGGER_OFF_VALUE, + TRIGGER_ON_VALUE, ZERO_VEC, getControllerWorldLocation, projectOntoEntityXYPlane, ContextOverlay, HMD, + Picks, makeLaserLockInfo, Xform, makeLaserParams, AddressManager, getEntityParents, Selection, DISPATCHER_HOVERING_LIST, + Uuid, worldPositionToRegistrationFrameMatrix +*/ + +Script.include("/~/system/libraries/controllerDispatcherUtils.js"); +Script.include("/~/system/libraries/controllers.js"); +Script.include("/~/system/libraries/Xform.js"); + +(function() { + var GRABBABLE_PROPERTIES = [ + "position", + "registrationPoint", + "rotation", + "gravity", + "collidesWith", + "dynamic", + "collisionless", + "locked", + "name", + "shapeType", + "parentID", + "parentJointIndex", + "density", + "dimensions", + "userData" + ]; + + var MARGIN = 25; + + function TargetObject(entityID, entityProps) { + this.entityID = entityID; + this.entityProps = entityProps; + this.targetEntityID = null; + this.targetEntityProps = null; + this.previousCollisionStatus = null; + + this.getTargetEntity = function() { + var parentPropsLength = this.parentProps.length; + if (parentPropsLength !== 0) { + var targetEntity = { + id: this.parentProps[parentPropsLength - 1].id, + props: this.parentProps[parentPropsLength - 1]}; + this.targetEntityID = targetEntity.id; + this.targetEntityProps = targetEntity.props; + return targetEntity; + } + this.targetEntityID = this.entityID; + this.targetEntityProps = this.entityProps; + return { + id: this.entityID, + props: this.entityProps}; + }; + } + + function FarActionGrabEntity(hand) { + this.hand = hand; + this.grabbedThingID = null; + this.targetObject = null; + this.actionID = null; // action this script created... + this.entityToLockOnto = null; + this.potentialEntityWithContextOverlay = false; + this.entityWithContextOverlay = false; + this.contextOverlayTimer = false; + this.previousCollisionStatus = false; + this.locked = false; + this.highlightedEntity = null; + this.reticleMinX = MARGIN; + this.reticleMaxX = 0; + this.reticleMinY = MARGIN; + this.reticleMaxY = 0; + + var ACTION_TTL = 15; // seconds + + var DISTANCE_HOLDING_RADIUS_FACTOR = 3.5; // multiplied by distance between hand and object + var DISTANCE_HOLDING_ACTION_TIMEFRAME = 0.1; // how quickly objects move to their new position + var DISTANCE_HOLDING_UNITY_MASS = 1200; // The mass at which the distance holding action timeframe is unmodified + var DISTANCE_HOLDING_UNITY_DISTANCE = 6; // The distance at which the distance holding action timeframe is unmodified + + this.parameters = makeDispatcherModuleParameters( + 550, + this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"], + [], + 100, + makeLaserParams(this.hand, false)); + + + this.handToController = function() { + return (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand; + }; + + this.distanceGrabTimescale = function(mass, distance) { + var timeScale = DISTANCE_HOLDING_ACTION_TIMEFRAME * mass / + DISTANCE_HOLDING_UNITY_MASS * distance / + DISTANCE_HOLDING_UNITY_DISTANCE; + if (timeScale < DISTANCE_HOLDING_ACTION_TIMEFRAME) { + timeScale = DISTANCE_HOLDING_ACTION_TIMEFRAME; + } + return timeScale; + }; + + this.getMass = function(dimensions, density) { + return (dimensions.x * dimensions.y * dimensions.z) * density; + }; + + this.startFarGrabAction = function (controllerData, grabbedProperties) { + var controllerLocation = controllerData.controllerLocations[this.hand]; + var worldControllerPosition = controllerLocation.position; + var worldControllerRotation = controllerLocation.orientation; + + // transform the position into room space + var worldToSensorMat = Mat4.inverse(MyAvatar.getSensorToWorldMatrix()); + var roomControllerPosition = Mat4.transformPoint(worldToSensorMat, worldControllerPosition); + + var now = Date.now(); + + // add the action and initialize some variables + this.currentObjectPosition = grabbedProperties.position; + this.currentObjectRotation = grabbedProperties.rotation; + this.currentObjectTime = now; + this.currentCameraOrientation = Camera.orientation; + + this.grabRadius = this.grabbedDistance; + this.grabRadialVelocity = 0.0; + + // offset between controller vector at the grab radius and the entity position + var targetPosition = Vec3.multiply(this.grabRadius, Quat.getUp(worldControllerRotation)); + targetPosition = Vec3.sum(targetPosition, worldControllerPosition); + this.offsetPosition = Vec3.subtract(this.currentObjectPosition, targetPosition); + + // compute a constant based on the initial conditions which we use below to exaggerate hand motion + // onto the held object + this.radiusScalar = Math.log(this.grabRadius + 1.0); + if (this.radiusScalar < 1.0) { + this.radiusScalar = 1.0; + } + + // compute the mass for the purpose of energy and how quickly to move object + this.mass = this.getMass(grabbedProperties.dimensions, grabbedProperties.density); + var distanceToObject = Vec3.length(Vec3.subtract(MyAvatar.position, grabbedProperties.position)); + var timeScale = this.distanceGrabTimescale(this.mass, distanceToObject); + this.linearTimeScale = timeScale; + this.actionID = Entities.addAction("far-grab", this.grabbedThingID, { + targetPosition: this.currentObjectPosition, + linearTimeScale: timeScale, + targetRotation: this.currentObjectRotation, + angularTimeScale: timeScale, + tag: "far-grab-" + MyAvatar.sessionUUID, + ttl: ACTION_TTL + }); + if (this.actionID === Uuid.NULL) { + this.actionID = null; + } + + if (this.actionID !== null) { + var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID]; + Entities.callEntityMethod(this.grabbedThingID, "startDistanceGrab", args); + } + + Controller.triggerHapticPulse(HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, this.hand); + this.previousRoomControllerPosition = roomControllerPosition; + }; + + this.continueDistanceHolding = function(controllerData) { + var controllerLocation = controllerData.controllerLocations[this.hand]; + var worldControllerPosition = controllerLocation.position; + var worldControllerRotation = controllerLocation.orientation; + + // also transform the position into room space + var worldToSensorMat = Mat4.inverse(MyAvatar.getSensorToWorldMatrix()); + var roomControllerPosition = Mat4.transformPoint(worldToSensorMat, worldControllerPosition); + + var grabbedProperties = Entities.getEntityProperties(this.grabbedThingID, GRABBABLE_PROPERTIES); + var now = Date.now(); + var deltaObjectTime = (now - this.currentObjectTime) / MSECS_PER_SEC; // convert to seconds + this.currentObjectTime = now; + + // the action was set up when this.distanceHolding was called. update the targets. + var radius = Vec3.distance(this.currentObjectPosition, worldControllerPosition) * + this.radiusScalar * DISTANCE_HOLDING_RADIUS_FACTOR; + if (radius < 1.0) { + radius = 1.0; + } + + var roomHandDelta = Vec3.subtract(roomControllerPosition, this.previousRoomControllerPosition); + var worldHandDelta = Mat4.transformVector(MyAvatar.getSensorToWorldMatrix(), roomHandDelta); + var handMoved = Vec3.multiply(worldHandDelta, radius); + this.currentObjectPosition = Vec3.sum(this.currentObjectPosition, handMoved); + + var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID]; + Entities.callEntityMethod(this.grabbedThingID, "continueDistanceGrab", args); + + // Update radialVelocity + var lastVelocity = Vec3.multiply(worldHandDelta, 1.0 / deltaObjectTime); + var delta = Vec3.normalize(Vec3.subtract(grabbedProperties.position, worldControllerPosition)); + var newRadialVelocity = Vec3.dot(lastVelocity, delta); + + var VELOCITY_AVERAGING_TIME = 0.016; + var blendFactor = deltaObjectTime / VELOCITY_AVERAGING_TIME; + if (blendFactor < 0.0) { + blendFactor = 0.0; + } else if (blendFactor > 1.0) { + blendFactor = 1.0; + } + this.grabRadialVelocity = blendFactor * newRadialVelocity + (1.0 - blendFactor) * this.grabRadialVelocity; + + var RADIAL_GRAB_AMPLIFIER = 10.0; + if (Math.abs(this.grabRadialVelocity) > 0.0) { + this.grabRadius = this.grabRadius + (this.grabRadialVelocity * deltaObjectTime * + this.grabRadius * RADIAL_GRAB_AMPLIFIER); + } + + // don't let grabRadius go all the way to zero, because it can't come back from that + var MINIMUM_GRAB_RADIUS = 0.1; + if (this.grabRadius < MINIMUM_GRAB_RADIUS) { + this.grabRadius = MINIMUM_GRAB_RADIUS; + } + var newTargetPosition = Vec3.multiply(this.grabRadius, Quat.getUp(worldControllerRotation)); + newTargetPosition = Vec3.sum(newTargetPosition, worldControllerPosition); + newTargetPosition = Vec3.sum(newTargetPosition, this.offsetPosition); + + // XXX + // this.maybeScale(grabbedProperties); + + var distanceToObject = Vec3.length(Vec3.subtract(MyAvatar.position, this.currentObjectPosition)); + + this.linearTimeScale = (this.linearTimeScale / 2); + if (this.linearTimeScale <= DISTANCE_HOLDING_ACTION_TIMEFRAME) { + this.linearTimeScale = DISTANCE_HOLDING_ACTION_TIMEFRAME; + } + var success = Entities.updateAction(this.grabbedThingID, this.actionID, { + targetPosition: newTargetPosition, + linearTimeScale: this.linearTimeScale, + targetRotation: this.currentObjectRotation, + angularTimeScale: this.distanceGrabTimescale(this.mass, distanceToObject), + ttl: ACTION_TTL + }); + if (!success) { + print("farActionGrabEntity continueDistanceHolding -- updateAction failed: " + this.actionID); + this.actionID = null; + } + + this.previousRoomControllerPosition = roomControllerPosition; + }; + + this.endFarGrabAction = function () { + this.distanceHolding = false; + this.distanceRotating = false; + Entities.deleteAction(this.grabbedThingID, this.actionID); + + var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID]; + Entities.callEntityMethod(this.grabbedThingID, "releaseGrab", args); + this.actionID = null; + this.grabbedThingID = null; + this.targetObject = null; + this.potentialEntityWithContextOverlay = false; + }; + + this.updateRecommendedArea = function() { + var dims = Controller.getViewportDimensions(); + this.reticleMaxX = dims.x - MARGIN; + this.reticleMaxY = dims.y - MARGIN; + }; + + this.calculateNewReticlePosition = function(intersection) { + this.updateRecommendedArea(); + var point2d = HMD.overlayFromWorldPoint(intersection); + point2d.x = Math.max(this.reticleMinX, Math.min(point2d.x, this.reticleMaxX)); + point2d.y = Math.max(this.reticleMinY, Math.min(point2d.y, this.reticleMaxY)); + return point2d; + }; + + this.notPointingAtEntity = function(controllerData) { + var intersection = controllerData.rayPicks[this.hand]; + var entityProperty = Entities.getEntityProperties(intersection.objectID); + var entityType = entityProperty.type; + var hudRayPick = controllerData.hudRayPicks[this.hand]; + var point2d = this.calculateNewReticlePosition(hudRayPick.intersection); + if ((intersection.type === Picks.INTERSECTED_ENTITY && entityType === "Web") || + intersection.type === Picks.INTERSECTED_OVERLAY || Window.isPointOnDesktopWindow(point2d)) { + return true; + } + return false; + }; + + this.distanceRotate = function(otherFarGrabModule) { + this.distanceRotating = true; + this.distanceHolding = false; + + var worldControllerRotation = getControllerWorldLocation(this.handToController(), true).orientation; + var controllerRotationDelta = + Quat.multiply(worldControllerRotation, Quat.inverse(this.previousWorldControllerRotation)); + // Rotate entity by twice the delta rotation. + controllerRotationDelta = Quat.multiply(controllerRotationDelta, controllerRotationDelta); + + // Perform the rotation in the translation controller's action update. + otherFarGrabModule.currentObjectRotation = Quat.multiply(controllerRotationDelta, + otherFarGrabModule.currentObjectRotation); + + this.previousWorldControllerRotation = worldControllerRotation; + }; + + this.prepareDistanceRotatingData = function(controllerData) { + var intersection = controllerData.rayPicks[this.hand]; + + var controllerLocation = getControllerWorldLocation(this.handToController(), true); + var worldControllerPosition = controllerLocation.position; + var worldControllerRotation = controllerLocation.orientation; + + var grabbedProperties = Entities.getEntityProperties(intersection.objectID, GRABBABLE_PROPERTIES); + this.currentObjectPosition = grabbedProperties.position; + this.grabRadius = intersection.distance; + + // Offset between controller vector at the grab radius and the entity position. + var targetPosition = Vec3.multiply(this.grabRadius, Quat.getUp(worldControllerRotation)); + targetPosition = Vec3.sum(targetPosition, worldControllerPosition); + this.offsetPosition = Vec3.subtract(this.currentObjectPosition, targetPosition); + + // Initial controller rotation. + this.previousWorldControllerRotation = worldControllerRotation; + }; + + this.destroyContextOverlay = function(controllerData) { + if (this.entityWithContextOverlay) { + ContextOverlay.destroyContextOverlay(this.entityWithContextOverlay); + this.entityWithContextOverlay = false; + this.potentialEntityWithContextOverlay = false; + } + }; + + this.targetIsNull = function() { + var properties = Entities.getEntityProperties(this.grabbedThingID); + if (Object.keys(properties).length === 0 && this.distanceHolding) { + return true; + } + return false; + }; + + this.getTargetProps = function (controllerData) { + var targetEntityID = controllerData.rayPicks[this.hand].objectID; + if (targetEntityID) { + return Entities.getEntityProperties(targetEntityID); + } + return null; + }; + + this.isReady = function (controllerData) { + if (HMD.active) { + if (this.notPointingAtEntity(controllerData)) { + return makeRunningValues(false, [], []); + } + + this.distanceHolding = false; + this.distanceRotating = false; + + if (controllerData.triggerValues[this.hand] > TRIGGER_ON_VALUE) { + this.prepareDistanceRotatingData(controllerData); + return makeRunningValues(true, [], []); + } else { + this.destroyContextOverlay(); + return makeRunningValues(false, [], []); + } + } + return makeRunningValues(false, [], []); + }; + + this.run = function (controllerData) { + if (controllerData.triggerValues[this.hand] < TRIGGER_OFF_VALUE || + this.notPointingAtEntity(controllerData) || this.targetIsNull()) { + this.endFarGrabAction(); + Selection.removeFromSelectedItemsList(DISPATCHER_HOVERING_LIST, "entity", + this.highlightedEntity); + this.highlightedEntity = null; + return makeRunningValues(false, [], []); + } + this.intersectionDistance = controllerData.rayPicks[this.hand].distance; + + var otherModuleName = this.hand === RIGHT_HAND ? "LeftFarActionGrabEntity" : "RightFarActionGrabEntity"; + var otherFarGrabModule = getEnabledModuleByName(otherModuleName); + + // gather up the readiness of the near-grab modules + var nearGrabNames = [ + this.hand === RIGHT_HAND ? "RightScaleAvatar" : "LeftScaleAvatar", + this.hand === RIGHT_HAND ? "RightFarTriggerEntity" : "LeftFarTriggerEntity", + this.hand === RIGHT_HAND ? "RightNearActionGrabEntity" : "LeftNearActionGrabEntity", + this.hand === RIGHT_HAND ? "RightNearParentingGrabEntity" : "LeftNearParentingGrabEntity", + this.hand === RIGHT_HAND ? "RightNearParentingGrabOverlay" : "LeftNearParentingGrabOverlay" + ]; + + var nearGrabReadiness = []; + for (var i = 0; i < nearGrabNames.length; i++) { + var nearGrabModule = getEnabledModuleByName(nearGrabNames[i]); + var ready = nearGrabModule ? nearGrabModule.isReady(controllerData) : makeRunningValues(false, [], []); + nearGrabReadiness.push(ready); + } + + if (this.actionID) { + // if we are doing a distance grab and the object or tablet gets close enough to the controller, + // stop the far-grab so the near-grab or equip can take over. + for (var k = 0; k < nearGrabReadiness.length; k++) { + if (nearGrabReadiness[k].active && (nearGrabReadiness[k].targets[0] === this.grabbedThingID || + HMD.tabletID && nearGrabReadiness[k].targets[0] === HMD.tabletID)) { + this.endFarGrabAction(); + return makeRunningValues(false, [], []); + } + } + + this.continueDistanceHolding(controllerData); + } else { + // if we are doing a distance search and this controller moves into a position + // where it could near-grab something, stop searching. + for (var j = 0; j < nearGrabReadiness.length; j++) { + if (nearGrabReadiness[j].active) { + this.endFarGrabAction(); + return makeRunningValues(false, [], []); + } + } + + var rayPickInfo = controllerData.rayPicks[this.hand]; + if (rayPickInfo.type === Picks.INTERSECTED_ENTITY) { + if (controllerData.triggerClicks[this.hand]) { + var entityID = rayPickInfo.objectID; + Selection.removeFromSelectedItemsList(DISPATCHER_HOVERING_LIST, "entity", + this.highlightedEntity); + this.highlightedEntity = null; + var targetProps = Entities.getEntityProperties(entityID, [ + "dynamic", "shapeType", "position", + "rotation", "dimensions", "density", + "userData", "locked", "type", "href" + ]); + if (targetProps.href !== "") { + AddressManager.handleLookupString(targetProps.href); + return makeRunningValues(false, [], []); + } + + this.targetObject = new TargetObject(entityID, targetProps); + this.targetObject.parentProps = getEntityParents(targetProps); + + if (this.contextOverlayTimer) { + Script.clearTimeout(this.contextOverlayTimer); + } + this.contextOverlayTimer = false; + if (entityID === this.entityWithContextOverlay) { + this.destroyContextOverlay(); + } else { + Selection.removeFromSelectedItemsList("contextOverlayHighlightList", "entity", entityID); + } + + var targetEntity = this.targetObject.getTargetEntity(); + entityID = targetEntity.id; + targetProps = targetEntity.props; + + if (!targetProps.dynamic && !this.targetObject.entityProps.dynamic) { + // let farParentGrabEntity handle it + return makeRunningValues(false, [], []); + } + + if (entityIsGrabbable(targetProps) || entityIsGrabbable(this.targetObject.entityProps)) { + if (!this.distanceRotating) { + this.grabbedThingID = entityID; + this.grabbedDistance = rayPickInfo.distance; + } + + if (otherFarGrabModule.grabbedThingID === this.grabbedThingID && + otherFarGrabModule.distanceHolding) { + this.prepareDistanceRotatingData(controllerData); + this.distanceRotate(otherFarGrabModule); + } else { + this.distanceHolding = true; + this.distanceRotating = false; + this.startFarGrabAction(controllerData, targetProps); + } + } + } else { + var targetEntityID = rayPickInfo.objectID; + if (this.highlightedEntity !== targetEntityID) { + Selection.removeFromSelectedItemsList(DISPATCHER_HOVERING_LIST, "entity", + this.highlightedEntity); + var selectionTargetProps = Entities.getEntityProperties(targetEntityID, [ + "dynamic", "shapeType", "position", + "rotation", "dimensions", "density", + "userData", "locked", "type", "href" + ]); + + var selectionTargetObject = new TargetObject(targetEntityID, selectionTargetProps); + selectionTargetObject.parentProps = getEntityParents(selectionTargetProps); + var selectionTargetEntity = selectionTargetObject.getTargetEntity(); + + if (entityIsGrabbable(selectionTargetEntity.props) || + entityIsGrabbable(selectionTargetObject.entityProps)) { + + Selection.addToSelectedItemsList(DISPATCHER_HOVERING_LIST, "entity", rayPickInfo.objectID); + } + this.highlightedEntity = rayPickInfo.objectID; + } + + if (!this.entityWithContextOverlay) { + var _this = this; + + if (_this.potentialEntityWithContextOverlay !== rayPickInfo.objectID) { + if (_this.contextOverlayTimer) { + Script.clearTimeout(_this.contextOverlayTimer); + } + _this.contextOverlayTimer = false; + _this.potentialEntityWithContextOverlay = rayPickInfo.objectID; + } + + if (!_this.contextOverlayTimer) { + _this.contextOverlayTimer = Script.setTimeout(function () { + if (!_this.entityWithContextOverlay && + _this.contextOverlayTimer && + _this.potentialEntityWithContextOverlay === rayPickInfo.objectID) { + var pEvProps = Entities.getEntityProperties(rayPickInfo.objectID); + var pointerEvent = { + type: "Move", + id: _this.hand + 1, // 0 is reserved for hardware mouse + pos2D: projectOntoEntityXYPlane(rayPickInfo.objectID, + rayPickInfo.intersection, pEvProps), + pos3D: rayPickInfo.intersection, + normal: rayPickInfo.surfaceNormal, + direction: Vec3.subtract(ZERO_VEC, rayPickInfo.surfaceNormal), + button: "Secondary" + }; + if (ContextOverlay.createOrDestroyContextOverlay(rayPickInfo.objectID, pointerEvent)) { + _this.entityWithContextOverlay = rayPickInfo.objectID; + } + } + _this.contextOverlayTimer = false; + }, 500); + } + } + } + } else if (this.distanceRotating) { + this.distanceRotate(otherFarGrabModule); + } else if (this.highlightedEntity) { + Selection.removeFromSelectedItemsList(DISPATCHER_HOVERING_LIST, "entity", this.highlightedEntity); + this.highlightedEntity = null; + } + } + return this.exitIfDisabled(controllerData); + }; + + this.exitIfDisabled = function(controllerData) { + var moduleName = this.hand === RIGHT_HAND ? "RightDisableModules" : "LeftDisableModules"; + var disableModule = getEnabledModuleByName(moduleName); + if (disableModule) { + if (disableModule.disableModules) { + this.endFarGrabAction(); + Selection.removeFromSelectedItemsList(DISPATCHER_HOVERING_LIST, "entity", + this.highlightedEntity); + this.highlightedEntity = null; + return makeRunningValues(false, [], []); + } + } + var grabbedThing = (this.distanceHolding || this.distanceRotating) ? this.targetObject.entityID : null; + var offset = this.calculateOffset(controllerData); + var laserLockInfo = makeLaserLockInfo(grabbedThing, false, this.hand, offset); + return makeRunningValues(true, [], [], laserLockInfo); + }; + + this.calculateOffset = function(controllerData) { + if (this.distanceHolding || this.distanceRotating) { + var targetProps = Entities.getEntityProperties(this.targetObject.entityID, + [ "position", "rotation", "registrationPoint", "dimensions" ]); + return worldPositionToRegistrationFrameMatrix(targetProps, controllerData.rayPicks[this.hand].intersection); + } + return undefined; + }; + } + + var leftFarActionGrabEntity = new FarActionGrabEntity(LEFT_HAND); + var rightFarActionGrabEntity = new FarActionGrabEntity(RIGHT_HAND); + + enableDispatcherModule("LeftFarActionGrabEntity", leftFarActionGrabEntity); + enableDispatcherModule("RightFarActionGrabEntity", rightFarActionGrabEntity); + + function cleanup() { + disableDispatcherModule("LeftFarActionGrabEntity"); + disableDispatcherModule("RightFarActionGrabEntity"); + } + Script.scriptEnding.connect(cleanup); +}()); diff --git a/scripts/system/controllers/controllerModules/farParentGrabEntity.js b/scripts/system/controllers/controllerModules/farParentGrabEntity.js new file mode 100644 index 0000000000..a9ec246a32 --- /dev/null +++ b/scripts/system/controllers/controllerModules/farParentGrabEntity.js @@ -0,0 +1,638 @@ +"use strict"; + +// farParentGrabEntity.js +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html + +/* jslint bitwise: true */ + +/* global Script, Controller, RIGHT_HAND, LEFT_HAND, Mat4, MyAvatar, Vec3, Quat, getEnabledModuleByName, makeRunningValues, + Entities, enableDispatcherModule, disableDispatcherModule, entityIsGrabbable, makeDispatcherModuleParameters, MSECS_PER_SEC, + HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, TRIGGER_OFF_VALUE, TRIGGER_ON_VALUE, ZERO_VEC, getControllerWorldLocation, + projectOntoEntityXYPlane, ContextOverlay, HMD, Picks, makeLaserLockInfo, Xform, makeLaserParams, AddressManager, + getEntityParents, Selection, DISPATCHER_HOVERING_LIST, unhighlightTargetEntity, Messages, Uuid, findGroupParent, + worldPositionToRegistrationFrameMatrix +*/ + +Script.include("/~/system/libraries/controllerDispatcherUtils.js"); +Script.include("/~/system/libraries/controllers.js"); +Script.include("/~/system/libraries/Xform.js"); + +(function() { + var GRABBABLE_PROPERTIES = [ + "position", + "registrationPoint", + "rotation", + "gravity", + "collidesWith", + "dynamic", + "collisionless", + "locked", + "name", + "shapeType", + "parentID", + "parentJointIndex", + "density", + "dimensions", + "userData" + ]; + + var MARGIN = 25; + + function TargetObject(entityID, entityProps) { + this.entityID = entityID; + this.entityProps = entityProps; + this.targetEntityID = null; + this.targetEntityProps = null; + + this.getTargetEntity = function() { + var parentPropsLength = this.parentProps.length; + if (parentPropsLength !== 0) { + var targetEntity = { + id: this.parentProps[parentPropsLength - 1].id, + props: this.parentProps[parentPropsLength - 1]}; + this.targetEntityID = targetEntity.id; + this.targetEntityProps = targetEntity.props; + return targetEntity; + } + this.targetEntityID = this.entityID; + this.targetEntityProps = this.entityProps; + return { + id: this.entityID, + props: this.entityProps}; + }; + } + + function FarParentGrabEntity(hand) { + this.hand = hand; + this.targetEntityID = null; + this.targetObject = null; + this.previousParentID = {}; + this.previousParentJointIndex = {}; + this.potentialEntityWithContextOverlay = false; + this.entityWithContextOverlay = false; + this.contextOverlayTimer = false; + this.highlightedEntity = null; + this.reticleMinX = MARGIN; + this.reticleMaxX = 0; + this.reticleMinY = MARGIN; + this.reticleMaxY = 0; + + var FAR_GRAB_JOINTS = [65527, 65528]; // FARGRAB_LEFTHAND_INDEX, FARGRAB_RIGHTHAND_INDEX + + var DISTANCE_HOLDING_RADIUS_FACTOR = 3.5; // multiplied by distance between hand and object + var DISTANCE_HOLDING_ACTION_TIMEFRAME = 0.1; // how quickly objects move to their new position + var DISTANCE_HOLDING_UNITY_MASS = 1200; // The mass at which the distance holding action timeframe is unmodified + var DISTANCE_HOLDING_UNITY_DISTANCE = 6; // The distance at which the distance holding action timeframe is unmodified + + this.parameters = makeDispatcherModuleParameters( + 540, + this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"], + [], + 100, + makeLaserParams(this.hand, false)); + + + this.handToController = function() { + return (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand; + }; + + this.distanceGrabTimescale = function(mass, distance) { + var timeScale = DISTANCE_HOLDING_ACTION_TIMEFRAME * mass / + DISTANCE_HOLDING_UNITY_MASS * distance / + DISTANCE_HOLDING_UNITY_DISTANCE; + if (timeScale < DISTANCE_HOLDING_ACTION_TIMEFRAME) { + timeScale = DISTANCE_HOLDING_ACTION_TIMEFRAME; + } + return timeScale; + }; + + this.getMass = function(dimensions, density) { + return (dimensions.x * dimensions.y * dimensions.z) * density; + }; + + this.thisFarGrabJointIsParent = function(isParentProps) { + if (!isParentProps) { + return false; + } + + if (isParentProps.parentID !== MyAvatar.sessionUUID && isParentProps.parentID !== MyAvatar.SELF_ID) { + return false; + } + + if (isParentProps.parentJointIndex === FAR_GRAB_JOINTS[this.hand]) { + return true; + } + + return false; + }; + + this.startFarParentGrab = function (controllerData, grabbedProperties) { + var controllerLocation = controllerData.controllerLocations[this.hand]; + var worldControllerPosition = controllerLocation.position; + var worldControllerRotation = controllerLocation.orientation; + // transform the position into room space + var worldToSensorMat = Mat4.inverse(MyAvatar.getSensorToWorldMatrix()); + var roomControllerPosition = Mat4.transformPoint(worldToSensorMat, worldControllerPosition); + + var now = Date.now(); + + // add the action and initialize some variables + this.currentObjectPosition = grabbedProperties.position; + this.currentObjectRotation = grabbedProperties.rotation; + this.currentObjectTime = now; + + this.grabRadius = this.grabbedDistance; + this.grabRadialVelocity = 0.0; + + // offset between controller vector at the grab radius and the entity position + var targetPosition = Vec3.multiply(this.grabRadius, Quat.getUp(worldControllerRotation)); + targetPosition = Vec3.sum(targetPosition, worldControllerPosition); + this.offsetPosition = Vec3.subtract(this.currentObjectPosition, targetPosition); + + // compute a constant based on the initial conditions which we use below to exaggerate hand motion + // onto the held object + this.radiusScalar = Math.log(this.grabRadius + 1.0); + if (this.radiusScalar < 1.0) { + this.radiusScalar = 1.0; + } + + // compute the mass for the purpose of energy and how quickly to move object + this.mass = this.getMass(grabbedProperties.dimensions, grabbedProperties.density); + + Controller.triggerHapticPulse(HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, this.hand); + unhighlightTargetEntity(this.targetEntityID); + var message = { + hand: this.hand, + entityID: this.targetEntityID + }; + + Messages.sendLocalMessage('Hifi-unhighlight-entity', JSON.stringify(message)); + + var newTargetPosLocal = MyAvatar.worldToJointPoint(grabbedProperties.position); + MyAvatar.setJointTranslation(FAR_GRAB_JOINTS[this.hand], newTargetPosLocal); + + var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID]; + Entities.callEntityMethod(grabbedProperties.id, "startNearGrab", args); + + var reparentProps = { + parentID: MyAvatar.SELF_ID, + parentJointIndex: FAR_GRAB_JOINTS[this.hand], + localVelocity: {x: 0, y: 0, z: 0}, + localAngularVelocity: {x: 0, y: 0, z: 0} + }; + + if (this.thisFarGrabJointIsParent(grabbedProperties)) { + // this should never happen, but if it does, don't set previous parent to be this hand. + this.previousParentID[grabbedProperties.id] = null; + this.previousParentJointIndex[grabbedProperties.id] = -1; + } else { + this.previousParentID[grabbedProperties.id] = grabbedProperties.parentID; + this.previousParentJointIndex[grabbedProperties.id] = grabbedProperties.parentJointIndex; + } + + this.targetEntityID = grabbedProperties.id; + Entities.editEntity(grabbedProperties.id, reparentProps); + + Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({ + action: 'grab', + grabbedEntity: grabbedProperties.id, + joint: this.hand === RIGHT_HAND ? "RightHand" : "LeftHand" + })); + this.grabbing = true; + + this.previousRoomControllerPosition = roomControllerPosition; + }; + + this.continueDistanceHolding = function(controllerData) { + var controllerLocation = controllerData.controllerLocations[this.hand]; + var worldControllerPosition = controllerLocation.position; + var worldControllerRotation = controllerLocation.orientation; + + // also transform the position into room space + var worldToSensorMat = Mat4.inverse(MyAvatar.getSensorToWorldMatrix()); + var roomControllerPosition = Mat4.transformPoint(worldToSensorMat, worldControllerPosition); + + var grabbedProperties = Entities.getEntityProperties(this.targetEntityID, GRABBABLE_PROPERTIES); + var now = Date.now(); + var deltaObjectTime = (now - this.currentObjectTime) / MSECS_PER_SEC; // convert to seconds + this.currentObjectTime = now; + + // the action was set up when this.distanceHolding was called. update the targets. + var radius = Vec3.distance(this.currentObjectPosition, worldControllerPosition) * + this.radiusScalar * DISTANCE_HOLDING_RADIUS_FACTOR; + if (radius < 1.0) { + radius = 1.0; + } + + var roomHandDelta = Vec3.subtract(roomControllerPosition, this.previousRoomControllerPosition); + var worldHandDelta = Mat4.transformVector(MyAvatar.getSensorToWorldMatrix(), roomHandDelta); + var handMoved = Vec3.multiply(worldHandDelta, radius); + this.currentObjectPosition = Vec3.sum(this.currentObjectPosition, handMoved); + + var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID]; + Entities.callEntityMethod(this.targetEntityID, "continueDistanceGrab", args); + + // Update radialVelocity + var lastVelocity = Vec3.multiply(worldHandDelta, 1.0 / deltaObjectTime); + var delta = Vec3.normalize(Vec3.subtract(grabbedProperties.position, worldControllerPosition)); + var newRadialVelocity = Vec3.dot(lastVelocity, delta); + + var VELOCITY_AVERAGING_TIME = 0.016; + var blendFactor = deltaObjectTime / VELOCITY_AVERAGING_TIME; + if (blendFactor < 0.0) { + blendFactor = 0.0; + } else if (blendFactor > 1.0) { + blendFactor = 1.0; + } + this.grabRadialVelocity = blendFactor * newRadialVelocity + (1.0 - blendFactor) * this.grabRadialVelocity; + + var RADIAL_GRAB_AMPLIFIER = 10.0; + if (Math.abs(this.grabRadialVelocity) > 0.0) { + this.grabRadius = this.grabRadius + (this.grabRadialVelocity * deltaObjectTime * + this.grabRadius * RADIAL_GRAB_AMPLIFIER); + } + + // don't let grabRadius go all the way to zero, because it can't come back from that + var MINIMUM_GRAB_RADIUS = 0.1; + if (this.grabRadius < MINIMUM_GRAB_RADIUS) { + this.grabRadius = MINIMUM_GRAB_RADIUS; + } + var newTargetPosition = Vec3.multiply(this.grabRadius, Quat.getUp(worldControllerRotation)); + newTargetPosition = Vec3.sum(newTargetPosition, worldControllerPosition); + newTargetPosition = Vec3.sum(newTargetPosition, this.offsetPosition); + + // MyAvatar.setJointTranslation(FAR_GRAB_JOINTS[this.hand], MyAvatar.worldToJointPoint(newTargetPosition)); + + // var newTargetPosLocal = Mat4.transformPoint(MyAvatar.getSensorToWorldMatrix(), newTargetPosition); + var newTargetPosLocal = MyAvatar.worldToJointPoint(newTargetPosition); + MyAvatar.setJointTranslation(FAR_GRAB_JOINTS[this.hand], newTargetPosLocal); + + this.previousRoomControllerPosition = roomControllerPosition; + }; + + this.endFarParentGrab = function (controllerData) { + this.hapticTargetID = null; + // var endProps = controllerData.nearbyEntityPropertiesByID[this.targetEntityID]; + var endProps = Entities.getEntityProperties(this.targetEntityID, GRABBABLE_PROPERTIES); + if (this.thisFarGrabJointIsParent(endProps)) { + Entities.editEntity(this.targetEntityID, { + parentID: this.previousParentID[this.targetEntityID], + parentJointIndex: this.previousParentJointIndex[this.targetEntityID] + }); + } + + var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID]; + Entities.callEntityMethod(this.targetEntityID, "releaseGrab", args); + Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({ + action: 'release', + grabbedEntity: this.targetEntityID, + joint: this.hand === RIGHT_HAND ? "RightHand" : "LeftHand" + })); + unhighlightTargetEntity(this.targetEntityID); + this.grabbing = false; + this.targetEntityID = null; + this.potentialEntityWithContextOverlay = false; + MyAvatar.clearJointData(FAR_GRAB_JOINTS[this.hand]); + }; + + this.updateRecommendedArea = function() { + var dims = Controller.getViewportDimensions(); + this.reticleMaxX = dims.x - MARGIN; + this.reticleMaxY = dims.y - MARGIN; + }; + + this.calculateNewReticlePosition = function(intersection) { + this.updateRecommendedArea(); + var point2d = HMD.overlayFromWorldPoint(intersection); + point2d.x = Math.max(this.reticleMinX, Math.min(point2d.x, this.reticleMaxX)); + point2d.y = Math.max(this.reticleMinY, Math.min(point2d.y, this.reticleMaxY)); + return point2d; + }; + + this.notPointingAtEntity = function(controllerData) { + var intersection = controllerData.rayPicks[this.hand]; + var entityProperty = Entities.getEntityProperties(intersection.objectID); + var entityType = entityProperty.type; + var hudRayPick = controllerData.hudRayPicks[this.hand]; + var point2d = this.calculateNewReticlePosition(hudRayPick.intersection); + if ((intersection.type === Picks.INTERSECTED_ENTITY && entityType === "Web") || + intersection.type === Picks.INTERSECTED_OVERLAY || Window.isPointOnDesktopWindow(point2d)) { + return true; + } + return false; + }; + + this.distanceRotate = function(otherFarGrabModule) { + this.distanceRotating = true; + this.distanceHolding = false; + + var worldControllerRotation = getControllerWorldLocation(this.handToController(), true).orientation; + var controllerRotationDelta = + Quat.multiply(worldControllerRotation, Quat.inverse(this.previousWorldControllerRotation)); + // Rotate entity by twice the delta rotation. + controllerRotationDelta = Quat.multiply(controllerRotationDelta, controllerRotationDelta); + + // Perform the rotation in the translation controller's action update. + otherFarGrabModule.currentObjectRotation = Quat.multiply(controllerRotationDelta, + otherFarGrabModule.currentObjectRotation); + + this.previousWorldControllerRotation = worldControllerRotation; + }; + + this.prepareDistanceRotatingData = function(controllerData) { + var intersection = controllerData.rayPicks[this.hand]; + + var controllerLocation = getControllerWorldLocation(this.handToController(), true); + var worldControllerPosition = controllerLocation.position; + var worldControllerRotation = controllerLocation.orientation; + + var grabbedProperties = Entities.getEntityProperties(intersection.objectID, GRABBABLE_PROPERTIES); + this.currentObjectPosition = grabbedProperties.position; + this.grabRadius = intersection.distance; + + // Offset between controller vector at the grab radius and the entity position. + var targetPosition = Vec3.multiply(this.grabRadius, Quat.getUp(worldControllerRotation)); + targetPosition = Vec3.sum(targetPosition, worldControllerPosition); + this.offsetPosition = Vec3.subtract(this.currentObjectPosition, targetPosition); + + // Initial controller rotation. + this.previousWorldControllerRotation = worldControllerRotation; + }; + + this.destroyContextOverlay = function(controllerData) { + if (this.entityWithContextOverlay) { + ContextOverlay.destroyContextOverlay(this.entityWithContextOverlay); + this.entityWithContextOverlay = false; + this.potentialEntityWithContextOverlay = false; + } + }; + + this.targetIsNull = function() { + var properties = Entities.getEntityProperties(this.targetEntityID, GRABBABLE_PROPERTIES); + if (Object.keys(properties).length === 0 && this.distanceHolding) { + return true; + } + return false; + }; + + this.getTargetProps = function (controllerData) { + var targetEntity = controllerData.rayPicks[this.hand].objectID; + if (targetEntity) { + var gtProps = Entities.getEntityProperties(targetEntity, GRABBABLE_PROPERTIES); + if (entityIsGrabbable(gtProps)) { + // give haptic feedback + if (gtProps.id !== this.hapticTargetID) { + Controller.triggerHapticPulse(HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, this.hand); + this.hapticTargetID = gtProps.id; + } + // if we've attempted to grab a child, roll up to the root of the tree + var groupRootProps = findGroupParent(controllerData, gtProps); + if (entityIsGrabbable(groupRootProps)) { + return groupRootProps; + } + return gtProps; + } + } + return null; + }; + + this.isReady = function (controllerData) { + if (HMD.active) { + if (this.notPointingAtEntity(controllerData)) { + return makeRunningValues(false, [], []); + } + + this.distanceHolding = false; + this.distanceRotating = false; + + if (controllerData.triggerValues[this.hand] > TRIGGER_ON_VALUE) { + var targetProps = this.getTargetProps(controllerData); + if (targetProps && (targetProps.dynamic && targetProps.parentID === Uuid.NULL)) { + return makeRunningValues(false, [], []); // let farActionGrabEntity handle it + } else { + this.prepareDistanceRotatingData(controllerData); + return makeRunningValues(true, [], []); + } + } else { + this.destroyContextOverlay(); + return makeRunningValues(false, [], []); + } + } + return makeRunningValues(false, [], []); + }; + + this.run = function (controllerData) { + if (controllerData.triggerValues[this.hand] < TRIGGER_OFF_VALUE || + this.notPointingAtEntity(controllerData) || this.targetIsNull()) { + this.endFarParentGrab(controllerData); + Selection.removeFromSelectedItemsList(DISPATCHER_HOVERING_LIST, "entity", this.highlightedEntity); + this.highlightedEntity = null; + return makeRunningValues(false, [], []); + } + this.intersectionDistance = controllerData.rayPicks[this.hand].distance; + + var otherModuleName = this.hand === RIGHT_HAND ? "LeftFarParentGrabEntity" : "RightFarParentGrabEntity"; + var otherFarGrabModule = getEnabledModuleByName(otherModuleName); + + // gather up the readiness of the near-grab modules + var nearGrabNames = [ + this.hand === RIGHT_HAND ? "RightScaleAvatar" : "LeftScaleAvatar", + this.hand === RIGHT_HAND ? "RightFarTriggerEntity" : "LeftFarTriggerEntity", + this.hand === RIGHT_HAND ? "RightNearActionGrabEntity" : "LeftNearActionGrabEntity", + this.hand === RIGHT_HAND ? "RightNearParentingGrabEntity" : "LeftNearParentingGrabEntity", + this.hand === RIGHT_HAND ? "RightNearParentingGrabOverlay" : "LeftNearParentingGrabOverlay" + ]; + + var nearGrabReadiness = []; + for (var i = 0; i < nearGrabNames.length; i++) { + var nearGrabModule = getEnabledModuleByName(nearGrabNames[i]); + var ready = nearGrabModule ? nearGrabModule.isReady(controllerData) : makeRunningValues(false, [], []); + nearGrabReadiness.push(ready); + } + + if (this.targetEntityID) { + // if we are doing a distance grab and the object or tablet gets close enough to the controller, + // stop the far-grab so the near-grab or equip can take over. + for (var k = 0; k < nearGrabReadiness.length; k++) { + if (nearGrabReadiness[k].active && (nearGrabReadiness[k].targets[0] === this.targetEntityID || + HMD.tabletID && nearGrabReadiness[k].targets[0] === HMD.tabletID)) { + this.endFarParentGrab(controllerData); + return makeRunningValues(false, [], []); + } + } + + this.continueDistanceHolding(controllerData); + } else { + // if we are doing a distance search and this controller moves into a position + // where it could near-grab something, stop searching. + for (var j = 0; j < nearGrabReadiness.length; j++) { + if (nearGrabReadiness[j].active) { + this.endFarParentGrab(controllerData); + return makeRunningValues(false, [], []); + } + } + + var rayPickInfo = controllerData.rayPicks[this.hand]; + if (rayPickInfo.type === Picks.INTERSECTED_ENTITY) { + if (controllerData.triggerClicks[this.hand]) { + var entityID = rayPickInfo.objectID; + Selection.removeFromSelectedItemsList(DISPATCHER_HOVERING_LIST, "entity", this.highlightedEntity); + this.highlightedEntity = null; + var targetProps = Entities.getEntityProperties(entityID, [ + "dynamic", "shapeType", "position", + "rotation", "dimensions", "density", + "userData", "locked", "type", "href" + ]); + if (targetProps.href !== "") { + AddressManager.handleLookupString(targetProps.href); + return makeRunningValues(false, [], []); + } + + this.targetObject = new TargetObject(entityID, targetProps); + this.targetObject.parentProps = getEntityParents(targetProps); + + if (this.contextOverlayTimer) { + Script.clearTimeout(this.contextOverlayTimer); + } + this.contextOverlayTimer = false; + if (entityID === this.entityWithContextOverlay) { + this.destroyContextOverlay(); + } else { + Selection.removeFromSelectedItemsList("contextOverlayHighlightList", "entity", entityID); + } + + var targetEntity = this.targetObject.getTargetEntity(); + entityID = targetEntity.id; + targetProps = targetEntity.props; + + if (targetProps.dynamic || this.targetObject.entityProps.dynamic) { + // let farActionGrabEntity handle it + return makeRunningValues(false, [], []); + } + + if (entityIsGrabbable(targetProps) || entityIsGrabbable(this.targetObject.entityProps)) { + + if (!this.distanceRotating) { + this.targetEntityID = entityID; + this.grabbedDistance = rayPickInfo.distance; + } + + if (otherFarGrabModule.targetEntityID === this.targetEntityID && + otherFarGrabModule.distanceHolding) { + this.prepareDistanceRotatingData(controllerData); + this.distanceRotate(otherFarGrabModule); + } else { + this.distanceHolding = true; + this.distanceRotating = false; + this.startFarParentGrab(controllerData, targetProps); + } + } + } else { + var targetEntityID = rayPickInfo.objectID; + if (this.highlightedEntity !== targetEntityID) { + Selection.removeFromSelectedItemsList(DISPATCHER_HOVERING_LIST, "entity", this.highlightedEntity); + var selectionTargetProps = Entities.getEntityProperties(targetEntityID, [ + "dynamic", "shapeType", "position", + "rotation", "dimensions", "density", + "userData", "locked", "type", "href" + ]); + + var selectionTargetObject = new TargetObject(targetEntityID, selectionTargetProps); + selectionTargetObject.parentProps = getEntityParents(selectionTargetProps); + var selectionTargetEntity = selectionTargetObject.getTargetEntity(); + + if (entityIsGrabbable(selectionTargetEntity.props) || + entityIsGrabbable(selectionTargetObject.entityProps)) { + + Selection.addToSelectedItemsList(DISPATCHER_HOVERING_LIST, "entity", rayPickInfo.objectID); + } + this.highlightedEntity = rayPickInfo.objectID; + } + + if (!this.entityWithContextOverlay) { + var _this = this; + + if (_this.potentialEntityWithContextOverlay !== rayPickInfo.objectID) { + if (_this.contextOverlayTimer) { + Script.clearTimeout(_this.contextOverlayTimer); + } + _this.contextOverlayTimer = false; + _this.potentialEntityWithContextOverlay = rayPickInfo.objectID; + } + + if (!_this.contextOverlayTimer) { + _this.contextOverlayTimer = Script.setTimeout(function () { + if (!_this.entityWithContextOverlay && + _this.contextOverlayTimer && + _this.potentialEntityWithContextOverlay === rayPickInfo.objectID) { + var cotProps = Entities.getEntityProperties(rayPickInfo.objectID); + var pointerEvent = { + type: "Move", + id: _this.hand + 1, // 0 is reserved for hardware mouse + pos2D: projectOntoEntityXYPlane(rayPickInfo.objectID, + rayPickInfo.intersection, cotProps), + pos3D: rayPickInfo.intersection, + normal: rayPickInfo.surfaceNormal, + direction: Vec3.subtract(ZERO_VEC, rayPickInfo.surfaceNormal), + button: "Secondary" + }; + if (ContextOverlay.createOrDestroyContextOverlay(rayPickInfo.objectID, pointerEvent)) { + _this.entityWithContextOverlay = rayPickInfo.objectID; + } + } + _this.contextOverlayTimer = false; + }, 500); + } + } + } + } else if (this.distanceRotating) { + this.distanceRotate(otherFarGrabModule); + } else if (this.highlightedEntity) { + Selection.removeFromSelectedItemsList(DISPATCHER_HOVERING_LIST, "entity", this.highlightedEntity); + this.highlightedEntity = null; + } + } + return this.exitIfDisabled(controllerData); + }; + + this.exitIfDisabled = function(controllerData) { + var moduleName = this.hand === RIGHT_HAND ? "RightDisableModules" : "LeftDisableModules"; + var disableModule = getEnabledModuleByName(moduleName); + if (disableModule) { + if (disableModule.disableModules) { + this.endFarParentGrab(controllerData); + Selection.removeFromSelectedItemsList(DISPATCHER_HOVERING_LIST, "entity", this.highlightedEntity); + this.highlightedEntity = null; + return makeRunningValues(false, [], []); + } + } + var grabbedThing = (this.distanceHolding || this.distanceRotating) ? this.targetObject.entityID : null; + var offset = this.calculateOffset(controllerData); + var laserLockInfo = makeLaserLockInfo(grabbedThing, false, this.hand, offset); + return makeRunningValues(true, [], [], laserLockInfo); + }; + + this.calculateOffset = function(controllerData) { + if (this.distanceHolding || this.distanceRotating) { + var targetProps = Entities.getEntityProperties(this.targetObject.entityID, + [ "position", "rotation", "registrationPoint", "dimensions" ]); + return worldPositionToRegistrationFrameMatrix(targetProps, controllerData.rayPicks[this.hand].intersection); + } + return undefined; + }; + } + + var leftFarParentGrabEntity = new FarParentGrabEntity(LEFT_HAND); + var rightFarParentGrabEntity = new FarParentGrabEntity(RIGHT_HAND); + + enableDispatcherModule("LeftFarParentGrabEntity", leftFarParentGrabEntity); + enableDispatcherModule("RightFarParentGrabEntity", rightFarParentGrabEntity); + + function cleanup() { + disableDispatcherModule("LeftFarParentGrabEntity"); + disableDispatcherModule("RightFarParentGrabEntity"); + } + Script.scriptEnding.connect(cleanup); +}()); diff --git a/scripts/system/controllers/controllerModules/inEditMode.js b/scripts/system/controllers/controllerModules/inEditMode.js index a724c2037b..d590545532 100644 --- a/scripts/system/controllers/controllerModules/inEditMode.js +++ b/scripts/system/controllers/controllerModules/inEditMode.js @@ -10,7 +10,7 @@ /* global Script, Controller, RIGHT_HAND, LEFT_HAND, enableDispatcherModule, disableDispatcherModule, makeRunningValues, Messages, makeDispatcherModuleParameters, HMD, getGrabPointSphereOffset, COLORS_GRAB_SEARCHING_HALF_SQUEEZE, COLORS_GRAB_SEARCHING_FULL_SQUEEZE, COLORS_GRAB_DISTANCE_HOLD, DEFAULT_SEARCH_SPHERE_DISTANCE, TRIGGER_ON_VALUE, - getEnabledModuleByName, PICK_MAX_DISTANCE, isInEditMode, Picks, makeLaserParams + getEnabledModuleByName, PICK_MAX_DISTANCE, isInEditMode, Picks, makeLaserParams, Entities */ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); @@ -19,7 +19,6 @@ Script.include("/~/system/libraries/utils.js"); (function () { var MARGIN = 25; - function InEditMode(hand) { this.hand = hand; this.triggerClicked = false; @@ -53,7 +52,7 @@ Script.include("/~/system/libraries/utils.js"); return (HMD.tabletScreenID && objectID === HMD.tabletScreenID) || (HMD.homeButtonID && objectID === HMD.homeButtonID); }; - + this.calculateNewReticlePosition = function(intersection) { var dims = Controller.getViewportDimensions(); this.reticleMaxX = dims.x - MARGIN; @@ -66,32 +65,36 @@ Script.include("/~/system/libraries/utils.js"); this.sendPickData = function(controllerData) { if (controllerData.triggerClicks[this.hand]) { + var hand = this.hand === RIGHT_HAND ? Controller.Standard.RightHand : Controller.Standard.LeftHand; if (!this.triggerClicked) { this.selectedTarget = controllerData.rayPicks[this.hand]; if (!this.selectedTarget.intersects) { Messages.sendLocalMessage("entityToolUpdates", JSON.stringify({ - method: "clearSelection" + method: "clearSelection", + hand: hand })); } } if (this.selectedTarget.type === Picks.INTERSECTED_ENTITY) { Messages.sendLocalMessage("entityToolUpdates", JSON.stringify({ method: "selectEntity", - entityID: this.selectedTarget.objectID + entityID: this.selectedTarget.objectID, + hand: hand })); } else if (this.selectedTarget.type === Picks.INTERSECTED_OVERLAY) { Messages.sendLocalMessage("entityToolUpdates", JSON.stringify({ method: "selectOverlay", - overlayID: this.selectedTarget.objectID + overlayID: this.selectedTarget.objectID, + hand: hand })); } this.triggerClicked = true; } - + this.sendPointingAtData(controllerData); }; - + this.sendPointingAtData = function(controllerData) { var rayPick = controllerData.rayPicks[this.hand]; var hudRayPick = controllerData.hudRayPicks[this.hand]; diff --git a/scripts/system/controllers/controllerModules/nearActionGrabEntity.js b/scripts/system/controllers/controllerModules/nearActionGrabEntity.js index f528c6f80f..a8de76aebd 100644 --- a/scripts/system/controllers/controllerModules/nearActionGrabEntity.js +++ b/scripts/system/controllers/controllerModules/nearActionGrabEntity.js @@ -174,10 +174,12 @@ Script.include("/~/system/libraries/cloneEntityUtils.js"); Controller.triggerHapticPulse(HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, this.hand); this.hapticTargetID = props.id; } - // if we've attempted to grab a child, roll up to the root of the tree - var groupRootProps = findGroupParent(controllerData, props); - if (entityIsGrabbable(groupRootProps)) { - return groupRootProps; + if (!entityIsCloneable(props)) { + // if we've attempted to grab a non-cloneable child, roll up to the root of the tree + var groupRootProps = findGroupParent(controllerData, props); + if (entityIsGrabbable(groupRootProps)) { + return groupRootProps; + } } return props; } diff --git a/scripts/system/controllers/controllerModules/nearParentGrabEntity.js b/scripts/system/controllers/controllerModules/nearParentGrabEntity.js index 38334f5523..035c150a5d 100644 --- a/scripts/system/controllers/controllerModules/nearParentGrabEntity.js +++ b/scripts/system/controllers/controllerModules/nearParentGrabEntity.js @@ -12,17 +12,32 @@ findGroupParent, Vec3, cloneEntity, entityIsCloneable, propsAreCloneDynamic, HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, BUMPER_ON_VALUE, findHandChildEntities, TEAR_AWAY_DISTANCE, MSECS_PER_SEC, TEAR_AWAY_CHECK_TIME, TEAR_AWAY_COUNT, distanceBetweenPointAndEntityBoundingBox, print, Uuid, highlightTargetEntity, unhighlightTargetEntity, - distanceBetweenEntityLocalPositionAndBoundingBox + distanceBetweenEntityLocalPositionAndBoundingBox, GRAB_POINT_SPHERE_OFFSET */ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); Script.include("/~/system/libraries/cloneEntityUtils.js"); +Script.include("/~/system/libraries/controllers.js"); (function() { // XXX this.ignoreIK = (grabbableData.ignoreIK !== undefined) ? grabbableData.ignoreIK : true; // XXX this.kinematicGrab = (grabbableData.kinematic !== undefined) ? grabbableData.kinematic : NEAR_GRABBING_KINEMATIC; + function getGrabOffset(handController) { + var offset = GRAB_POINT_SPHERE_OFFSET; + if (handController === Controller.Standard.LeftHand) { + offset = { + x: -GRAB_POINT_SPHERE_OFFSET.x, + y: GRAB_POINT_SPHERE_OFFSET.y, + z: GRAB_POINT_SPHERE_OFFSET.z + }; + } + + offset.y = -GRAB_POINT_SPHERE_OFFSET.y; + return Vec3.multiply(MyAvatar.sensorToWorldScale, offset); + } + function NearParentingGrabEntity(hand) { this.hand = hand; this.targetEntityID = null; @@ -85,6 +100,7 @@ Script.include("/~/system/libraries/cloneEntityUtils.js"); this.startNearParentingGrabEntity = function (controllerData, targetProps) { Controller.triggerHapticPulse(HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, this.hand); unhighlightTargetEntity(this.targetEntityID); + this.highlightedEntity = null; var message = { hand: this.hand, entityID: this.targetEntityID @@ -162,6 +178,7 @@ Script.include("/~/system/libraries/cloneEntityUtils.js"); joint: this.hand === RIGHT_HAND ? "RightHand" : "LeftHand" })); unhighlightTargetEntity(this.targetEntityID); + this.highlightedEntity = null; this.grabbing = false; this.targetEntityID = null; this.robbed = false; @@ -174,7 +191,9 @@ Script.include("/~/system/libraries/cloneEntityUtils.js"); this.lastUnequipCheckTime = now; if (props.parentID === MyAvatar.SELF_ID) { var tearAwayDistance = TEAR_AWAY_DISTANCE * MyAvatar.sensorToWorldScale; - var distance = distanceBetweenEntityLocalPositionAndBoundingBox(props); + var controllerIndex = (this.hand === LEFT_HAND ? Controller.Standard.LeftHand : Controller.Standard.RightHand); + var controllerGrabOffset = getGrabOffset(controllerIndex); + var distance = distanceBetweenEntityLocalPositionAndBoundingBox(props, controllerGrabOffset); if (distance > tearAwayDistance) { this.autoUnequipCounter++; } else { @@ -251,10 +270,12 @@ Script.include("/~/system/libraries/cloneEntityUtils.js"); Controller.triggerHapticPulse(HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, this.hand); this.hapticTargetID = props.id; } - // if we've attempted to grab a child, roll up to the root of the tree - var groupRootProps = findGroupParent(controllerData, props); - if (entityIsGrabbable(groupRootProps)) { - return groupRootProps; + if (!entityIsCloneable(props)) { + // if we've attempted to grab a non-cloneable child, roll up to the root of the tree + var groupRootProps = findGroupParent(controllerData, props); + if (entityIsGrabbable(groupRootProps)) { + return groupRootProps; + } } return props; } @@ -287,6 +308,10 @@ Script.include("/~/system/libraries/cloneEntityUtils.js"); return makeRunningValues(true, [this.targetEntityID], []); } } else { + if (this.highlightedEntity) { + unhighlightTargetEntity(this.highlightedEntity); + this.highlightedEntity = null; + } this.hapticTargetID = null; this.robbed = false; return makeRunningValues(false, [], []); @@ -305,6 +330,7 @@ Script.include("/~/system/libraries/cloneEntityUtils.js"); if (!props) { // entity was deleted unhighlightTargetEntity(this.targetEntityID); + this.highlightedEntity = null; this.grabbing = false; this.targetEntityID = null; this.hapticTargetID = null; @@ -327,6 +353,7 @@ Script.include("/~/system/libraries/cloneEntityUtils.js"); if (!readiness.active) { this.robbed = false; unhighlightTargetEntity(this.highlightedEntity); + this.highlightedEntity = null; return readiness; } if (controllerData.triggerClicks[this.hand] || controllerData.secondaryValues[this.hand] > BUMPER_ON_VALUE) { diff --git a/scripts/system/controllers/controllerModules/teleport.js b/scripts/system/controllers/controllerModules/teleport.js index 3bf99ca26a..e4dd1c43fa 100644 --- a/scripts/system/controllers/controllerModules/teleport.js +++ b/scripts/system/controllers/controllerModules/teleport.js @@ -22,7 +22,6 @@ Script.include("/~/system/libraries/controllers.js"); (function() { // BEGIN LOCAL_SCOPE var TARGET_MODEL_URL = Script.resolvePath("../../assets/models/teleport-destination.fbx"); - var TOO_CLOSE_MODEL_URL = Script.resolvePath("../../assets/models/teleport-cancel.fbx"); var SEAT_MODEL_URL = Script.resolvePath("../../assets/models/teleport-seat.fbx"); var TARGET_MODEL_DIMENSIONS = { @@ -49,9 +48,6 @@ Script.include("/~/system/libraries/controllers.js"); blue: 73 }; - var TELEPORT_CANCEL_RANGE = 1; - var COOL_IN_DURATION = 300; - var handInfo = { right: { controllerInput: Controller.Standard.RightHand @@ -62,66 +58,46 @@ Script.include("/~/system/libraries/controllers.js"); }; var cancelPath = { - type: "line3d", color: COLORS_TELEPORT_CANCEL, - ignoreRayIntersection: true, alpha: 1, - solid: true, - drawInFront: true, - glow: 1.0 + width: 0.025 }; var teleportPath = { - type: "line3d", color: COLORS_TELEPORT_CAN_TELEPORT, - ignoreRayIntersection: true, alpha: 1, - solid: true, - drawInFront: true, - glow: 1.0 + width: 0.025 }; var seatPath = { - type: "line3d", color: COLORS_TELEPORT_SEAT, - ignoreRayIntersection: true, alpha: 1, - solid: true, - drawInFront: true, - glow: 1.0 - }; - var cancelEnd = { - type: "model", - url: TOO_CLOSE_MODEL_URL, - dimensions: TARGET_MODEL_DIMENSIONS, - ignoreRayIntersection: true + width: 0.025 }; var teleportEnd = { type: "model", url: TARGET_MODEL_URL, dimensions: TARGET_MODEL_DIMENSIONS, - ignoreRayIntersection: true + ignorePickIntersection: true }; var seatEnd = { type: "model", url: SEAT_MODEL_URL, dimensions: TARGET_MODEL_DIMENSIONS, - ignoreRayIntersection: true + ignorePickIntersection: true }; - var teleportRenderStates = [{name: "cancel", path: cancelPath, end: cancelEnd}, + var teleportRenderStates = [{name: "cancel", path: cancelPath}, {name: "teleport", path: teleportPath, end: teleportEnd}, {name: "seat", path: seatPath, end: seatEnd}]; - var DEFAULT_DISTANCE = 50; + var DEFAULT_DISTANCE = 8.0; var teleportDefaultRenderStates = [{name: "cancel", distance: DEFAULT_DISTANCE, path: cancelPath}]; - var coolInTimeout = null; var ignoredEntities = []; var TELEPORTER_STATES = { IDLE: 'idle', - COOL_IN: 'cool_in', TARGETTING: 'targetting', TARGETTING_INVALID: 'targetting_invalid' }; @@ -134,6 +110,9 @@ Script.include("/~/system/libraries/controllers.js"); SEAT: 'seat' // The current target is a seat }; + var speed = 9.3; + var accelerationAxis = {x: 0.0, y: -5.0, z: 0.0}; + function Teleporter(hand) { var _this = this; this.hand = hand; @@ -149,50 +128,104 @@ Script.include("/~/system/libraries/controllers.js"); return otherModule; }; - this.teleportRayHandVisible = Pointers.createPointer(PickType.Ray, { - joint: (_this.hand === RIGHT_HAND) ? "RightHand" : "LeftHand", + this.teleportParabolaHandVisible = Pointers.createPointer(PickType.Parabola, { + joint: (_this.hand === RIGHT_HAND) ? "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND" : "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND", + dirOffset: { x: 0, y: 1, z: 0.1 }, + posOffset: { x: (_this.hand === RIGHT_HAND) ? 0.03 : -0.03, y: 0.2, z: 0.02 }, filter: Picks.PICK_ENTITIES, faceAvatar: true, scaleWithAvatar: true, centerEndY: false, + speed: speed, + accelerationAxis: accelerationAxis, + rotateAccelerationWithAvatar: true, renderStates: teleportRenderStates, - defaultRenderStates: teleportDefaultRenderStates + defaultRenderStates: teleportDefaultRenderStates, + maxDistance: 8.0 }); - this.teleportRayHandInvisible = Pointers.createPointer(PickType.Ray, { - joint: (_this.hand === RIGHT_HAND) ? "RightHand" : "LeftHand", + this.teleportParabolaHandInvisible = Pointers.createPointer(PickType.Parabola, { + joint: (_this.hand === RIGHT_HAND) ? "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND" : "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND", + dirOffset: { x: 0, y: 1, z: 0.1 }, + posOffset: { x: (_this.hand === RIGHT_HAND) ? 0.03 : -0.03, y: 0.2, z: 0.02 }, filter: Picks.PICK_ENTITIES | Picks.PICK_INCLUDE_INVISIBLE, faceAvatar: true, scaleWithAvatar: true, centerEndY: false, - renderStates: teleportRenderStates + speed: speed, + accelerationAxis: accelerationAxis, + rotateAccelerationWithAvatar: true, + renderStates: teleportRenderStates, + maxDistance: 8.0 }); - this.teleportRayHeadVisible = Pointers.createPointer(PickType.Ray, { + this.teleportParabolaHeadVisible = Pointers.createPointer(PickType.Parabola, { joint: "Avatar", filter: Picks.PICK_ENTITIES, faceAvatar: true, scaleWithAvatar: true, centerEndY: false, + speed: speed, + accelerationAxis: accelerationAxis, + rotateAccelerationWithAvatar: true, renderStates: teleportRenderStates, - defaultRenderStates: teleportDefaultRenderStates + defaultRenderStates: teleportDefaultRenderStates, + maxDistance: 8.0 }); - this.teleportRayHeadInvisible = Pointers.createPointer(PickType.Ray, { + this.teleportParabolaHeadInvisible = Pointers.createPointer(PickType.Parabola, { joint: "Avatar", filter: Picks.PICK_ENTITIES | Picks.PICK_INCLUDE_INVISIBLE, faceAvatar: true, scaleWithAvatar: true, centerEndY: false, - renderStates: teleportRenderStates + speed: speed, + accelerationAxis: accelerationAxis, + rotateAccelerationWithAvatar: true, + renderStates: teleportRenderStates, + maxDistance: 8.0 }); this.cleanup = function() { - Pointers.removePointer(this.teleportRayHandVisible); - Pointers.removePointer(this.teleportRayHandInvisible); - Pointers.removePointer(this.teleportRayHeadVisible); - Pointers.removePointer(this.teleportRayHeadInvisible); + Pointers.removePointer(this.teleportParabolaHandVisible); + Pointers.removePointer(this.teleportParabolaHandInvisible); + Pointers.removePointer(this.teleportParabolaHeadVisible); + Pointers.removePointer(this.teleportParabolaHeadInvisible); }; - this.buttonPress = function(value) { - _this.buttonValue = value; + this.axisButtonStateX = 0; // Left/right axis button pressed. + this.axisButtonStateY = 0; // Up/down axis button pressed. + this.BUTTON_TRANSITION_DELAY = 100; // Allow time for transition from direction buttons to touch-pad. + + this.axisButtonChangeX = function (value) { + if (value !== 0) { + _this.axisButtonStateX = value; + } else { + // Delay direction button release until after teleport possibly pressed. + Script.setTimeout(function () { + _this.axisButtonStateX = value; + }, _this.BUTTON_TRANSITION_DELAY); + } + }; + + this.axisButtonChangeY = function (value) { + if (value !== 0) { + _this.axisButtonStateY = value; + } else { + // Delay direction button release until after teleport possibly pressed. + Script.setTimeout(function () { + _this.axisButtonStateY = value; + }, _this.BUTTON_TRANSITION_DELAY); + } + }; + + this.teleportLocked = function () { + // Lock teleport if in advanced movement mode and have just transitioned from pressing a direction button. + return Controller.getValue(Controller.Hardware.Application.AdvancedMovement) + && (_this.axisButtonStateX !== 0 || _this.axisButtonStateY !== 0); + }; + + this.buttonPress = function (value) { + if (value === 0 || !_this.teleportLocked()) { + _this.buttonValue = value; + } }; this.parameters = makeDispatcherModuleParameters( @@ -202,44 +235,7 @@ Script.include("/~/system/libraries/controllers.js"); 100); this.enterTeleport = function() { - if (coolInTimeout !== null) { - Script.clearTimeout(coolInTimeout); - } - - this.state = TELEPORTER_STATES.COOL_IN; - coolInTimeout = Script.setTimeout(function() { - if (_this.state === TELEPORTER_STATES.COOL_IN) { - _this.state = TELEPORTER_STATES.TARGETTING; - } - }, COOL_IN_DURATION); - - // pad scale with avatar size - var AVATAR_PROPORTIONAL_TARGET_MODEL_DIMENSIONS = Vec3.multiply(MyAvatar.sensorToWorldScale, TARGET_MODEL_DIMENSIONS); - - if (!Vec3.equal(AVATAR_PROPORTIONAL_TARGET_MODEL_DIMENSIONS, cancelEnd.dimensions)) { - cancelEnd.dimensions = AVATAR_PROPORTIONAL_TARGET_MODEL_DIMENSIONS; - teleportEnd.dimensions = AVATAR_PROPORTIONAL_TARGET_MODEL_DIMENSIONS; - seatEnd.dimensions = AVATAR_PROPORTIONAL_TARGET_MODEL_DIMENSIONS; - - teleportRenderStates = [{name: "cancel", path: cancelPath, end: cancelEnd}, - {name: "teleport", path: teleportPath, end: teleportEnd}, - {name: "seat", path: seatPath, end: seatEnd}]; - - Pointers.editRenderState(this.teleportRayHandVisible, "cancel", teleportRenderStates[0]); - Pointers.editRenderState(this.teleportRayHandInvisible, "cancel", teleportRenderStates[0]); - Pointers.editRenderState(this.teleportRayHeadVisible, "cancel", teleportRenderStates[0]); - Pointers.editRenderState(this.teleportRayHeadInvisible, "cancel", teleportRenderStates[0]); - - Pointers.editRenderState(this.teleportRayHandVisible, "teleport", teleportRenderStates[1]); - Pointers.editRenderState(this.teleportRayHandInvisible, "teleport", teleportRenderStates[1]); - Pointers.editRenderState(this.teleportRayHeadVisible, "teleport", teleportRenderStates[1]); - Pointers.editRenderState(this.teleportRayHeadInvisible, "teleport", teleportRenderStates[1]); - - Pointers.editRenderState(this.teleportRayHandVisible, "seat", teleportRenderStates[2]); - Pointers.editRenderState(this.teleportRayHandInvisible, "seat", teleportRenderStates[2]); - Pointers.editRenderState(this.teleportRayHeadVisible, "seat", teleportRenderStates[2]); - Pointers.editRenderState(this.teleportRayHeadInvisible, "seat", teleportRenderStates[2]); - } + this.state = TELEPORTER_STATES.TARGETTING; }; this.isReady = function(controllerData, deltaTime) { @@ -258,18 +254,18 @@ Script.include("/~/system/libraries/controllers.js"); var pose = Controller.getPoseValue(handInfo[(_this.hand === RIGHT_HAND) ? 'right' : 'left'].controllerInput); var mode = pose.valid ? _this.hand : 'head'; if (!pose.valid) { - Pointers.disablePointer(_this.teleportRayHandVisible); - Pointers.disablePointer(_this.teleportRayHandInvisible); - Pointers.enablePointer(_this.teleportRayHeadVisible); - Pointers.enablePointer(_this.teleportRayHeadInvisible); + Pointers.disablePointer(_this.teleportParabolaHandVisible); + Pointers.disablePointer(_this.teleportParabolaHandInvisible); + Pointers.enablePointer(_this.teleportParabolaHeadVisible); + Pointers.enablePointer(_this.teleportParabolaHeadInvisible); } else { - Pointers.enablePointer(_this.teleportRayHandVisible); - Pointers.enablePointer(_this.teleportRayHandInvisible); - Pointers.disablePointer(_this.teleportRayHeadVisible); - Pointers.disablePointer(_this.teleportRayHeadInvisible); + Pointers.enablePointer(_this.teleportParabolaHandVisible); + Pointers.enablePointer(_this.teleportParabolaHandInvisible); + Pointers.disablePointer(_this.teleportParabolaHeadVisible); + Pointers.disablePointer(_this.teleportParabolaHeadInvisible); } - // We do up to 2 ray picks to find a teleport location. + // We do up to 2 picks to find a teleport location. // There are 2 types of teleport locations we are interested in: // 1. A visible floor. This can be any entity surface that points within some degree of "up" // 2. A seat. The seat can be visible or invisible. @@ -280,17 +276,17 @@ Script.include("/~/system/libraries/controllers.js"); // var result; if (mode === 'head') { - result = Pointers.getPrevPickResult(_this.teleportRayHeadInvisible); + result = Pointers.getPrevPickResult(_this.teleportParabolaHeadInvisible); } else { - result = Pointers.getPrevPickResult(_this.teleportRayHandInvisible); + result = Pointers.getPrevPickResult(_this.teleportParabolaHandInvisible); } var teleportLocationType = getTeleportTargetType(result); if (teleportLocationType === TARGET.INVISIBLE) { if (mode === 'head') { - result = Pointers.getPrevPickResult(_this.teleportRayHeadVisible); + result = Pointers.getPrevPickResult(_this.teleportParabolaHeadVisible); } else { - result = Pointers.getPrevPickResult(_this.teleportRayHandVisible); + result = Pointers.getPrevPickResult(_this.teleportParabolaHandVisible); } teleportLocationType = getTeleportTargetType(result); } @@ -301,11 +297,7 @@ Script.include("/~/system/libraries/controllers.js"); } else if (teleportLocationType === TARGET.INVALID || teleportLocationType === TARGET.INVISIBLE) { this.setTeleportState(mode, "", "cancel"); } else if (teleportLocationType === TARGET.SURFACE) { - if (this.state === TELEPORTER_STATES.COOL_IN) { - this.setTeleportState(mode, "cancel", ""); - } else { - this.setTeleportState(mode, "teleport", ""); - } + this.setTeleportState(mode, "teleport", ""); } else if (teleportLocationType === TARGET.SEAT) { this.setTeleportState(mode, "", "seat"); } @@ -318,14 +310,14 @@ Script.include("/~/system/libraries/controllers.js"); return makeRunningValues(true, [], []); } - if (target === TARGET.NONE || target === TARGET.INVALID || this.state === TELEPORTER_STATES.COOL_IN) { + if (target === TARGET.NONE || target === TARGET.INVALID) { // Do nothing } else if (target === TARGET.SEAT) { Entities.callEntityMethod(result.objectID, 'sit'); } else if (target === TARGET.SURFACE) { var offset = getAvatarFootOffset(); result.intersection.y += offset; - MyAvatar.goToLocation(result.intersection, false, {x: 0, y: 0, z: 0, w: 1}, false); + MyAvatar.goToLocation(result.intersection, true, HMD.orientation, false); HMD.centerUI(); MyAvatar.centerBody(); } @@ -336,27 +328,27 @@ Script.include("/~/system/libraries/controllers.js"); }; this.disableLasers = function() { - Pointers.disablePointer(_this.teleportRayHandVisible); - Pointers.disablePointer(_this.teleportRayHandInvisible); - Pointers.disablePointer(_this.teleportRayHeadVisible); - Pointers.disablePointer(_this.teleportRayHeadInvisible); + Pointers.disablePointer(_this.teleportParabolaHandVisible); + Pointers.disablePointer(_this.teleportParabolaHandInvisible); + Pointers.disablePointer(_this.teleportParabolaHeadVisible); + Pointers.disablePointer(_this.teleportParabolaHeadInvisible); }; this.setTeleportState = function(mode, visibleState, invisibleState) { if (mode === 'head') { - Pointers.setRenderState(_this.teleportRayHeadVisible, visibleState); - Pointers.setRenderState(_this.teleportRayHeadInvisible, invisibleState); + Pointers.setRenderState(_this.teleportParabolaHeadVisible, visibleState); + Pointers.setRenderState(_this.teleportParabolaHeadInvisible, invisibleState); } else { - Pointers.setRenderState(_this.teleportRayHandVisible, visibleState); - Pointers.setRenderState(_this.teleportRayHandInvisible, invisibleState); + Pointers.setRenderState(_this.teleportParabolaHandVisible, visibleState); + Pointers.setRenderState(_this.teleportParabolaHandInvisible, invisibleState); } }; this.setIgnoreEntities = function(entitiesToIgnore) { - Pointers.setIgnoreItems(this.teleportRayHandVisible, entitiesToIgnore); - Pointers.setIgnoreItems(this.teleportRayHandInvisible, entitiesToIgnore); - Pointers.setIgnoreItems(this.teleportRayHeadVisible, entitiesToIgnore); - Pointers.setIgnoreItems(this.teleportRayHeadInvisible, entitiesToIgnore); + Pointers.setIgnoreItems(this.teleportParabolaHandVisible, entitiesToIgnore); + Pointers.setIgnoreItems(this.teleportParabolaHandInvisible, entitiesToIgnore); + Pointers.setIgnoreItems(this.teleportParabolaHeadVisible, entitiesToIgnore); + Pointers.setIgnoreItems(this.teleportParabolaHeadInvisible, entitiesToIgnore); }; } @@ -389,6 +381,7 @@ Script.include("/~/system/libraries/controllers.js"); } var mappingName, teleportMapping; + var isViveMapped = false; function parseJSON(json) { try { @@ -399,7 +392,7 @@ Script.include("/~/system/libraries/controllers.js"); } // When determininig whether you can teleport to a location, the normal of the // point that is being intersected with is looked at. If this normal is more - // than MAX_ANGLE_FROM_UP_TO_TELEPORT degrees from <0, 1, 0> (straight up), then + // than MAX_ANGLE_FROM_UP_TO_TELEPORT degrees from your avatar's up, then // you can't teleport there. var MAX_ANGLE_FROM_UP_TO_TELEPORT = 70; function getTeleportTargetType(result) { @@ -423,24 +416,48 @@ Script.include("/~/system/libraries/controllers.js"); } var surfaceNormal = result.surfaceNormal; - var adj = Math.sqrt(surfaceNormal.x * surfaceNormal.x + surfaceNormal.z * surfaceNormal.z); - var angleUp = Math.atan2(surfaceNormal.y, adj) * (180 / Math.PI); + var angle = Math.acos(Vec3.dot(surfaceNormal, Quat.getUp(MyAvatar.orientation))) * (180.0 / Math.PI); - if (angleUp < (90 - MAX_ANGLE_FROM_UP_TO_TELEPORT) || - angleUp > (90 + MAX_ANGLE_FROM_UP_TO_TELEPORT) || - Vec3.distance(MyAvatar.position, result.intersection) <= TELEPORT_CANCEL_RANGE * MyAvatar.sensorToWorldScale) { + if (angle > MAX_ANGLE_FROM_UP_TO_TELEPORT) { return TARGET.INVALID; } else { return TARGET.SURFACE; } } + function registerViveTeleportMapping() { + // Disable Vive teleport if touch is transitioning across touch-pad after pressing a direction button. + if (Controller.Hardware.Vive) { + var mappingName = 'Hifi-Teleporter-Dev-Vive-' + Math.random(); + var viveTeleportMapping = Controller.newMapping(mappingName); + viveTeleportMapping.from(Controller.Hardware.Vive.LSX).peek().to(leftTeleporter.axisButtonChangeX); + viveTeleportMapping.from(Controller.Hardware.Vive.LSY).peek().to(leftTeleporter.axisButtonChangeY); + viveTeleportMapping.from(Controller.Hardware.Vive.RSX).peek().to(rightTeleporter.axisButtonChangeX); + viveTeleportMapping.from(Controller.Hardware.Vive.RSY).peek().to(rightTeleporter.axisButtonChangeY); + Controller.enableMapping(mappingName); + isViveMapped = true; + } + } + + function onHardwareChanged() { + // Controller.Hardware.Vive is not immediately available at Interface start-up. + if (!isViveMapped && Controller.Hardware.Vive) { + registerViveTeleportMapping(); + } + } + + Controller.hardwareChanged.connect(onHardwareChanged); + function registerMappings() { mappingName = 'Hifi-Teleporter-Dev-' + Math.random(); teleportMapping = Controller.newMapping(mappingName); - teleportMapping.from(Controller.Standard.RightPrimaryThumb).peek().to(rightTeleporter.buttonPress); + // Vive teleport button lock-out. + registerViveTeleportMapping(); + + // Teleport actions. teleportMapping.from(Controller.Standard.LeftPrimaryThumb).peek().to(leftTeleporter.buttonPress); + teleportMapping.from(Controller.Standard.RightPrimaryThumb).peek().to(rightTeleporter.buttonPress); } var leftTeleporter = new Teleporter(LEFT_HAND); @@ -452,6 +469,7 @@ Script.include("/~/system/libraries/controllers.js"); Controller.enableMapping(mappingName); function cleanup() { + Controller.hardwareChanged.disconnect(onHardwareChanged); teleportMapping.disable(); leftTeleporter.cleanup(); rightTeleporter.cleanup(); diff --git a/scripts/system/controllers/controllerScripts.js b/scripts/system/controllers/controllerScripts.js index ce93c6a010..6899577de2 100644 --- a/scripts/system/controllers/controllerScripts.js +++ b/scripts/system/controllers/controllerScripts.js @@ -19,7 +19,8 @@ var CONTOLLER_SCRIPTS = [ "controllerModules/nearParentGrabEntity.js", "controllerModules/nearParentGrabOverlay.js", "controllerModules/nearActionGrabEntity.js", - "controllerModules/farActionGrabEntity.js", + // "controllerModules/farActionGrabEntity.js", + // "controllerModules/farParentGrabEntity.js", "controllerModules/stylusInput.js", "controllerModules/equipEntity.js", "controllerModules/nearTrigger.js", @@ -37,6 +38,13 @@ var CONTOLLER_SCRIPTS = [ "controllerModules/mouseHighlightEntities.js" ]; +if (Settings.getValue("useFarGrabJoints", false)) { + CONTOLLER_SCRIPTS.push("controllerModules/farActionGrabEntityDynOnly.js"); + CONTOLLER_SCRIPTS.push("controllerModules/farParentGrabEntity.js"); +} else { + CONTOLLER_SCRIPTS.push("controllerModules/farActionGrabEntity.js"); +} + var DEBUG_MENU_ITEM = "Debug defaultScripts.js"; diff --git a/scripts/system/controllers/touchControllerConfiguration.js b/scripts/system/controllers/touchControllerConfiguration.js index 9df8f9e97d..f22252f646 100644 --- a/scripts/system/controllers/touchControllerConfiguration.js +++ b/scripts/system/controllers/touchControllerConfiguration.js @@ -44,7 +44,7 @@ TOUCH_CONTROLLER_CONFIGURATION_LEFT = { controllers: [ { modelURL: BASE_URL + "touch_l_body.fbx", - jointIndex: MyAvatar.getJointIndex("_CONTROLLER_LEFTHAND"), + jointIndex: MyAvatar.getJointIndex("_CAMERA_RELATIVE_CONTROLLER_LEFTHAND"), naturalPosition: { x: 0.01648625358939171, y: -0.03551870584487915, z: -0.018527675420045853 }, dimensions: { x: 0.11053799837827682, y: 0.0995776429772377, z: 0.10139888525009155 }, rotation: leftBaseRotation, @@ -209,7 +209,7 @@ TOUCH_CONTROLLER_CONFIGURATION_RIGHT = { controllers: [ { modelURL: BASE_URL + "touch_r_body.fbx", - jointIndex: MyAvatar.getJointIndex("_CONTROLLER_RIGHTHAND"), + jointIndex: MyAvatar.getJointIndex("_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND"), naturalPosition: { x: -0.016486231237649918, y: -0.03551865369081497, z: -0.018527653068304062 }, dimensions: { x: 0.11053784191608429, y: 0.09957750141620636, z: 0.10139875113964081 }, rotation: rightBaseRotation, diff --git a/scripts/system/edit.js b/scripts/system/edit.js index 1c0b7c2dcb..e340c75a8b 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -20,6 +20,8 @@ var EDIT_TOGGLE_BUTTON = "com.highfidelity.interface.system.editButton"; +var CONTROLLER_MAPPING_NAME = "com.highfidelity.editMode"; + Script.include([ "libraries/stringHelpers.js", "libraries/dataViewHelpers.js", @@ -314,12 +316,10 @@ var toolBar = (function () { direction = MyAvatar.orientation; } direction = Vec3.multiplyQbyV(direction, Vec3.UNIT_Z); - // Align entity with Avatar orientation. - properties.rotation = MyAvatar.orientation; - + var PRE_ADJUST_ENTITY_TYPES = ["Box", "Sphere", "Shape", "Text", "Web", "Material"]; if (PRE_ADJUST_ENTITY_TYPES.indexOf(properties.type) !== -1) { - + // Adjust position of entity per bounding box prior to creating it. var registration = properties.registration; if (registration === undefined) { @@ -352,7 +352,12 @@ var toolBar = (function () { properties.userData = JSON.stringify({ grabbableKey: { grabbable: false } }); } + SelectionManager.saveProperties(); entityID = Entities.addEntity(properties); + pushCommandForSelections([{ + entityID: entityID, + properties: properties + }], [], true); if (properties.type === "ParticleEffect") { selectParticleEntity(entityID); @@ -392,6 +397,8 @@ var toolBar = (function () { entityListTool.sendUpdate(); selectionManager.setSelections([entityID]); + Window.setFocus(); + return entityID; } @@ -799,6 +806,14 @@ var toolBar = (function () { addButton("newMaterialButton", createNewEntityDialogButtonCallback("Material")); + var deactivateCreateIfDesktopWindowsHidden = function() { + if (!shouldUseEditTabletApp() && !entityListTool.isVisible() && !createToolsWindow.isVisible()) { + that.setActive(false); + } + }; + entityListTool.interactiveWindowHidden.addListener(this, deactivateCreateIfDesktopWindowsHidden); + createToolsWindow.interactiveWindowHidden.addListener(this, deactivateCreateIfDesktopWindowsHidden); + that.setActive(false); } @@ -847,6 +862,7 @@ var toolBar = (function () { cameraManager.disable(); selectionDisplay.triggerMapping.disable(); tablet.landscape = false; + Controller.disableMapping(CONTROLLER_MAPPING_NAME); } else { if (shouldUseEditTabletApp()) { tablet.loadQMLSource("hifi/tablet/Edit.qml", true); @@ -856,12 +872,14 @@ var toolBar = (function () { } UserActivityLogger.enabledEdit(); entityListTool.setVisible(true); + entityListTool.sendUpdate(); gridTool.setVisible(true); grid.setEnabled(true); propertiesTool.setVisible(true); selectionDisplay.triggerMapping.enable(); print("starting tablet in landscape mode"); tablet.landscape = true; + Controller.enableMapping(CONTROLLER_MAPPING_NAME); // Not sure what the following was meant to accomplish, but it currently causes // everybody else to think that Interface has lost focus overall. fogbugzid:558 // Window.setFocus(); @@ -961,13 +979,15 @@ function handleOverlaySelectionToolUpdates(channel, message, sender) { var data = JSON.parse(message); if (data.method === "selectOverlay") { - if (wantDebug) { - print("setting selection to overlay " + data.overlayID); - } - var entity = entityIconOverlayManager.findEntity(data.overlayID); + if (!selectionDisplay.triggered() || selectionDisplay.triggeredHand === data.hand) { + if (wantDebug) { + print("setting selection to overlay " + data.overlayID); + } + var entity = entityIconOverlayManager.findEntity(data.overlayID); - if (entity !== null) { - selectionManager.setSelections([entity]); + if (entity !== null) { + selectionManager.setSelections([entity]); + } } } } @@ -1588,7 +1608,7 @@ function deleteSelectedEntities() { Entities.deleteEntity(entityID); } } - + if (savedProperties.length > 0) { SelectionManager.clearSelections(); pushCommandForSelections([], savedProperties); @@ -1843,30 +1863,7 @@ var keyReleaseEvent = function (event) { cameraManager.keyReleaseEvent(event); } // since sometimes our menu shortcut keys don't work, trap our menu items here also and fire the appropriate menu items - if (event.text === "DELETE") { - deleteSelectedEntities(); - } else if (event.text === 'd' && event.isControl) { - selectionManager.clearSelections(); - } else if (event.text === "t") { - selectionDisplay.toggleSpaceMode(); - } else if (event.text === "f") { - if (isActive) { - if (selectionManager.hasSelection()) { - cameraManager.enable(); - cameraManager.focus(selectionManager.worldPosition, - selectionManager.worldDimensions, - Menu.isOptionChecked(MENU_EASE_ON_FOCUS)); - } - } - } else if (event.text === '[') { - if (isActive) { - cameraManager.enable(); - } - } else if (event.text === 'g') { - if (isActive && selectionManager.hasSelection()) { - grid.moveToSelection(); - } - } else if (event.key === KEY_P && event.isControl && !event.isAutoRepeat ) { + if (event.key === KEY_P && event.isControl && !event.isAutoRepeat) { if (event.isShifted) { unparentSelectedEntities(); } else { @@ -1877,13 +1874,54 @@ var keyReleaseEvent = function (event) { Controller.keyReleaseEvent.connect(keyReleaseEvent); Controller.keyPressEvent.connect(keyPressEvent); +function deleteKey(value) { + if (value === 0) { // on release + deleteSelectedEntities(); + } +} +function deselectKey(value) { + if (value === 0) { // on release + selectionManager.clearSelections(); + } +} +function toggleKey(value) { + if (value === 0) { // on release + selectionDisplay.toggleSpaceMode(); + } +} +function focusKey(value) { + if (value === 0) { // on release + cameraManager.enable(); + if (selectionManager.hasSelection()) { + cameraManager.focus(selectionManager.worldPosition, selectionManager.worldDimensions, + Menu.isOptionChecked(MENU_EASE_ON_FOCUS)); + } + } +} +function gridKey(value) { + if (value === 0) { // on release + if (selectionManager.hasSelection()) { + grid.moveToSelection(); + } + } +} +var mapping = Controller.newMapping(CONTROLLER_MAPPING_NAME); +mapping.from([Controller.Hardware.Keyboard.Delete]).when([!Controller.Hardware.Application.PlatformMac]).to(deleteKey); +mapping.from([Controller.Hardware.Keyboard.Backspace]).when([Controller.Hardware.Application.PlatformMac]).to(deleteKey); +mapping.from([Controller.Hardware.Keyboard.D]).when([Controller.Hardware.Keyboard.Control]).to(deselectKey); +mapping.from([Controller.Hardware.Keyboard.T]).to(toggleKey); +mapping.from([Controller.Hardware.Keyboard.F]).to(focusKey); +mapping.from([Controller.Hardware.Keyboard.G]).to(gridKey); + function recursiveAdd(newParentID, parentData) { - var children = parentData.children; - for (var i = 0; i < children.length; i++) { - var childProperties = children[i].properties; - childProperties.parentID = newParentID; - var newChildID = Entities.addEntity(childProperties); - recursiveAdd(newChildID, children[i]); + if (parentData.children !== undefined) { + var children = parentData.children; + for (var i = 0; i < children.length; i++) { + var childProperties = children[i].properties; + childProperties.parentID = newParentID; + var newChildID = Entities.addEntity(childProperties); + recursiveAdd(newChildID, children[i]); + } } } @@ -1893,16 +1931,22 @@ function recursiveAdd(newParentID, parentData) { var DELETED_ENTITY_MAP = {}; function applyEntityProperties(data) { - var properties = data.setProperties; + var editEntities = data.editEntities; var selectedEntityIDs = []; + var selectEdits = data.createEntities.length == 0 || !data.selectCreated; var i, entityID; - for (i = 0; i < properties.length; i++) { - entityID = properties[i].entityID; + for (i = 0; i < editEntities.length; i++) { + var entityID = editEntities[i].entityID; if (DELETED_ENTITY_MAP[entityID] !== undefined) { entityID = DELETED_ENTITY_MAP[entityID]; } - Entities.editEntity(entityID, properties[i].properties); - selectedEntityIDs.push(entityID); + var entityProperties = editEntities[i].properties; + if (entityProperties !== null) { + Entities.editEntity(entityID, entityProperties); + } + if (selectEdits) { + selectedEntityIDs.push(entityID); + } } for (i = 0; i < data.createEntities.length; i++) { entityID = data.createEntities[i].entityID; @@ -1931,31 +1975,39 @@ function applyEntityProperties(data) { // For currently selected entities, push a command to the UndoStack that uses the current entity properties for the // redo command, and the saved properties for the undo command. Also, include create and delete entity data. -function pushCommandForSelections(createdEntityData, deletedEntityData) { +function pushCommandForSelections(createdEntityData, deletedEntityData, doNotSaveEditProperties) { + doNotSaveEditProperties = false; var undoData = { - setProperties: [], + editEntities: [], createEntities: deletedEntityData || [], deleteEntities: createdEntityData || [], selectCreated: true }; var redoData = { - setProperties: [], + editEntities: [], createEntities: createdEntityData || [], deleteEntities: deletedEntityData || [], - selectCreated: false + selectCreated: true }; for (var i = 0; i < SelectionManager.selections.length; i++) { var entityID = SelectionManager.selections[i]; var initialProperties = SelectionManager.savedProperties[entityID]; - var currentProperties = Entities.getEntityProperties(entityID); + var currentProperties = null; if (!initialProperties) { continue; } - undoData.setProperties.push({ + + if (doNotSaveEditProperties) { + initialProperties = null; + } else { + currentProperties = Entities.getEntityProperties(entityID); + } + + undoData.editEntities.push({ entityID: entityID, properties: initialProperties }); - redoData.setProperties.push({ + redoData.editEntities.push({ entityID: entityID, properties: currentProperties }); @@ -2013,10 +2065,16 @@ var PropertiesTool = function (opts) { }; that.setVisible(false); + + function emitScriptEvent(data) { + var dataString = JSON.stringify(data); + webView.emitScriptEvent(dataString); + createToolsWindow.emitScriptEvent(dataString); + } function updateScriptStatus(info) { info.type = "server_script_status"; - webView.emitScriptEvent(JSON.stringify(info)); + emitScriptEvent(info); } function resetScriptStatus() { @@ -2069,8 +2127,7 @@ var PropertiesTool = function (opts) { } data.selections = selections; - webView.emitScriptEvent(JSON.stringify(data)); - createToolsWindow.emitScriptEvent(JSON.stringify(data)); + emitScriptEvent(data); } selectionManager.addEventListener(updateSelections); @@ -2227,7 +2284,7 @@ var PropertiesTool = function (opts) { updateSelections(true); } }; - + createToolsWindow.webEventReceived.addListener(this, onWebEventReceived); webView.webEventReceived.connect(onWebEventReceived); diff --git a/scripts/system/emote.js b/scripts/system/emote.js index d484078b7b..6dfd1ae1ef 100644 --- a/scripts/system/emote.js +++ b/scripts/system/emote.js @@ -46,7 +46,8 @@ var activeTimer = false; // Used to cancel active timer if a user plays an anima var activeEmote = false; // To keep track of the currently playing emote button = tablet.addButton({ - icon: "icons/tablet-icons/EmoteAppIcon.svg", + icon: "icons/tablet-icons/emote-i.svg", + activeIcon: "icons/tablet-icons/emote-a.svg", text: EMOTE_LABEL, sortOrder: EMOTE_APP_SORT_ORDER }); diff --git a/scripts/system/html/entityProperties.html b/scripts/system/html/entityProperties.html index 8d63261f4c..744150253d 100644 --- a/scripts/system/html/entityProperties.html +++ b/scripts/system/html/entityProperties.html @@ -126,8 +126,8 @@

- - + +
@@ -678,6 +678,52 @@
+
+ + Bloom + +
+ Inherit + Off + On +
+
+
+
+
+ + + +
+
+
+
+
+
+
+
+
+ + + +
+
+
+
+
+
+
+
+
+ + + +
+
+
+
+
+
diff --git a/scripts/system/html/js/entityList.js b/scripts/system/html/js/entityList.js index a8c0e22ae6..5cd5f6d610 100644 --- a/scripts/system/html/js/entityList.js +++ b/scripts/system/html/js/entityList.js @@ -28,436 +28,442 @@ debugPrint = function (message) { }; function loaded() { - openEventBridge(function() { - entityList = new List('entity-list', { valueNames: ['name', 'type', 'url', 'locked', 'visible'], page: MAX_ITEMS}); - entityList.clear(); - elEntityTable = document.getElementById("entity-table"); - elEntityTableBody = document.getElementById("entity-table-body"); - elRefresh = document.getElementById("refresh"); - elToggleLocked = document.getElementById("locked"); - elToggleVisible = document.getElementById("visible"); - elDelete = document.getElementById("delete"); - elFilter = document.getElementById("filter"); - elInView = document.getElementById("in-view") - elRadius = document.getElementById("radius"); - elExport = document.getElementById("export"); - elPal = document.getElementById("pal"); - elEntityTable = document.getElementById("entity-table"); - elInfoToggle = document.getElementById("info-toggle"); - elInfoToggleGlyph = elInfoToggle.firstChild; - elFooter = document.getElementById("footer-text"); - elNoEntitiesMessage = document.getElementById("no-entities"); - elNoEntitiesInView = document.getElementById("no-entities-in-view"); - elNoEntitiesRadius = document.getElementById("no-entities-radius"); - elEntityTableScroll = document.getElementById("entity-table-scroll"); + openEventBridge(function() { + entityList = new List('entity-list', { valueNames: ['name', 'type', 'url', 'locked', 'visible'], page: MAX_ITEMS}); + entityList.clear(); + elEntityTable = document.getElementById("entity-table"); + elEntityTableBody = document.getElementById("entity-table-body"); + elRefresh = document.getElementById("refresh"); + elToggleLocked = document.getElementById("locked"); + elToggleVisible = document.getElementById("visible"); + elDelete = document.getElementById("delete"); + elFilter = document.getElementById("filter"); + elInView = document.getElementById("in-view") + elRadius = document.getElementById("radius"); + elExport = document.getElementById("export"); + elPal = document.getElementById("pal"); + elEntityTable = document.getElementById("entity-table"); + elInfoToggle = document.getElementById("info-toggle"); + elInfoToggleGlyph = elInfoToggle.firstChild; + elFooter = document.getElementById("footer-text"); + elNoEntitiesMessage = document.getElementById("no-entities"); + elNoEntitiesInView = document.getElementById("no-entities-in-view"); + elNoEntitiesRadius = document.getElementById("no-entities-radius"); + elEntityTableScroll = document.getElementById("entity-table-scroll"); - document.getElementById("entity-name").onclick = function() { - setSortColumn('name'); - }; - document.getElementById("entity-type").onclick = function() { - setSortColumn('type'); - }; - document.getElementById("entity-url").onclick = function() { - setSortColumn('url'); - }; - document.getElementById("entity-locked").onclick = function () { - setSortColumn('locked'); - }; - document.getElementById("entity-visible").onclick = function () { - setSortColumn('visible'); - }; - document.getElementById("entity-verticesCount").onclick = function () { - setSortColumn('verticesCount'); - }; - document.getElementById("entity-texturesCount").onclick = function () { - setSortColumn('texturesCount'); - }; - document.getElementById("entity-texturesSize").onclick = function () { - setSortColumn('texturesSize'); - }; - document.getElementById("entity-hasTransparent").onclick = function () { - setSortColumn('hasTransparent'); - }; - document.getElementById("entity-isBaked").onclick = function () { - setSortColumn('isBaked'); - }; - document.getElementById("entity-drawCalls").onclick = function () { - setSortColumn('drawCalls'); - }; - document.getElementById("entity-hasScript").onclick = function () { - setSortColumn('hasScript'); - }; + document.getElementById("entity-name").onclick = function() { + setSortColumn('name'); + }; + document.getElementById("entity-type").onclick = function() { + setSortColumn('type'); + }; + document.getElementById("entity-url").onclick = function() { + setSortColumn('url'); + }; + document.getElementById("entity-locked").onclick = function () { + setSortColumn('locked'); + }; + document.getElementById("entity-visible").onclick = function () { + setSortColumn('visible'); + }; + document.getElementById("entity-verticesCount").onclick = function () { + setSortColumn('verticesCount'); + }; + document.getElementById("entity-texturesCount").onclick = function () { + setSortColumn('texturesCount'); + }; + document.getElementById("entity-texturesSize").onclick = function () { + setSortColumn('texturesSize'); + }; + document.getElementById("entity-hasTransparent").onclick = function () { + setSortColumn('hasTransparent'); + }; + document.getElementById("entity-isBaked").onclick = function () { + setSortColumn('isBaked'); + }; + document.getElementById("entity-drawCalls").onclick = function () { + setSortColumn('drawCalls'); + }; + document.getElementById("entity-hasScript").onclick = function () { + setSortColumn('hasScript'); + }; - function onRowClicked(clickEvent) { - var id = this.dataset.entityId; - var selection = [this.dataset.entityId]; - if (clickEvent.ctrlKey) { - selection = selection.concat(selectedEntities); - } else if (clickEvent.shiftKey && selectedEntities.length > 0) { - var previousItemFound = -1; - var clickedItemFound = -1; - for (var entity in entityList.visibleItems) { - if (clickedItemFound === -1 && this.dataset.entityId == entityList.visibleItems[entity].values().id) { - clickedItemFound = entity; - } else if(previousItemFound === -1 && selectedEntities[0] == entityList.visibleItems[entity].values().id) { - previousItemFound = entity; - } - } - if (previousItemFound !== -1 && clickedItemFound !== -1) { - var betweenItems = []; - var toItem = Math.max(previousItemFound, clickedItemFound); - // skip first and last item in this loop, we add them to selection after the loop - for (var i = (Math.min(previousItemFound, clickedItemFound) + 1); i < toItem; i++) { - entityList.visibleItems[i].elm.className = 'selected'; - betweenItems.push(entityList.visibleItems[i].values().id); - } - if (previousItemFound > clickedItemFound) { - // always make sure that we add the items in the right order - betweenItems.reverse(); - } - selection = selection.concat(betweenItems, selectedEntities); - } - } + function onRowClicked(clickEvent) { + var id = this.dataset.entityId; + var selection = [this.dataset.entityId]; + if (clickEvent.ctrlKey) { + var selectedIndex = selectedEntities.indexOf(id); + if (selectedIndex >= 0) { + selection = selectedEntities; + selection.splice(selectedIndex, 1) + } else { + selection = selection.concat(selectedEntities); + } + } else if (clickEvent.shiftKey && selectedEntities.length > 0) { + var previousItemFound = -1; + var clickedItemFound = -1; + for (var entity in entityList.visibleItems) { + if (clickedItemFound === -1 && this.dataset.entityId == entityList.visibleItems[entity].values().id) { + clickedItemFound = entity; + } else if(previousItemFound === -1 && selectedEntities[0] == entityList.visibleItems[entity].values().id) { + previousItemFound = entity; + } + } + if (previousItemFound !== -1 && clickedItemFound !== -1) { + var betweenItems = []; + var toItem = Math.max(previousItemFound, clickedItemFound); + // skip first and last item in this loop, we add them to selection after the loop + for (var i = (Math.min(previousItemFound, clickedItemFound) + 1); i < toItem; i++) { + entityList.visibleItems[i].elm.className = 'selected'; + betweenItems.push(entityList.visibleItems[i].values().id); + } + if (previousItemFound > clickedItemFound) { + // always make sure that we add the items in the right order + betweenItems.reverse(); + } + selection = selection.concat(betweenItems, selectedEntities); + } + } - selectedEntities = selection; + selectedEntities = selection; - this.className = 'selected'; + this.className = 'selected'; - EventBridge.emitWebEvent(JSON.stringify({ - type: "selectionUpdate", - focus: false, - entityIds: selection, - })); + EventBridge.emitWebEvent(JSON.stringify({ + type: "selectionUpdate", + focus: false, + entityIds: selection, + })); - refreshFooter(); - } + refreshFooter(); + } - function onRowDoubleClicked() { - EventBridge.emitWebEvent(JSON.stringify({ - type: "selectionUpdate", - focus: true, - entityIds: [this.dataset.entityId], - })); - } + function onRowDoubleClicked() { + EventBridge.emitWebEvent(JSON.stringify({ + type: "selectionUpdate", + focus: true, + entityIds: [this.dataset.entityId], + })); + } - const BYTES_PER_MEGABYTE = 1024 * 1024; + const BYTES_PER_MEGABYTE = 1024 * 1024; - function decimalMegabytes(number) { - return number ? (number / BYTES_PER_MEGABYTE).toFixed(1) : ""; - } + function decimalMegabytes(number) { + return number ? (number / BYTES_PER_MEGABYTE).toFixed(1) : ""; + } - function displayIfNonZero(number) { - return number ? number : ""; - } + function displayIfNonZero(number) { + return number ? number : ""; + } - function addEntity(id, name, type, url, locked, visible, verticesCount, texturesCount, texturesSize, hasTransparent, - isBaked, drawCalls, hasScript) { + function addEntity(id, name, type, url, locked, visible, verticesCount, texturesCount, texturesSize, hasTransparent, + isBaked, drawCalls, hasScript) { - var urlParts = url.split('/'); - var filename = urlParts[urlParts.length - 1]; + var urlParts = url.split('/'); + var filename = urlParts[urlParts.length - 1]; - var IMAGE_MODEL_NAME = 'default-image-model.fbx'; + var IMAGE_MODEL_NAME = 'default-image-model.fbx'; - if (filename === IMAGE_MODEL_NAME) { - type = "Image"; - } + if (filename === IMAGE_MODEL_NAME) { + type = "Image"; + } - if (entities[id] === undefined) { - entityList.add([{ - id: id, name: name, type: type, url: filename, locked: locked, visible: visible, - verticesCount: displayIfNonZero(verticesCount), texturesCount: displayIfNonZero(texturesCount), - texturesSize: decimalMegabytes(texturesSize), hasTransparent: hasTransparent, - isBaked: isBaked, drawCalls: displayIfNonZero(drawCalls), hasScript: hasScript - }], - function (items) { - var currentElement = items[0].elm; - var id = items[0]._values.id; - entities[id] = { - id: id, - name: name, - el: currentElement, - item: items[0] - }; - currentElement.setAttribute('id', 'entity_' + id); - currentElement.setAttribute('title', url); - currentElement.dataset.entityId = id; - currentElement.onclick = onRowClicked; - currentElement.ondblclick = onRowDoubleClicked; - }); - } else { - var item = entities[id].item; - item.values({ name: name, url: filename, locked: locked, visible: visible }); - } - } + if (entities[id] === undefined) { + entityList.add([{ + id: id, name: name, type: type, url: filename, locked: locked, visible: visible, + verticesCount: displayIfNonZero(verticesCount), texturesCount: displayIfNonZero(texturesCount), + texturesSize: decimalMegabytes(texturesSize), hasTransparent: hasTransparent, + isBaked: isBaked, drawCalls: displayIfNonZero(drawCalls), hasScript: hasScript + }], + function (items) { + var currentElement = items[0].elm; + var id = items[0]._values.id; + entities[id] = { + id: id, + name: name, + el: currentElement, + item: items[0] + }; + currentElement.setAttribute('id', 'entity_' + id); + currentElement.setAttribute('title', url); + currentElement.dataset.entityId = id; + currentElement.onclick = onRowClicked; + currentElement.ondblclick = onRowDoubleClicked; + }); + } else { + var item = entities[id].item; + item.values({ name: name, url: filename, locked: locked, visible: visible }); + } + } - function removeEntities(deletedIDs) { - for (i = 0, length = deletedIDs.length; i < length; i++) { - delete entities[deletedIDs[i]]; - entityList.remove("id", deletedIDs[i]); - } - } + function removeEntities(deletedIDs) { + for (i = 0, length = deletedIDs.length; i < length; i++) { + delete entities[deletedIDs[i]]; + entityList.remove("id", deletedIDs[i]); + } + } - function scheduleRefreshEntityList() { - var REFRESH_DELAY = 50; - if (refreshEntityListTimer) { - clearTimeout(refreshEntityListTimer); - } - refreshEntityListTimer = setTimeout(refreshEntityListObject, REFRESH_DELAY); - } + function scheduleRefreshEntityList() { + var REFRESH_DELAY = 50; + if (refreshEntityListTimer) { + clearTimeout(refreshEntityListTimer); + } + refreshEntityListTimer = setTimeout(refreshEntityListObject, REFRESH_DELAY); + } - function clearEntities() { - entities = {}; - entityList.clear(); - refreshFooter(); - } + function clearEntities() { + entities = {}; + entityList.clear(); + refreshFooter(); + } - var elSortOrder = { - name: document.querySelector('#entity-name .sort-order'), - type: document.querySelector('#entity-type .sort-order'), - url: document.querySelector('#entity-url .sort-order'), - locked: document.querySelector('#entity-locked .sort-order'), - visible: document.querySelector('#entity-visible .sort-order'), - verticesCount: document.querySelector('#entity-verticesCount .sort-order'), - texturesCount: document.querySelector('#entity-texturesCount .sort-order'), - texturesSize: document.querySelector('#entity-texturesSize .sort-order'), - hasTransparent: document.querySelector('#entity-hasTransparent .sort-order'), - isBaked: document.querySelector('#entity-isBaked .sort-order'), - drawCalls: document.querySelector('#entity-drawCalls .sort-order'), - hasScript: document.querySelector('#entity-hasScript .sort-order'), - } - function setSortColumn(column) { - if (currentSortColumn == column) { - currentSortOrder = currentSortOrder == "asc" ? "desc" : "asc"; - } else { - elSortOrder[currentSortColumn].innerHTML = ""; - currentSortColumn = column; - currentSortOrder = "asc"; - } - elSortOrder[column].innerHTML = currentSortOrder == "asc" ? ASCENDING_STRING : DESCENDING_STRING; - entityList.sort(currentSortColumn, { order: currentSortOrder }); - } - setSortColumn('type'); + var elSortOrder = { + name: document.querySelector('#entity-name .sort-order'), + type: document.querySelector('#entity-type .sort-order'), + url: document.querySelector('#entity-url .sort-order'), + locked: document.querySelector('#entity-locked .sort-order'), + visible: document.querySelector('#entity-visible .sort-order'), + verticesCount: document.querySelector('#entity-verticesCount .sort-order'), + texturesCount: document.querySelector('#entity-texturesCount .sort-order'), + texturesSize: document.querySelector('#entity-texturesSize .sort-order'), + hasTransparent: document.querySelector('#entity-hasTransparent .sort-order'), + isBaked: document.querySelector('#entity-isBaked .sort-order'), + drawCalls: document.querySelector('#entity-drawCalls .sort-order'), + hasScript: document.querySelector('#entity-hasScript .sort-order'), + } + function setSortColumn(column) { + if (currentSortColumn == column) { + currentSortOrder = currentSortOrder == "asc" ? "desc" : "asc"; + } else { + elSortOrder[currentSortColumn].innerHTML = ""; + currentSortColumn = column; + currentSortOrder = "asc"; + } + elSortOrder[column].innerHTML = currentSortOrder == "asc" ? ASCENDING_STRING : DESCENDING_STRING; + entityList.sort(currentSortColumn, { order: currentSortOrder }); + } + setSortColumn('type'); - function refreshEntities() { - clearEntities(); - EventBridge.emitWebEvent(JSON.stringify({ type: 'refresh' })); - } + function refreshEntities() { + clearEntities(); + EventBridge.emitWebEvent(JSON.stringify({ type: 'refresh' })); + } - function refreshFooter() { - if (selectedEntities.length > 1) { - elFooter.firstChild.nodeValue = selectedEntities.length + " entities selected"; - } else if (selectedEntities.length === 1) { - elFooter.firstChild.nodeValue = "1 entity selected"; - } else if (entityList.visibleItems.length === 1) { - elFooter.firstChild.nodeValue = "1 entity found"; - } else { - elFooter.firstChild.nodeValue = entityList.visibleItems.length + " entities found"; - } - } + function refreshFooter() { + if (selectedEntities.length > 1) { + elFooter.firstChild.nodeValue = selectedEntities.length + " entities selected"; + } else if (selectedEntities.length === 1) { + elFooter.firstChild.nodeValue = "1 entity selected"; + } else if (entityList.visibleItems.length === 1) { + elFooter.firstChild.nodeValue = "1 entity found"; + } else { + elFooter.firstChild.nodeValue = entityList.visibleItems.length + " entities found"; + } + } - function refreshEntityListObject() { - refreshEntityListTimer = null; - entityList.sort(currentSortColumn, { order: currentSortOrder }); - entityList.search(elFilter.value); - refreshFooter(); - } + function refreshEntityListObject() { + refreshEntityListTimer = null; + entityList.sort(currentSortColumn, { order: currentSortOrder }); + entityList.search(elFilter.value); + refreshFooter(); + } - function updateSelectedEntities(selectedIDs) { - var notFound = false; - for (var id in entities) { - entities[id].el.className = ''; - } + function updateSelectedEntities(selectedIDs) { + var notFound = false; + for (var id in entities) { + entities[id].el.className = ''; + } - selectedEntities = []; - for (var i = 0; i < selectedIDs.length; i++) { - var id = selectedIDs[i]; - selectedEntities.push(id); - if (id in entities) { - var entity = entities[id]; - entity.el.className = 'selected'; - } else { - notFound = true; - } - } + selectedEntities = []; + for (var i = 0; i < selectedIDs.length; i++) { + var id = selectedIDs[i]; + selectedEntities.push(id); + if (id in entities) { + var entity = entities[id]; + entity.el.className = 'selected'; + } else { + notFound = true; + } + } - refreshFooter(); + refreshFooter(); - return notFound; - } + return notFound; + } - elRefresh.onclick = function() { - refreshEntities(); - } - elToggleLocked.onclick = function () { - EventBridge.emitWebEvent(JSON.stringify({ type: 'toggleLocked' })); - } - elToggleVisible.onclick = function () { - EventBridge.emitWebEvent(JSON.stringify({ type: 'toggleVisible' })); - } - elExport.onclick = function() { - EventBridge.emitWebEvent(JSON.stringify({ type: 'export'})); - } - elPal.onclick = function () { - EventBridge.emitWebEvent(JSON.stringify({ type: 'pal' })); - } - elDelete.onclick = function() { - EventBridge.emitWebEvent(JSON.stringify({ type: 'delete' })); - } + elRefresh.onclick = function() { + refreshEntities(); + } + elToggleLocked.onclick = function () { + EventBridge.emitWebEvent(JSON.stringify({ type: 'toggleLocked' })); + } + elToggleVisible.onclick = function () { + EventBridge.emitWebEvent(JSON.stringify({ type: 'toggleVisible' })); + } + elExport.onclick = function() { + EventBridge.emitWebEvent(JSON.stringify({ type: 'export'})); + } + elPal.onclick = function () { + EventBridge.emitWebEvent(JSON.stringify({ type: 'pal' })); + } + elDelete.onclick = function() { + EventBridge.emitWebEvent(JSON.stringify({ type: 'delete' })); + } - document.addEventListener("keydown", function (keyDownEvent) { - if (keyDownEvent.target.nodeName === "INPUT") { - return; - } - var keyCode = keyDownEvent.keyCode; - if (keyCode === DELETE) { - EventBridge.emitWebEvent(JSON.stringify({ type: 'delete' })); - refreshEntities(); - } - if (keyDownEvent.keyCode === KEY_P && keyDownEvent.ctrlKey) { - if (keyDownEvent.shiftKey) { - EventBridge.emitWebEvent(JSON.stringify({ type: 'unparent' })); - } else { - EventBridge.emitWebEvent(JSON.stringify({ type: 'parent' })); - } - } - }, false); + document.addEventListener("keydown", function (keyDownEvent) { + if (keyDownEvent.target.nodeName === "INPUT") { + return; + } + var keyCode = keyDownEvent.keyCode; + if (keyCode === DELETE) { + EventBridge.emitWebEvent(JSON.stringify({ type: 'delete' })); + refreshEntities(); + } + if (keyDownEvent.keyCode === KEY_P && keyDownEvent.ctrlKey) { + if (keyDownEvent.shiftKey) { + EventBridge.emitWebEvent(JSON.stringify({ type: 'unparent' })); + } else { + EventBridge.emitWebEvent(JSON.stringify({ type: 'parent' })); + } + } + }, false); - var isFilterInView = false; - var FILTER_IN_VIEW_ATTRIBUTE = "pressed"; - elNoEntitiesInView.style.display = "none"; - elInView.onclick = function () { - isFilterInView = !isFilterInView; - if (isFilterInView) { - elInView.setAttribute(FILTER_IN_VIEW_ATTRIBUTE, FILTER_IN_VIEW_ATTRIBUTE); - elNoEntitiesInView.style.display = "inline"; - } else { - elInView.removeAttribute(FILTER_IN_VIEW_ATTRIBUTE); - elNoEntitiesInView.style.display = "none"; - } - EventBridge.emitWebEvent(JSON.stringify({ type: "filterInView", filterInView: isFilterInView })); - refreshEntities(); - } + var isFilterInView = false; + var FILTER_IN_VIEW_ATTRIBUTE = "pressed"; + elNoEntitiesInView.style.display = "none"; + elInView.onclick = function () { + isFilterInView = !isFilterInView; + if (isFilterInView) { + elInView.setAttribute(FILTER_IN_VIEW_ATTRIBUTE, FILTER_IN_VIEW_ATTRIBUTE); + elNoEntitiesInView.style.display = "inline"; + } else { + elInView.removeAttribute(FILTER_IN_VIEW_ATTRIBUTE); + elNoEntitiesInView.style.display = "none"; + } + EventBridge.emitWebEvent(JSON.stringify({ type: "filterInView", filterInView: isFilterInView })); + refreshEntities(); + } - elRadius.onchange = function () { - elRadius.value = Math.max(elRadius.value, 0); - EventBridge.emitWebEvent(JSON.stringify({ type: 'radius', radius: elRadius.value })); - refreshEntities(); - elNoEntitiesRadius.firstChild.nodeValue = elRadius.value; - } + elRadius.onchange = function () { + elRadius.value = Math.max(elRadius.value, 0); + EventBridge.emitWebEvent(JSON.stringify({ type: 'radius', radius: elRadius.value })); + refreshEntities(); + elNoEntitiesRadius.firstChild.nodeValue = elRadius.value; + } - if (window.EventBridge !== undefined) { - EventBridge.scriptEventReceived.connect(function(data) { - data = JSON.parse(data); + if (window.EventBridge !== undefined) { + EventBridge.scriptEventReceived.connect(function(data) { + data = JSON.parse(data); - if (data.type === "clearEntityList") { - clearEntities(); - } else if (data.type == "selectionUpdate") { - var notFound = updateSelectedEntities(data.selectedIDs); - if (notFound) { - refreshEntities(); - } - } else if (data.type === "update" && data.selectedIDs !== undefined) { - var newEntities = data.entities; - if (newEntities && newEntities.length == 0) { - elNoEntitiesMessage.style.display = "block"; - elFooter.firstChild.nodeValue = "0 entities found"; - } else if (newEntities) { - elNoEntitiesMessage.style.display = "none"; - for (var i = 0; i < newEntities.length; i++) { - var id = newEntities[i].id; - addEntity(id, newEntities[i].name, newEntities[i].type, newEntities[i].url, - newEntities[i].locked ? LOCKED_GLYPH : null, - newEntities[i].visible ? VISIBLE_GLYPH : null, - newEntities[i].verticesCount, newEntities[i].texturesCount, newEntities[i].texturesSize, - newEntities[i].hasTransparent ? TRANSPARENCY_GLYPH : null, - newEntities[i].isBaked ? BAKED_GLYPH : null, - newEntities[i].drawCalls, - newEntities[i].hasScript ? SCRIPT_GLYPH : null); - } - updateSelectedEntities(data.selectedIDs); - scheduleRefreshEntityList(); - resize(); - } - } else if (data.type === "removeEntities" && data.deletedIDs !== undefined && data.selectedIDs !== undefined) { - removeEntities(data.deletedIDs); - updateSelectedEntities(data.selectedIDs); - scheduleRefreshEntityList(); - } else if (data.type === "deleted" && data.ids) { - removeEntities(data.ids); - refreshFooter(); - } - }); - setTimeout(refreshEntities, 1000); - } + if (data.type === "clearEntityList") { + clearEntities(); + } else if (data.type == "selectionUpdate") { + var notFound = updateSelectedEntities(data.selectedIDs); + if (notFound) { + refreshEntities(); + } + } else if (data.type === "update" && data.selectedIDs !== undefined) { + var newEntities = data.entities; + if (newEntities && newEntities.length == 0) { + elNoEntitiesMessage.style.display = "block"; + elFooter.firstChild.nodeValue = "0 entities found"; + } else if (newEntities) { + elNoEntitiesMessage.style.display = "none"; + for (var i = 0; i < newEntities.length; i++) { + var id = newEntities[i].id; + addEntity(id, newEntities[i].name, newEntities[i].type, newEntities[i].url, + newEntities[i].locked ? LOCKED_GLYPH : null, + newEntities[i].visible ? VISIBLE_GLYPH : null, + newEntities[i].verticesCount, newEntities[i].texturesCount, newEntities[i].texturesSize, + newEntities[i].hasTransparent ? TRANSPARENCY_GLYPH : null, + newEntities[i].isBaked ? BAKED_GLYPH : null, + newEntities[i].drawCalls, + newEntities[i].hasScript ? SCRIPT_GLYPH : null); + } + updateSelectedEntities(data.selectedIDs); + scheduleRefreshEntityList(); + resize(); + } + } else if (data.type === "removeEntities" && data.deletedIDs !== undefined && data.selectedIDs !== undefined) { + removeEntities(data.deletedIDs); + updateSelectedEntities(data.selectedIDs); + scheduleRefreshEntityList(); + } else if (data.type === "deleted" && data.ids) { + removeEntities(data.ids); + refreshFooter(); + } + }); + setTimeout(refreshEntities, 1000); + } - function resize() { - // Take up available window space - elEntityTableScroll.style.height = window.innerHeight - 207; + function resize() { + // Take up available window space + elEntityTableScroll.style.height = window.innerHeight - 207; - var SCROLLABAR_WIDTH = 21; - var tds = document.querySelectorAll("#entity-table-body tr:first-child td"); - var ths = document.querySelectorAll("#entity-table thead th"); - if (tds.length >= ths.length) { - // Update the widths of the header cells to match the body - for (var i = 0; i < ths.length; i++) { - ths[i].width = tds[i].offsetWidth; - } - } else { - // Reasonable widths if nothing is displayed - var tableWidth = document.getElementById("entity-table").offsetWidth - SCROLLABAR_WIDTH; - if (showExtraInfo) { - ths[0].width = 0.10 * tableWidth; - ths[1].width = 0.20 * tableWidth; - ths[2].width = 0.20 * tableWidth; - ths[3].width = 0.04 * tableWidth; - ths[4].width = 0.04 * tableWidth; - ths[5].width = 0.08 * tableWidth; - ths[6].width = 0.08 * tableWidth; - ths[7].width = 0.10 * tableWidth; - ths[8].width = 0.04 * tableWidth; - ths[9].width = 0.08 * tableWidth; - ths[10].width = 0.04 * tableWidth + SCROLLABAR_WIDTH; - } else { - ths[0].width = 0.16 * tableWidth; - ths[1].width = 0.34 * tableWidth; - ths[2].width = 0.34 * tableWidth; - ths[3].width = 0.08 * tableWidth; - ths[4].width = 0.08 * tableWidth; - } - } - }; + var SCROLLABAR_WIDTH = 21; + var tds = document.querySelectorAll("#entity-table-body tr:first-child td"); + var ths = document.querySelectorAll("#entity-table thead th"); + if (tds.length >= ths.length) { + // Update the widths of the header cells to match the body + for (var i = 0; i < ths.length; i++) { + ths[i].width = tds[i].offsetWidth; + } + } else { + // Reasonable widths if nothing is displayed + var tableWidth = document.getElementById("entity-table").offsetWidth - SCROLLABAR_WIDTH; + if (showExtraInfo) { + ths[0].width = 0.10 * tableWidth; + ths[1].width = 0.20 * tableWidth; + ths[2].width = 0.20 * tableWidth; + ths[3].width = 0.04 * tableWidth; + ths[4].width = 0.04 * tableWidth; + ths[5].width = 0.08 * tableWidth; + ths[6].width = 0.08 * tableWidth; + ths[7].width = 0.10 * tableWidth; + ths[8].width = 0.04 * tableWidth; + ths[9].width = 0.08 * tableWidth; + ths[10].width = 0.04 * tableWidth + SCROLLABAR_WIDTH; + } else { + ths[0].width = 0.16 * tableWidth; + ths[1].width = 0.34 * tableWidth; + ths[2].width = 0.34 * tableWidth; + ths[3].width = 0.08 * tableWidth; + ths[4].width = 0.08 * tableWidth; + } + } + }; - window.onresize = resize; - elFilter.onchange = resize; - elFilter.onblur = refreshFooter; + window.onresize = resize; + elFilter.onchange = resize; + elFilter.onblur = refreshFooter; - var showExtraInfo = false; - var COLLAPSE_EXTRA_INFO = "E"; - var EXPAND_EXTRA_INFO = "D"; + var showExtraInfo = false; + var COLLAPSE_EXTRA_INFO = "E"; + var EXPAND_EXTRA_INFO = "D"; - function toggleInfo(event) { - showExtraInfo = !showExtraInfo; - if (showExtraInfo) { - elEntityTable.className = "showExtraInfo"; - elInfoToggleGlyph.innerHTML = COLLAPSE_EXTRA_INFO; - } else { - elEntityTable.className = ""; - elInfoToggleGlyph.innerHTML = EXPAND_EXTRA_INFO; - } - resize(); - event.stopPropagation(); - } - elInfoToggle.addEventListener("click", toggleInfo, true); + function toggleInfo(event) { + showExtraInfo = !showExtraInfo; + if (showExtraInfo) { + elEntityTable.className = "showExtraInfo"; + elInfoToggleGlyph.innerHTML = COLLAPSE_EXTRA_INFO; + } else { + elEntityTable.className = ""; + elInfoToggleGlyph.innerHTML = EXPAND_EXTRA_INFO; + } + resize(); + event.stopPropagation(); + } + elInfoToggle.addEventListener("click", toggleInfo, true); - resize(); - }); + resize(); + }); - augmentSpinButtons(); + augmentSpinButtons(); - // Disable right-click context menu which is not visible in the HMD and makes it seem like the app has locked - document.addEventListener("contextmenu", function (event) { - event.preventDefault(); - }, false); + // Disable right-click context menu which is not visible in the HMD and makes it seem like the app has locked + document.addEventListener("contextmenu", function (event) { + event.preventDefault(); + }, false); } diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index a6a781b35f..de9027586e 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -36,6 +36,8 @@ var lastEntityID = null; var MATERIAL_PREFIX_STRING = "mat::"; +var PENDING_SCRIPT_STATUS = "[ Fetching status ]"; + function debugPrint(message) { EventBridge.emitWebEvent( JSON.stringify({ @@ -308,9 +310,10 @@ function setUserDataFromEditor(noUpdate) { } } -function multiDataUpdater(groupName, updateKeyPair, userDataElement, defaults) { +function multiDataUpdater(groupName, updateKeyPair, userDataElement, defaults, removeKeys) { var properties = {}; var parsedData = {}; + var keysToBeRemoved = removeKeys ? removeKeys : []; try { if ($('#userdata-editor').css('height') !== "0px") { // if there is an expanded, we want to use its json. @@ -342,6 +345,12 @@ function multiDataUpdater(groupName, updateKeyPair, userDataElement, defaults) { parsedData[groupName][key] = defaults[key]; } }); + keysToBeRemoved.forEach(function(key) { + if (parsedData[groupName].hasOwnProperty(key)) { + delete parsedData[groupName][key]; + } + }); + if (Object.keys(parsedData[groupName]).length === 0) { delete parsedData[groupName]; } @@ -355,11 +364,11 @@ function multiDataUpdater(groupName, updateKeyPair, userDataElement, defaults) { updateProperties(properties); } -function userDataChanger(groupName, keyName, values, userDataElement, defaultValue) { +function userDataChanger(groupName, keyName, values, userDataElement, defaultValue, removeKeys) { var val = {}, def = {}; val[keyName] = values; def[keyName] = defaultValue; - multiDataUpdater(groupName, val, userDataElement, def); + multiDataUpdater(groupName, val, userDataElement, def, removeKeys); } function setMaterialDataFromEditor(noUpdate) { @@ -711,7 +720,7 @@ function loaded() { var elCloneableLifetime = document.getElementById("property-cloneable-lifetime"); var elCloneableLimit = document.getElementById("property-cloneable-limit"); - var elWantsTrigger = document.getElementById("property-wants-trigger"); + var elTriggerable = document.getElementById("property-triggerable"); var elIgnoreIK = document.getElementById("property-ignore-ik"); var elLifetime = document.getElementById("property-lifetime"); @@ -793,6 +802,7 @@ function loaded() { var elTextTextColorRed = document.getElementById("property-text-text-color-red"); var elTextTextColorGreen = document.getElementById("property-text-text-color-green"); var elTextTextColorBlue = document.getElementById("property-text-text-color-blue"); + var elTextBackgroundColor = document.getElementById("property-text-background-color"); var elTextBackgroundColorRed = document.getElementById("property-text-background-color-red"); var elTextBackgroundColorGreen = document.getElementById("property-text-background-color-green"); var elTextBackgroundColorBlue = document.getElementById("property-text-background-color-blue"); @@ -831,7 +841,7 @@ function loaded() { var elZoneHazeModeInherit = document.getElementById("property-zone-haze-mode-inherit"); var elZoneHazeModeDisabled = document.getElementById("property-zone-haze-mode-disabled"); var elZoneHazeModeEnabled = document.getElementById("property-zone-haze-mode-enabled"); - + var elZoneHazeRange = document.getElementById("property-zone-haze-range"); var elZoneHazeColor = document.getElementById("property-zone-haze-color"); var elZoneHazeColorRed = document.getElementById("property-zone-haze-color-red"); @@ -842,7 +852,7 @@ function loaded() { var elZoneHazeGlareColorGreen = document.getElementById("property-zone-haze-glare-color-green"); var elZoneHazeGlareColorBlue = document.getElementById("property-zone-haze-glare-color-blue"); var elZoneHazeEnableGlare = document.getElementById("property-zone-haze-enable-light-blend"); - var elZonehazeGlareAngle = document.getElementById("property-zone-haze-blend-angle"); + var elZoneHazeGlareAngle = document.getElementById("property-zone-haze-blend-angle"); var elZoneHazeAltitudeEffect = document.getElementById("property-zone-haze-altitude-effect"); var elZoneHazeBaseRef = document.getElementById("property-zone-haze-base"); @@ -850,6 +860,15 @@ function loaded() { var elZoneHazeBackgroundBlend = document.getElementById("property-zone-haze-background-blend"); + // Bloom + var elZoneBloomModeInherit = document.getElementById("property-zone-bloom-mode-inherit"); + var elZoneBloomModeDisabled = document.getElementById("property-zone-bloom-mode-disabled"); + var elZoneBloomModeEnabled = document.getElementById("property-zone-bloom-mode-enabled"); + + var elZoneBloomIntensity = document.getElementById("property-zone-bloom-intensity"); + var elZoneBloomThreshold = document.getElementById("property-zone-bloom-threshold"); + var elZoneBloomSize = document.getElementById("property-zone-bloom-size"); + var elZoneSkyboxColor = document.getElementById("property-zone-skybox-color"); var elZoneSkyboxColorRed = document.getElementById("property-zone-skybox-color-red"); var elZoneSkyboxColorGreen = document.getElementById("property-zone-skybox-color-green"); @@ -906,10 +925,210 @@ function loaded() { deleteJSONMaterialEditor(); } } + elTypeIcon.style.display = "none"; elType.innerHTML = "No selection"; - elID.value = ""; elPropertiesList.className = ''; + + elID.value = ""; + elName.value = ""; + elLocked.checked = false; + elVisible.checked = false; + + elParentID.value = ""; + elParentJointIndex.value = ""; + + elColorRed.value = ""; + elColorGreen.value = ""; + elColorBlue.value = ""; + elColorControlVariant2.style.backgroundColor = "rgb(" + 0 + "," + 0 + "," + 0 + ")"; + + elPositionX.value = ""; + elPositionY.value = ""; + elPositionZ.value = ""; + + elRotationX.value = ""; + elRotationY.value = ""; + elRotationZ.value = ""; + + elDimensionsX.value = ""; + elDimensionsY.value = ""; + elDimensionsZ.value = ""; + + elRegistrationX.value = ""; + elRegistrationY.value = ""; + elRegistrationZ.value = ""; + + elLinearVelocityX.value = ""; + elLinearVelocityY.value = ""; + elLinearVelocityZ.value = ""; + elLinearDamping.value = ""; + + elAngularVelocityX.value = ""; + elAngularVelocityY.value = ""; + elAngularVelocityZ.value = ""; + elAngularDamping.value = ""; + + elGravityX.value = ""; + elGravityY.value = ""; + elGravityZ.value = ""; + + elAccelerationX.value = ""; + elAccelerationY.value = ""; + elAccelerationZ.value = ""; + + elRestitution.value = ""; + elFriction.value = ""; + elDensity.value = ""; + + elCollisionless.checked = false; + elDynamic.checked = false; + + elCollideStatic.checked = false; + elCollideKinematic.checked = false; + elCollideDynamic.checked = false; + elCollideMyAvatar.checked = false; + elCollideOtherAvatar.checked = false; + + elGrabbable.checked = false; + elTriggerable.checked = false; + elIgnoreIK.checked = false; + + elCloneable.checked = false; + elCloneableDynamic.checked = false; + elCloneableAvatarEntity.checked = false; + elCloneableGroup.style.display = "none"; + elCloneableLimit.value = ""; + elCloneableLifetime.value = ""; + + showElements(document.getElementsByClassName('can-cast-shadow-section'), true); + elCanCastShadow.checked = false; + + elCollisionSoundURL.value = ""; + elLifetime.value = ""; + elScriptURL.value = ""; + elServerScripts.value = ""; + elHyperlinkHref.value = ""; + elDescription.value = ""; + + deleteJSONEditor(); + elUserData.value = ""; + showUserDataTextArea(); + showSaveUserDataButton(); + showNewJSONEditorButton(); + + // Shape Properties + elShape.value = "Cube"; + setDropdownText(elShape); + + // Light Properties + elLightSpotLight.checked = false; + elLightColor.style.backgroundColor = "rgb(" + 0 + "," + 0 + "," + 0 + ")"; + elLightColorRed.value = ""; + elLightColorGreen.value = ""; + elLightColorBlue.value = ""; + elLightIntensity.value = ""; + elLightFalloffRadius.value = ""; + elLightExponent.value = ""; + elLightCutoff.value = ""; + + // Model Properties + elModelURL.value = ""; + elCompoundShapeURL.value = ""; + elShapeType.value = "none"; + setDropdownText(elShapeType); + elModelAnimationURL.value = "" + elModelAnimationPlaying.checked = false; + elModelAnimationFPS.value = ""; + elModelAnimationFrame.value = ""; + elModelAnimationFirstFrame.value = ""; + elModelAnimationLastFrame.value = ""; + elModelAnimationLoop.checked = false; + elModelAnimationHold.checked = false; + elModelAnimationAllowTranslation.checked = false; + elModelTextures.value = ""; + elModelOriginalTextures.value = ""; + + // Zone Properties + elZoneFlyingAllowed.checked = false; + elZoneGhostingAllowed.checked = false; + elZoneFilterURL.value = ""; + elZoneKeyLightColor.style.backgroundColor = "rgb(" + 0 + "," + 0 + "," + 0 + ")"; + elZoneKeyLightColorRed.value = ""; + elZoneKeyLightColorGreen.value = ""; + elZoneKeyLightColorBlue.value = ""; + elZoneKeyLightIntensity.value = ""; + elZoneKeyLightDirectionX.value = ""; + elZoneKeyLightDirectionY.value = ""; + elZoneKeyLightCastShadows.checked = false; + elZoneAmbientLightIntensity.value = ""; + elZoneAmbientLightURL.value = ""; + elZoneHazeRange.value = ""; + elZoneHazeColor.style.backgroundColor = "rgb(" + 0 + "," + 0 + "," + 0 + ")"; + elZoneHazeColorRed.value = ""; + elZoneHazeColorGreen.value = ""; + elZoneHazeColorBlue.value = ""; + elZoneHazeBackgroundBlend.value = 0; + elZoneHazeGlareColor.style.backgroundColor = "rgb(" + 0 + "," + 0 + "," + 0 + ")"; + elZoneHazeGlareColorRed.value = ""; + elZoneHazeGlareColorGreen.value = ""; + elZoneHazeGlareColorBlue.value = ""; + elZoneHazeEnableGlare.checked = false; + elZoneHazeGlareAngle.value = ""; + elZoneHazeAltitudeEffect.checked = false; + elZoneHazeBaseRef.value = ""; + elZoneHazeCeiling.value = ""; + elZoneBloomIntensity.value = ""; + elZoneBloomThreshold.value = ""; + elZoneBloomSize.value = ""; + elZoneSkyboxColor.style.backgroundColor = "rgb(" + 0 + "," + 0 + "," + 0 + ")"; + elZoneSkyboxColorRed.value = ""; + elZoneSkyboxColorGreen.value = ""; + elZoneSkyboxColorBlue.value = ""; + elZoneSkyboxURL.value = ""; + showElements(document.getElementsByClassName('keylight-section'), true); + showElements(document.getElementsByClassName('skybox-section'), true); + showElements(document.getElementsByClassName('ambient-section'), true); + showElements(document.getElementsByClassName('haze-section'), true); + showElements(document.getElementsByClassName('bloom-section'), true); + + // Text Properties + elTextText.value = ""; + elTextLineHeight.value = ""; + elTextFaceCamera.checked = false; + elTextTextColor.style.backgroundColor = "rgb(" + 0 + "," + 0 + "," + 0 + ")"; + elTextTextColorRed.value = ""; + elTextTextColorGreen.value = ""; + elTextTextColorBlue.value = ""; + elTextBackgroundColor.style.backgroundColor = "rgb(" + 0 + "," + 0 + "," + 0 + ")"; + elTextBackgroundColorRed.value = ""; + elTextBackgroundColorGreen.value = ""; + elTextBackgroundColorBlue.value = ""; + + // Image Properties + elImageURL.value = ""; + + // Web Properties + elWebSourceURL.value = ""; + elWebDPI.value = ""; + + // Material Properties + elMaterialURL.value = ""; + elParentMaterialNameNumber.value = ""; + elParentMaterialNameCheckbox.checked = false; + elPriority.value = ""; + elMaterialMappingPosX.value = ""; + elMaterialMappingPosY.value = ""; + elMaterialMappingScaleX.value = ""; + elMaterialMappingScaleY.value = ""; + elMaterialMappingRot.value = ""; + + deleteJSONMaterialEditor(); + elMaterialData.value = ""; + showMaterialDataTextArea(); + showSaveMaterialDataButton(); + showNewJSONMaterialEditorButton(); + disableProperties(); } else if (data.selections.length > 1) { deleteJSONEditor(); @@ -1037,7 +1256,7 @@ function loaded() { elGrabbable.checked = properties.dynamic; - elWantsTrigger.checked = false; + elTriggerable.checked = false; elIgnoreIK.checked = true; elCloneable.checked = properties.cloneable; @@ -1046,7 +1265,7 @@ function loaded() { elCloneableGroup.style.display = elCloneable.checked ? "block": "none"; elCloneableLimit.value = properties.cloneLimit; elCloneableLifetime.value = properties.cloneLifetime; - + var grabbablesSet = false; var parsedUserData = {}; try { @@ -1060,10 +1279,12 @@ function loaded() { } else { elGrabbable.checked = true; } - if ("wantsTrigger" in grabbableData) { - elWantsTrigger.checked = grabbableData.wantsTrigger; + if ("triggerable" in grabbableData) { + elTriggerable.checked = grabbableData.triggerable; + } else if ("wantsTrigger" in grabbableData) { + elTriggerable.checked = grabbableData.wantsTrigger; } else { - elWantsTrigger.checked = false; + elTriggerable.checked = false; } if ("ignoreIK" in grabbableData) { elIgnoreIK.checked = grabbableData.ignoreIK; @@ -1076,7 +1297,7 @@ function loaded() { } if (!grabbablesSet) { elGrabbable.checked = true; - elWantsTrigger.checked = false; + elTriggerable.checked = false; elIgnoreIK.checked = true; elCloneable.checked = false; } @@ -1184,10 +1405,14 @@ function loaded() { elTextLineHeight.value = properties.lineHeight.toFixed(4); elTextFaceCamera.checked = properties.faceCamera; elTextTextColor.style.backgroundColor = "rgb(" + properties.textColor.red + "," + - properties.textColor.green + "," + properties.textColor.blue + ")"; + properties.textColor.green + "," + + properties.textColor.blue + ")"; elTextTextColorRed.value = properties.textColor.red; elTextTextColorGreen.value = properties.textColor.green; elTextTextColorBlue.value = properties.textColor.blue; + elTextBackgroundColor.style.backgroundColor = "rgb(" + properties.backgroundColor.red + "," + + properties.backgroundColor.green + "," + + properties.backgroundColor.blue + ")"; elTextBackgroundColorRed.value = properties.backgroundColor.red; elTextBackgroundColorGreen.value = properties.backgroundColor.green; elTextBackgroundColorBlue.value = properties.backgroundColor.blue; @@ -1260,13 +1485,20 @@ function loaded() { elZoneHazeGlareColorBlue.value = properties.haze.hazeGlareColor.blue; elZoneHazeEnableGlare.checked = properties.haze.hazeEnableGlare; - elZonehazeGlareAngle.value = properties.haze.hazeGlareAngle.toFixed(0); + elZoneHazeGlareAngle.value = properties.haze.hazeGlareAngle.toFixed(0); elZoneHazeAltitudeEffect.checked = properties.haze.hazeAltitudeEffect; elZoneHazeBaseRef.value = properties.haze.hazeBaseRef.toFixed(0); elZoneHazeCeiling.value = properties.haze.hazeCeiling.toFixed(0); - elZoneHazeBackgroundBlend.value = properties.haze.hazeBackgroundBlend.toFixed(2); + elZoneBloomModeInherit.checked = (properties.bloomMode === 'inherit'); + elZoneBloomModeDisabled.checked = (properties.bloomMode === 'disabled'); + elZoneBloomModeEnabled.checked = (properties.bloomMode === 'enabled'); + + elZoneBloomIntensity.value = properties.bloom.bloomIntensity.toFixed(2); + elZoneBloomThreshold.value = properties.bloom.bloomThreshold.toFixed(2); + elZoneBloomSize.value = properties.bloom.bloomSize.toFixed(2); + elShapeType.value = properties.shapeType; elCompoundShapeURL.value = properties.compoundShapeURL; @@ -1293,6 +1525,9 @@ function loaded() { showElements(document.getElementsByClassName('haze-section'), elZoneHazeModeEnabled.checked); + + showElements(document.getElementsByClassName('bloom-section'), + elZoneBloomModeEnabled.checked); } else if (properties.type === "PolyVox") { elVoxelVolumeSizeX.value = properties.voxelVolumeSize.x.toFixed(2); elVoxelVolumeSizeY.value = properties.voxelVolumeSize.y.toFixed(2); @@ -1447,8 +1682,8 @@ function loaded() { elCloneableLifetime.addEventListener('change', createEmitNumberPropertyUpdateFunction('cloneLifetime')); elCloneableLimit.addEventListener('change', createEmitNumberPropertyUpdateFunction('cloneLimit')); - elWantsTrigger.addEventListener('change', function() { - userDataChanger("grabbableKey", "wantsTrigger", elWantsTrigger, elUserData, false); + elTriggerable.addEventListener('change', function() { + userDataChanger("grabbableKey", "triggerable", elTriggerable, elUserData, false, ['wantsTrigger']); }); elIgnoreIK.addEventListener('change', function() { userDataChanger("grabbableKey", "ignoreIK", elIgnoreIK, elUserData, true); @@ -1462,7 +1697,7 @@ function loaded() { elServerScripts.addEventListener('change', createEmitTextPropertyUpdateFunction('serverScripts')); elServerScripts.addEventListener('change', function() { // invalidate the current status (so that same-same updates can still be observed visually) - elServerScriptStatus.innerText = '[' + elServerScriptStatus.innerText + ']'; + elServerScriptStatus.innerText = PENDING_SCRIPT_STATUS; }); elClearUserData.addEventListener("click", function() { @@ -1848,7 +2083,7 @@ function loaded() { elZoneHazeEnableGlare.addEventListener('change', createEmitGroupCheckedPropertyUpdateFunction('haze', 'hazeEnableGlare')); - elZonehazeGlareAngle.addEventListener('change', createEmitGroupNumberPropertyUpdateFunction('haze', 'hazeGlareAngle')); + elZoneHazeGlareAngle.addEventListener('change', createEmitGroupNumberPropertyUpdateFunction('haze', 'hazeGlareAngle')); elZoneHazeAltitudeEffect.addEventListener('change', createEmitGroupCheckedPropertyUpdateFunction('haze', 'hazeAltitudeEffect')); @@ -1858,6 +2093,18 @@ function loaded() { elZoneHazeBackgroundBlend.addEventListener('change', createEmitGroupNumberPropertyUpdateFunction('haze', 'hazeBackgroundBlend')); + // Bloom + var bloomModeChanged = createZoneComponentModeChangedFunction('bloomMode', + elZoneBloomModeInherit, elZoneBloomModeDisabled, elZoneBloomModeEnabled); + + elZoneBloomModeInherit.addEventListener('change', bloomModeChanged); + elZoneBloomModeDisabled.addEventListener('change', bloomModeChanged); + elZoneBloomModeEnabled.addEventListener('change', bloomModeChanged); + + elZoneBloomIntensity.addEventListener('change', createEmitGroupNumberPropertyUpdateFunction('bloom', 'bloomIntensity')); + elZoneBloomThreshold.addEventListener('change', createEmitGroupNumberPropertyUpdateFunction('bloom', 'bloomThreshold')); + elZoneBloomSize.addEventListener('change', createEmitGroupNumberPropertyUpdateFunction('bloom', 'bloomSize')); + var zoneSkyboxColorChangeFunction = createEmitGroupColorPropertyUpdateFunction('skybox', 'color', elZoneSkyboxColorRed, elZoneSkyboxColorGreen, elZoneSkyboxColorBlue); elZoneSkyboxColorRed.addEventListener('change', zoneSkyboxColorChangeFunction); @@ -1936,7 +2183,7 @@ function loaded() { }); elReloadServerScriptsButton.addEventListener("click", function() { // invalidate the current status (so that same-same updates can still be observed visually) - elServerScriptStatus.innerText = '[' + elServerScriptStatus.innerText + ']'; + elServerScriptStatus.innerText = PENDING_SCRIPT_STATUS; EventBridge.emitWebEvent(JSON.stringify({ type: "action", action: "reloadServerScripts" diff --git a/scripts/system/libraries/WebTablet.js b/scripts/system/libraries/WebTablet.js index 532f58493f..c201a251d0 100644 --- a/scripts/system/libraries/WebTablet.js +++ b/scripts/system/libraries/WebTablet.js @@ -30,6 +30,8 @@ var INCHES_TO_METERS = 1 / 39.3701; var NO_HANDS = -1; var DELAY_FOR_30HZ = 33; // milliseconds +var TABLET_MATERIAL_ENTITY_NAME = 'Tablet-Material-Entity'; + // will need to be recaclulated if dimensions of fbx model change. var TABLET_NATURAL_DIMENSIONS = {x: 32.083, y: 48.553, z: 2.269}; @@ -37,8 +39,6 @@ var TABLET_NATURAL_DIMENSIONS = {x: 32.083, y: 48.553, z: 2.269}; var HOME_BUTTON_TEXTURE = Script.resourcesPath() + "images/button-close.png"; // var HOME_BUTTON_TEXTURE = Script.resourcesPath() + "meshes/tablet-with-home-button.fbx/tablet-with-home-button.fbm/button-close.png"; // var TABLET_MODEL_PATH = "http://hifi-content.s3.amazonaws.com/alan/dev/tablet-with-home-button.fbx"; -var LOCAL_BEZEL_HIGHLIGHT = Script.resourcesPath() + "images/buttonBezel_highlight.png"; -var LOCAL_NORMAL_BEZEL = Script.resourcesPath() + "images/buttonBezel.png"; var LOCAL_TABLET_MODEL_PATH = Script.resourcesPath() + "meshes/tablet-with-home-button-small-bezel.fbx"; var HIGH_PRIORITY = 1; @@ -79,6 +79,18 @@ function calcSpawnInfo(hand, landscape) { }; } + +cleanUpOldMaterialEntities = function() { + var avatarEntityData = MyAvatar.getAvatarEntityData(); + for (var entityID in avatarEntityData) { + var entityName = Entities.getEntityProperties(entityID, ["name"]).name; + + if (entityName === TABLET_MATERIAL_ENTITY_NAME) { + Entities.deleteEntity(entityID); + } + } +}; + /** * WebTablet * @param url [string] url of content to show on the tablet. @@ -134,6 +146,7 @@ WebTablet = function (url, width, dpi, hand, clientOnly, location, visible) { } this.cleanUpOldTablets(); + cleanUpOldMaterialEntities(); this.tabletEntityID = Overlays.addOverlay("model", tabletProperties); @@ -178,43 +191,21 @@ WebTablet = function (url, width, dpi, hand, clientOnly, location, visible) { parentJointIndex: -1 }); - this.homeButtonUnhighlightMaterial = Entities.addEntity({ - type: "Material", - materialURL: "materialData", - localPosition: { x: 0.0, y: 0.0, z: 0.0 }, - priority: HIGH_PRIORITY, - materialData: JSON.stringify({ - materials: { - albedoMap: LOCAL_NORMAL_BEZEL - } - - }), - userData: JSON.stringify({ - "grabbableKey": {"grabbable": false} - }), - visible: false, - parentMaterialName: SUBMESH, - parentID: this.tabletEntityID - }, true); - - this.homeButtonHighlightMaterial = Entities.addEntity({ - type: "Material", - materialURL: "materialData", - localPosition: { x: 0.0, y: 0.0, z: 0.0 }, - priority: LOW_PRIORITY, - visible: false, - materialData: JSON.stringify({ - materials: { - emissiveMap: LOCAL_BEZEL_HIGHLIGHT - } - - }), - userData: JSON.stringify({ - "grabbableKey": {"grabbable": false} - }), - parentMaterialName: SUBMESH, - parentID: this.tabletEntityID - }, true); + this.homeButtonHighlightID = Overlays.addOverlay("circle3d", { + name: "homeButtonHighlight", + localPosition: { x: -HOME_BUTTON_X_OFFSET, y: HOME_BUTTON_Y_OFFSET, z: -HOME_BUTTON_Z_OFFSET }, + localRotation: { x: 0, y: 1, z: 0, w: 0}, + dimensions: { x: homeButtonDim, y: homeButtonDim, z: homeButtonDim }, + color: {red: 255, green: 255, blue: 255}, + solid: true, + innerRadius: 0.9, + ignoreIntersection: true, + alpha: 0.0, + visible: visible, + drawInFront: false, + parentID: this.tabletEntityID, + parentJointIndex: -1 + }); this.receive = function (channel, senderID, senderUUID, localOnly) { if (_this.homeButtonID === senderID) { @@ -369,8 +360,7 @@ WebTablet.prototype.destroy = function () { Overlays.deleteOverlay(this.webOverlayID); Overlays.deleteOverlay(this.tabletEntityID); Overlays.deleteOverlay(this.homeButtonID); - Entities.deleteEntity(this.homeButtonUnhighlightMaterial); - Entities.deleteEntity(this.homeButtonHighlightMaterial); + Overlays.deleteOverlay(this.homeButtonHighlightID); HMD.displayModeChanged.disconnect(this.myOnHmdChanged); Controller.mousePressEvent.disconnect(this.myMousePressEvent); @@ -464,22 +454,19 @@ WebTablet.prototype.calculateWorldAttitudeRelativeToCamera = function (windowPos WebTablet.prototype.onHoverEnterOverlay = function (overlayID, pointerEvent) { if (overlayID === this.homeButtonID) { - Entities.editEntity(this.homeButtonUnhighlightMaterial, {priority: LOW_PRIORITY}); - Entities.editEntity(this.homeButtonHighlightMaterial, {priority: HIGH_PRIORITY}); + Overlays.editOverlay(this.homeButtonHighlightID, { alpha: 1.0 }); } }; WebTablet.prototype.onHoverOverOverlay = function (overlayID, pointerEvent) { if (overlayID !== this.homeButtonID) { - Entities.editEntity(this.homeButtonUnhighlightMaterial, {priority: HIGH_PRIORITY}); - Entities.editEntity(this.homeButtonHighlightMaterial, {priority: LOW_PRIORITY}); + Overlays.editOverlay(this.homeButtonHighlightID, { alpha: 0.0 }); } }; WebTablet.prototype.onHoverLeaveOverlay = function (overlayID, pointerEvent) { if (overlayID === this.homeButtonID) { - Entities.editEntity(this.homeButtonUnhighlightMaterial, {priority: HIGH_PRIORITY}); - Entities.editEntity(this.homeButtonHighlightMaterial, {priority: LOW_PRIORITY}); + Overlays.editOverlay(this.homeButtonHighlightID, { alpha: 0.0 }); } }; @@ -609,6 +596,21 @@ WebTablet.prototype.scheduleMouseMoveProcessor = function() { } }; +WebTablet.prototype.handleHomeButtonHover = function(x, y) { + var pickRay = Camera.computePickRay(x, y); + var entityPickResults; + var homebuttonHovered = false; + entityPickResults = Overlays.findRayIntersection(pickRay, true, [this.tabletEntityID]); + if (entityPickResults.intersects && (entityPickResults.entityID === this.tabletEntityID || + entityPickResults.overlayID === this.tabletEntityID)) { + var overlayPickResults = Overlays.findRayIntersection(pickRay, true, [this.homeButtonID], []); + if (overlayPickResults.intersects && overlayPickResults.overlayID === this.homeButtonID) { + homebuttonHovered = true; + } + } + Overlays.editOverlay(this.homeButtonHighlightID, { alpha: homebuttonHovered ? 1.0 : 0.0 }); +}; + WebTablet.prototype.mouseMoveEvent = function (event) { if (this.dragging) { this.currentMouse = { @@ -616,6 +618,8 @@ WebTablet.prototype.mouseMoveEvent = function (event) { y: event.y }; this.scheduleMouseMoveProcessor(); + } else { + this.handleHomeButtonHover(event.x, event.y); } }; @@ -642,6 +646,8 @@ WebTablet.prototype.mouseMoveProcessor = function () { }); } this.scheduleMouseMoveProcessor(); + } else { + this.handleHomeButtonHover(this.currentMouse.x, this.currentMouse.y); } }; diff --git a/scripts/system/libraries/controllerDispatcherUtils.js b/scripts/system/libraries/controllerDispatcherUtils.js index e817bb4ee1..c34fd76802 100644 --- a/scripts/system/libraries/controllerDispatcherUtils.js +++ b/scripts/system/libraries/controllerDispatcherUtils.js @@ -5,8 +5,7 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html - -/* global module, Camera, HMD, MyAvatar, controllerDispatcherPlugins:true, Quat, Vec3, Overlays, Xform, +/* global module, Camera, HMD, MyAvatar, controllerDispatcherPlugins:true, Quat, Vec3, Overlays, Xform, Mat4, Selection, Uuid, MSECS_PER_SEC:true , LEFT_HAND:true, RIGHT_HAND:true, FORBIDDEN_GRAB_TYPES:true, HAPTIC_PULSE_STRENGTH:true, HAPTIC_PULSE_DURATION:true, ZERO_VEC:true, ONE_VEC:true, @@ -58,8 +57,9 @@ entityIsFarGrabbedByOther:true, highlightTargetEntity:true, clearHighlightedEntities:true, - unhighlightTargetEntity:true - distanceBetweenEntityLocalPositionAndBoundingBox: true + unhighlightTargetEntity:true, + distanceBetweenEntityLocalPositionAndBoundingBox: true, + worldPositionToRegistrationFrameMatrix: true */ MSECS_PER_SEC = 1000.0; @@ -95,7 +95,7 @@ COLORS_GRAB_DISTANCE_HOLD = { red: 238, green: 75, blue: 214 }; NEAR_GRAB_RADIUS = 1.0; -TEAR_AWAY_DISTANCE = 0.1; // ungrab an entity if its bounding-box moves this far from the hand +TEAR_AWAY_DISTANCE = 0.15; // ungrab an entity if its bounding-box moves this far from the hand TEAR_AWAY_COUNT = 2; // multiply by TEAR_AWAY_CHECK_TIME to know how long the item must be away TEAR_AWAY_CHECK_TIME = 0.15; // seconds, duration between checks DISPATCHER_HOVERING_LIST = "dispactherHoveringList"; @@ -203,15 +203,15 @@ getEnabledModuleByName = function (moduleName) { return null; }; -getGrabbableData = function (props) { +getGrabbableData = function (ggdProps) { // look in userData for a "grabbable" key, return the value or some defaults var grabbableData = {}; var userDataParsed = null; try { - if (!props.userDataParsed) { - props.userDataParsed = JSON.parse(props.userData); + if (!ggdProps.userDataParsed) { + ggdProps.userDataParsed = JSON.parse(ggdProps.userData); } - userDataParsed = props.userDataParsed; + userDataParsed = ggdProps.userDataParsed; } catch (err) { userDataParsed = {}; } @@ -237,11 +237,11 @@ getGrabbableData = function (props) { return grabbableData; }; -entityIsGrabbable = function (props) { - var grabbable = getGrabbableData(props).grabbable; +entityIsGrabbable = function (eigProps) { + var grabbable = getGrabbableData(eigProps).grabbable; if (!grabbable || - props.locked || - FORBIDDEN_GRAB_TYPES.indexOf(props.type) >= 0) { + eigProps.locked || + FORBIDDEN_GRAB_TYPES.indexOf(eigProps.type) >= 0) { return false; } return true; @@ -259,13 +259,13 @@ unhighlightTargetEntity = function(entityID) { Selection.removeFromSelectedItemsList(DISPATCHER_HOVERING_LIST, "entity", entityID); }; -entityIsDistanceGrabbable = function(props) { - if (!entityIsGrabbable(props)) { +entityIsDistanceGrabbable = function(eidgProps) { + if (!entityIsGrabbable(eidgProps)) { return false; } // we can't distance-grab non-physical - var isPhysical = propsArePhysical(props); + var isPhysical = propsArePhysical(eidgProps); if (!isPhysical) { return false; } @@ -304,11 +304,11 @@ getControllerJointIndex = function (hand) { return -1; }; -propsArePhysical = function (props) { - if (!props.dynamic) { +propsArePhysical = function (papProps) { + if (!papProps.dynamic) { return false; } - var isPhysical = (props.shapeType && props.shapeType !== 'none'); + var isPhysical = (papProps.shapeType && papProps.shapeType !== 'none'); return isPhysical; }; @@ -328,8 +328,9 @@ projectOntoXYPlane = function (worldPos, position, rotation, dimensions, registr }; }; -projectOntoEntityXYPlane = function (entityID, worldPos, props) { - return projectOntoXYPlane(worldPos, props.position, props.rotation, props.dimensions, props.registrationPoint); +projectOntoEntityXYPlane = function (entityID, worldPos, popProps) { + return projectOntoXYPlane(worldPos, popProps.position, popProps.rotation, + popProps.dimensions, popProps.registrationPoint); }; projectOntoOverlayXYPlane = function projectOntoOverlayXYPlane(overlayID, worldPos) { @@ -348,9 +349,9 @@ entityHasActions = function (entityID) { ensureDynamic = function (entityID) { // if we distance hold something and keep it very still before releasing it, it ends up // non-dynamic in bullet. If it's too still, give it a little bounce so it will fall. - var props = Entities.getEntityProperties(entityID, ["velocity", "dynamic", "parentID"]); - if (props.dynamic && props.parentID === Uuid.NULL) { - var velocity = props.velocity; + var edProps = Entities.getEntityProperties(entityID, ["velocity", "dynamic", "parentID"]); + if (edProps.dynamic && edProps.parentID === Uuid.NULL) { + var velocity = edProps.velocity; if (Vec3.length(velocity) < 0.05) { // see EntityMotionState.cpp DYNAMIC_LINEAR_VELOCITY_THRESHOLD velocity = { x: 0.0, y: 0.2, z: 0.0 }; Entities.editEntity(entityID, { velocity: velocity }); @@ -416,13 +417,18 @@ findHandChildEntities = function(hand) { }); }; -distanceBetweenEntityLocalPositionAndBoundingBox = function(entityProps) { - var localPoint = entityProps.localPosition; - var entityXform = new Xform(entityProps.rotation, entityProps.position); - var minOffset = Vec3.multiplyVbyV(entityProps.registrationPoint, entityProps.dimensions); - var maxOffset = Vec3.multiplyVbyV(Vec3.subtract(ONE_VEC, entityProps.registrationPoint), entityProps.dimensions); - var localMin = Vec3.subtract(entityXform.trans, minOffset); - var localMax = Vec3.sum(entityXform.trans, maxOffset); +distanceBetweenEntityLocalPositionAndBoundingBox = function(entityProps, jointGrabOffset) { + var DEFAULT_REGISTRATION_POINT = { x: 0.5, y: 0.5, z: 0.5 }; + var rotInv = Quat.inverse(entityProps.localRotation); + var localPosition = Vec3.sum(entityProps.localPosition, jointGrabOffset); + var localPoint = Vec3.multiplyQbyV(rotInv, Vec3.multiply(localPosition, -1.0)); + + var halfDims = Vec3.multiply(entityProps.dimensions, 0.5); + var regRatio = Vec3.subtract(DEFAULT_REGISTRATION_POINT, entityProps.registrationPoint); + var entityCenter = Vec3.multiplyVbyV(regRatio, entityProps.dimensions); + var localMin = Vec3.subtract(entityCenter, halfDims); + var localMax = Vec3.sum(entityCenter, halfDims); + var v = {x: localPoint.x, y: localPoint.y, z: localPoint.z}; v.x = Math.max(v.x, localMin.x); @@ -482,6 +488,30 @@ entityIsFarGrabbedByOther = function(entityID) { return false; }; + +worldPositionToRegistrationFrameMatrix = function(wptrProps, pos) { + // get world matrix for intersection point + var intersectionMat = new Xform({ x: 0, y: 0, z:0, w: 1 }, pos); + + // calculate world matrix for registrationPoint addjusted entity + var DEFAULT_REGISTRATION_POINT = { x: 0.5, y: 0.5, z: 0.5 }; + var regRatio = Vec3.subtract(DEFAULT_REGISTRATION_POINT, wptrProps.registrationPoint); + var regOffset = Vec3.multiplyVbyV(regRatio, wptrProps.dimensions); + var regOffsetRot = Vec3.multiplyQbyV(wptrProps.rotation, regOffset); + var modelMat = new Xform(wptrProps.rotation, Vec3.sum(wptrProps.position, regOffsetRot)); + + // get inverse of model matrix + var modelMatInv = modelMat.inv(); + + // transform world intersection point into object's registrationPoint frame + var xformMat = Xform.mul(modelMatInv, intersectionMat); + + // convert to Mat4 + var offsetMat = Mat4.createFromRotAndTrans(xformMat.rot, xformMat.pos); + return offsetMat; +}; + + if (typeof module !== 'undefined') { module.exports = { makeDispatcherModuleParameters: makeDispatcherModuleParameters, @@ -503,6 +533,7 @@ if (typeof module !== 'undefined') { projectOntoEntityXYPlane: projectOntoEntityXYPlane, TRIGGER_OFF_VALUE: TRIGGER_OFF_VALUE, TRIGGER_ON_VALUE: TRIGGER_ON_VALUE, - DISPATCHER_HOVERING_LIST: DISPATCHER_HOVERING_LIST + DISPATCHER_HOVERING_LIST: DISPATCHER_HOVERING_LIST, + worldPositionToRegistrationFrameMatrix: worldPositionToRegistrationFrameMatrix }; } diff --git a/scripts/system/libraries/controllers.js b/scripts/system/libraries/controllers.js index d99fd0db48..cc20c196aa 100644 --- a/scripts/system/libraries/controllers.js +++ b/scripts/system/libraries/controllers.js @@ -38,30 +38,34 @@ getGrabPointSphereOffset = function(handController, ignoreSensorToWorldScale) { getControllerWorldLocation = function (handController, doOffset) { var orientation; var position; - var pose = Controller.getPoseValue(handController); - var valid = pose.valid; - var controllerJointIndex; - if (pose.valid) { - if (handController === Controller.Standard.RightHand) { - controllerJointIndex = MyAvatar.getJointIndex("_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND"); - } else { - controllerJointIndex = MyAvatar.getJointIndex("_CAMERA_RELATIVE_CONTROLLER_LEFTHAND"); - } - orientation = Quat.multiply(MyAvatar.orientation, MyAvatar.getAbsoluteJointRotationInObjectFrame(controllerJointIndex)); - position = Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, MyAvatar.getAbsoluteJointTranslationInObjectFrame(controllerJointIndex))); + var valid = false; + + if (handController >= 0) { + var pose = Controller.getPoseValue(handController); + valid = pose.valid; + var controllerJointIndex; + if (pose.valid) { + if (handController === Controller.Standard.RightHand) { + controllerJointIndex = MyAvatar.getJointIndex("_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND"); + } else { + controllerJointIndex = MyAvatar.getJointIndex("_CAMERA_RELATIVE_CONTROLLER_LEFTHAND"); + } + orientation = Quat.multiply(MyAvatar.orientation, MyAvatar.getAbsoluteJointRotationInObjectFrame(controllerJointIndex)); + position = Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, MyAvatar.getAbsoluteJointTranslationInObjectFrame(controllerJointIndex))); - // add to the real position so the grab-point is out in front of the hand, a bit - if (doOffset) { - var offset = getGrabPointSphereOffset(handController); - position = Vec3.sum(position, Vec3.multiplyQbyV(orientation, offset)); - } + // add to the real position so the grab-point is out in front of the hand, a bit + if (doOffset) { + var offset = getGrabPointSphereOffset(handController); + position = Vec3.sum(position, Vec3.multiplyQbyV(orientation, offset)); + } - } else if (!HMD.isHandControllerAvailable()) { - // NOTE: keep this offset in sync with scripts/system/controllers/handControllerPointer.js:493 - var VERTICAL_HEAD_LASER_OFFSET = 0.1 * MyAvatar.sensorToWorldScale; - position = Vec3.sum(Camera.position, Vec3.multiplyQbyV(Camera.orientation, {x: 0, y: VERTICAL_HEAD_LASER_OFFSET, z: 0})); - orientation = Quat.multiply(Camera.orientation, Quat.angleAxis(-90, { x: 1, y: 0, z: 0 })); - valid = true; + } else if (!HMD.isHandControllerAvailable()) { + // NOTE: keep this offset in sync with scripts/system/controllers/handControllerPointer.js:493 + var VERTICAL_HEAD_LASER_OFFSET = 0.1 * MyAvatar.sensorToWorldScale; + position = Vec3.sum(Camera.position, Vec3.multiplyQbyV(Camera.orientation, {x: 0, y: VERTICAL_HEAD_LASER_OFFSET, z: 0})); + orientation = Quat.multiply(Camera.orientation, Quat.angleAxis(-90, { x: 1, y: 0, z: 0 })); + valid = true; + } } return {position: position, diff --git a/scripts/system/libraries/entityCameraTool.js b/scripts/system/libraries/entityCameraTool.js index fb808cc7ea..73e73d67a6 100644 --- a/scripts/system/libraries/entityCameraTool.js +++ b/scripts/system/libraries/entityCameraTool.js @@ -28,7 +28,9 @@ var FOCUS_MIN_ZOOM = 0.5; var ZOOM_SCALING = 0.02; var MIN_ZOOM_DISTANCE = 0.01; -var MAX_ZOOM_DISTANCE = 200; + +// The maximum usable zoom level is somewhere around 14km, further than that the edit handles will fade-out. (FIXME: MS17493) +var MAX_ZOOM_DISTANCE = 14000; var MODE_INACTIVE = 'inactive'; var MODE_ORBIT = 'orbit'; @@ -77,17 +79,17 @@ CameraManager = function() { } var keyToActionMapping = { - "a": "orbitLeft", - "d": "orbitRight", - "w": "orbitForward", - "s": "orbitBackward", - "e": "orbitUp", - "c": "orbitDown", - - "LEFT": "orbitLeft", - "RIGHT": "orbitRight", - "UP": "orbitForward", - "DOWN": "orbitBackward", + 65: "orbitLeft", // "a" + 68: "orbitRight", // "d" + 87: "orbitForward", // "w" + 83: "orbitBackward", // "s" + 69: "orbitUp", // "e" + 67: "orbitDown", // "c" + + 16777234: "orbitLeft", // "LEFT" + 16777236: "orbitRight", // "RIGHT" + 16777235: "orbitForward", // "UP" + 16777237: "orbitBackward", // "DOWN" } var CAPTURED_KEYS = []; @@ -96,7 +98,7 @@ CameraManager = function() { } function getActionForKeyEvent(event) { - var action = keyToActionMapping[event.text]; + var action = keyToActionMapping[event.key]; if (action !== undefined) { if (event.isShifted) { if (action === "orbitForward") { @@ -143,6 +145,10 @@ CameraManager = function() { }); } + for (var action in actions) { + actions[action] = 0; + } + that.enabled = true; that.mode = MODE_INACTIVE; @@ -255,14 +261,6 @@ CameraManager = function() { that.updateCamera(); } - that.getZoomPercentage = function() { - return (that.zoomDistance - MIN_ZOOM_DISTANCE) / MAX_ZOOM_DISTANCE; - } - - that.setZoomPercentage = function(pct) { - that.targetZoomDistance = pct * (MAX_ZOOM_DISTANCE - MIN_ZOOM_DISTANCE); - } - that.pan = function(offset) { var up = Quat.getUp(Camera.getOrientation()); var right = Quat.getRight(Camera.getOrientation()); diff --git a/scripts/system/libraries/entityList.js b/scripts/system/libraries/entityList.js index fb876302dd..678b2eeb0b 100644 --- a/scripts/system/libraries/entityList.js +++ b/scripts/system/libraries/entityList.js @@ -59,6 +59,10 @@ EntityListTool = function(shouldUseEditTabletApp) { entityListWindow.setVisible(!shouldUseEditTabletApp() && visible); }; + that.isVisible = function() { + return entityListWindow.isVisible(); + }; + that.setVisible(false); function emitJSONScriptEvent(data) { @@ -244,6 +248,7 @@ EntityListTool = function(shouldUseEditTabletApp) { webView.webEventReceived.connect(onWebEventReceived); entityListWindow.webEventReceived.addListener(onWebEventReceived); + that.interactiveWindowHidden = entityListWindow.interactiveWindowHidden; return that; }; diff --git a/scripts/system/libraries/entitySelectionTool.js b/scripts/system/libraries/entitySelectionTool.js index 96a3b2b015..1c7d5244a1 100644 --- a/scripts/system/libraries/entitySelectionTool.js +++ b/scripts/system/libraries/entitySelectionTool.js @@ -28,6 +28,17 @@ Script.include([ SelectionManager = (function() { var that = {}; + /** + * @description Removes known to be broken properties from a properties object + * @param properties + * @return properties + */ + var fixRemoveBrokenProperties = function (properties) { + // Reason: Entity property is always set to 0,0,0 which causes it to override angularVelocity (see MS17131) + delete properties.localAngularVelocity; + return properties; + } + // FUNCTION: SUBSCRIBE TO UPDATE MESSAGES function subscribeToUpdateMessages() { Messages.subscribe("entityToolUpdates"); @@ -53,14 +64,18 @@ SelectionManager = (function() { } if (messageParsed.method === "selectEntity") { - if (wantDebug) { - print("setting selection to " + messageParsed.entityID); + if (!SelectionDisplay.triggered() || SelectionDisplay.triggeredHand === messageParsed.hand) { + if (wantDebug) { + print("setting selection to " + messageParsed.entityID); + } + that.setSelections([messageParsed.entityID]); } - that.setSelections([messageParsed.entityID]); } else if (messageParsed.method === "clearSelection") { - that.clearSelections(); + if (!SelectionDisplay.triggered() || SelectionDisplay.triggeredHand === messageParsed.hand) { + that.clearSelections(); + } } else if (messageParsed.method === "pointingAt") { - if (messageParsed.rightHand) { + if (messageParsed.hand === Controller.Standard.RightHand) { that.pointingAtDesktopWindowRight = messageParsed.desktopWindow; that.pointingAtTabletRight = messageParsed.tablet; } else { @@ -114,7 +129,7 @@ SelectionManager = (function() { that.savedProperties = {}; for (var i = 0; i < that.selections.length; i++) { var entityID = that.selections[i]; - that.savedProperties[entityID] = Entities.getEntityProperties(entityID); + that.savedProperties[entityID] = fixRemoveBrokenProperties(Entities.getEntityProperties(entityID)); } }; @@ -194,11 +209,40 @@ SelectionManager = (function() { } }; + // Return true if the given entity with `properties` is being grabbed by an avatar. + // This is mostly a heuristic - there is no perfect way to know if an entity is being + // grabbed. + function nonDynamicEntityIsBeingGrabbedByAvatar(properties) { + if (properties.dynamic || Uuid.isNull(properties.parentID)) { + return false; + } + + var avatar = AvatarList.getAvatar(properties.parentID); + if (Uuid.isNull(avatar.sessionUUID)) { + return false; + } + + var grabJointNames = [ + 'RightHand', 'LeftHand', + '_CONTROLLER_RIGHTHAND', '_CONTROLLER_LEFTHAND', + '_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND', '_CAMERA_RELATIVE_CONTROLLER_LEFTHAND']; + + for (var i = 0; i < grabJointNames.length; ++i) { + if (avatar.getJointIndex(grabJointNames[i]) === properties.parentJointIndex) { + return true; + } + } + + return false; + } + that.duplicateSelection = function() { var entitiesToDuplicate = []; var duplicatedEntityIDs = []; var duplicatedChildrenWithOldParents = []; var originalEntityToNewEntityID = []; + + SelectionManager.saveProperties(); // build list of entities to duplicate by including any unselected children of selected parent entities Object.keys(that.savedProperties).forEach(function(originalEntityID) { @@ -213,10 +257,33 @@ SelectionManager = (function() { var originalEntityID = entitiesToDuplicate[i]; var properties = that.savedProperties[originalEntityID]; if (properties === undefined) { - properties = Entities.getEntityProperties(originalEntityID); + properties = fixRemoveBrokenProperties(Entities.getEntityProperties(originalEntityID)); } if (!properties.locked && (!properties.clientOnly || properties.owningAvatarID === MyAvatar.sessionUUID)) { + if (nonDynamicEntityIsBeingGrabbedByAvatar(properties)) { + properties.parentID = null; + properties.parentJointIndex = null; + properties.localPosition = properties.position; + properties.localRotation = properties.rotation; + } + delete properties.actionData; var newEntityID = Entities.addEntity(properties); + + // Re-apply actions from the original entity + var actionIDs = Entities.getActionIDs(properties.id); + for (var j = 0; j < actionIDs.length; ++j) { + var actionID = actionIDs[j]; + var actionArguments = Entities.getActionArguments(properties.id, actionID); + if (actionArguments) { + var type = actionArguments.type; + if (type == 'hold' || type == 'far-grab') { + continue; + } + delete actionArguments.ttl; + Entities.addAction(type, newEntityID, actionArguments); + } + } + duplicatedEntityIDs.push({ entityID: newEntityID, properties: properties @@ -255,7 +322,8 @@ SelectionManager = (function() { that.worldPosition = null; that.worldRotation = null; } else if (that.selections.length === 1) { - properties = Entities.getEntityProperties(that.selections[0]); + properties = Entities.getEntityProperties(that.selections[0], + ['dimensions', 'position', 'rotation', 'registrationPoint', 'boundingBox', 'type']); that.localDimensions = properties.dimensions; that.localPosition = properties.position; that.localRotation = properties.rotation; @@ -263,7 +331,7 @@ SelectionManager = (function() { that.worldDimensions = properties.boundingBox.dimensions; that.worldPosition = properties.boundingBox.center; - that.worldRotation = properties.boundingBox.rotation; + that.worldRotation = Quat.IDENTITY; that.entityType = properties.type; @@ -271,11 +339,7 @@ SelectionManager = (function() { SelectionDisplay.setSpaceMode(SPACE_LOCAL); } } else { - that.localRotation = null; - that.localDimensions = null; - that.localPosition = null; - - properties = Entities.getEntityProperties(that.selections[0]); + properties = Entities.getEntityProperties(that.selections[0], ['type', 'boundingBox']); that.entityType = properties.type; @@ -283,7 +347,7 @@ SelectionManager = (function() { var tfl = properties.boundingBox.tfl; for (var i = 1; i < that.selections.length; i++) { - properties = Entities.getEntityProperties(that.selections[i]); + properties = Entities.getEntityProperties(that.selections[i], 'boundingBox'); var bb = properties.boundingBox; brn.x = Math.min(bb.brn.x, brn.x); brn.y = Math.min(bb.brn.y, brn.y); @@ -293,6 +357,7 @@ SelectionManager = (function() { tfl.z = Math.max(bb.tfl.z, tfl.z); } + that.localRotation = null; that.localDimensions = null; that.localPosition = null; that.worldDimensions = { @@ -300,6 +365,7 @@ SelectionManager = (function() { y: tfl.y - brn.y, z: tfl.z - brn.z }; + that.worldRotation = Quat.IDENTITY; that.worldPosition = { x: brn.x + (that.worldDimensions.x / 2), y: brn.y + (that.worldDimensions.y / 2), @@ -358,7 +424,7 @@ SelectionDisplay = (function() { var ROTATE_CTRL_SNAP_ANGLE = 22.5; var ROTATE_DEFAULT_SNAP_ANGLE = 1; var ROTATE_DEFAULT_TICK_MARKS_ANGLE = 5; - var ROTATE_RING_IDLE_INNER_RADIUS = 0.95; + var ROTATE_RING_IDLE_INNER_RADIUS = 0.92; var ROTATE_RING_SELECTED_INNER_RADIUS = 0.9; // These are multipliers for sizing the rotation degrees display while rotating an entity @@ -381,6 +447,8 @@ SelectionDisplay = (function() { var CTRL_KEY_CODE = 16777249; + var RAIL_AXIS_LENGTH = 10000; + var TRANSLATE_DIRECTION = { X: 0, Y: 1, @@ -410,6 +478,8 @@ SelectionDisplay = (function() { YAW: 1, ROLL: 2 }; + + var NO_TRIGGER_HAND = -1; var spaceMode = SPACE_LOCAL; var overlayNames = []; @@ -435,6 +505,7 @@ SelectionDisplay = (function() { that.replaceCollisionsAfterStretch = false; var handlePropertiesTranslateArrowCones = { + alpha: 1, shape: "Cone", solid: true, visible: false, @@ -442,6 +513,7 @@ SelectionDisplay = (function() { drawInFront: true }; var handlePropertiesTranslateArrowCylinders = { + alpha: 1, shape: "Cylinder", solid: true, visible: false, @@ -518,6 +590,7 @@ SelectionDisplay = (function() { }); var handlePropertiesStretchSpheres = { + alpha: 1, shape: "Sphere", solid: true, visible: false, @@ -547,6 +620,7 @@ SelectionDisplay = (function() { Overlays.editOverlay(handleStretchZPanel, { color: COLOR_BLUE }); var handlePropertiesScaleCubes = { + alpha: 1, size: 0.025, color: COLOR_SCALE_CUBE, solid: true, @@ -565,6 +639,7 @@ SelectionDisplay = (function() { var handleScaleRTFCube = Overlays.addOverlay("cube", handlePropertiesScaleCubes); // ( x, y, z) var handlePropertiesScaleEdge = { + alpha: 1, color: COLOR_SCALE_EDGE, visible: false, ignoreRayIntersection: true, @@ -585,6 +660,7 @@ SelectionDisplay = (function() { var handleScaleFLEdge = Overlays.addOverlay("line3d", handlePropertiesScaleEdge); var handleCloner = Overlays.addOverlay("cube", { + alpha: 1, size: 0.05, color: COLOR_GREEN, solid: true, @@ -616,6 +692,40 @@ SelectionDisplay = (function() { dashed: false }); + var xRailOverlay = Overlays.addOverlay("line3d", { + visible: false, + start: Vec3.ZERO, + end: Vec3.ZERO, + color: { + red: 255, + green: 0, + blue: 0 + }, + ignoreRayIntersection: true // always ignore this + }); + var yRailOverlay = Overlays.addOverlay("line3d", { + visible: false, + start: Vec3.ZERO, + end: Vec3.ZERO, + color: { + red: 0, + green: 255, + blue: 0 + }, + ignoreRayIntersection: true // always ignore this + }); + var zRailOverlay = Overlays.addOverlay("line3d", { + visible: false, + start: Vec3.ZERO, + end: Vec3.ZERO, + color: { + red: 0, + green: 0, + blue: 255 + }, + ignoreRayIntersection: true // always ignore this +}); + var allOverlays = [ handleTranslateXCone, handleTranslateXCylinder, @@ -656,7 +766,11 @@ SelectionDisplay = (function() { handleScaleFLEdge, handleCloner, selectionBox, - iconSelectionBox + iconSelectionBox, + xRailOverlay, + yRailOverlay, + zRailOverlay + ]; var maximumHandleInAllOverlays = handleCloner; @@ -713,20 +827,17 @@ SelectionDisplay = (function() { // But we dont' get mousePressEvents. that.triggerMapping = Controller.newMapping(Script.resolvePath('') + '-click'); Script.scriptEnding.connect(that.triggerMapping.disable); - that.TRIGGER_GRAB_VALUE = 0.85; // From handControllerGrab/Pointer.js. Should refactor. - that.TRIGGER_ON_VALUE = 0.4; - that.TRIGGER_OFF_VALUE = 0.15; - that.triggered = false; - var activeHand = Controller.Standard.RightHand; - function makeTriggerHandler(hand) { - return function (value) { - if (!that.triggered && (value > that.TRIGGER_GRAB_VALUE)) { // should we smooth? - that.triggered = true; - if (activeHand !== hand) { - // No switching while the other is already triggered, so no need to release. - activeHand = (activeHand === Controller.Standard.RightHand) ? - Controller.Standard.LeftHand : Controller.Standard.RightHand; - } + that.triggeredHand = NO_TRIGGER_HAND; + that.triggered = function() { + return that.triggeredHand !== NO_TRIGGER_HAND; + } + function makeClickHandler(hand) { + return function (clicked) { + // Don't allow both hands to trigger at the same time + if (that.triggered() && hand !== that.triggeredHand) { + return; + } + if (!that.triggered() && clicked) { var pointingAtDesktopWindow = (hand === Controller.Standard.RightHand && SelectionManager.pointingAtDesktopWindowRight) || (hand === Controller.Standard.LeftHand && @@ -736,15 +847,16 @@ SelectionDisplay = (function() { if (pointingAtDesktopWindow || pointingAtTablet) { return; } + that.triggeredHand = hand; that.mousePressEvent({}); - } else if (that.triggered && (value < that.TRIGGER_OFF_VALUE)) { - that.triggered = false; + } else if (that.triggered() && !clicked) { + that.triggeredHand = NO_TRIGGER_HAND; that.mouseReleaseEvent({}); } }; } - that.triggerMapping.from(Controller.Standard.RT).peek().to(makeTriggerHandler(Controller.Standard.RightHand)); - that.triggerMapping.from(Controller.Standard.LT).peek().to(makeTriggerHandler(Controller.Standard.LeftHand)); + that.triggerMapping.from(Controller.Standard.RTClick).peek().to(makeClickHandler(Controller.Standard.RightHand)); + that.triggerMapping.from(Controller.Standard.LTClick).peek().to(makeClickHandler(Controller.Standard.LeftHand)); // FUNCTION DEF(s): Intersection Check Helpers function testRayIntersect(queryRay, overlayIncludes, overlayExcludes) { @@ -795,7 +907,7 @@ SelectionDisplay = (function() { if (wantDebug) { print("=============== eST::MousePressEvent BEG ======================="); } - if (!event.isLeftButton && !that.triggered) { + if (!event.isLeftButton && !that.triggered()) { // EARLY EXIT-(if another mouse button than left is pressed ignore it) return false; } @@ -873,11 +985,13 @@ SelectionDisplay = (function() { }; // FUNCTION: MOUSE MOVE EVENT + var lastMouseEvent = null; that.mouseMoveEvent = function(event) { var wantDebug = false; if (wantDebug) { print("=============== eST::MouseMoveEvent BEG ======================="); } + lastMouseEvent = event; if (activeTool) { if (wantDebug) { print(" Trigger ActiveTool(" + activeTool.mode + ")'s onMove"); @@ -999,19 +1113,35 @@ SelectionDisplay = (function() { }; // Control key remains active only while key is held down - that.keyReleaseEvent = function(key) { - if (key.key === CTRL_KEY_CODE) { + that.keyReleaseEvent = function(event) { + if (event.key === CTRL_KEY_CODE) { ctrlPressed = false; that.updateActiveRotateRing(); } + if (activeTool && lastMouseEvent !== null) { + lastMouseEvent.isShifted = event.isShifted; + lastMouseEvent.isMeta = event.isMeta; + lastMouseEvent.isControl = event.isControl; + lastMouseEvent.isAlt = event.isAlt; + activeTool.onMove(lastMouseEvent); + SelectionManager._update(); + } }; // Triggers notification on specific key driven events - that.keyPressEvent = function(key) { - if (key.key === CTRL_KEY_CODE) { + that.keyPressEvent = function(event) { + if (event.key === CTRL_KEY_CODE) { ctrlPressed = true; that.updateActiveRotateRing(); } + if (activeTool && lastMouseEvent !== null) { + lastMouseEvent.isShifted = event.isShifted; + lastMouseEvent.isMeta = event.isMeta; + lastMouseEvent.isControl = event.isControl; + lastMouseEvent.isAlt = event.isAlt; + activeTool.onMove(lastMouseEvent); + SelectionManager._update(); + } }; // NOTE: mousePressEvent and mouseMoveEvent from the main script should call us., so we don't hook these: @@ -1023,9 +1153,9 @@ SelectionDisplay = (function() { that.checkControllerMove = function() { if (SelectionManager.hasSelection()) { - var controllerPose = getControllerWorldLocation(activeHand, true); - var hand = (activeHand === Controller.Standard.LeftHand) ? 0 : 1; - if (controllerPose.valid && lastControllerPoses[hand].valid && that.triggered) { + var controllerPose = getControllerWorldLocation(that.triggeredHand, true); + var hand = (that.triggeredHand === Controller.Standard.LeftHand) ? 0 : 1; + if (controllerPose.valid && lastControllerPoses[hand].valid && that.triggered()) { if (!Vec3.equal(controllerPose.position, lastControllerPoses[hand].position) || !Vec3.equal(controllerPose.rotation, lastControllerPoses[hand].rotation)) { that.mouseMoveEvent({}); @@ -1036,8 +1166,8 @@ SelectionDisplay = (function() { }; function controllerComputePickRay() { - var controllerPose = getControllerWorldLocation(activeHand, true); - if (controllerPose.valid && that.triggered) { + var controllerPose = getControllerWorldLocation(that.triggeredHand, true); + if (controllerPose.valid && that.triggered()) { var controllerPosition = controllerPose.translation; // This gets point direction right, but if you want general quaternion it would be more complicated: var controllerDirection = Quat.getUp(controllerPose.rotation); @@ -1660,6 +1790,20 @@ SelectionDisplay = (function() { Vec3.print(" pickResult.intersection", pickResult.intersection); } + // Duplicate entities if alt is pressed. This will make a + // copy of the selected entities and move the _original_ entities, not + // the new ones. + if (event.isAlt || doClone) { + duplicatedEntityIDs = SelectionManager.duplicateSelection(); + var ids = []; + for (var i = 0; i < duplicatedEntityIDs.length; ++i) { + ids.push(duplicatedEntityIDs[i].entityID); + } + SelectionManager.setSelections(ids); + } else { + duplicatedEntityIDs = null; + } + SelectionManager.saveProperties(); that.resetPreviousHandleColor(); @@ -1689,15 +1833,6 @@ SelectionDisplay = (function() { z: 0 }); - // Duplicate entities if alt is pressed. This will make a - // copy of the selected entities and move the _original_ entities, not - // the new ones. - if (event.isAlt || doClone) { - duplicatedEntityIDs = SelectionManager.duplicateSelection(); - } else { - duplicatedEntityIDs = null; - } - isConstrained = false; if (wantDebug) { print("================== TRANSLATE_XZ(End) <- ======================="); @@ -1705,6 +1840,14 @@ SelectionDisplay = (function() { }, onEnd: function(event, reason) { pushCommandForSelections(duplicatedEntityIDs); + if (isConstrained) { + Overlays.editOverlay(xRailOverlay, { + visible: false + }); + Overlays.editOverlay(zRailOverlay, { + visible: false + }); + } }, elevation: function(origin, intersection) { return (origin.y - intersection.y) / Vec3.distance(origin, intersection); @@ -1768,10 +1911,46 @@ SelectionDisplay = (function() { vector.x = 0; } if (!isConstrained) { + var xStart = Vec3.sum(startPosition, { + x: -RAIL_AXIS_LENGTH, + y: 0, + z: 0 + }); + var xEnd = Vec3.sum(startPosition, { + x: RAIL_AXIS_LENGTH, + y: 0, + z: 0 + }); + var zStart = Vec3.sum(startPosition, { + x: 0, + y: 0, + z: -RAIL_AXIS_LENGTH + }); + var zEnd = Vec3.sum(startPosition, { + x: 0, + y: 0, + z: RAIL_AXIS_LENGTH + }); + Overlays.editOverlay(xRailOverlay, { + start: xStart, + end: xEnd, + visible: true + }); + Overlays.editOverlay(zRailOverlay, { + start: zStart, + end: zEnd, + visible: true + }); isConstrained = true; } } else { if (isConstrained) { + Overlays.editOverlay(xRailOverlay, { + visible: false + }); + Overlays.editOverlay(zRailOverlay, { + visible: false + }); isConstrained = false; } } @@ -1829,6 +2008,20 @@ SelectionDisplay = (function() { addHandleTool(overlay, { mode: mode, onBegin: function(event, pickRay, pickResult) { + // Duplicate entities if alt is pressed. This will make a + // copy of the selected entities and move the _original_ entities, not + // the new ones. + if (event.isAlt) { + duplicatedEntityIDs = SelectionManager.duplicateSelection(); + var ids = []; + for (var i = 0; i < duplicatedEntityIDs.length; ++i) { + ids.push(duplicatedEntityIDs[i].entityID); + } + SelectionManager.setSelections(ids); + } else { + duplicatedEntityIDs = null; + } + var axisVector; if (direction === TRANSLATE_DIRECTION.X) { axisVector = { x: 1, y: 0, z: 0 }; @@ -1855,15 +2048,6 @@ SelectionDisplay = (function() { that.setHandleStretchVisible(false); that.setHandleScaleCubeVisible(false); that.setHandleClonerVisible(false); - - // Duplicate entities if alt is pressed. This will make a - // copy of the selected entities and move the _original_ entities, not - // the new ones. - if (event.isAlt) { - duplicatedEntityIDs = SelectionManager.duplicateSelection(); - } else { - duplicatedEntityIDs = null; - } previousPickRay = pickRay; }, @@ -2143,11 +2327,11 @@ SelectionDisplay = (function() { } // Are we using handControllers or Mouse - only relevant for 3D tools - var controllerPose = getControllerWorldLocation(activeHand, true); + var controllerPose = getControllerWorldLocation(that.triggeredHand, true); var vector = null; var newPick = null; if (HMD.isHMDAvailable() && HMD.isHandControllerAvailable() && - controllerPose.valid && that.triggered && directionFor3DStretch) { + controllerPose.valid && that.triggered() && directionFor3DStretch) { localDeltaPivot = deltaPivot3D; newPick = pickRay.origin; vector = Vec3.subtract(newPick, lastPick3D); diff --git a/scripts/system/libraries/pointersUtils.js b/scripts/system/libraries/pointersUtils.js index 53959b91f8..a2a1e674b1 100644 --- a/scripts/system/libraries/pointersUtils.js +++ b/scripts/system/libraries/pointersUtils.js @@ -7,17 +7,14 @@ /* jslint bitwise: true */ -/* global Script, Entities, Overlays, Controller, Vec3, Quat, getControllerWorldLocation, RayPick, - controllerDispatcherPlugins:true, controllerDispatcherPluginsNeedSort:true, - LEFT_HAND, RIGHT_HAND, NEAR_GRAB_PICK_RADIUS, DEFAULT_SEARCH_SPHERE_DISTANCE, DISPATCHER_PROPERTIES, - getGrabPointSphereOffset, HMD, MyAvatar, Messages, findHandChildEntities, Pointers, PickType, COLORS_GRAB_SEARCHING_HALF_SQUEEZE - COLORS_GRAB_SEARCHING_FULL_SQUEEZE, COLORS_GRAB_DISTANCE_HOLD, Picks, TRIGGER_ON_VALUE +/* global Script, Pointers, + DEFAULT_SEARCH_SPHERE_DISTANCE, COLORS_GRAB_SEARCHING_HALF_SQUEEZE, COLORS_GRAB_SEARCHING_FULL_SQUEEZE, + COLORS_GRAB_DISTANCE_HOLD, TRIGGER_ON_VALUE, + Pointer:true, PointerManager:true */ - Script.include("/~/system/libraries/controllerDispatcherUtils.js"); Pointer = function(hudLayer, pickType, pointerData) { - var _this = this; this.SEARCH_SPHERE_SIZE = 0.0132; this.dim = {x: this.SEARCH_SPHERE_SIZE, y: this.SEARCH_SPHERE_SIZE, z: this.SEARCH_SPHERE_SIZE}; this.halfPath = { diff --git a/scripts/system/libraries/utils.js b/scripts/system/libraries/utils.js index 220ecd1959..f7b5f6db8d 100644 --- a/scripts/system/libraries/utils.js +++ b/scripts/system/libraries/utils.js @@ -370,7 +370,7 @@ getTabletWidthFromSettings = function () { resizeTablet = function (width, newParentJointIndex, sensorToWorldScaleOverride) { - if (!HMD.tabletID || !HMD.tabletScreenID || !HMD.homeButtonID) { + if (!HMD.tabletID || !HMD.tabletScreenID || !HMD.homeButtonID || !HMD.homeButtonHighlightID) { return; } var sensorScaleFactor = sensorToWorldScaleOverride || MyAvatar.sensorToWorldScale; @@ -422,6 +422,12 @@ resizeTablet = function (width, newParentJointIndex, sensorToWorldScaleOverride) localRotation: { x: 0, y: 1, z: 0, w: 0 }, dimensions: { x: homeButtonDim, y: homeButtonDim, z: homeButtonDim } }); + + Overlays.editOverlay(HMD.homeButtonHighlightID, { + localPosition: { x: -HOME_BUTTON_X_OFFSET, y: HOME_BUTTON_Y_OFFSET, z: -HOME_BUTTON_Z_OFFSET }, + localRotation: { x: 0, y: 1, z: 0, w: 0 }, + dimensions: { x: homeButtonDim, y: homeButtonDim, z: homeButtonDim } + }); }; getMainTabletIDs = function () { diff --git a/scripts/system/makeUserConnection.js b/scripts/system/makeUserConnection.js index 8642fc5ce6..5dee36d147 100644 --- a/scripts/system/makeUserConnection.js +++ b/scripts/system/makeUserConnection.js @@ -228,6 +228,7 @@ animationData.rightHandPosition.y += verticalOffset; } animationData.rightHandRotation = Quat.fromPitchYawRollDegrees(90, 0, 90); + animationData.rightHandType = 0; // RotationAndPosition, see IKTargets.h } function shakeHandsAnimation() { return animationData; diff --git a/scripts/system/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js index fd7b9c703a..7b4f05193f 100644 --- a/scripts/system/marketplaces/marketplaces.js +++ b/scripts/system/marketplaces/marketplaces.js @@ -114,6 +114,7 @@ var selectionDisplay = null; // for gridTool.js to ignore Overlays.editOverlay(HMD.tabletID, { isVisibleInSecondaryCamera : visibleInSecondaryCam }); Overlays.editOverlay(HMD.homeButtonID, { isVisibleInSecondaryCamera : visibleInSecondaryCam }); + Overlays.editOverlay(HMD.homeButtonHighlightID, { isVisibleInSecondaryCamera : visibleInSecondaryCam }); Overlays.editOverlay(HMD.tabletScreenID, { isVisibleInSecondaryCamera : visibleInSecondaryCam }); } diff --git a/scripts/system/modules/createWindow.js b/scripts/system/modules/createWindow.js index 185991d2ef..7369cf91f8 100644 --- a/scripts/system/modules/createWindow.js +++ b/scripts/system/modules/createWindow.js @@ -75,6 +75,7 @@ module.exports = (function() { this.settingsKey = settingsKey; this.defaultRect = defaultRect; this.webEventReceived = new CallableEvent(); + this.interactiveWindowHidden = new CallableEvent(); this.fromQml = new CallableEvent(); if (createOnStartup) { this.createWindow(); @@ -108,10 +109,16 @@ module.exports = (function() { this.window.sizeChanged.connect(this, windowRectChanged); this.window.positionChanged.connect(this, windowRectChanged); - this.window.webEventReceived.connect(this, function (data) { + this.window.webEventReceived.connect(this, function(data) { this.webEventReceived.call(data); }); + this.window.visibleChanged.connect(this, function() { + if (!this.window.visible) { + this.interactiveWindowHidden.call(); + } + }); + this.window.fromQml.connect(this, function (data) { this.fromQml.call(data); }); @@ -133,6 +140,12 @@ module.exports = (function() { } } }, + isVisible: function() { + if (this.window) { + return this.window.visible; + } + return false; + }, emitScriptEvent: function(data) { if (this.window) { this.window.emitScriptEvent(data); @@ -144,6 +157,7 @@ module.exports = (function() { } }, webEventReceived: null, + interactiveWindowHidden: null, fromQml: null }; diff --git a/scripts/system/pal.js b/scripts/system/pal.js index 9485b8b49a..ebb45130e5 100644 --- a/scripts/system/pal.js +++ b/scripts/system/pal.js @@ -268,7 +268,6 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See break; case 'refreshConnections': print('Refreshing Connections...'); - getConnectionData(false); UserActivityLogger.palAction("refresh_connections", ""); break; case 'removeConnection': @@ -281,7 +280,7 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See print("Error: unable to remove connection", connectionUserName, error || response.status); return; } - getConnectionData(false); + sendToQml({ method: 'refreshConnections' }); }); break; @@ -361,8 +360,9 @@ function getProfilePicture(username, callback) { // callback(url) if successfull callback(matched[1]); }); } +var SAFETY_LIMIT = 400; function getAvailableConnections(domain, callback) { // callback([{usename, location}...]) if successfull. (Logs otherwise) - var url = METAVERSE_BASE + '/api/v1/users?per_page=400&'; + var url = METAVERSE_BASE + '/api/v1/users?per_page=' + SAFETY_LIMIT + '&'; if (domain) { url += 'status=' + domain.slice(1, -1); // without curly braces } else { @@ -373,8 +373,10 @@ function getAvailableConnections(domain, callback) { // callback([{usename, loca }); } function getInfoAboutUser(specificUsername, callback) { - var url = METAVERSE_BASE + '/api/v1/users?filter=connections'; + var url = METAVERSE_BASE + '/api/v1/users?filter=connections&per_page=' + SAFETY_LIMIT + '&search=' + encodeURIComponent(specificUsername); requestJSON(url, function (connectionsData) { + // You could have (up to SAFETY_LIMIT connections whose usernames contain the specificUsername. + // Search returns all such matches. for (user in connectionsData.users) { if (connectionsData.users[user].username === specificUsername) { callback(connectionsData.users[user]); @@ -406,16 +408,14 @@ function getConnectionData(specificUsername, domain) { // Update all the usernam print('Error: Unable to find information about ' + specificUsername + ' in connectionsData!'); } }); - } else { + } else if (domain) { getAvailableConnections(domain, function (users) { - if (domain) { - users.forEach(function (user) { - updateUser(frob(user)); - }); - } else { - sendToQml({ method: 'connections', params: users.map(frob) }); - } + users.forEach(function (user) { + updateUser(frob(user)); + }); }); + } else { + print("Error: unrecognized getConnectionData()"); } } diff --git a/scripts/system/particle_explorer/particleExplorer.js b/scripts/system/particle_explorer/particleExplorer.js index cb2c2f3374..f1b7c8600f 100644 --- a/scripts/system/particle_explorer/particleExplorer.js +++ b/scripts/system/particle_explorer/particleExplorer.js @@ -361,6 +361,55 @@ type: "Row" } ], + Spin: [ + { + id: "particleSpin", + name: "Particle Spin", + type: "SliderRadian", + min: -360.0, + max: 360.0 + }, + { + type: "Row" + }, + { + id: "spinSpread", + name: "Spin Spread", + type: "SliderRadian", + max: 360.0 + }, + { + type: "Row" + }, + { + id: "spinStart", + name: "Spin Start", + type: "SliderRadian", + min: -360.0, + max: 360.0 + }, + { + type: "Row" + }, + { + id: "spinFinish", + name: "Spin Finish", + type: "SliderRadian", + min: -360.0, + max: 360.0 + }, + { + type: "Row" + }, + { + id: "rotateWithEntity", + name: "Rotate with Entity", + type: "Boolean" + }, + { + type: "Row" + } + ], Polar: [ { id: "polarStart", diff --git a/scripts/system/particle_explorer/particleExplorerTool.js b/scripts/system/particle_explorer/particleExplorerTool.js index 1914180ff9..a3be004329 100644 --- a/scripts/system/particle_explorer/particleExplorerTool.js +++ b/scripts/system/particle_explorer/particleExplorerTool.js @@ -75,6 +75,12 @@ ParticleExplorerTool = function(createToolsWindow) { if (isNaN(properties.colorFinish.red)) { properties.colorFinish = properties.color; } + if (isNaN(properties.spinStart)) { + properties.spinStart = properties.particleSpin; + } + if (isNaN(properties.spinFinish)) { + properties.spinFinish = properties.particleSpin; + } sendParticleProperties(properties); } @@ -88,8 +94,8 @@ ParticleExplorerTool = function(createToolsWindow) { if (data.messageType === "settings_update") { var updatedSettings = data.updatedSettings; - var optionalProps = ["alphaStart", "alphaFinish", "radiusStart", "radiusFinish", "colorStart", "colorFinish"]; - var fallbackProps = ["alpha", "particleRadius", "color"]; + var optionalProps = ["alphaStart", "alphaFinish", "radiusStart", "radiusFinish", "colorStart", "colorFinish", "spinStart", "spinFinish"]; + var fallbackProps = ["alpha", "particleRadius", "color", "particleSpin"]; for (var i = 0; i < optionalProps.length; i++) { var fallbackProp = fallbackProps[Math.floor(i / 2)]; var optionalValue = updatedSettings[optionalProps[i]]; diff --git a/scripts/system/snapshot.js b/scripts/system/snapshot.js index 3ddbeb997d..37270f896e 100644 --- a/scripts/system/snapshot.js +++ b/scripts/system/snapshot.js @@ -279,9 +279,21 @@ function onMessage(message) { } var POLAROID_PRINT_SOUND = SoundCache.getSound(Script.resourcesPath() + "sounds/snapshot/sound-print-photo.wav"); -var POLAROID_MODEL_URL = 'http://hifi-content.s3.amazonaws.com/alan/dev/Test/snapshot.fbx'; +var POLAROID_MODEL_URL = 'http://hifi-content.s3.amazonaws.com/alan/dev/Test/snapshot.fbx'; +var POLAROID_RATE_LIMIT_MS = 1000; +var polaroidPrintingIsRateLimited = false; function printToPolaroid(image_url) { + + // Rate-limit printing + if (polaroidPrintingIsRateLimited) { + return; + } + polaroidPrintingIsRateLimited = true; + Script.setTimeout(function () { + polaroidPrintingIsRateLimited = false; + }, POLAROID_RATE_LIMIT_MS); + var polaroid_url = image_url; var model_pos = Vec3.sum(MyAvatar.position, Vec3.multiply(1.25, Quat.getForward(MyAvatar.orientation))); @@ -340,7 +352,7 @@ function fillImageDataFromPrevious() { containsGif: previousAnimatedSnapPath !== "", processingGif: false, shouldUpload: false, - canBlast: location.domainID === Settings.getValue("previousSnapshotDomainID"), + canBlast: snapshotDomainID === Settings.getValue("previousSnapshotDomainID"), isLoggedIn: isLoggedIn }; imageData = []; @@ -415,7 +427,7 @@ function snapshotUploaded(isError, reply) { } isUploadingPrintableStill = false; } -var href, domainID; +var href, snapshotDomainID; function takeSnapshot() { tablet.emitScriptEvent(JSON.stringify({ type: "snapshot", @@ -440,8 +452,8 @@ function takeSnapshot() { // Even the domainID could change (e.g., if the user falls into a teleporter while recording). href = location.href; Settings.setValue("previousSnapshotHref", href); - domainID = location.domainID; - Settings.setValue("previousSnapshotDomainID", domainID); + snapshotDomainID = location.domainID; + Settings.setValue("previousSnapshotDomainID", snapshotDomainID); maybeDeleteSnapshotStories(); @@ -539,7 +551,7 @@ function stillSnapshotTaken(pathStillSnapshot, notify) { HMD.openTablet(); - isDomainOpen(domainID, function (canShare) { + isDomainOpen(snapshotDomainID, function (canShare) { snapshotOptions = { containsGif: false, processingGif: false, @@ -582,7 +594,7 @@ function processingGifStarted(pathStillSnapshot) { HMD.openTablet(); - isDomainOpen(domainID, function (canShare) { + isDomainOpen(snapshotDomainID, function (canShare) { snapshotOptions = { containsGif: true, processingGif: true, @@ -610,7 +622,7 @@ function processingGifCompleted(pathAnimatedSnapshot) { Settings.setValue("previousAnimatedSnapPath", pathAnimatedSnapshot); - isDomainOpen(domainID, function (canShare) { + isDomainOpen(snapshotDomainID, function (canShare) { snapshotOptions = { containsGif: true, processingGif: false, diff --git a/scripts/system/tablet-ui/tabletUI.js b/scripts/system/tablet-ui/tabletUI.js index 29dc457197..80ddbeca8b 100644 --- a/scripts/system/tablet-ui/tabletUI.js +++ b/scripts/system/tablet-ui/tabletUI.js @@ -13,7 +13,7 @@ // /* global Script, HMD, WebTablet, UIWebTablet, UserActivityLogger, Settings, Entities, Messages, Tablet, Overlays, - MyAvatar, Menu, AvatarInputs, Vec3 */ + MyAvatar, Menu, AvatarInputs, Vec3, cleanUpOldMaterialEntities */ (function() { // BEGIN LOCAL_SCOPE var tabletRezzed = false; @@ -31,6 +31,14 @@ Script.include("../libraries/WebTablet.js"); + function cleanupMaterialEntities() { + if (Window.isPhysicsEnabled()) { + cleanUpOldMaterialEntities(); + return; + } + Script.setTimeout(cleanupMaterialEntities, 100); + } + function checkTablet() { if (gTablet === null) { gTablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); @@ -104,8 +112,7 @@ HMD.tabletID = UIWebTablet.tabletEntityID; HMD.homeButtonID = UIWebTablet.homeButtonID; HMD.tabletScreenID = UIWebTablet.webOverlayID; - HMD.homeButtonHighlightMaterialID = UIWebTablet.homeButtonHighlightMaterial; - HMD.homeButtonUnhighlightMaterialID = UIWebTablet.homeButtonUnhighlightMaterial; + HMD.homeButtonHighlightID = UIWebTablet.homeButtonHighlightID; HMD.displayModeChanged.connect(onHmdChanged); MyAvatar.sensorToWorldScaleChanged.connect(onSensorToWorldScaleChanged); @@ -131,6 +138,7 @@ tabletProperties.visible = true; Overlays.editOverlay(HMD.tabletID, tabletProperties); Overlays.editOverlay(HMD.homeButtonID, { visible: true }); + Overlays.editOverlay(HMD.homeButtonHighlightID, { visible: true }); Overlays.editOverlay(HMD.tabletScreenID, { visible: true }); Overlays.editOverlay(HMD.tabletScreenID, { maxFPS: 90 }); updateTabletWidthFromSettings(true); @@ -151,6 +159,7 @@ Overlays.editOverlay(HMD.tabletID, { visible: false }); Overlays.editOverlay(HMD.homeButtonID, { visible: false }); + Overlays.editOverlay(HMD.homeButtonHighlightID, { visible: false }); Overlays.editOverlay(HMD.tabletScreenID, { visible: false }); Overlays.editOverlay(HMD.tabletScreenID, { maxFPS: 1 }); } @@ -171,6 +180,7 @@ UIWebTablet = null; HMD.tabletID = null; HMD.homeButtonID = null; + HMD.homeButtonHighlightID = null; HMD.tabletScreenID = null; } else if (debugTablet) { print("TABLET closeTabletUI, UIWebTablet is null"); @@ -296,7 +306,7 @@ } wantsMenu = clicked; }); - + clickMapping.from(Controller.Standard.Start).peek().to(function (clicked) { if (clicked) { //activeHudPoint2dGamePad(); @@ -323,8 +333,8 @@ Overlays.deleteOverlay(tabletID); HMD.tabletID = null; HMD.homeButtonID = null; + HMD.homeButtonHighlightID = null; HMD.tabletScreenID = null; - HMD.homeButtonHighlightMaterialID = null; - HMD.homeButtonUnhighlightMaterialID = null; }); + Script.setTimeout(cleanupMaterialEntities, 100); }()); // END LOCAL_SCOPE diff --git a/server-console/src/main.js b/server-console/src/main.js index dbbe699325..92ebdbf36c 100644 --- a/server-console/src/main.js +++ b/server-console/src/main.js @@ -405,7 +405,7 @@ LogWindow.prototype = { } }; -function goHomeClicked() { +function visitSandboxClicked() { if (interfacePath) { startInterface('hifi://localhost'); } else { @@ -439,8 +439,8 @@ var labels = { } }, goHome: { - label: 'Go Home', - click: goHomeClicked, + label: 'Visit Sandbox', + click: visitSandboxClicked, enabled: false }, quit: { diff --git a/tests-manual/gpu-textures/CMakeLists.txt b/tests-manual/gpu-textures/CMakeLists.txt index c10f2eda3f..84f5027411 100644 --- a/tests-manual/gpu-textures/CMakeLists.txt +++ b/tests-manual/gpu-textures/CMakeLists.txt @@ -1,5 +1,4 @@ set(TARGET_NAME gpu-textures-tests) -AUTOSCRIBE_SHADER_LIB(gpu graphics render-utils) # This is not a testcase -- just set it up as a regular hifi project setup_hifi_project(Quick Gui Script) setup_memory_debugger() diff --git a/tests-manual/gpu-textures/src/TestHelpers.cpp b/tests-manual/gpu-textures/src/TestHelpers.cpp index f952a4385f..f0fe31cec6 100644 --- a/tests-manual/gpu-textures/src/TestHelpers.cpp +++ b/tests-manual/gpu-textures/src/TestHelpers.cpp @@ -9,17 +9,6 @@ #include "TestHelpers.h" #include -gpu::ShaderPointer makeShader(const std::string & vertexShaderSrc, const std::string & fragmentShaderSrc, const gpu::Shader::BindingSet & bindings) { - auto vs = gpu::Shader::createVertex(vertexShaderSrc); - auto fs = gpu::Shader::createPixel(fragmentShaderSrc); - auto shader = gpu::Shader::createProgram(vs, fs); - if (!gpu::Shader::makeProgram(*shader, bindings)) { - printf("Could not compile shader\n"); - exit(-1); - } - return shader; -} - QString projectRootDir() { static QString projectRootPath = QFileInfo(QFileInfo(__FILE__).absolutePath() + "/..").absoluteFilePath(); return projectRootPath; diff --git a/tests-manual/gpu-textures/src/TestHelpers.h b/tests-manual/gpu-textures/src/TestHelpers.h index 17730c3642..e752df7bf7 100644 --- a/tests-manual/gpu-textures/src/TestHelpers.h +++ b/tests-manual/gpu-textures/src/TestHelpers.h @@ -36,5 +36,4 @@ public: }; uint32_t toCompactColor(const glm::vec4& color); -gpu::ShaderPointer makeShader(const std::string & vertexShaderSrc, const std::string & fragmentShaderSrc, const gpu::Shader::BindingSet & bindings); QString projectRootDir(); diff --git a/tests-manual/gpu-textures/src/TestTextures.cpp b/tests-manual/gpu-textures/src/TestTextures.cpp index 7aedb506da..701e60fab8 100644 --- a/tests-manual/gpu-textures/src/TestTextures.cpp +++ b/tests-manual/gpu-textures/src/TestTextures.cpp @@ -81,11 +81,9 @@ TexturesTest::TexturesTest() { connect(&stats, &TextureTestStats::prevTexture, this, &TexturesTest::onPrevTexture); connect(&stats, &TextureTestStats::maxTextureMemory, this, &TexturesTest::onMaxTextureMemory); { - auto VS = gpu::Shader::createVertex(vertexShaderSource); - auto PS = gpu::Shader::createPixel(fragmentShaderSource); + auto VS = gpu::Shader::createVertex({ vertexShaderSource, {} }); + auto PS = gpu::Shader::createPixel({ fragmentShaderSource, {} }); auto program = gpu::Shader::createProgram(VS, PS); - gpu::Shader::BindingSet slotBindings; - gpu::Shader::makeProgram(*program, slotBindings); // If the pipeline did not exist, make it auto state = std::make_shared(); state->setCullMode(gpu::State::CULL_NONE); diff --git a/tests-manual/gpu/CMakeLists.txt b/tests-manual/gpu/CMakeLists.txt index 336dcf753c..30218f3f97 100644 --- a/tests-manual/gpu/CMakeLists.txt +++ b/tests-manual/gpu/CMakeLists.txt @@ -1,5 +1,4 @@ set(TARGET_NAME gpu-test) -AUTOSCRIBE_SHADER_LIB(gpu graphics render-utils) # This is not a testcase -- just set it up as a regular hifi project setup_hifi_project(Quick Gui Script) setup_memory_debugger() diff --git a/tests-manual/gpu/src/TestHelpers.cpp b/tests-manual/gpu/src/TestHelpers.cpp index 75586da904..f46e997567 100644 --- a/tests-manual/gpu/src/TestHelpers.cpp +++ b/tests-manual/gpu/src/TestHelpers.cpp @@ -7,14 +7,3 @@ // #include "TestHelpers.h" - -gpu::ShaderPointer makeShader(const std::string & vertexShaderSrc, const std::string & fragmentShaderSrc, const gpu::Shader::BindingSet & bindings) { - auto vs = gpu::Shader::createVertex(vertexShaderSrc); - auto fs = gpu::Shader::createPixel(fragmentShaderSrc); - auto shader = gpu::Shader::createProgram(vs, fs); - if (!gpu::Shader::makeProgram(*shader, bindings)) { - printf("Could not compile shader\n"); - exit(-1); - } - return shader; -} diff --git a/tests-manual/gpu/src/TestHelpers.h b/tests-manual/gpu/src/TestHelpers.h index fd8989f628..4f8137e641 100644 --- a/tests-manual/gpu/src/TestHelpers.h +++ b/tests-manual/gpu/src/TestHelpers.h @@ -29,5 +29,4 @@ public: }; uint32_t toCompactColor(const glm::vec4& color); -gpu::ShaderPointer makeShader(const std::string & vertexShaderSrc, const std::string & fragmentShaderSrc, const gpu::Shader::BindingSet & bindings); diff --git a/tests-manual/render-perf/CMakeLists.txt b/tests-manual/render-perf/CMakeLists.txt index 93ff325a98..0cc3f87bd9 100644 --- a/tests-manual/render-perf/CMakeLists.txt +++ b/tests-manual/render-perf/CMakeLists.txt @@ -14,7 +14,8 @@ set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Tests/manual-tests/") # link in the shared libraries link_hifi_libraries( shared task workload networking animation - ktx image octree gl gpu ${PLATFORM_GL_BACKEND} + ktx image octree + shaders gl gpu ${PLATFORM_GL_BACKEND} render render-utils graphics fbx model-networking graphics-scripting entities entities-renderer audio avatars script-engine diff --git a/tests-manual/render-perf/src/main.cpp b/tests-manual/render-perf/src/main.cpp index 7126b333f6..c9724ea352 100644 --- a/tests-manual/render-perf/src/main.cpp +++ b/tests-manual/render-perf/src/main.cpp @@ -42,13 +42,13 @@ #include #include #include +#include #include #include #include #include -#include #include @@ -120,18 +120,26 @@ public: class QWindowCamera : public SimpleCamera { Key forKey(int key) { switch (key) { - case Qt::Key_W: return FORWARD; - case Qt::Key_S: return BACK; - case Qt::Key_A: return LEFT; - case Qt::Key_D: return RIGHT; - case Qt::Key_E: return UP; - case Qt::Key_C: return DOWN; - default: break; + case Qt::Key_W: + return FORWARD; + case Qt::Key_S: + return BACK; + case Qt::Key_A: + return LEFT; + case Qt::Key_D: + return RIGHT; + case Qt::Key_E: + return UP; + case Qt::Key_C: + return DOWN; + default: + break; } return INVALID; } vec2 _lastMouse; + public: void onKeyPress(QKeyEvent* event) { Key k = forKey(event->key()); @@ -168,7 +176,7 @@ public: }; static QString toHumanSize(size_t size, size_t maxUnit = std::numeric_limits::max()) { - static const std::vector SUFFIXES { { "B", "KB", "MB", "GB", "TB", "PB" } }; + static const std::vector SUFFIXES{ { "B", "KB", "MB", "GB", "TB", "PB" } }; const size_t maxIndex = std::min(maxUnit, SUFFIXES.size() - 1); size_t suffixIndex = 0; @@ -180,40 +188,28 @@ static QString toHumanSize(size_t size, size_t maxUnit = std::numeric_limits _presentCount; QElapsedTimer _elapsed; - std::atomic _fps { 1 }; + std::atomic _fps{ 1 }; RateCounter<200> _fpsCounter; std::mutex _mutex; std::shared_ptr _backend; std::vector _frameTimes; - size_t _frameIndex { 0 }; + size_t _frameIndex{ 0 }; std::mutex _frameLock; std::queue _pendingFrames; gpu::FramePointer _activeFrame; QSize _size; - static const size_t FRAME_TIME_BUFFER_SIZE { 8192 }; + static const size_t FRAME_TIME_BUFFER_SIZE{ 8192 }; void submitFrame(const gpu::FramePointer& frame) { std::unique_lock lock(_frameLock); @@ -246,18 +242,12 @@ public: RENDER_THREAD = QThread::currentThread(); // Wait until the context has been moved to this thread - { - std::unique_lock lock(_mutex); - } + { std::unique_lock lock(_mutex); } _context.makeCurrent(); _frameTimes.resize(FRAME_TIME_BUFFER_SIZE, 0); { - auto vs = gpu::StandardShaderLib::getDrawUnitQuadTexcoordVS(); - auto ps = gpu::Shader::createPixel(std::string(SRGB_TO_LINEAR_FRAG)); - gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); - gpu::Shader::BindingSet slotBindings; - gpu::Shader::makeProgram(*program, slotBindings); + gpu::ShaderPointer program = gpu::Shader::createProgram(shader::display_plugins::program::SrgbToLinear); gpu::StatePointer state = gpu::StatePointer(new gpu::State()); _presentPipeline = gpu::Pipeline::create(program, state); } @@ -287,7 +277,6 @@ public: _gpuContext->executeFrame(frame); { - auto geometryCache = DependencyManager::get(); gpu::Batch presentBatch; presentBatch.setViewportTransform({ 0, 0, _size.width(), _size.height() }); @@ -304,7 +293,7 @@ public: _context.makeCurrent(); _context.swapBuffers(); _fpsCounter.increment(); - static size_t _frameCount { 0 }; + static size_t _frameCount{ 0 }; ++_frameCount; if (_elapsed.elapsed() >= 500) { _fps = _fpsCounter.rate(); @@ -374,16 +363,13 @@ public: class TestActionFactory : public EntityDynamicFactoryInterface { public: virtual EntityDynamicPointer factory(EntityDynamicType type, - const QUuid& id, - EntityItemPointer ownerEntity, - QVariantMap arguments) override { + const QUuid& id, + EntityItemPointer ownerEntity, + QVariantMap arguments) override { return EntityDynamicPointer(); } - - virtual EntityDynamicPointer factoryBA(EntityItemPointer ownerEntity, QByteArray data) override { - return nullptr; - } + virtual EntityDynamicPointer factoryBA(EntityItemPointer ownerEntity, QByteArray data) override { return nullptr; } }; // Background Render Data & rendering functions @@ -391,96 +377,77 @@ class BackgroundRenderData { public: typedef render::Payload Payload; typedef Payload::DataPointer Pointer; - static render::ItemID _item; // unique WorldBoxRenderData + static render::ItemID _item; // unique WorldBoxRenderData }; render::ItemID BackgroundRenderData::_item = 0; QSharedPointer logger; namespace render { - template <> const ItemKey payloadGetKey(const BackgroundRenderData::Pointer& stuff) { - return ItemKey::Builder::background(); - } - - template <> const Item::Bound payloadGetBound(const BackgroundRenderData::Pointer& stuff) { - return Item::Bound(); - } - - template <> void payloadRender(const BackgroundRenderData::Pointer& background, RenderArgs* args) { - Q_ASSERT(args->_batch); - gpu::Batch& batch = *args->_batch; - - // Background rendering decision - auto skyStage = DependencyManager::get()->getSkyStage(); - auto backgroundMode = skyStage->getBackgroundMode(); - - switch (backgroundMode) { - case graphics::SunSkyStage::SKY_BOX: { - auto skybox = skyStage->getSkybox(); - if (skybox) { - PerformanceTimer perfTimer("skybox"); - skybox->render(batch, args->getViewFrustum()); - break; - } - } - default: - // this line intentionally left blank - break; - } - } +template <> +const ItemKey payloadGetKey(const BackgroundRenderData::Pointer& stuff) { + return ItemKey::Builder::background(); } -OffscreenGLCanvas* _chromiumShareContext{ nullptr }; -Q_GUI_EXPORT void qt_gl_set_global_share_context(QOpenGLContext *context); +template <> +const Item::Bound payloadGetBound(const BackgroundRenderData::Pointer& stuff) { + return Item::Bound(); +} +template <> +void payloadRender(const BackgroundRenderData::Pointer& background, RenderArgs* args) { + Q_ASSERT(args->_batch); + gpu::Batch& batch = *args->_batch; + + // Background rendering decision + auto skyStage = DependencyManager::get()->getSkyStage(); + auto backgroundMode = skyStage->getBackgroundMode(); + + switch (backgroundMode) { + case graphics::SunSkyStage::SKY_BOX: { + auto skybox = skyStage->getSkybox(); + if (skybox) { + PerformanceTimer perfTimer("skybox"); + skybox->render(batch, args->getViewFrustum()); + break; + } + } + default: + // this line intentionally left blank + break; + } +} +} // namespace render + +OffscreenGLCanvas* _chromiumShareContext{ nullptr }; +Q_GUI_EXPORT void qt_gl_set_global_share_context(QOpenGLContext* context); // Create a simple OpenGL window that renders text in various ways class QTestWindow : public QWindow, public AbstractViewStateInterface { - protected: - void copyCurrentViewFrustum(ViewFrustum& viewOut) const override { - viewOut = _viewFrustum; - } + void copyCurrentViewFrustum(ViewFrustum& viewOut) const override { viewOut = _viewFrustum; } - const ConicalViewFrustums& getConicalViews() const override { - return _view; - } + const ConicalViewFrustums& getConicalViews() const override { return _view; } - QThread* getMainThread() override { - return QThread::currentThread(); - } + QThread* getMainThread() override { return QThread::currentThread(); } - PickRay computePickRay(float x, float y) const override { - return PickRay(); - } + PickRay computePickRay(float x, float y) const override { return PickRay(); } - glm::vec3 getAvatarPosition() const override { - return vec3(); - } + glm::vec3 getAvatarPosition() const override { return vec3(); } void postLambdaEvent(const std::function& f) override {} void sendLambdaEvent(const std::function& f) override {} - qreal getDevicePixelRatio() override { - return 1.0f; - } + qreal getDevicePixelRatio() override { return 1.0f; } - render::ScenePointer getMain3DScene() override { - return _main3DScene; - } + render::ScenePointer getMain3DScene() override { return _main3DScene; } - render::EnginePointer getRenderEngine() override { - return _renderEngine; - } + render::EnginePointer getRenderEngine() override { return _renderEngine; } std::map> _postUpdateLambdas; - void pushPostUpdateLambda(void* key, const std::function& func) override { - _postUpdateLambdas[key] = func; - } + void pushPostUpdateLambda(void* key, const std::function& func) override { _postUpdateLambdas[key] = func; } - bool isHMDMode() const override { - return false; - } + bool isHMDMode() const override { return false; } public: //"/-17.2049,-8.08629,-19.4153/0,0.881994,0,-0.47126" @@ -521,7 +488,7 @@ public: nodeList->setPermissions(permissions); { - SimpleEntitySimulationPointer simpleSimulation { new SimpleEntitySimulation() }; + SimpleEntitySimulationPointer simpleSimulation{ new SimpleEntitySimulation() }; simpleSimulation->setEntityTree(_octree->getTree()); _octree->getTree()->setSimulation(simpleSimulation); _entitySimulation = simpleSimulation; @@ -556,7 +523,6 @@ public: _initContext.makeCurrent(); } - // FIXME use a wait condition QThread::msleep(1000); _renderThread.submitFrame(gpu::FramePointer()); @@ -575,16 +541,14 @@ public: restorePosition(); QTimer* timer = new QTimer(this); - timer->setInterval(0); // Qt::CoarseTimer acceptable - connect(timer, &QTimer::timeout, this, [this] { - draw(); - }); + timer->setInterval(0); // Qt::CoarseTimer acceptable + connect(timer, &QTimer::timeout, this, [this] { draw(); }); timer->start(); _ready = true; } virtual ~QTestWindow() { - getEntities()->shutdown(); // tell the entities system we're shutting down, so it will stop running scripts + getEntities()->shutdown(); // tell the entities system we're shutting down, so it will stop running scripts _renderEngine.reset(); _main3DScene.reset(); EntityTreePointer tree = getEntities()->getTree(); @@ -611,8 +575,7 @@ public: } protected: - - bool eventFilter(QObject *obj, QEvent *event) override { + bool eventFilter(QObject* obj, QEvent* event) override { if (event->type() == QEvent::Close) { _renderThread.terminate(); } @@ -668,22 +631,16 @@ protected: _camera.onKeyPress(event); } - void keyReleaseEvent(QKeyEvent* event) override { - _camera.onKeyRelease(event); - } + void keyReleaseEvent(QKeyEvent* event) override { _camera.onKeyRelease(event); } - void mouseMoveEvent(QMouseEvent* event) override { - _camera.onMouseMove(event); - } + void mouseMoveEvent(QMouseEvent* event) override { _camera.onMouseMove(event); } - void resizeEvent(QResizeEvent* ev) override { - resizeWindow(ev->size()); - } + void resizeEvent(QResizeEvent* ev) override { resizeWindow(ev->size()); } private: - static bool cull(const RenderArgs* args, const AABox& bounds) { - float renderAccuracy = calculateRenderAccuracy(args->getViewFrustum().getPosition(), bounds, args->_sizeScale, args->_boundaryLevelAdjust); + float renderAccuracy = + calculateRenderAccuracy(args->getViewFrustum().getPosition(), bounds, args->_sizeScale, args->_boundaryLevelAdjust); return (renderAccuracy > 0.0f); } @@ -702,10 +659,8 @@ private: update(); _initContext.makeCurrent(); - RenderArgs renderArgs(_renderThread._gpuContext, DEFAULT_OCTREE_SIZE_SCALE, - 0, RenderArgs::DEFAULT_RENDER_MODE, - RenderArgs::MONO, RenderArgs::RENDER_DEBUG_NONE); - + RenderArgs renderArgs(_renderThread._gpuContext, DEFAULT_OCTREE_SIZE_SCALE, 0, getPerspectiveAccuracyAngleTan(DEFAULT_OCTREE_SIZE_SCALE, 0), RenderArgs::DEFAULT_RENDER_MODE, + RenderArgs::MONO, RenderArgs::RENDER_DEBUG_NONE); QSize windowSize = _size; if (_renderMode == NORMAL) { @@ -719,16 +674,16 @@ private: eyeProjections[i] = _viewFrustum.getProjection(); } } else if (_renderMode == HMD) { - eyeOffsets[0][3] = vec4 { -0.0327499993, 0.0, 0.0149999997, 1.0 }; - eyeOffsets[1][3] = vec4 { 0.0327499993, 0.0, 0.0149999997, 1.0 }; - eyeProjections[0][0] = vec4 { 0.759056330, 0.000000000, 0.000000000, 0.000000000 }; - eyeProjections[0][1] = vec4 { 0.000000000, 0.682773232, 0.000000000, 0.000000000 }; - eyeProjections[0][2] = vec4 { -0.0580431037, -0.00619550655, -1.00000489, -1.00000000 }; - eyeProjections[0][3] = vec4 { 0.000000000, 0.000000000, -0.0800003856, 0.000000000 }; - eyeProjections[1][0] = vec4 { 0.752847493, 0.000000000, 0.000000000, 0.000000000 }; - eyeProjections[1][1] = vec4 { 0.000000000, 0.678060353, 0.000000000, 0.000000000 }; - eyeProjections[1][2] = vec4 { 0.0578232110, -0.00669418881, -1.00000489, -1.000000000 }; - eyeProjections[1][3] = vec4 { 0.000000000, 0.000000000, -0.0800003856, 0.000000000 }; + eyeOffsets[0][3] = vec4{ -0.0327499993, 0.0, 0.0149999997, 1.0 }; + eyeOffsets[1][3] = vec4{ 0.0327499993, 0.0, 0.0149999997, 1.0 }; + eyeProjections[0][0] = vec4{ 0.759056330, 0.000000000, 0.000000000, 0.000000000 }; + eyeProjections[0][1] = vec4{ 0.000000000, 0.682773232, 0.000000000, 0.000000000 }; + eyeProjections[0][2] = vec4{ -0.0580431037, -0.00619550655, -1.00000489, -1.00000000 }; + eyeProjections[0][3] = vec4{ 0.000000000, 0.000000000, -0.0800003856, 0.000000000 }; + eyeProjections[1][0] = vec4{ 0.752847493, 0.000000000, 0.000000000, 0.000000000 }; + eyeProjections[1][1] = vec4{ 0.000000000, 0.678060353, 0.000000000, 0.000000000 }; + eyeProjections[1][2] = vec4{ 0.0578232110, -0.00669418881, -1.00000489, -1.000000000 }; + eyeProjections[1][3] = vec4{ 0.000000000, 0.000000000, -0.0800003856, 0.000000000 }; windowSize = { 2048, 2048 }; } renderArgs._context->setStereoProjections(eyeProjections); @@ -774,10 +729,11 @@ private: void updateText() { QString title = QString("FPS %1 Culling %2 TextureMemory GPU %3 CPU %4 Max GPU %5") - .arg(_fps).arg(_cullingEnabled) - .arg(toHumanSize(gpu::Context::getTextureGPUMemSize(), 2)) - .arg(toHumanSize(gpu::Texture::getTextureCPUMemSize(), 2)) - .arg(toHumanSize(gpu::Texture::getAllowedGPUMemoryUsage(), 2)); + .arg(_fps) + .arg(_cullingEnabled) + .arg(toHumanSize(gpu::Context::getTextureGPUMemSize(), 2)) + .arg(toHumanSize(gpu::Texture::getTextureCPUMemSize(), 2)) + .arg(toHumanSize(gpu::Texture::getAllowedGPUMemoryUsage(), 2)); setTitle(title); #if 0 { @@ -818,34 +774,34 @@ private: if (commandParams.length() < 2) { qDebug() << "No wait time specified"; return; - } + } int seconds = commandParams[1].toInt(); _nextCommandTime = usecTimestampNow() + seconds * USECS_PER_SECOND; - } else if (verb == "load") { - if (commandParams.length() < 2) { - qDebug() << "No load file specified"; - return; - } - QString file = commandParams[1]; - if (QFileInfo(file).isRelative()) { - file = _commandPath + "/" + file; - } - if (!QFileInfo(file).exists()) { - qDebug() << "Cannot find scene file " + file; - return; - } + } else if (verb == "load") { + if (commandParams.length() < 2) { + qDebug() << "No load file specified"; + return; + } + QString file = commandParams[1]; + if (QFileInfo(file).isRelative()) { + file = _commandPath + "/" + file; + } + if (!QFileInfo(file).exists()) { + qDebug() << "Cannot find scene file " + file; + return; + } - importScene(file); - } else if (verb == "go") { - if (commandParams.length() < 2) { - qDebug() << "No destination specified for go command"; - return; + importScene(file); + } else if (verb == "go") { + if (commandParams.length() < 2) { + qDebug() << "No destination specified for go command"; + return; + } + parsePath(commandParams[1]); + } else { + qDebug() << "Unknown command " << command; } - parsePath(commandParams[1]); - } else { - qDebug() << "Unknown command " << command; } -} void runNextCommand(quint64 now) { if (_commands.empty()) { @@ -918,15 +874,12 @@ private: _main3DScene->processTransactionQueue(); } - } void render(RenderArgs* renderArgs) { auto& gpuContext = renderArgs->_context; gpuContext->beginFrame(); - gpu::doInBatch("QTestWindow::render", gpuContext, [&](gpu::Batch& batch) { - batch.resetStages(); - }); + gpu::doInBatch("QTestWindow::render", gpuContext, [&](gpu::Batch& batch) { batch.resetStages(); }); PROFILE_RANGE(render, __FUNCTION__); PerformanceTimer perfTimer("draw"); // The pending changes collecting the changes here @@ -953,8 +906,6 @@ private: if (!_renderThread.isThreaded()) { _renderThread.process(); } - - } void resizeWindow(const QSize& size) { @@ -972,31 +923,27 @@ private: static const QString FLOAT_REGEX_STRING = "([-+]?[0-9]*\\.?[0-9]+(?:[eE][-+]?[0-9]+)?)"; static const QString SPACED_COMMA_REGEX_STRING = "\\s*,\\s*"; static const QString POSITION_REGEX_STRING = QString("\\/") + FLOAT_REGEX_STRING + SPACED_COMMA_REGEX_STRING + - FLOAT_REGEX_STRING + SPACED_COMMA_REGEX_STRING + FLOAT_REGEX_STRING + "\\s*(?:$|\\/)"; + FLOAT_REGEX_STRING + SPACED_COMMA_REGEX_STRING + FLOAT_REGEX_STRING + + "\\s*(?:$|\\/)"; static const QString QUAT_REGEX_STRING = QString("\\/") + FLOAT_REGEX_STRING + SPACED_COMMA_REGEX_STRING + - FLOAT_REGEX_STRING + SPACED_COMMA_REGEX_STRING + FLOAT_REGEX_STRING + SPACED_COMMA_REGEX_STRING + - FLOAT_REGEX_STRING + "\\s*$"; + FLOAT_REGEX_STRING + SPACED_COMMA_REGEX_STRING + FLOAT_REGEX_STRING + + SPACED_COMMA_REGEX_STRING + FLOAT_REGEX_STRING + "\\s*$"; static QRegExp orientationRegex(QUAT_REGEX_STRING); static QRegExp positionRegex(POSITION_REGEX_STRING); if (positionRegex.indexIn(viewpointString) != -1) { // we have at least a position, so emit our signal to say we need to change position - glm::vec3 newPosition(positionRegex.cap(1).toFloat(), - positionRegex.cap(2).toFloat(), - positionRegex.cap(3).toFloat()); + glm::vec3 newPosition(positionRegex.cap(1).toFloat(), positionRegex.cap(2).toFloat(), + positionRegex.cap(3).toFloat()); _camera.setPosition(newPosition); if (!glm::any(glm::isnan(newPosition))) { // we may also have an orientation - if (viewpointString[positionRegex.matchedLength() - 1] == QChar('/') - && orientationRegex.indexIn(viewpointString, positionRegex.matchedLength() - 1) != -1) { - - glm::vec4 v = glm::vec4( - orientationRegex.cap(1).toFloat(), - orientationRegex.cap(2).toFloat(), - orientationRegex.cap(3).toFloat(), - orientationRegex.cap(4).toFloat()); + if (viewpointString[positionRegex.matchedLength() - 1] == QChar('/') && + orientationRegex.indexIn(viewpointString, positionRegex.matchedLength() - 1) != -1) { + glm::vec4 v = glm::vec4(orientationRegex.cap(1).toFloat(), orientationRegex.cap(2).toFloat(), + orientationRegex.cap(3).toFloat(), orientationRegex.cap(4).toFloat()); if (!glm::any(glm::isnan(v))) { _camera.setRotation(glm::quat(v.w, v.x, v.y, v.z)); } @@ -1056,9 +1003,7 @@ private: // /-17.2049,-8.08629,-19.4153/0,-0.48551,0,0.874231 glm::quat q = _camera.getOrientation(); glm::vec3 v = _camera.position; - QString viewpoint = QString("/%1,%2,%3/%4,%5,%6,%7"). - arg(v.x).arg(v.y).arg(v.z). - arg(q.x).arg(q.y).arg(q.z).arg(q.w); + QString viewpoint = QString("/%1,%2,%3/%4,%5,%6,%7").arg(v.x).arg(v.y).arg(v.z).arg(q.x).arg(q.y).arg(q.z).arg(q.w); _settings.setValue(LAST_LOCATION_KEY, viewpoint); _camera.setRotation(q); } @@ -1076,30 +1021,26 @@ private: _camera.setPosition(vec3()); } - void toggleCulling() { - _cullingEnabled = !_cullingEnabled; - } + void toggleCulling() { _cullingEnabled = !_cullingEnabled; } void cycleMode() { static auto defaultProjection = SimpleCamera().matrices.perspective; _renderMode = (RenderMode)((_renderMode + 1) % RENDER_MODE_COUNT); if (_renderMode == HMD) { - _camera.matrices.perspective[0] = vec4 { 0.759056330, 0.000000000, 0.000000000, 0.000000000 }; - _camera.matrices.perspective[1] = vec4 { 0.000000000, 0.682773232, 0.000000000, 0.000000000 }; - _camera.matrices.perspective[2] = vec4 { -0.0580431037, -0.00619550655, -1.00000489, -1.00000000 }; - _camera.matrices.perspective[3] = vec4 { 0.000000000, 0.000000000, -0.0800003856, 0.000000000 }; + _camera.matrices.perspective[0] = vec4{ 0.759056330, 0.000000000, 0.000000000, 0.000000000 }; + _camera.matrices.perspective[1] = vec4{ 0.000000000, 0.682773232, 0.000000000, 0.000000000 }; + _camera.matrices.perspective[2] = vec4{ -0.0580431037, -0.00619550655, -1.00000489, -1.00000000 }; + _camera.matrices.perspective[3] = vec4{ 0.000000000, 0.000000000, -0.0800003856, 0.000000000 }; } else { _camera.matrices.perspective = defaultProjection; _camera.setAspectRatio((float)_size.width() / (float)_size.height()); } } - QSharedPointer getEntities() { - return _octree; - } + QSharedPointer getEntities() { return _octree; } private: - render::CullFunctor _cullFunctor { [&](const RenderArgs* args, const AABox& bounds)->bool { + render::CullFunctor _cullFunctor{ [&](const RenderArgs* args, const AABox& bounds) -> bool { if (_cullingEnabled) { return cull(args, bounds); } else { @@ -1107,8 +1048,8 @@ private: } } }; - render::EnginePointer _renderEngine { new render::RenderEngine() }; - render::ScenePointer _main3DScene { new render::Scene(glm::vec3(-0.5f * (float)TREE_SCALE), (float)TREE_SCALE) }; + render::EnginePointer _renderEngine{ new render::RenderEngine() }; + render::ScenePointer _main3DScene{ new render::Scene(glm::vec3(-0.5f * (float)TREE_SCALE), (float)TREE_SCALE) }; QSize _size; QSettings _settings; @@ -1116,39 +1057,39 @@ private: gl::OffscreenContext _initContext; RenderThread _renderThread; QWindowCamera _camera; - ViewFrustum _viewFrustum; // current state of view frustum, perspective, orientation, etc. + ViewFrustum _viewFrustum; // current state of view frustum, perspective, orientation, etc. graphics::SunSkyStage _sunSkyStage; - graphics::LightPointer _globalLight { std::make_shared() }; - bool _ready { false }; + graphics::LightPointer _globalLight{ std::make_shared() }; + bool _ready{ false }; EntitySimulationPointer _entitySimulation; ConicalViewFrustums _view; QStringList _commands; QString _commandPath; - int _commandLoops { 0 }; - int _commandIndex { -1 }; - uint64_t _nextCommandTime { 0 }; + int _commandLoops{ 0 }; + int _commandIndex{ -1 }; + uint64_t _nextCommandTime{ 0 }; //TextOverlay* _textOverlay; static bool _cullingEnabled; - enum RenderMode { + enum RenderMode + { NORMAL = 0, STEREO, HMD, RENDER_MODE_COUNT }; - RenderMode _renderMode { NORMAL }; + RenderMode _renderMode{ NORMAL }; QSharedPointer _octree; }; bool QTestWindow::_cullingEnabled = true; -const char * LOG_FILTER_RULES = R"V0G0N( +const char* LOG_FILTER_RULES = R"V0G0N( hifi.gpu=true )V0G0N"; - int main(int argc, char** argv) { setupHifiApplication("RenderPerf"); @@ -1164,4 +1105,3 @@ int main(int argc, char** argv) { } #include "main.moc" - diff --git a/tests-manual/render-texture-load/CMakeLists.txt b/tests-manual/render-texture-load/CMakeLists.txt index b3e49d830b..36526ecd42 100644 --- a/tests-manual/render-texture-load/CMakeLists.txt +++ b/tests-manual/render-texture-load/CMakeLists.txt @@ -14,7 +14,7 @@ set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Tests/manual-tests/") # link in the shared libraries link_hifi_libraries( shared task networking octree - gl gpu render ktx image animation + shaders gl gpu render ktx image animation graphics fbx model-networking render-utils entities entities-renderer audio avatars diff --git a/tests-manual/render-texture-load/src/main.cpp b/tests-manual/render-texture-load/src/main.cpp index ce666065e3..b6dca48979 100644 --- a/tests-manual/render-texture-load/src/main.cpp +++ b/tests-manual/render-texture-load/src/main.cpp @@ -44,11 +44,11 @@ #include #include #include +#include #include #include #include -#include #include #include @@ -154,11 +154,7 @@ public: //wglSwapIntervalEXT(0); _frameTimes.resize(FRAME_TIME_BUFFER_SIZE, 0); { - auto vs = gpu::StandardShaderLib::getDrawUnitQuadTexcoordVS(); - auto ps = gpu::StandardShaderLib::getDrawTexturePS(); - gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); - gpu::Shader::BindingSet slotBindings; - gpu::Shader::makeProgram(*program, slotBindings); + gpu::ShaderPointer program = gpu::Shader::createProgram(shader::gpu::program::drawTransformUnitQuadTextureOpaque); gpu::StatePointer state = gpu::StatePointer(new gpu::State()); _presentPipeline = gpu::Pipeline::create(program, state); } @@ -345,10 +341,7 @@ public: _renderThread.submitFrame(gpu::FramePointer()); _initContext.makeCurrent(); { - auto vs = gpu::StandardShaderLib::getDrawUnitQuadTexcoordVS(); - auto ps = gpu::StandardShaderLib::getDrawTexturePS(); - gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); - gpu::Shader::makeProgram(*program); + gpu::ShaderPointer program = gpu::Shader::createProgram(shader::gpu::program::drawTransformUnitQuadTextureOpaque); gpu::StatePointer state = gpu::StatePointer(new gpu::State()); state->setDepthTest(gpu::State::DepthTest(false)); state->setScissorEnable(true); diff --git a/tests-manual/render-utils/src/main.cpp b/tests-manual/render-utils/src/main.cpp index e30a80f3d9..63f56e77ed 100644 --- a/tests-manual/render-utils/src/main.cpp +++ b/tests-manual/render-utils/src/main.cpp @@ -94,16 +94,9 @@ public: QTestWindow() { setSurfaceType(QSurface::OpenGLSurface); - QSurfaceFormat format; - // Qt Quick may need a depth and stencil buffer. Always make sure these are available. - format.setDepthBufferSize(16); - format.setStencilBufferSize(8); - setGLFormatVersion(format); - format.setProfile(QSurfaceFormat::OpenGLContextProfile::CoreProfile); + QSurfaceFormat format = getDefaultOpenGLSurfaceFormat(); format.setOption(QSurfaceFormat::DebugContext); - setFormat(format); - _context.setFormat(format); _context.create(); @@ -153,13 +146,6 @@ protected: //static const wchar_t* EXAMPLE_TEXT = L"Hello"; //static const wchar_t* EXAMPLE_TEXT = L"\xC1y Hello 1.0\ny\xC1 line 2\n\xC1y"; -void testShaderBuild(const char* vs_src, const char * fs_src) { - auto vs = gpu::Shader::createVertex(std::string(vs_src)); - auto fs = gpu::Shader::createPixel(std::string(fs_src)); - auto pr = gpu::Shader::createProgram(vs, fs); - gpu::Shader::makeProgram(*pr); -} - void QTestWindow::draw() { if (!isVisible()) { return; diff --git a/tests-manual/shaders/CMakeLists.txt b/tests-manual/shaders/CMakeLists.txt deleted file mode 100644 index 44394db6a0..0000000000 --- a/tests-manual/shaders/CMakeLists.txt +++ /dev/null @@ -1,27 +0,0 @@ - -set(TARGET_NAME shaders-test) - -# This is not a testcase -- just set it up as a regular hifi project -setup_hifi_project(Quick Gui) -set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Tests/manual-tests/") - -setup_memory_debugger() - -# link in the shared libraries -link_hifi_libraries( - shared octree gl gpu graphics render fbx networking entities - script-engine physics - render-utils entities-renderer - ${PLATFORM_GL_BACKEND} -) - -include_directories("${PROJECT_BINARY_DIR}/../../libraries/gpu/") -include_directories("${PROJECT_BINARY_DIR}/../../libraries/render-utils/") -include_directories("${PROJECT_BINARY_DIR}/../../libraries/entities-renderer/") -include_directories("${PROJECT_BINARY_DIR}/../../libraries/graphics/") - -if (WIN32) - add_dependency_external_projects(wasapi) -endif () - -package_libraries_for_deployment() diff --git a/tests-manual/shaders/src/main.cpp b/tests-manual/shaders/src/main.cpp deleted file mode 100644 index 6e117b33cb..0000000000 --- a/tests-manual/shaders/src/main.cpp +++ /dev/null @@ -1,222 +0,0 @@ -// -// Copyright 2014 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#include -#include - -#include - -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#include - -#include -#include -#include -#include - -#include -#include -#include - -#include -#include - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#include -#include - -#include -#include - -#include -#include -#include -#include -#include -#include - -#include -#include - -#include -#include - -#include -#include - -// Create a simple OpenGL window that renders text in various ways -class QTestWindow : public QWindow { - Q_OBJECT - QOpenGLContextWrapper _context; - -protected: - void renderText(); - -public: - QTestWindow() { - setSurfaceType(QSurface::OpenGLSurface); - QSurfaceFormat format = getDefaultOpenGLSurfaceFormat(); - setFormat(format); - _context.setFormat(format); - _context.create(); - - show(); - makeCurrent(); - gl::initModuleGl(); - gpu::Context::init(); - makeCurrent(); - resize(QSize(800, 600)); - } - - virtual ~QTestWindow() { - } - - void draw(); - void makeCurrent() { - _context.makeCurrent(this); - } -}; - - - -const std::string VERTEX_SHADER_DEFINES{ R"GLSL( -#version 410 core -#define GPU_VERTEX_SHADER -#define GPU_TRANSFORM_IS_STEREO -#define GPU_TRANSFORM_STEREO_CAMERA -#define GPU_TRANSFORM_STEREO_CAMERA_INSTANCED -#define GPU_TRANSFORM_STEREO_SPLIT_SCREEN -)GLSL" }; - -const std::string PIXEL_SHADER_DEFINES{ R"GLSL( -#version 410 core -#define GPU_PIXEL_SHADER -#define GPU_TRANSFORM_IS_STEREO -#define GPU_TRANSFORM_STEREO_CAMERA -#define GPU_TRANSFORM_STEREO_CAMERA_INSTANCED -#define GPU_TRANSFORM_STEREO_SPLIT_SCREEN -)GLSL" }; - -void testShaderBuild(const std::string& vs_src, const std::string& fs_src) { - std::string error; - GLuint vs, fs; - if (!gl::compileShader(GL_VERTEX_SHADER, VERTEX_SHADER_DEFINES + vs_src, vs, error) || - !gl::compileShader(GL_FRAGMENT_SHADER, PIXEL_SHADER_DEFINES + fs_src, fs, error)) { - throw std::runtime_error("Failed to compile shader"); - } - gl::CachedShader binary; - auto pr = gl::compileProgram({ vs, fs }, error, binary); - if (!pr) { - throw std::runtime_error("Failed to link shader"); - } -} - -void QTestWindow::draw() { - if (!isVisible()) { - return; - } - - makeCurrent(); - glClearColor(0.2f, 0.2f, 0.2f, 1.0f); - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - - static std::once_flag once; - std::call_once(once, [&]{ - testShaderBuild(sdf_text3D_vert::getSource(), sdf_text3D_frag::getSource()); - - testShaderBuild(DrawTransformUnitQuad_vert::getSource(), DrawTexture_frag::getSource()); - testShaderBuild(DrawTexcoordRectTransformUnitQuad_vert::getSource(), DrawTexture_frag::getSource()); - testShaderBuild(DrawViewportQuadTransformTexcoord_vert::getSource(), DrawTexture_frag::getSource()); - testShaderBuild(DrawTransformUnitQuad_vert::getSource(), DrawTextureOpaque_frag::getSource()); - testShaderBuild(DrawTransformUnitQuad_vert::getSource(), DrawColoredTexture_frag::getSource()); - - testShaderBuild(skybox_vert::getSource(), skybox_frag::getSource()); - testShaderBuild(simple_vert::getSource(), simple_frag::getSource()); - testShaderBuild(simple_vert::getSource(), simple_textured_frag::getSource()); - testShaderBuild(simple_vert::getSource(), simple_textured_unlit_frag::getSource()); - testShaderBuild(deferred_light_vert::getSource(), directional_ambient_light_frag::getSource()); - testShaderBuild(deferred_light_vert::getSource(), directional_skybox_light_frag::getSource()); - testShaderBuild(standardTransformPNTC_vert::getSource(), standardDrawTexture_frag::getSource()); - testShaderBuild(standardTransformPNTC_vert::getSource(), DrawTextureOpaque_frag::getSource()); - - testShaderBuild(model_vert::getSource(), model_frag::getSource()); - testShaderBuild(model_normal_map_vert::getSource(), model_normal_map_frag::getSource()); - testShaderBuild(model_vert::getSource(), model_translucent_frag::getSource()); - testShaderBuild(model_normal_map_vert::getSource(), model_translucent_frag::getSource()); - testShaderBuild(model_lightmap_vert::getSource(), model_lightmap_frag::getSource()); - testShaderBuild(model_lightmap_normal_map_vert::getSource(), model_lightmap_normal_map_frag::getSource()); - - testShaderBuild(skin_model_vert::getSource(), model_frag::getSource()); - testShaderBuild(skin_model_normal_map_vert::getSource(), model_normal_map_frag::getSource()); - testShaderBuild(skin_model_vert::getSource(), model_translucent_frag::getSource()); - testShaderBuild(skin_model_normal_map_vert::getSource(), model_translucent_frag::getSource()); - - testShaderBuild(model_shadow_vert::getSource(), model_shadow_frag::getSource()); - testShaderBuild(textured_particle_vert::getSource(), textured_particle_frag::getSource()); -/* FIXME: Bring back the ssao shader tests - testShaderBuild(gaussian_blur_vert::getSource()ical_vert::getSource(), gaussian_blur_frag::getSource()); - testShaderBuild(gaussian_blur_horizontal_vert::getSource(), gaussian_blur_frag::getSource()); - testShaderBuild(ambient_occlusion_vert::getSource(), ambient_occlusion_frag::getSource()); - testShaderBuild(ambient_occlusion_vert::getSource(), occlusion_blend_frag::getSource()); -*/ - - testShaderBuild(paintStroke_vert::getSource(),paintStroke_frag::getSource()); - testShaderBuild(polyvox_vert::getSource(), polyvox_frag::getSource()); - - }); - _context.swapBuffers(this); -} - -const char * LOG_FILTER_RULES = R"V0G0N( -hifi.gpu=true -)V0G0N"; - -int main(int argc, char** argv) { - setupHifiApplication("Shaders Test"); - - QGuiApplication app(argc, argv); - QLoggingCategory::setFilterRules(LOG_FILTER_RULES); - QTestWindow window; - QTimer timer; - timer.setInterval(1); - app.connect(&timer, &QTimer::timeout, &app, [&] { - window.draw(); - }); - timer.start(); - app.exec(); - return 0; -} - -#include "main.moc" diff --git a/tests/gpu/src/ShaderLoadTest.cpp b/tests/gpu/src/ShaderLoadTest.cpp index 09752dc385..ba738bbe9a 100644 --- a/tests/gpu/src/ShaderLoadTest.cpp +++ b/tests/gpu/src/ShaderLoadTest.cpp @@ -23,23 +23,14 @@ #include +#include + QTEST_MAIN(ShaderLoadTest) extern std::atomic gpuBinaryShadersLoaded; extern const QString& getShaderCacheFile(); - -QtMessageHandler originalHandler; - -void messageHandler(QtMsgType type, const QMessageLogContext& context, const QString& message) { -#if defined(Q_OS_WIN) - OutputDebugStringA(message.toStdString().c_str()); - OutputDebugStringA("\n"); -#endif - originalHandler(type, context, message); -} - std::pair>> parseCachedShaderString(const QString& cachedShaderString) { std::pair>> result; @@ -194,7 +185,7 @@ bool ShaderLoadTest::buildProgram(const Program& programFiles) { } void ShaderLoadTest::initTestCase() { - originalHandler = qInstallMessageHandler(messageHandler); + installTestMessageHandler(); DependencyManager::set(); { const auto& shaderCacheFile = getShaderCacheFile(); @@ -237,9 +228,15 @@ void ShaderLoadTest::cleanupTestCase() { } void ShaderLoadTest::testShaderLoad() { - auto gpuContext = std::make_shared(); + _gpuContext = std::make_shared(); QVERIFY(gpuBinaryShadersLoaded == 0); + auto backend = std::static_pointer_cast(_gpuContext->getBackend()); + std::unordered_set shaderNames; + for (const auto& entry : _shaderSources) { + shaderNames.insert(entry.first); + } + QElapsedTimer timer; // Initial load of all the shaders @@ -252,7 +249,7 @@ void ShaderLoadTest::testShaderLoad() { qDebug() << "Uncached shader load took" << timer.elapsed() << "ms"; QVERIFY(gpuBinaryShadersLoaded == 0); } - gpuContext->recycle(); + _gpuContext->recycle(); glFinish(); // Reload the shaders within the same GPU context lifetime. @@ -270,10 +267,10 @@ void ShaderLoadTest::testShaderLoad() { // Shaders will use the cached binaries from disk { gpuBinaryShadersLoaded = 0; - gpuContext->recycle(); - gpuContext->shutdown(); - gpuContext.reset(); - gpuContext = std::make_shared(); + _gpuContext->recycle(); + _gpuContext->shutdown(); + _gpuContext.reset(); + _gpuContext = std::make_shared(); _canvas.makeCurrent(); timer.start(); for (const auto& program : _programs) { diff --git a/tests/gpu/src/ShaderLoadTest.h b/tests/gpu/src/ShaderLoadTest.h index cfb01501b2..8321d8e5e7 100644 --- a/tests/gpu/src/ShaderLoadTest.h +++ b/tests/gpu/src/ShaderLoadTest.h @@ -17,7 +17,7 @@ #include #include -#define USE_LOCAL_SHADERS 0 +#define USE_LOCAL_SHADERS 1 namespace std { template <> @@ -52,12 +52,11 @@ private slots: void cleanupTestCase(); void testShaderLoad(); - private: - ShadersByName _shaderSources; Programs _programs; QString _resourcesPath; OffscreenGLCanvas _canvas; + gpu::ContextPointer _gpuContext; const glm::uvec2 _size{ 640, 480 }; }; diff --git a/tests/gpu/src/TextureTest.cpp b/tests/gpu/src/TextureTest.cpp index 72fe1bfbfe..23e9c35dcc 100644 --- a/tests/gpu/src/TextureTest.cpp +++ b/tests/gpu/src/TextureTest.cpp @@ -16,12 +16,12 @@ #include #include #include -#include #include #include #include +#include QTEST_MAIN(TextureTest) @@ -86,7 +86,6 @@ void TextureTest::initTestCase() { gpu::Context::init(); _gpuContext = std::make_shared(); - if (QProcessEnvironment::systemEnvironment().contains(KTX_TEST_DIR_ENV)) { // For local testing with larger data sets _resourcesPath = QProcessEnvironment::systemEnvironment().value(KTX_TEST_DIR_ENV); @@ -94,16 +93,14 @@ void TextureTest::initTestCase() { _resourcesPath = QStandardPaths::writableLocation(QStandardPaths::TempLocation) + "/" + TEST_DIR_NAME; if (!QFileInfo(_resourcesPath).exists()) { QDir(_resourcesPath).mkpath("."); - FileDownloader(TEST_DATA, - [&](const QByteArray& data) { - QTemporaryFile zipFile; - if (zipFile.open()) { - zipFile.write(data); - zipFile.close(); - } - JlCompress::extractDir(zipFile.fileName(), _resourcesPath); - }) - .waitForDownload(); + downloadFile(TEST_DATA, [&](const QByteArray& data) { + QTemporaryFile zipFile; + if (zipFile.open()) { + zipFile.write(data); + zipFile.close(); + } + JlCompress::extractDir(zipFile.fileName(), _resourcesPath); + }); } } @@ -114,8 +111,6 @@ void TextureTest::initTestCase() { auto VS = gpu::Shader::createVertex(vertexShaderSource); auto PS = gpu::Shader::createPixel(fragmentShaderSource); auto program = gpu::Shader::createProgram(VS, PS); - gpu::Shader::BindingSet slotBindings; - gpu::Shader::makeProgram(*program, slotBindings); // If the pipeline did not exist, make it auto state = std::make_shared(); state->setCullMode(gpu::State::CULL_NONE); @@ -180,7 +175,6 @@ void TextureTest::endFrame() { QThread::msleep(10); } - void TextureTest::renderFrame(const std::function& renderLambda) { beginFrame(); gpu::doInBatch("Test::body", _gpuContext, renderLambda); @@ -190,7 +184,7 @@ void TextureTest::renderFrame(const std::function& renderLamb extern QString getTextureMemoryPressureModeString(); void TextureTest::testTextureLoading() { - QBENCHMARK{ + QBENCHMARK { _frameCount = 0; auto textures = loadTestTextures(); QVERIFY(textures.size() > 0); diff --git a/tests/physics/src/CollisionRenderMeshCacheTests.cpp b/tests/physics/src/CollisionRenderMeshCacheTests.cpp deleted file mode 100644 index da28598dda..0000000000 --- a/tests/physics/src/CollisionRenderMeshCacheTests.cpp +++ /dev/null @@ -1,277 +0,0 @@ -// -// CollisionRenderMeshCacheTests.cpp -// tests/physics/src -// -// Created by Andrew Meadows on 2014.10.30 -// Copyright 2014 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#include "CollisionRenderMeshCacheTests.h" - -#include -#include - -#include -#include - -#include -#include // for MAX_HULL_POINTS - -#include "MeshUtil.h" - - -QTEST_MAIN(CollisionRenderMeshCacheTests) - -const float INV_SQRT_THREE = 0.577350269f; - -const uint32_t numSphereDirections = 6 + 8; -btVector3 sphereDirections[] = { - btVector3(1.0f, 0.0f, 0.0f), - btVector3(-1.0f, 0.0f, 0.0f), - btVector3(0.0f, 1.0f, 0.0f), - btVector3(0.0f, -1.0f, 0.0f), - btVector3(0.0f, 0.0f, 1.0f), - btVector3(0.0f, 0.0f, -1.0f), - btVector3(INV_SQRT_THREE, INV_SQRT_THREE, INV_SQRT_THREE), - btVector3(INV_SQRT_THREE, INV_SQRT_THREE, -INV_SQRT_THREE), - btVector3(INV_SQRT_THREE, -INV_SQRT_THREE, INV_SQRT_THREE), - btVector3(INV_SQRT_THREE, -INV_SQRT_THREE, -INV_SQRT_THREE), - btVector3(-INV_SQRT_THREE, INV_SQRT_THREE, INV_SQRT_THREE), - btVector3(-INV_SQRT_THREE, INV_SQRT_THREE, -INV_SQRT_THREE), - btVector3(-INV_SQRT_THREE, -INV_SQRT_THREE, INV_SQRT_THREE), - btVector3(-INV_SQRT_THREE, -INV_SQRT_THREE, -INV_SQRT_THREE) -}; - -float randomFloat() { - return 2.0f * ((float)rand() / (float)RAND_MAX) - 1.0f; -} - -btBoxShape* createBoxShape(const btVector3& extent) { - btBoxShape* shape = new btBoxShape(0.5f * extent); - return shape; -} - -btConvexHullShape* createConvexHull(float radius) { - btConvexHullShape* hull = new btConvexHullShape(); - for (uint32_t i = 0; i < numSphereDirections; ++i) { - btVector3 point = radius * sphereDirections[i]; - hull->addPoint(point, false); - } - hull->recalcLocalAabb(); - return hull; -} - -void CollisionRenderMeshCacheTests::testShapeHullManifold() { - // make a box shape - btVector3 extent(1.0f, 2.0f, 3.0f); - btBoxShape* box = createBoxShape(extent); - - // wrap it with a ShapeHull - btShapeHull hull(box); - const float MARGIN = 0.0f; - hull.buildHull(MARGIN); - - // verify the vertex count is capped - uint32_t numVertices = (uint32_t)hull.numVertices(); - QVERIFY(numVertices <= MAX_HULL_POINTS); - - // verify the mesh is inside the radius - btVector3 halfExtents = box->getHalfExtentsWithMargin(); - float ACCEPTABLE_EXTENTS_ERROR = 0.01f; - float maxRadius = halfExtents.length() + ACCEPTABLE_EXTENTS_ERROR; - const btVector3* meshVertices = hull.getVertexPointer(); - for (uint32_t i = 0; i < numVertices; ++i) { - btVector3 vertex = meshVertices[i]; - QVERIFY(vertex.length() <= maxRadius); - } - - // verify the index count is capped - uint32_t numIndices = (uint32_t)hull.numIndices(); - QVERIFY(numIndices < 6 * MAX_HULL_POINTS); - - // verify the index count is a multiple of 3 - QVERIFY(numIndices % 3 == 0); - - // verify the mesh is closed - const uint32_t* meshIndices = hull.getIndexPointer(); - bool isClosed = MeshUtil::isClosedManifold(meshIndices, numIndices); - QVERIFY(isClosed); - - // verify the triangle normals are outward using right-hand-rule - const uint32_t INDICES_PER_TRIANGLE = 3; - for (uint32_t i = 0; i < numIndices; i += INDICES_PER_TRIANGLE) { - btVector3 A = meshVertices[meshIndices[i]]; - btVector3 B = meshVertices[meshIndices[i+1]]; - btVector3 C = meshVertices[meshIndices[i+2]]; - - btVector3 face = (B - A).cross(C - B); - btVector3 center = (A + B + C) / 3.0f; - QVERIFY(face.dot(center) > 0.0f); - } - - // delete unmanaged memory - delete box; -} - -void CollisionRenderMeshCacheTests::testCompoundShape() { - uint32_t numSubShapes = 3; - - btVector3 centers[] = { - btVector3(1.0f, 0.0f, 0.0f), - btVector3(0.0f, -2.0f, 0.0f), - btVector3(0.0f, 0.0f, 3.0f), - }; - - float radii[] = { 3.0f, 2.0f, 1.0f }; - - btCompoundShape* compoundShape = new btCompoundShape(); - for (uint32_t i = 0; i < numSubShapes; ++i) { - btTransform transform; - transform.setOrigin(centers[i]); - btConvexHullShape* hull = createConvexHull(radii[i]); - compoundShape->addChildShape(transform, hull); - } - - // create the cache - CollisionRenderMeshCache cache; - QVERIFY(cache.getNumMeshes() == 0); - - // get the mesh once - graphics::MeshPointer mesh = cache.getMesh(compoundShape); - QVERIFY((bool)mesh); - QVERIFY(cache.getNumMeshes() == 1); - - // get the mesh again - graphics::MeshPointer mesh2 = cache.getMesh(compoundShape); - QVERIFY(mesh2 == mesh); - QVERIFY(cache.getNumMeshes() == 1); - - // forget the mesh once - cache.releaseMesh(compoundShape); - mesh.reset(); - QVERIFY(cache.getNumMeshes() == 1); - - // collect garbage (should still cache mesh) - cache.collectGarbage(); - QVERIFY(cache.getNumMeshes() == 1); - - // forget the mesh a second time (should still cache mesh) - cache.releaseMesh(compoundShape); - mesh2.reset(); - QVERIFY(cache.getNumMeshes() == 1); - - // collect garbage (should no longer cache mesh) - cache.collectGarbage(); - QVERIFY(cache.getNumMeshes() == 0); - - // delete unmanaged memory - for (int i = 0; i < compoundShape->getNumChildShapes(); ++i) { - delete compoundShape->getChildShape(i); - } - delete compoundShape; -} - -void CollisionRenderMeshCacheTests::testMultipleShapes() { - // shapeA is compound of hulls - uint32_t numSubShapes = 3; - btVector3 centers[] = { - btVector3(1.0f, 0.0f, 0.0f), - btVector3(0.0f, -2.0f, 0.0f), - btVector3(0.0f, 0.0f, 3.0f), - }; - float radii[] = { 3.0f, 2.0f, 1.0f }; - btCompoundShape* shapeA = new btCompoundShape(); - for (uint32_t i = 0; i < numSubShapes; ++i) { - btTransform transform; - transform.setOrigin(centers[i]); - btConvexHullShape* hull = createConvexHull(radii[i]); - shapeA->addChildShape(transform, hull); - } - - // shapeB is compound of boxes - btVector3 extents[] = { - btVector3(1.0f, 2.0f, 3.0f), - btVector3(2.0f, 3.0f, 1.0f), - btVector3(3.0f, 1.0f, 2.0f), - }; - btCompoundShape* shapeB = new btCompoundShape(); - for (uint32_t i = 0; i < numSubShapes; ++i) { - btTransform transform; - transform.setOrigin(centers[i]); - btBoxShape* box = createBoxShape(extents[i]); - shapeB->addChildShape(transform, box); - } - - // shapeC is just a box - btVector3 extentC(7.0f, 3.0f, 5.0f); - btBoxShape* shapeC = createBoxShape(extentC); - - // create the cache - CollisionRenderMeshCache cache; - QVERIFY(cache.getNumMeshes() == 0); - - // get the meshes - graphics::MeshPointer meshA = cache.getMesh(shapeA); - graphics::MeshPointer meshB = cache.getMesh(shapeB); - graphics::MeshPointer meshC = cache.getMesh(shapeC); - QVERIFY((bool)meshA); - QVERIFY((bool)meshB); - QVERIFY((bool)meshC); - QVERIFY(cache.getNumMeshes() == 3); - - // get the meshes again - graphics::MeshPointer meshA2 = cache.getMesh(shapeA); - graphics::MeshPointer meshB2 = cache.getMesh(shapeB); - graphics::MeshPointer meshC2 = cache.getMesh(shapeC); - QVERIFY(meshA == meshA2); - QVERIFY(meshB == meshB2); - QVERIFY(meshC == meshC2); - QVERIFY(cache.getNumMeshes() == 3); - - // forget the meshes once - cache.releaseMesh(shapeA); - cache.releaseMesh(shapeB); - cache.releaseMesh(shapeC); - meshA2.reset(); - meshB2.reset(); - meshC2.reset(); - QVERIFY(cache.getNumMeshes() == 3); - - // collect garbage (should still cache mesh) - cache.collectGarbage(); - QVERIFY(cache.getNumMeshes() == 3); - - // forget again, one mesh at a time... - // shapeA... - cache.releaseMesh(shapeA); - meshA.reset(); - QVERIFY(cache.getNumMeshes() == 3); - cache.collectGarbage(); - QVERIFY(cache.getNumMeshes() == 2); - // shapeB... - cache.releaseMesh(shapeB); - meshB.reset(); - QVERIFY(cache.getNumMeshes() == 2); - cache.collectGarbage(); - QVERIFY(cache.getNumMeshes() == 1); - // shapeC... - cache.releaseMesh(shapeC); - meshC.reset(); - QVERIFY(cache.getNumMeshes() == 1); - cache.collectGarbage(); - QVERIFY(cache.getNumMeshes() == 0); - - // delete unmanaged memory - for (int i = 0; i < shapeA->getNumChildShapes(); ++i) { - delete shapeA->getChildShape(i); - } - delete shapeA; - for (int i = 0; i < shapeB->getNumChildShapes(); ++i) { - delete shapeB->getChildShape(i); - } - delete shapeB; - delete shapeC; -} diff --git a/tests/physics/src/CollisionRenderMeshCacheTests.h b/tests/physics/src/CollisionRenderMeshCacheTests.h deleted file mode 100644 index 640314a2a0..0000000000 --- a/tests/physics/src/CollisionRenderMeshCacheTests.h +++ /dev/null @@ -1,26 +0,0 @@ -// -// CollisionRenderMeshCacheTests.h -// tests/physics/src -// -// Created by Andrew Meadows on 2014.10.30 -// Copyright 2014 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#ifndef hifi_CollisionRenderMeshCacheTests_h -#define hifi_CollisionRenderMeshCacheTests_h - -#include - -class CollisionRenderMeshCacheTests : public QObject { - Q_OBJECT - -private slots: - void testShapeHullManifold(); - void testCompoundShape(); - void testMultipleShapes(); -}; - -#endif // hifi_CollisionRenderMeshCacheTests_h diff --git a/tests/shaders/CMakeLists.txt b/tests/shaders/CMakeLists.txt new file mode 100644 index 0000000000..08678c1c26 --- /dev/null +++ b/tests/shaders/CMakeLists.txt @@ -0,0 +1,9 @@ + +# Declare dependencies +macro (setup_testcase_dependencies) + # link in the shared libraries + link_hifi_libraries(shared test-utils gpu shaders gl ${PLATFORM_GL_BACKEND}) + package_libraries_for_deployment() +endmacro () + +setup_hifi_testcase(Gui) diff --git a/tests/shaders/src/ShaderTests.cpp b/tests/shaders/src/ShaderTests.cpp new file mode 100644 index 0000000000..4dd15710f9 --- /dev/null +++ b/tests/shaders/src/ShaderTests.cpp @@ -0,0 +1,315 @@ +// +// ShaderTests.cpp +// tests/octree/src +// +// Created by Andrew Meadows on 2016.02.19 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "ShaderTests.h" + +#include +#include +#include +#include + +#include + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +QTEST_MAIN(ShaderTests) + +#pragma optimize("", off) +void ShaderTests::initTestCase() { + _window = new QWindow(); + _window->setSurfaceType(QSurface::SurfaceType::OpenGLSurface); + _context = new ::gl::Context(_window); + getDefaultOpenGLSurfaceFormat(); + _context->create(); + if (!_context->makeCurrent()) { + qFatal("Unable to make test GL context current"); + } + QOpenGLContextWrapper(_context->qglContext()).makeCurrent(_window); + if (!_context->makeCurrent()) { + qFatal("Unable to make test GL context current"); + } + gl::initModuleGl(); + if (!_context->makeCurrent()) { + qFatal("Unable to make test GL context current"); + } + gpu::Context::init(); + if (!_context->makeCurrent()) { + qFatal("Unable to make test GL context current"); + } + _gpuContext = std::make_shared(); +} + +void ShaderTests::cleanupTestCase() { + qDebug() << "Done"; +} + +template +QStringList toQStringList(const C& c) { + QStringList result; + for (const auto& v : c) { + result << v.c_str(); + } + return result; +} + +template +std::unordered_set toStringSet(const C& c, F f) { + std::unordered_set result; + for (const auto& v : c) { + result.insert(f(v)); + } + return result; +} + +template +bool isSubset(const C& parent, const C& child) { + for (const auto& v : child) { + if (0 == parent.count(v)) { + return false; + } + } + return true; +} + +gpu::Shader::ReflectionMap mergeReflection(const std::initializer_list& list) { + gpu::Shader::ReflectionMap result; + std::unordered_map> usedLocationsByType; + for (const auto& source : list) { + const auto& reflection = source.getReflection(); + for (const auto& entry : reflection) { + const auto& type = entry.first; + if (entry.first == gpu::Shader::BindingType::INPUT || entry.first == gpu::Shader::BindingType::OUTPUT) { + continue; + } + auto& outLocationMap = result[type]; + auto& usedLocations = usedLocationsByType[type]; + const auto& locationMap = entry.second; + for (const auto& entry : locationMap) { + const auto& name = entry.first; + const auto& location = entry.second; + if (0 != usedLocations.count(location) && usedLocations[location] != name) { + qWarning() << QString("Location %1 used by both %2 and %3") + .arg(location) + .arg(name.c_str()) + .arg(usedLocations[location].c_str()); + throw std::runtime_error("Location collision"); + } + usedLocations[location] = name; + outLocationMap[name] = location; + } + } + } + return result; +} + +template +std::unordered_map invertMap(const std::unordered_map& map) { + std::unordered_map result; + for (const auto& entry : map) { + result[entry.second] = entry.first; + } + return result; +} + +static void verifyBindings(const gpu::Shader::Source& source) { + const auto reflection = source.getReflection(); + for (const auto& entry : reflection) { + const auto& map = entry.second; + const auto reverseMap = invertMap(map); + if (map.size() != reverseMap.size()) { + QFAIL("Bindings are not unique"); + } + } + +} + + +static void verifyInterface(const gpu::Shader::Source& vertexSource, const gpu::Shader::Source& fragmentSource) { + if (0 == fragmentSource.getReflection().count(gpu::Shader::BindingType::INPUT)) { + return; + } + auto fragIn = fragmentSource.getReflection().at(gpu::Shader::BindingType::INPUT); + if (0 == vertexSource.getReflection().count(gpu::Shader::BindingType::OUTPUT)) { + qDebug() << "No vertex output for fragment input"; + //QFAIL("No vertex output for fragment input"); + return; + } + auto vout = vertexSource.getReflection().at(gpu::Shader::BindingType::OUTPUT); + auto vrev = invertMap(vout); + static const std::string IN_STEREO_SIDE_STRING = "_inStereoSide"; + for (const auto entry : fragIn) { + const auto& name = entry.first; + // The presence of "_inStereoSide" in fragment shaders is a bug due to the way we do reflection + // and use preprocessor macros in the shaders + if (name == IN_STEREO_SIDE_STRING) { + continue; + } + if (0 == vout.count(name)) { + qDebug() << "Vertex output missing"; + //QFAIL("Vertex output missing"); + continue; + } + const auto& inLocation = entry.second; + const auto& outLocation = vout.at(name); + if (inLocation != outLocation) { + qDebug() << "Mismatch in vertex / fragment interface"; + //QFAIL("Mismatch in vertex / fragment interface"); + continue; + } + } +} + +template +bool compareBindings(const C& actual, const gpu::Shader::LocationMap& expected) { + if (actual.size() != expected.size()) { + auto actualNames = toStringSet(actual, [](const auto& v) { return v.name; }); + auto expectedNames = toStringSet(expected, [](const auto& v) { return v.first; }); + if (!isSubset(expectedNames, actualNames)) { + qDebug() << "Found" << toQStringList(actualNames); + qDebug() << "Expected" << toQStringList(expectedNames); + return false; + } + } + return true; +} + +void ShaderTests::testShaderLoad() { + std::set usedShaders; + uint32_t maxShader = 0; + try { + +#if 0 + uint32_t testPrograms[] = { + shader::render_utils::program::parabola, + shader::INVALID_PROGRAM, + }; +#else + const auto& testPrograms = shader::all_programs; +#endif + + size_t index = 0; + while (shader::INVALID_PROGRAM != testPrograms[index]) { + auto programId = testPrograms[index]; + ++index; + + uint32_t vertexId = shader::getVertexId(programId); + uint32_t fragmentId = shader::getFragmentId(programId); + usedShaders.insert(vertexId); + usedShaders.insert(fragmentId); + maxShader = std::max(maxShader, std::max(fragmentId, vertexId)); + auto vertexSource = gpu::Shader::getShaderSource(vertexId); + QVERIFY(!vertexSource.getCode().empty()); + verifyBindings(vertexSource); + auto fragmentSource = gpu::Shader::getShaderSource(fragmentId); + QVERIFY(!fragmentSource.getCode().empty()); + verifyBindings(fragmentSource); + verifyInterface(vertexSource, fragmentSource); + + auto expectedBindings = mergeReflection({ vertexSource, fragmentSource }); + + auto program = gpu::Shader::createProgram(programId); + auto glBackend = std::static_pointer_cast(_gpuContext->getBackend()); + auto glshader = gpu::gl::GLShader::sync(*glBackend, *program); + if (!glshader) { + qDebug() << "Failed to compile or link vertex " << vertexId << " fragment " << fragmentId; + continue; + } + + QVERIFY(glshader != nullptr); + for (const auto& shaderObject : glshader->_shaderObjects) { + const auto& program = shaderObject.glprogram; + + // Uniforms + { + auto uniforms = gl::Uniform::load(program); + const auto& uniformRemap = shaderObject.uniformRemap; + auto expectedUniforms = expectedBindings[gpu::Shader::BindingType::UNIFORM]; + if (!compareBindings(uniforms, expectedUniforms)) { + qDebug() << "Uniforms mismatch"; + } + for (const auto& uniform : uniforms) { + if (0 != expectedUniforms.count(uniform.name)) { + auto expectedLocation = expectedUniforms[uniform.name]; + if (0 != uniformRemap.count(expectedLocation)) { + expectedLocation = uniformRemap.at(expectedLocation); + } + QVERIFY(expectedLocation == uniform.binding); + } + } + } + + // Textures + { + auto textures = gl::Uniform::loadTextures(program); + auto expiredBegin = std::remove_if(textures.begin(), textures.end(), [&](const gl::Uniform& uniform) -> bool { + return uniform.name == "transformObjectBuffer"; + }); + textures.erase(expiredBegin, textures.end()); + + const auto expectedTextures = expectedBindings[gpu::Shader::BindingType::TEXTURE]; + if (!compareBindings(textures, expectedTextures)) { + qDebug() << "Textures mismatch"; + } + for (const auto& texture : textures) { + if (0 != expectedTextures.count(texture.name)) { + const auto& location = texture.binding; + const auto& expectedUnit = expectedTextures.at(texture.name); + GLint actualUnit = -1; + glGetUniformiv(program, location, &actualUnit); + QVERIFY(expectedUnit == actualUnit); + } + } + } + + // UBOs + { + auto ubos = gl::UniformBlock::load(program); + auto expectedUbos = expectedBindings[gpu::Shader::BindingType::UNIFORM_BUFFER]; + if (!compareBindings(ubos, expectedUbos)) { + qDebug() << "UBOs mismatch"; + } + for (const auto& ubo : ubos) { + if (0 != expectedUbos.count(ubo.name)) { + QVERIFY(expectedUbos[ubo.name] == ubo.binding); + } + } + } + + // FIXME add storage buffer validation + } + } + } catch (const std::runtime_error& error) { + QFAIL(error.what()); + } + + for (uint32_t i = 0; i <= maxShader; ++i) { + auto used = usedShaders.count(i); + if (0 != usedShaders.count(i)) { + continue; + } + auto reflectionJson = shader::loadShaderReflection(i); + auto name = QJsonDocument::fromJson(reflectionJson.c_str()).object()["name"].toString(); + qDebug() << "Unused shader" << name; + } + + qDebug() << "Completed all shaders"; +} diff --git a/tests/shaders/src/ShaderTests.h b/tests/shaders/src/ShaderTests.h new file mode 100644 index 0000000000..d109341c1f --- /dev/null +++ b/tests/shaders/src/ShaderTests.h @@ -0,0 +1,31 @@ +// +// Created by Bradley Austin Davis on 2018/06/21 +// Copyright 2013-2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_ShaderTests_h +#define hifi_ShaderTests_h + +#include +#include +#include +#include + +class ShaderTests : public QObject { + Q_OBJECT + +private slots: + void initTestCase(); + void cleanupTestCase(); + void testShaderLoad(); + +private: + QWindow* _window{ nullptr }; + gl::Context* _context{ nullptr }; + gpu::ContextPointer _gpuContext; +}; + +#endif // hifi_ViewFruxtumTests_h diff --git a/tests/shared/src/AACubeTests.cpp b/tests/shared/src/AACubeTests.cpp index 469dcfa981..4ed3ee2813 100644 --- a/tests/shared/src/AACubeTests.cpp +++ b/tests/shared/src/AACubeTests.cpp @@ -152,3 +152,48 @@ void AACubeTests::touchesSphere() { } } +void AACubeTests::rayVsParabolaPerformance() { + // Test performance of findRayIntersection vs. findParabolaIntersection + // 100000 cubes with scale 500 in the +x +y +z quadrant + const int NUM_CUBES = 100000; + const float MAX_POS = 1000.0f; + const float MAX_SCALE = 500.0f; + int numRayHits = 0; + int numParabolaHits = 0; + std::vector cubes; + cubes.reserve(NUM_CUBES); + for (int i = 0; i < NUM_CUBES; i++) { + cubes.emplace_back(glm::vec3(randFloatInRange(0.0f, MAX_POS), randFloatInRange(0.0f, MAX_POS), randFloatInRange(0.0f, MAX_POS)), MAX_SCALE); + } + + glm::vec3 origin(0.0f); + glm::vec3 direction = glm::normalize(glm::vec3(1.0f)); + glm::vec3 invDirection = 1.0f / direction; + float distance; + BoxFace face; + glm::vec3 normal; + auto start = std::chrono::high_resolution_clock::now(); + for (auto& cube : cubes) { + if (cube.findRayIntersection(origin, direction, invDirection, distance, face, normal)) { + numRayHits++; + } + } + + auto rayTime = std::chrono::high_resolution_clock::now() - start; + start = std::chrono::high_resolution_clock::now(); + direction = 10.0f * direction; + glm::vec3 acceleration = glm::vec3(-0.0001f, -0.0001f, -0.0001f); + for (auto& cube : cubes) { + if (cube.findParabolaIntersection(origin, direction, acceleration, distance, face, normal)) { + numParabolaHits++; + } + } + auto parabolaTime = std::chrono::high_resolution_clock::now() - start; + + qDebug() << "Ray vs. Parabola perfomance: rayHit%:" << numRayHits / ((float)NUM_CUBES) * 100.0f << ", rayTime:" << rayTime.count() << + ", parabolaHit%:" << numParabolaHits / ((float)NUM_CUBES) * 100.0f << ", parabolaTime:" << parabolaTime.count() << ", parabolaTime/rayTime: " << (float)parabolaTime.count()/(float)rayTime.count(); +} + +void AACubeTests::cleanupTestCase() { + +} \ No newline at end of file diff --git a/tests/shared/src/AACubeTests.h b/tests/shared/src/AACubeTests.h index a2b2e08cc5..569c978929 100644 --- a/tests/shared/src/AACubeTests.h +++ b/tests/shared/src/AACubeTests.h @@ -23,6 +23,8 @@ private slots: void ctorsAndSetters(); void containsPoint(); void touchesSphere(); + void rayVsParabolaPerformance(); + void cleanupTestCase(); }; #endif // hifi_AACubeTests_h diff --git a/tests/shared/src/GLMHelpersTests.cpp b/tests/shared/src/GLMHelpersTests.cpp index 93c4735a6d..71877e89f6 100644 --- a/tests/shared/src/GLMHelpersTests.cpp +++ b/tests/shared/src/GLMHelpersTests.cpp @@ -214,3 +214,39 @@ void GLMHelpersTests::testGenerateBasisVectors() { QCOMPARE_WITH_ABS_ERROR(w, z, EPSILON); } } + +void GLMHelpersTests::roundPerf() { + const int NUM_VECS = 1000000; + const float MAX_VEC = 500.0f; + std::vector vecs; + vecs.reserve(NUM_VECS); + for (int i = 0; i < NUM_VECS; i++) { + vecs.emplace_back(randFloatInRange(-MAX_VEC, MAX_VEC), randFloatInRange(-MAX_VEC, MAX_VEC), randFloatInRange(-MAX_VEC, MAX_VEC)); + } + std::vector vecs2 = vecs; + std::vector originalVecs = vecs; + + auto start = std::chrono::high_resolution_clock::now(); + for (auto& vec : vecs) { + vec = glm::round(vec); + } + + auto glmTime = std::chrono::high_resolution_clock::now() - start; + start = std::chrono::high_resolution_clock::now(); + for (auto& vec : vecs2) { + vec = glm::vec3(fastLrintf(vec.x), fastLrintf(vec.y), fastLrintf(vec.z)); + } + auto manualTime = std::chrono::high_resolution_clock::now() - start; + + bool identical = true; + for (int i = 0; i < vecs.size(); i++) { + identical &= vecs[i] == vecs2[i]; + if (vecs[i] != vecs2[i]) { + qDebug() << "glm: " << vecs[i].x << vecs[i].y << vecs[i].z << ", manual: " << vecs2[i].x << vecs2[i].y << vecs2[i].z; + qDebug() << "original: " << originalVecs[i].x << originalVecs[i].y << originalVecs[i].z; + break; + } + } + + qDebug() << "ratio: " << (float)glmTime.count() / (float)manualTime.count() << ", identical: " << identical; +} \ No newline at end of file diff --git a/tests/shared/src/GLMHelpersTests.h b/tests/shared/src/GLMHelpersTests.h index 030f2d477f..4d9bd0bb60 100644 --- a/tests/shared/src/GLMHelpersTests.h +++ b/tests/shared/src/GLMHelpersTests.h @@ -22,6 +22,7 @@ private slots: void testSixByteOrientationCompression(); void testSimd(); void testGenerateBasisVectors(); + void roundPerf(); }; float getErrorDifference(const float& a, const float& b); diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index 1c36306410..9b36180bc2 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -2,6 +2,9 @@ add_subdirectory(scribe) set_target_properties(scribe PROPERTIES FOLDER "Tools") +add_subdirectory(shreflect) +set_target_properties(shreflect PROPERTIES FOLDER "Tools") + find_npm() if (NPM_EXECUTABLE) add_subdirectory(jsdoc) diff --git a/tools/auto-tester/CMakeLists.txt b/tools/auto-tester/CMakeLists.txt index a2589bb760..1546a35f4c 100644 --- a/tools/auto-tester/CMakeLists.txt +++ b/tools/auto-tester/CMakeLists.txt @@ -5,7 +5,7 @@ project(${TARGET_NAME}) SET (CMAKE_AUTOUIC ON) SET (CMAKE_AUTOMOC ON) -setup_hifi_project (Core Widgets Network) +setup_hifi_project (Core Widgets Network Xml) link_hifi_libraries () # FIX: Qt was built with -reduce-relocations @@ -18,7 +18,7 @@ include_directories (${CMAKE_CURRENT_SOURCE_DIR}) include_directories (${Qt5Core_INCLUDE_DIRS}) include_directories (${Qt5Widgets_INCLUDE_DIRS}) -set (QT_LIBRARIES Qt5::Core Qt5::Widgets) +set (QT_LIBRARIES Qt5::Core Qt5::Widgets QT::Gui Qt5::Xml) if (WIN32) # Do not show Console diff --git a/tools/auto-tester/Create.PNG b/tools/auto-tester/Create.PNG new file mode 100644 index 0000000000..d82d4873a2 Binary files /dev/null and b/tools/auto-tester/Create.PNG differ diff --git a/tools/auto-tester/Evaluate.PNG b/tools/auto-tester/Evaluate.PNG new file mode 100644 index 0000000000..d530dec994 Binary files /dev/null and b/tools/auto-tester/Evaluate.PNG differ diff --git a/tools/auto-tester/README.md b/tools/auto-tester/README.md new file mode 100644 index 0000000000..0924e77f8b --- /dev/null +++ b/tools/auto-tester/README.md @@ -0,0 +1,197 @@ +# Auto Tester + +The auto-tester is a stand alone application that provides a mechanism for regression testing. The general idea is simple: +* Each test folder has a script that produces a set of snapshots. +* The snapshots are compared to a 'canonical' set of images that have been produced beforehand. +* The result, if any test failed, is a zipped folder describing the failure. + +Auto-tester has 4 functions, separated into 4 tabs: +1. Creating tests, MD files and recursive scripts +2. Evaluating the results of running tests +3. TestRail interface +4. Windows task bar utility (Windows only) +## Installation +### Executable +1. Download the installer by browsing to [here](). +2. Double click on the installer and install to a convenient location +![](./setup_7z.PNG) +3. To run the auto-tester, double click **auto-tester.exe**. +### Python +The TestRail interface requires Python 3 to be installed. Auto-Tester has been tested with Python 3.7.0 but should work with newer versions. + +Python 3 can be downloaded from: +1. Windows installer +2. Linux (source) (**Gzipped source tarball**) +3. Mac (**macOS 64-bit/32-bit installer** or **macOS 64-bit/32-bit installer**) + +After installation - create an environment variable called PYTHON_PATH and set it to the folder containing the Python executable. +# Create +![](./Create.PNG) + +The Create tab provides functions to create tests from snapshots, MD files, a test outline and recursive scripts. +## Create Tests +### Usage +This function is used to create/update Expected Images after a successful run of a test, or multiple tests. + +The user will be asked for the snapshot folder and then the tests root folder. All snapshots located in the snapshot folder will be used to create or update the expected images in the relevant tests. +### Details +As an example - if the snapshots folder contains an image named `tests.content.entity.zone.zoneOrientation.00003.png`, then this file will be copied to `tests/contente/enity/zone/zoneOrientation/ExpectedImage0003.png`. +## Create MD file +### Usage +This function creates a file named `test.md` from a `test.js` script. The user will be asked for the folder containing the test script: +### Details +The process to produce the MD file is a simplistic parse of the test script. +- The string in the `autoTester.perform(...)` function call will be the title of the file + +- Instructions to run the script are then provided: + +**Run this script URL: [Manual]() [Auto]()(from menu/Edit/Open and Run scripts from URL...).** + +- The step description is the string in the addStep/addStepStepSnapshot commands + +- Image links are provided where applicable to the local Expected Images files +## Create all MD files +### Usage +This function creates all MD files recursively from the user-selected root folder. This can be any folder in the tests hierarchy (e.g. all engine\material tests). + +The file provides a hierarchial list of all the tests +## Create Tests Outline +### Usage +This function creates an MD file in the (user-selected) tests root folder. The file provides links to both the tests and the MD files. +## Create Recursive Script +### Usage +After the user selects a folder within the tests hierarchy, a script is created, named `testRecursive.js`. This script calls all `test.js` scripts in the subfolders. +### Details +The various scripts are called in alphabetical order. + +An example of a recursive script is as follows: +``` +// This is an automatically generated file, created by auto-tester on Jul 5 2018, 10:19 + +PATH_TO_THE_REPO_PATH_UTILS_FILE = "https://raw.githubusercontent.com/highfidelity/hifi_tests/master/tests/utils/branchUtils.js"; +Script.include(PATH_TO_THE_REPO_PATH_UTILS_FILE); +var autoTester = createAutoTester(Script.resolvePath(".")); + +var testsRootPath = autoTester.getTestsRootPath(); + +if (typeof Test !== 'undefined') { + Test.wait(10000); +}; + +autoTester.enableRecursive(); +autoTester.enableAuto(); + +Script.include(testsRootPath + "content/overlay/layer/drawInFront/shape/test.js"); +Script.include(testsRootPath + "content/overlay/layer/drawInFront/model/test.js"); +Script.include(testsRootPath + "content/overlay/layer/drawHUDLayer/test.js"); + +autoTester.runRecursive(); +``` +## Create all Recursive Scripts +### Usage +In this case all recursive scripts, from the selected folder down, are created. + +Running this function in the tests root folder will create (or update) all the recursive scripts. +# Evaluate +![](./Evaluate.PNG) + +The Evaluate tab provides a single function - evaluating the results of a test run. + +A checkbox (defaulting to checked) runs the evaluation in interactive mode. In this mode - every failure is shown to the user, who can then decide whether to pass the test, fail it or abort the whole evaluation. + +If any tests have failed, then a zipped folder will be created in the snapshots folder, with a description of each failed step in each test. +### Usage +Before starting the evaluation, make sure the GitHub user and branch are set correctly. The user should not normally be changed, but the branch may need to be set to the appropriate RC. + +After setting the checkbox as required and pressing Evaluate - the user will be asked for the snapshots folder. +### Details +Evaluation proceeds in a number of steps: + +1. A folder is created to store any failures + +1. The expecetd images are download from GitHub. They are named slightly differently from the snapshots (e.g. `tests.engine.render.effect.highlight.coverage.00000.png` and `tests.engine.render.effect.highlight.coverage.00000_EI.png`). + +1. The images are then pair-wise compared, using the SSIM algorithm. A fixed threshold is used to define a mismatch. + +1. In interactive mode - a window is opened showing the expected image, actual image, difference image and error: +![](./autoTesterMismatchExample.PNG) + +1. If not in interactive mode, or the user has defined the results as an error, an error is written into the error folder. The error itself is a folder with the 3 images and a small text file containing details. + +1. At the end of the test, the folder is zipped and the original folder is deleted. If there are no errors then the zipped folder will be empty. +# TestRail +![](./TestRail.PNG) + +Before updating TestRail, make sure the GitHub user and branch are set correctly. The user should not normally be changed, but the branch may need to be set to the appropriate RC. + +Any access to TestRail will require the TestRail account (default is High Fidelity's account), a user-name and a password: + +![](./TestRailSelector.PNG) + +- The default test rail user is shown, and can be changed as needed. +- The username is usually the user's email. +- The Project ID defaults to 14 - Interface. +- The Suite ID defaults to 1147 - Renderong. +- The TestRail page provides 3 functions for writing to TestRail. +## Create Test Cases +### Usage +This function can either create an XML file that can then be imported into TestRail through TestRail itself, or automatically create the appropriate TestRail Sections. + +The user will be first asked for the tests root folder and a folder to store temporary files (this is the output folder). + +If XML has been selected, then the XML file will be created in the output folder. + +If Python is selected, the user will then be prompted for TestRail data. After pressing `Accept` - the Release combo will be populated (as it needs to be read from TestRail). + +After selecting the appropriate Release, press OK. The Python script will be created in the output folder, and the user will be prompted to run it. + +A busy window will appear until the process is complete. +### Details +A number of Python scripts are created: +- `testrail.py` is the TestRail interface code. +- `stack.py` is a simple stack class +- `getReleases.py` reads the release names from TestRail +- `addTestCases` is the script that writes to TestRail. + +In addition - a file containing all the releases will be created - `releases.txt` +## Create Run +A Run is created from previously created Test Cases. + +The user will first be prompted for a temporary folder (for the Python scripts). + +After entering TestRail data and pressing `Accept` - the Sections combo will be populated (as it needs to be read from TestRail). + +After selecting the appropriate Section, press OK. The Python script will be created in the output folder, and the user will be prompted to run it. + +A busy window will appear until the process is complete. +### Details +A number of Python scripts are created: +- `testrail.py` is the TestRail interface code. +- `stack.py` is a simple stack class +- `getSections.py` reads the release names from TestRail +- `addRun` is the script that writes to TestRail. + +In addition - a file containing all the releases will be created - `sections.txt` +## Update Run Results +This function updates a Run with the results of an automated test. + +The user will first be prompted to enter the zipped results folder and a folder to store temporary files (this is the output folder). + +After entering TestRail data and pressing `Accept` - the Run combo will be populated (as it needs to be read from TestRail). + +After selecting the appropriate Run, press OK. The Python script will be created in the output folder, and the user will be prompted to run it. + +A busy window will appear until the process is complete. +### Details +A number of Python scripts are created: +- `testrail.py` is the TestRail interface code. +- `getRuns.py` reads the release names from TestRail +- `addRun` is the script that writes to TestRail. + +In addition - a file containing all the releases will be created - `runs.txt` +# Windows +![](./Windows.PNG) + +This tab is Windows-specific. It provides buttons to hide and show the task bar. + +The task bar should be hidden for all tests that use the primary camera. This is required to ensure that the snapshots are the right size. \ No newline at end of file diff --git a/tools/auto-tester/ReadMe.md b/tools/auto-tester/ReadMe.md deleted file mode 100644 index 57ec7ea623..0000000000 --- a/tools/auto-tester/ReadMe.md +++ /dev/null @@ -1,7 +0,0 @@ -After building auto-tester, it needs to be deployed to Amazon SW - -* In folder hifi/build/tools/auto-tester - * Right click on the Release folder - * Select 7-Zip -> Add to archive - * Select Option ```Create SFX archive``` to create Release.exe -* Use Cyberduck (or any other AWS S3 client) to copy Release.exe to hifi-content/nissim/autoTester/ \ No newline at end of file diff --git a/tools/auto-tester/TestRail.PNG b/tools/auto-tester/TestRail.PNG new file mode 100644 index 0000000000..042d0cf1cb Binary files /dev/null and b/tools/auto-tester/TestRail.PNG differ diff --git a/tools/auto-tester/TestRailSelector.PNG b/tools/auto-tester/TestRailSelector.PNG new file mode 100644 index 0000000000..00bcb360ed Binary files /dev/null and b/tools/auto-tester/TestRailSelector.PNG differ diff --git a/tools/auto-tester/Windows.PNG b/tools/auto-tester/Windows.PNG new file mode 100644 index 0000000000..bf7b76ba02 Binary files /dev/null and b/tools/auto-tester/Windows.PNG differ diff --git a/tools/auto-tester/autoTesterMismatchExample.PNG b/tools/auto-tester/autoTesterMismatchExample.PNG new file mode 100644 index 0000000000..ddabd2ed7f Binary files /dev/null and b/tools/auto-tester/autoTesterMismatchExample.PNG differ diff --git a/tools/auto-tester/setup_7z.PNG b/tools/auto-tester/setup_7z.PNG new file mode 100644 index 0000000000..aae4123cdf Binary files /dev/null and b/tools/auto-tester/setup_7z.PNG differ diff --git a/tools/auto-tester/src/Test.cpp b/tools/auto-tester/src/Test.cpp index 4f02544c12..3da789f405 100644 --- a/tools/auto-tester/src/Test.cpp +++ b/tools/auto-tester/src/Test.cpp @@ -24,45 +24,43 @@ extern AutoTester* autoTester; #include Test::Test() { - mismatchWindow.setModal(true); + _mismatchWindow.setModal(true); if (autoTester) { - autoTester->setUserText("highfidelity"); - autoTester->setBranchText("master"); + autoTester->setUserText(GIT_HUB_DEFAULT_USER); + autoTester->setBranchText(GIT_HUB_DEFAULT_BRANCH); } } bool Test::createTestResultsFolderPath(const QString& directory) { QDateTime now = QDateTime::currentDateTime(); - testResultsFolderPath = directory + "/" + TEST_RESULTS_FOLDER + "--" + now.toString(DATETIME_FORMAT); - QDir testResultsFolder(testResultsFolderPath); + _testResultsFolderPath = directory + "/" + TEST_RESULTS_FOLDER + "--" + now.toString(DATETIME_FORMAT); + QDir testResultsFolder(_testResultsFolderPath); // Create a new test results folder - return QDir().mkdir(testResultsFolderPath); + return QDir().mkdir(_testResultsFolderPath); } void Test::zipAndDeleteTestResultsFolder() { - QString zippedResultsFileName { testResultsFolderPath + ".zip" }; + QString zippedResultsFileName { _testResultsFolderPath + ".zip" }; QFileInfo fileInfo(zippedResultsFileName); if (!fileInfo.exists()) { QFile::remove(zippedResultsFileName); } - QDir testResultsFolder(testResultsFolderPath); - if (!testResultsFolder.isEmpty()) { - JlCompress::compressDir(testResultsFolderPath + ".zip", testResultsFolderPath); - } + QDir testResultsFolder(_testResultsFolderPath); + JlCompress::compressDir(_testResultsFolderPath + ".zip", _testResultsFolderPath); testResultsFolder.removeRecursively(); //In all cases, for the next evaluation - testResultsFolderPath = ""; - index = 1; + _testResultsFolderPath = ""; + _index = 1; } bool Test::compareImageLists(bool isInteractiveMode, QProgressBar* progressBar) { progressBar->setMinimum(0); - progressBar->setMaximum(expectedImagesFullFilenames.length() - 1); + progressBar->setMaximum(_expectedImagesFullFilenames.length() - 1); progressBar->setValue(0); progressBar->setVisible(true); @@ -70,10 +68,10 @@ bool Test::compareImageLists(bool isInteractiveMode, QProgressBar* progressBar) // Quit loop if user has aborted due to a failed test. bool success{ true }; bool keepOn{ true }; - for (int i = 0; keepOn && i < expectedImagesFullFilenames.length(); ++i) { + for (int i = 0; keepOn && i < _expectedImagesFullFilenames.length(); ++i) { // First check that images are the same size - QImage resultImage(resultImagesFullFilenames[i]); - QImage expectedImage(expectedImagesFullFilenames[i]); + QImage resultImage(_resultImagesFullFilenames[i]); + QImage expectedImage(_expectedImagesFullFilenames[i]); double similarityIndex; // in [-1.0 .. 1.0], where 1.0 means images are identical @@ -82,30 +80,30 @@ bool Test::compareImageLists(bool isInteractiveMode, QProgressBar* progressBar) QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Images are not the same size"); similarityIndex = -100.0; } else { - similarityIndex = imageComparer.compareImages(resultImage, expectedImage); + similarityIndex = _imageComparer.compareImages(resultImage, expectedImage); } if (similarityIndex < THRESHOLD) { TestFailure testFailure = TestFailure{ (float)similarityIndex, - expectedImagesFullFilenames[i].left(expectedImagesFullFilenames[i].lastIndexOf("/") + 1), // path to the test (including trailing /) - QFileInfo(expectedImagesFullFilenames[i].toStdString().c_str()).fileName(), // filename of expected image - QFileInfo(resultImagesFullFilenames[i].toStdString().c_str()).fileName() // filename of result image + _expectedImagesFullFilenames[i].left(_expectedImagesFullFilenames[i].lastIndexOf("/") + 1), // path to the test (including trailing /) + QFileInfo(_expectedImagesFullFilenames[i].toStdString().c_str()).fileName(), // filename of expected image + QFileInfo(_resultImagesFullFilenames[i].toStdString().c_str()).fileName() // filename of result image }; - mismatchWindow.setTestFailure(testFailure); + _mismatchWindow.setTestFailure(testFailure); if (!isInteractiveMode) { - appendTestResultsToFile(testResultsFolderPath, testFailure, mismatchWindow.getComparisonImage()); + appendTestResultsToFile(_testResultsFolderPath, testFailure, _mismatchWindow.getComparisonImage()); success = false; } else { - mismatchWindow.exec(); + _mismatchWindow.exec(); - switch (mismatchWindow.getUserResponse()) { + switch (_mismatchWindow.getUserResponse()) { case USER_RESPONSE_PASS: break; case USE_RESPONSE_FAIL: - appendTestResultsToFile(testResultsFolderPath, testFailure, mismatchWindow.getComparisonImage()); + appendTestResultsToFile(_testResultsFolderPath, testFailure, _mismatchWindow.getComparisonImage()); success = false; break; case USER_RESPONSE_ABORT: @@ -126,20 +124,18 @@ bool Test::compareImageLists(bool isInteractiveMode, QProgressBar* progressBar) return success; } -void Test::appendTestResultsToFile(const QString& testResultsFolderPath, TestFailure testFailure, QPixmap comparisonImage) { - if (!QDir().exists(testResultsFolderPath)) { - QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Folder " + testResultsFolderPath + " not found"); +void Test::appendTestResultsToFile(const QString& _testResultsFolderPath, TestFailure testFailure, QPixmap comparisonImage) { + if (!QDir().exists(_testResultsFolderPath)) { + QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Folder " + _testResultsFolderPath + " not found"); exit(-1); } - QString err = QString::number(testFailure._error).left(6); - - QString failureFolderPath { testResultsFolderPath + "/" + err + "-Failure_" + QString::number(index) + "--" + testFailure._actualImageFilename.left(testFailure._actualImageFilename.length() - 4) }; + QString failureFolderPath { _testResultsFolderPath + "/Failure_" + QString::number(_index) + "--" + testFailure._actualImageFilename.left(testFailure._actualImageFilename.length() - 4) }; if (!QDir().mkdir(failureFolderPath)) { QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Failed to create folder " + failureFolderPath); exit(-1); } - ++index; + ++_index; QFile descriptionFile(failureFolderPath + "/" + TEST_RESULTS_FILENAME); if (!descriptionFile.open(QIODevice::ReadWrite)) { @@ -152,7 +148,7 @@ void Test::appendTestResultsToFile(const QString& testResultsFolderPath, TestFai stream << "Test failed in folder " << testFailure._pathname.left(testFailure._pathname.length() - 1) << endl; // remove trailing '/' stream << "Expected image was " << testFailure._expectedImageFilename << endl; stream << "Actual image was " << testFailure._actualImageFilename << endl; - stream << "Similarity index was " << testFailure._error << endl; + stream << "Similarity _index was " << testFailure._error << endl; descriptionFile.close(); @@ -180,26 +176,26 @@ void Test::appendTestResultsToFile(const QString& testResultsFolderPath, TestFai void Test::startTestsEvaluation(const QString& testFolder, const QString& branchFromCommandLine, const QString& userFromCommandLine) { if (testFolder.isNull()) { // Get list of JPEG images in folder, sorted by name - QString previousSelection = snapshotDirectory; + QString previousSelection = _snapshotDirectory; QString parent = previousSelection.left(previousSelection.lastIndexOf('/')); if (!parent.isNull() && parent.right(1) != "/") { parent += "/"; } - snapshotDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select folder containing the test images", parent, + _snapshotDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select folder containing the test images", parent, QFileDialog::ShowDirsOnly); // If user cancelled then restore previous selection and return - if (snapshotDirectory == "") { - snapshotDirectory = previousSelection; + if (_snapshotDirectory == "") { + _snapshotDirectory = previousSelection; return; } } else { - snapshotDirectory = testFolder; - exitWhenComplete = true; + _snapshotDirectory = testFolder; + _exitWhenComplete = true; } // Quit if test results folder could not be created - if (!createTestResultsFolderPath(snapshotDirectory)) { + if (!createTestResultsFolderPath(_snapshotDirectory)) { return; } @@ -207,20 +203,20 @@ void Test::startTestsEvaluation(const QString& testFolder, const QString& branch // The expected images are represented as a URL to enable download from GitHub // Images that are in the wrong format are ignored. - QStringList sortedTestResultsFilenames = createListOfAll_imagesInDirectory("png", snapshotDirectory); + QStringList sortedTestResultsFilenames = createListOfAll_imagesInDirectory("png", _snapshotDirectory); QStringList expectedImagesURLs; - resultImagesFullFilenames.clear(); - expectedImagesFilenames.clear(); - expectedImagesFullFilenames.clear(); + _resultImagesFullFilenames.clear(); + _expectedImagesFilenames.clear(); + _expectedImagesFullFilenames.clear(); QString branch = (branchFromCommandLine.isNull()) ? autoTester->getSelectedBranch() : branchFromCommandLine; QString user = (userFromCommandLine.isNull()) ? autoTester->getSelectedUser() : userFromCommandLine; foreach(QString currentFilename, sortedTestResultsFilenames) { - QString fullCurrentFilename = snapshotDirectory + "/" + currentFilename; + QString fullCurrentFilename = _snapshotDirectory + "/" + currentFilename; if (isInSnapshotFilenameFormat("png", currentFilename)) { - resultImagesFullFilenames << fullCurrentFilename; + _resultImagesFullFilenames << fullCurrentFilename; QString expectedImagePartialSourceDirectory = getExpectedImagePartialSourceDirectory(currentFilename); @@ -237,12 +233,12 @@ void Test::startTestsEvaluation(const QString& testFolder, const QString& branch // The image retrieved from GitHub needs a unique name QString expectedImageFilename = currentFilename.replace("/", "_").replace(".png", "_EI.png"); - expectedImagesFilenames << expectedImageFilename; - expectedImagesFullFilenames << snapshotDirectory + "/" + expectedImageFilename; + _expectedImagesFilenames << expectedImageFilename; + _expectedImagesFullFilenames << _snapshotDirectory + "/" + expectedImageFilename; } } - autoTester->downloadImages(expectedImagesURLs, snapshotDirectory, expectedImagesFilenames); + autoTester->downloadImages(expectedImagesURLs, _snapshotDirectory, _expectedImagesFilenames); } void Test::finishTestsEvaluation(bool isRunningFromCommandline, bool interactiveMode, QProgressBar* progressBar) { @@ -258,7 +254,7 @@ void Test::finishTestsEvaluation(bool isRunningFromCommandline, bool interactive zipAndDeleteTestResultsFolder(); - if (exitWhenComplete) { + if (_exitWhenComplete) { exit(0); } } @@ -310,46 +306,46 @@ void Test::includeTest(QTextStream& textStream, const QString& testPathname) { // This script will run all text.js scripts in every applicable sub-folder void Test::createRecursiveScript() { // Select folder to start recursing from - QString previousSelection = testDirectory; + QString previousSelection = _testDirectory; QString parent = previousSelection.left(previousSelection.lastIndexOf('/')); if (!parent.isNull() && parent.right(1) != "/") { parent += "/"; } - testDirectory = + _testDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select folder that will contain the top level test script", parent, QFileDialog::ShowDirsOnly); // If user cancelled then restore previous selection and return - if (testDirectory == "") { - testDirectory = previousSelection; + if (_testDirectory == "") { + _testDirectory = previousSelection; return; } - createRecursiveScript(testDirectory, true); + createRecursiveScript(_testDirectory, true); } // This method creates a `testRecursive.js` script in every sub-folder. void Test::createAllRecursiveScripts() { // Select folder to start recursing from - QString previousSelection = testsRootDirectory; + QString previousSelection = _testsRootDirectory; QString parent = previousSelection.left(previousSelection.lastIndexOf('/')); if (!parent.isNull() && parent.right(1) != "/") { parent += "/"; } - testsRootDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select the root folder for the recursive scripts", + _testsRootDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select the root folder for the recursive scripts", parent, QFileDialog::ShowDirsOnly); // If user cancelled then restore previous selection and return - if (testsRootDirectory == "") { - testsRootDirectory = previousSelection; + if (_testsRootDirectory == "") { + _testsRootDirectory = previousSelection; return; } - createRecursiveScript(testsRootDirectory, false); + createRecursiveScript(_testsRootDirectory, false); - QDirIterator it(testsRootDirectory.toStdString().c_str(), QDirIterator::Subdirectories); + QDirIterator it(_testsRootDirectory.toStdString().c_str(), QDirIterator::Subdirectories); while (it.hasNext()) { QString directory = it.next(); @@ -477,42 +473,42 @@ void Test::createRecursiveScript(const QString& topLevelDirectory, bool interact void Test::createTests() { // Rename files sequentially, as ExpectedResult_00000.jpeg, ExpectedResult_00001.jpg and so on // Any existing expected result images will be deleted - QString previousSelection = snapshotDirectory; + QString previousSelection = _snapshotDirectory; QString parent = previousSelection.left(previousSelection.lastIndexOf('/')); if (!parent.isNull() && parent.right(1) != "/") { parent += "/"; } - snapshotDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select folder containing the test images", parent, + _snapshotDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select folder containing the test images", parent, QFileDialog::ShowDirsOnly); // If user cancelled then restore previous selection and return - if (snapshotDirectory == "") { - snapshotDirectory = previousSelection; + if (_snapshotDirectory == "") { + _snapshotDirectory = previousSelection; return; } - previousSelection = testsRootDirectory; + previousSelection = _testsRootDirectory; parent = previousSelection.left(previousSelection.lastIndexOf('/')); if (!parent.isNull() && parent.right(1) != "/") { parent += "/"; } - testsRootDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select test root folder", parent, + _testsRootDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select test root folder", parent, QFileDialog::ShowDirsOnly); // If user cancelled then restore previous selection and return - if (testsRootDirectory == "") { - testsRootDirectory = previousSelection; + if (_testsRootDirectory == "") { + _testsRootDirectory = previousSelection; return; } - QStringList sortedImageFilenames = createListOfAll_imagesInDirectory("png", snapshotDirectory); + QStringList sortedImageFilenames = createListOfAll_imagesInDirectory("png", _snapshotDirectory); int i = 1; const int maxImages = pow(10, NUM_DIGITS); foreach (QString currentFilename, sortedImageFilenames) { - QString fullCurrentFilename = snapshotDirectory + "/" + currentFilename; + QString fullCurrentFilename = _snapshotDirectory + "/" + currentFilename; if (isInSnapshotFilenameFormat("png", currentFilename)) { if (i >= maxImages) { QMessageBox::critical(0, "Error", "More than " + QString::number(maxImages) + " images not supported"); @@ -522,17 +518,17 @@ void Test::createTests() { // Path to test is extracted from the file name // Example: // filename is tests.engine.interaction.pointer.laser.distanceScaleEnd.00000.jpg - // path is /engine/interaction/pointer/laser/distanceScaleEnd + // path is <_testDirectory>/engine/interaction/pointer/laser/distanceScaleEnd // // Note: we don't use the first part and the last 2 parts of the filename at this stage // QStringList pathParts = currentFilename.split("."); - QString fullNewFileName = testsRootDirectory; + QString fullNewFileName = _testsRootDirectory; for (int j = 1; j < pathParts.size() - 2; ++j) { fullNewFileName += "/" + pathParts[j]; } - // The image index is the penultimate component of the path parts (the last being the file extension) + // The image _index is the penultimate component of the path parts (the last being the file extension) QString newFilename = "ExpectedImage_" + pathParts[pathParts.size() - 2].rightJustified(5, '0') + ".png"; fullNewFileName += "/" + newFilename; @@ -621,51 +617,51 @@ ExtractedText Test::getTestScriptLines(QString testFileName) { // The folder selected must contain a script named "test.js", the file produced is named "test.md" void Test::createMDFile() { // Folder selection - QString previousSelection = testDirectory; + QString previousSelection = _testDirectory; QString parent = previousSelection.left(previousSelection.lastIndexOf('/')); if (!parent.isNull() && parent.right(1) != "/") { parent += "/"; } - testDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select folder containing the test", parent, + _testDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select folder containing the test", parent, QFileDialog::ShowDirsOnly); // If user cancelled then restore previous selection and return - if (testDirectory == "") { - testDirectory = previousSelection; + if (_testDirectory == "") { + _testDirectory = previousSelection; return; } - createMDFile(testDirectory); + createMDFile(_testDirectory); QMessageBox::information(0, "Success", "MD file has been created"); } void Test::createAllMDFiles() { // Select folder to start recursing from - QString previousSelection = testsRootDirectory; + QString previousSelection = _testsRootDirectory; QString parent = previousSelection.left(previousSelection.lastIndexOf('/')); if (!parent.isNull() && parent.right(1) != "/") { parent += "/"; } - testsRootDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select the root folder for the MD files", parent, + _testsRootDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select the root folder for the MD files", parent, QFileDialog::ShowDirsOnly); // If user cancelled then restore previous selection and return - if (testsRootDirectory == "") { - testsRootDirectory = previousSelection; + if (_testsRootDirectory == "") { + _testsRootDirectory = previousSelection; return; } // First test if top-level folder has a test.js file - const QString testPathname{ testsRootDirectory + "/" + TEST_FILENAME }; + const QString testPathname{ _testsRootDirectory + "/" + TEST_FILENAME }; QFileInfo fileInfo(testPathname); if (fileInfo.exists()) { - createMDFile(testsRootDirectory); + createMDFile(_testsRootDirectory); } - QDirIterator it(testsRootDirectory.toStdString().c_str(), QDirIterator::Subdirectories); + QDirIterator it(_testsRootDirectory.toStdString().c_str(), QDirIterator::Subdirectories); while (it.hasNext()) { QString directory = it.next(); @@ -685,9 +681,9 @@ void Test::createAllMDFiles() { QMessageBox::information(0, "Success", "MD files have been created"); } -void Test::createMDFile(const QString& testDirectory) { +void Test::createMDFile(const QString& _testDirectory) { // Verify folder contains test.js file - QString testFileName(testDirectory + "/" + TEST_FILENAME); + QString testFileName(_testDirectory + "/" + TEST_FILENAME); QFileInfo testFileInfo(testFileName); if (!testFileInfo.exists()) { QMessageBox::critical(0, "Error", "Could not find file: " + TEST_FILENAME); @@ -696,7 +692,7 @@ void Test::createMDFile(const QString& testDirectory) { ExtractedText testScriptLines = getTestScriptLines(testFileName); - QString mdFilename(testDirectory + "/" + "test.md"); + QString mdFilename(_testDirectory + "/" + "test.md"); QFile mdFile(mdFilename); if (!mdFile.open(QIODevice::WriteOnly)) { QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Failed to create file " + mdFilename); @@ -710,7 +706,7 @@ void Test::createMDFile(const QString& testDirectory) { stream << "# " << testName << "\n"; // Find the relevant part of the path to the test (i.e. from "tests" down - QString partialPath = extractPathFromTestsDown(testDirectory); + QString partialPath = extractPathFromTestsDown(_testDirectory); stream << "## Run this script URL: [Manual](./test.js?raw=true) [Auto](./testAuto.js?raw=true)(from menu/Edit/Open and Run scripts from URL...)." << "\n\n"; @@ -734,23 +730,23 @@ void Test::createMDFile(const QString& testDirectory) { } void Test::createTestsOutline() { - QString previousSelection = testDirectory; + QString previousSelection = _testDirectory; QString parent = previousSelection.left(previousSelection.lastIndexOf('/')); if (!parent.isNull() && parent.right(1) != "/") { parent += "/"; } - testDirectory = + _testDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select the tests root folder", parent, QFileDialog::ShowDirsOnly); // If user cancelled then restore previous selection and return - if (testDirectory == "") { - testDirectory = previousSelection; + if (_testDirectory == "") { + _testDirectory = previousSelection; return; } const QString testsOutlineFilename { "testsOutline.md" }; - QString mdFilename(testDirectory + "/" + testsOutlineFilename); + QString mdFilename(_testDirectory + "/" + testsOutlineFilename); QFile mdFile(mdFilename); if (!mdFile.open(QIODevice::WriteOnly)) { QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Failed to create file " + mdFilename); @@ -764,10 +760,10 @@ void Test::createTestsOutline() { stream << "Directories with an appended (*) have an automatic test\n\n"; // We need to know our current depth, as this isn't given by QDirIterator - int rootDepth { testDirectory.count('/') }; + int rootDepth { _testDirectory.count('/') }; // Each test is shown as the folder name linking to the matching GitHub URL, and the path to the associated test.md file - QDirIterator it(testDirectory.toStdString().c_str(), QDirIterator::Subdirectories); + QDirIterator it(_testDirectory.toStdString().c_str(), QDirIterator::Subdirectories); while (it.hasNext()) { QString directory = it.next(); @@ -821,12 +817,72 @@ void Test::createTestsOutline() { QMessageBox::information(0, "Success", "Test outline file " + testsOutlineFilename + " has been created"); } +void Test::createTestRailTestCases() { + QString previousSelection = _testDirectory; + QString parent = previousSelection.left(previousSelection.lastIndexOf('/')); + if (!parent.isNull() && parent.right(1) != "/") { + parent += "/"; + } + + _testDirectory = + QFileDialog::getExistingDirectory(nullptr, "Please select the tests root folder", parent, QFileDialog::ShowDirsOnly); + + // If user cancelled then restore previous selection and return + if (_testDirectory.isNull()) { + _testDirectory = previousSelection; + return; + } + + QString outputDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select a folder to store generated files in", + nullptr, QFileDialog::ShowDirsOnly); + + // If user cancelled then return + if (outputDirectory.isNull()) { + return; + } + + if (_testRailCreateMode == PYTHON) { + _testRailInterface.createTestSuitePython(_testDirectory, outputDirectory, autoTester->getSelectedUser(), + autoTester->getSelectedBranch()); + } else { + _testRailInterface.createTestSuiteXML(_testDirectory, outputDirectory, autoTester->getSelectedUser(), + autoTester->getSelectedBranch()); + } +} + +void Test::createTestRailRun() { + QString outputDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select a folder to store generated files in", + nullptr, QFileDialog::ShowDirsOnly); + + if (outputDirectory.isNull()) { + return; + } + + _testRailInterface.createTestRailRun(outputDirectory); +} + +void Test::updateTestRailRunResult() { + QString testResults = QFileDialog::getOpenFileName(nullptr, "Please select the zipped test results to update from", nullptr, + "Zipped Test Results (*.zip)"); + if (testResults.isNull()) { + return; + } + + QString tempDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select a folder to store temporary files in", + nullptr, QFileDialog::ShowDirsOnly); + if (tempDirectory.isNull()) { + return; + } + + _testRailInterface.updateTestRailRunResults(testResults, tempDirectory); +} + QStringList Test::createListOfAll_imagesInDirectory(const QString& imageFormat, const QString& pathToImageDirectory) { - imageDirectory = QDir(pathToImageDirectory); + _imageDirectory = QDir(pathToImageDirectory); QStringList nameFilters; nameFilters << "*." + imageFormat; - return imageDirectory.entryList(nameFilters, QDir::Files, QDir::Name); + return _imageDirectory.entryList(nameFilters, QDir::Files, QDir::Name); } // Snapshots are files in the following format: @@ -889,3 +945,7 @@ QString Test::getExpectedImagePartialSourceDirectory(const QString& filename) { return result; } + +void Test::setTestRailCreateMode(TestRailCreateMode testRailCreateMode) { + _testRailCreateMode = testRailCreateMode; +} diff --git a/tools/auto-tester/src/Test.h b/tools/auto-tester/src/Test.h index 5c6d3e5686..853e9c98e2 100644 --- a/tools/auto-tester/src/Test.h +++ b/tools/auto-tester/src/Test.h @@ -18,6 +18,7 @@ #include "ImageComparer.h" #include "ui/MismatchWindow.h" +#include "TestRailInterface.h" class Step { public: @@ -33,6 +34,11 @@ public: StepList stepList; }; +enum TestRailCreateMode { + PYTHON, + XML +}; + class Test { public: Test(); @@ -45,11 +51,16 @@ public: void createRecursiveScript(const QString& topLevelDirectory, bool interactiveMode); void createTests(); + + void createTestsOutline(); + void createMDFile(); void createAllMDFiles(); void createMDFile(const QString& topLevelDirectory); - void createTestsOutline(); + void createTestRailTestCases(); + void createTestRailRun(); + void updateTestRailRunResult(); bool compareImageLists(bool isInteractiveMode, QProgressBar* progressBar); @@ -64,11 +75,15 @@ public: bool createTestResultsFolderPath(const QString& directory); void zipAndDeleteTestResultsFolder(); - bool isAValidDirectory(const QString& pathname); + static bool isAValidDirectory(const QString& pathname); QString extractPathFromTestsDown(const QString& fullPath); QString getExpectedImageDestinationDirectory(const QString& filename); QString getExpectedImagePartialSourceDirectory(const QString& filename); + ExtractedText getTestScriptLines(QString testFileName); + + void setTestRailCreateMode(TestRailCreateMode testRailCreateMode); + private: const QString TEST_FILENAME { "test.js" }; const QString TEST_RESULTS_FOLDER { "TestResults" }; @@ -76,14 +91,14 @@ private: const double THRESHOLD{ 0.96 }; - QDir imageDirectory; + QDir _imageDirectory; - MismatchWindow mismatchWindow; + MismatchWindow _mismatchWindow; - ImageComparer imageComparer; + ImageComparer _imageComparer; - QString testResultsFolderPath; - int index { 1 }; + QString _testResultsFolderPath; + int _index { 1 }; // Expected images are in the format ExpectedImage_dddd.jpg (d == decimal digit) const int NUM_DIGITS { 5 }; @@ -93,28 +108,32 @@ private: // The first is the directory containing the test we are working with // The second is the root directory of all tests // The third contains the snapshots taken for test runs that need to be evaluated - QString testDirectory; - QString testsRootDirectory; - QString snapshotDirectory; + QString _testDirectory; + QString _testsRootDirectory; + QString _snapshotDirectory; - QStringList expectedImagesFilenames; - QStringList expectedImagesFullFilenames; - QStringList resultImagesFullFilenames; + QStringList _expectedImagesFilenames; + QStringList _expectedImagesFullFilenames; + QStringList _resultImagesFullFilenames; // Used for accessing GitHub + const QString GIT_HUB_DEFAULT_USER{ "highfidelity" }; + const QString GIT_HUB_DEFAULT_BRANCH{ "master" }; const QString GIT_HUB_REPOSITORY{ "hifi_tests" }; const QString DATETIME_FORMAT{ "yyyy-MM-dd_hh-mm-ss" }; - ExtractedText getTestScriptLines(QString testFileName); - // NOTE: these need to match the appropriate var's in autoTester.js // var advanceKey = "n"; // var pathSeparator = "."; const QString ADVANCE_KEY{ "n" }; const QString PATH_SEPARATOR{ "." }; - bool exitWhenComplete{ false }; + bool _exitWhenComplete{ false }; + + TestRailInterface _testRailInterface; + + TestRailCreateMode _testRailCreateMode { PYTHON }; }; #endif // hifi_test_h \ No newline at end of file diff --git a/tools/auto-tester/src/TestRailInterface.cpp b/tools/auto-tester/src/TestRailInterface.cpp new file mode 100644 index 0000000000..93559490e5 --- /dev/null +++ b/tools/auto-tester/src/TestRailInterface.cpp @@ -0,0 +1,1174 @@ +// +// TestRailInterface.cpp +// +// Created by Nissim Hadar on 6 Jul 2018. +// Copyright 2013 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "TestRailInterface.h" +#include "Test.h" + +#include +#include + +#include +#include +#include +#include + +TestRailInterface::TestRailInterface() { + _testRailTestCasesSelectorWindow.setURL("https://highfidelity.testrail.net"); + ////_testRailTestCasesSelectorWindow.setURL("https://nissimhadar.testrail.io"); + _testRailTestCasesSelectorWindow.setUser("@highfidelity.io"); + ////_testRailTestCasesSelectorWindow.setUser("nissim.hadar@gmail.com"); + + _testRailTestCasesSelectorWindow.setProjectID(INTERFACE_PROJECT_ID); + ////_testRailTestCasesSelectorWindow.setProjectID(2); + + _testRailTestCasesSelectorWindow.setSuiteID(INTERFACE_SUITE_ID); + ////_testRailTestCasesSelectorWindow.setSuiteID(2); + + _testRailRunSelectorWindow.setURL("https://highfidelity.testrail.net"); + ////_testRailRunSelectorWindow.setURL("https://nissimhadar.testrail.io"); + _testRailRunSelectorWindow.setUser("@highfidelity.io"); + ////_testRailRunSelectorWindow.setUser("nissim.hadar@gmail.com"); + + _testRailRunSelectorWindow.setProjectID(INTERFACE_PROJECT_ID); + ////_testRailRunSelectorWindow.setProjectID(2); + + _testRailRunSelectorWindow.setSuiteID(INTERFACE_SUITE_ID); + ////_testRailRunSelectorWindow.setSuiteID(2); + + _testRailResultsSelectorWindow.setURL("https://highfidelity.testrail.net"); + ////_testRailResultsSelectorWindow.setURL("https://nissimhadar.testrail.io"); + _testRailResultsSelectorWindow.setUser("@highfidelity.io"); + ////_testRailResultsSelectorWindow.setUser("nissim.hadar@gmail.com"); + + _testRailResultsSelectorWindow.setProjectID(INTERFACE_PROJECT_ID); + ////_testRailResultsSelectorWindow.setProjectID(2); + + _testRailResultsSelectorWindow.setSuiteID(INTERFACE_SUITE_ID); + ////_testRailResultsSelectorWindow.setSuiteID(2); +} + +QString TestRailInterface::getObject(const QString& path) { + return path.right(path.length() - path.lastIndexOf("/") - 1); +} + + +bool TestRailInterface::setPythonCommand() { + if (QProcessEnvironment::systemEnvironment().contains("PYTHON_PATH")) { + QString _pythonPath = QProcessEnvironment::systemEnvironment().value("PYTHON_PATH"); + if (!QFile::exists(_pythonPath + "/" + pythonExe)) { + QMessageBox::critical(0, pythonExe, QString("Python executable not found in ") + _pythonPath); + } + _pythonCommand = _pythonPath + "/" + pythonExe; + return true; + } else { + QMessageBox::critical(0, "PYTHON_PATH not defined", + "Please set PYTHON_PATH to directory containing the Python executable"); + return false; + } + + return false; +} + +// Creates the testrail.py script +// This is the file linked to from http://docs.gurock.com/testrail-api2/bindings-python +void TestRailInterface::createTestRailDotPyScript() { + QFile file(_outputDirectory + "/testrail.py"); + if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { + QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), + "Could not create 'testrail.py'"); + exit(-1); + } + + QTextStream stream(&file); + + stream << "#\n"; + stream << "# TestRail API binding for Python 3.x (API v2, available since \n"; + stream << "# TestRail 3.0)\n"; + stream << "#\n"; + stream << "# Learn more:\n"; + stream << "#\n"; + stream << "# http://docs.gurock.com/testrail-api2/start\n"; + stream << "# http://docs.gurock.com/testrail-api2/accessing\n"; + stream << "#\n"; + stream << "# Copyright Gurock Software GmbH. See license.md for details.\n"; + stream << "#\n"; + stream << "\n"; + stream << "import urllib.request, urllib.error\n"; + stream << "import json, base64\n"; + stream << "\n"; + stream << "class APIClient:\n"; + stream << "\tdef __init__(self, base_url):\n"; + stream << "\t\tself.user = ''\n"; + stream << "\t\tself.password = ''\n"; + stream << "\t\tif not base_url.endswith('/'):\n"; + stream << "\t\t\tbase_url += '/'\n"; + stream << "\t\tself.__url = base_url + 'index.php?/api/v2/'\n"; + stream << "\n"; + stream << "\t#\n"; + stream << "\t# Send Get\n"; + stream << "\t#\n"; + stream << "\t# Issues a GET request (read) against the API and returns the result\n"; + stream << "\t# (as Python dict).\n"; + stream << "\t#\n"; + stream << "\t# Arguments:\n"; + stream << "\t#\n"; + stream << "\t# uri The API method to call including parameters\n"; + stream << "\t# (e.g. get_case/1)\n"; + stream << "\t#\n"; + stream << "\tdef send_get(self, uri):\n"; + stream << "\t\treturn self.__send_request('GET', uri, None)\n"; + stream << "\n"; + stream << "\t#\n"; + stream << "\t# Send POST\n"; + stream << "\t#\n"; + stream << "\t# Issues a POST request (write) against the API and returns the result\n"; + stream << "\t# (as Python dict).\n"; + stream << "\t#\n"; + stream << "\t# Arguments:\n"; + stream << "\t#\n"; + stream << "\t# uri The API method to call including parameters\n"; + stream << "\t# (e.g. add_case/1)\n"; + stream << "\t# data The data to submit as part of the request (as\n"; + stream << "\t# Python dict, strings must be UTF-8 encoded)\n"; + stream << "\t#\n"; + stream << "\tdef send_post(self, uri, data):\n"; + stream << "\t\treturn self.__send_request('POST', uri, data)\n"; + stream << "\n"; + stream << "\tdef __send_request(self, method, uri, data):\n"; + stream << "\t\turl = self.__url + uri\n"; + stream << "\t\trequest = urllib.request.Request(url)\n"; + stream << "\t\tif (method == 'POST'):\n"; + stream << "\t\t\trequest.data = bytes(json.dumps(data), 'utf-8')\n"; + stream << "\t\tauth = str(\n"; + stream << "\t\t\tbase64.b64encode(\n"; + stream << "\t\t\t\tbytes('%s:%s' % (self.user, self.password), 'utf-8')\n"; + stream << "\t\t\t),\n"; + stream << "\t\t\t'ascii'\n"; + stream << "\t\t).strip()\n"; + stream << "\t\trequest.add_header('Authorization', 'Basic %s' % auth)\n"; + stream << "\t\trequest.add_header('Content-Type', 'application/json')\n"; + stream << "\n"; + stream << "\t\te = None\n"; + stream << "\t\ttry:\n"; + stream << "\t\t\tresponse = urllib.request.urlopen(request).read()\n"; + stream << "\t\texcept urllib.error.HTTPError as ex:\n"; + stream << "\t\t\tresponse = ex.read()\n"; + stream << "\t\t\te = ex\n"; + stream << "\n"; + stream << "\t\tif response:\n"; + stream << "\t\t\tresult = json.loads(response.decode())\n"; + stream << "\t\telse:\n"; + stream << "\t\t\tresult = {}\n"; + stream << "\n"; + stream << "\t\tif e != None:\n"; + stream << "\t\t\tif result and 'error' in result:\n"; + stream << "\t\t\t\terror = '\"' + result['error'] + '\"'\n"; + stream << "\t\t\telse:\n"; + stream << "\t\t\t\terror = 'No additional error message received'\n"; + stream << "\t\t\traise APIError('TestRail API returned HTTP %s (%s)' % \n"; + stream << "\t\t\t\t(e.code, error))\n"; + stream << "\n"; + stream << "\t\treturn result\n"; + stream << "\n"; + stream << "class APIError(Exception):\n"; + stream << "\tpass\n"; + + file.close(); +} + +// Creates a Stack class +void TestRailInterface::createStackDotPyScript() { + QString filename = _outputDirectory + "/stack.py"; + if (QFile::exists(filename)) { + QFile::remove(filename); + } + QFile file(filename); + + if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { + QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), + "Could not create 'stack.py'"); + exit(-1); + } + + QTextStream stream(&file); + + stream << "class Stack:\n"; + + stream << "\tdef __init__(self):\n"; + stream << "\t\tself.items = []\n"; + stream << "\n"; + + stream << "\tdef is_empty(self):\n"; + stream << "\t\treturn self.items == []\n"; + stream << "\n"; + + stream << "\tdef push(self, item):\n"; + stream << "\t\tself.items.append(item)\n"; + stream << "\n"; + + stream << "\tdef pop(self):\n"; + stream << "\t\treturn self.items.pop()\n"; + stream << "\n"; + + stream << "\tdef peek(self):\n"; + stream << "\t\treturn self.items[len(self.items)-1]\n"; + stream << "\n"; + + stream << "\tdef size(self):\n"; + stream << "\t\treturn len(self.items)\n"; + stream << "\n"; + + file.close(); +} + +bool TestRailInterface::requestTestRailTestCasesDataFromUser() { + // Make sure correct fields are enabled before calling + _testRailTestCasesSelectorWindow.reset(); + _testRailTestCasesSelectorWindow.exec(); + + if (_testRailTestCasesSelectorWindow.getUserCancelled()) { + return false; + } + + _url = _testRailTestCasesSelectorWindow.getURL() + "/"; + _user = _testRailTestCasesSelectorWindow.getUser(); + _password = _testRailTestCasesSelectorWindow.getPassword(); + ////_password = "tutKA76";//// + _projectID = QString::number(_testRailTestCasesSelectorWindow.getProjectID()); + _suiteID = QString::number(_testRailTestCasesSelectorWindow.getSuiteID()); + + return true; +} + +bool TestRailInterface::requestTestRailRunDataFromUser() { + _testRailRunSelectorWindow.reset(); + _testRailRunSelectorWindow.exec(); + + if (_testRailRunSelectorWindow.getUserCancelled()) { + return false; + } + + _url = _testRailRunSelectorWindow.getURL() + "/"; + _user = _testRailRunSelectorWindow.getUser(); + _password = _testRailRunSelectorWindow.getPassword(); + ////_password = "tutKA76";//// + _projectID = QString::number(_testRailRunSelectorWindow.getProjectID()); + _suiteID = QString::number(_testRailRunSelectorWindow.getSuiteID()); + + return true; +} + +bool TestRailInterface::requestTestRailResultsDataFromUser() { + _testRailResultsSelectorWindow.reset(); + _testRailResultsSelectorWindow.exec(); + + if (_testRailResultsSelectorWindow.getUserCancelled()) { + return false; + } + + _url = _testRailResultsSelectorWindow.getURL() + "/"; + _user = _testRailResultsSelectorWindow.getUser(); + _password = _testRailResultsSelectorWindow.getPassword(); + ////_password = "tutKA76";//// + _projectID = QString::number(_testRailResultsSelectorWindow.getProjectID()); + _suiteID = QString::number(_testRailResultsSelectorWindow.getSuiteID()); + + return true; +} + +bool TestRailInterface::isAValidTestDirectory(const QString& directory) { + if (Test::isAValidDirectory(directory)) { + // Ignore the utils and preformance directories + if (directory.right(QString("utils").length()) == "utils" || + directory.right(QString("performance").length()) == "performance") { + return false; + } + return true; + } + + return false; +} + +void TestRailInterface::processDirectoryPython(const QString& directory, + QTextStream& stream, + const QString& userGitHub, + const QString& branchGitHub) { + // Loop over all entries in directory + QDirIterator it(directory.toStdString().c_str()); + while (it.hasNext()) { + QString nextDirectory = it.next(); + + QString objectName = getObject(nextDirectory); + + if (isAValidTestDirectory(nextDirectory)) { + // The name of the section is the directory at the end of the path + stream << "parent_id = parent_ids.peek()\n"; + stream << "data = { 'name': '" << objectName << "', 'suite_id': " + _suiteID + ", 'parent_id': parent_id }\n"; + + stream << "section = client.send_post('add_section/' + str(" << _projectID << "), data)\n"; + + // Now we push the parent_id, and recursively process each directory + stream << "parent_ids.push(section['id'])\n\n"; + processDirectoryPython(nextDirectory, stream, userGitHub, branchGitHub); + } else if (objectName == "test.js") { + processTestPython(nextDirectory, stream, userGitHub, branchGitHub); + } + } + + // pop the parent directory before leaving + stream << "parent_ids.pop()\n\n"; +} + +// A suite of TestRail test cases contains trees. +// The nodes of the trees are sections +// The leaves are the test cases +// +// Each node and leaf have an ID and a parent ID. +// Therefore, the tree is built top-down, using a stack to store the IDs of each node +// +void TestRailInterface::createAddTestCasesPythonScript(const QString& testDirectory, + const QString& userGitHub, + const QString& branchGitHub) { + QString filename = _outputDirectory + "/addTestCases.py"; + if (QFile::exists(filename)) { + QFile::remove(filename); + } + QFile file(filename); + + if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { + QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), + "Could not create 'addTestCases.py'"); + exit(-1); + } + + QTextStream stream(&file); + + // Code to access TestRail + stream << "from testrail import *\n"; + stream << "client = APIClient('" << _url.toStdString().c_str() << "')\n"; + stream << "client.user = '" << _user << "'\n"; + stream << "client.password = '" << _password << "'\n\n"; + + stream << "from stack import *\n"; + stream << "parent_ids = Stack()\n\n"; + + // top-level section + stream << "data = { 'name': '" + << "Test Section - " << QDateTime::currentDateTime().toString("yyyy-MM-ddTHH:mm") + "', " + << "'suite_id': " + _suiteID + "}\n"; + + stream << "section = client.send_post('add_section/' + str(" << _projectID << "), data)\n"; + + // Now we push the parent_id, and recursively process each directory + stream << "parent_ids.push(section['id'])\n\n"; + processDirectoryPython(testDirectory, stream, userGitHub, branchGitHub); + + file.close(); + + if (QMessageBox::Yes == QMessageBox(QMessageBox::Information, "Python script has been created", + "Do you want to run the script and update TestRail?", + QMessageBox::Yes | QMessageBox::No).exec() + ) { + QProcess* process = new QProcess(); + connect(process, &QProcess::started, this, [=]() { _busyWindow.exec(); }); + + connect(process, static_cast(&QProcess::finished), this, + [=](int exitCode, QProcess::ExitStatus exitStatus) { _busyWindow.hide(); }); + + QStringList parameters = QStringList() << _outputDirectory + "/addTestCases.py"; + process->start(_pythonCommand, parameters); + } +} + +void TestRailInterface::updateReleasesComboData(int exitCode, QProcess::ExitStatus exitStatus) { + // Quit if user has previously cancelled + if (_testRailTestCasesSelectorWindow.getUserCancelled()) { + return; + } + + // Check if process completed successfully + if (exitStatus != QProcess::NormalExit) { + QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), + "Could not get 'added to release' data from TestRail"); + exit(-1); + } + + // Create map of releases from the file created by the process + _releaseNames.clear(); + + QString filename = _outputDirectory + "/releases.txt"; + if (!QFile::exists(filename)) { + QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), + "Could not find releases.txt in " + _outputDirectory); + exit(-1); + } + + QFile file(filename); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), + "Could not open " + _outputDirectory + "/releases.txt"); + exit(-1); + } + + QTextStream in(&file); + QString line = in.readLine(); + while (!line.isNull()) { + _releaseNames << line; + line = in.readLine(); + } + + file.close(); + + // Update the combo + _testRailTestCasesSelectorWindow.updateReleasesComboBoxData(_releaseNames); + + _testRailTestCasesSelectorWindow.exec(); + + if (_testRailTestCasesSelectorWindow.getUserCancelled()) { + return; + } + + createAddTestCasesPythonScript(_testDirectory, _userGitHub, _branchGitHub); +} + +void TestRailInterface::addRun() { + QString filename = _outputDirectory + "/addRun.py"; + if (QFile::exists(filename)) { + QFile::remove(filename); + } + QFile file(filename); + + if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { + QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), + "Could not create " + filename); + exit(-1); + } + + QTextStream stream(&file); + + // Code to access TestRail + stream << "from testrail import *\n"; + stream << "client = APIClient('" << _url.toStdString().c_str() << "')\n"; + stream << "client.user = '" << _user << "'\n"; + stream << "client.password = '" << _password << "'\n\n"; + + // A test suite is a forest. Each node is a section and leaves are either sections or test cases + // The user has selected a root for the run + // To find the cases in this tree we need all the sections in the tree + // These are found by building a set of all relevant sections. The first section is the selected root + // As the sections are in an ordered array we use the following snippet to find the relevant sections: + // initialize section set with the root + // for each section in the ordered array of sections in the suite + // if the parent of the section is in the section set then + // add this section to the section set + // + stream << "sections = client.send_get('get_sections/" + _projectID + "&suite_id=" + _suiteID + "')\n\n"; + + int sectionID = _sectionIDs[_testRailRunSelectorWindow.getSectionID()]; + + stream << "relevantSections = { " + QString::number(sectionID) + " }\n"; + stream << "for section in sections:\n"; + stream << "\tif section['parent_id'] in relevantSections:\n"; + stream << "\t\trelevantSections.add(section['id'])\n\n"; + + // We now loop over each section in the set and collect the cases into an array + stream << "cases = []\n"; + stream << "for section_id in relevantSections:\n"; + stream << "\tcases = cases + client.send_get('get_cases/" + _projectID + "&suite_id=" + _suiteID + "§ion_id=' + str(section_id))\n\n"; + + // To create a run we need an array of the relevant case ids + stream << "case_ids = []\n"; + stream << "for case in cases:\n"; + stream << "\tcase_ids.append(case['id'])\n\n"; + + // Now, we can create the run + stream << "data = { 'name': '" + _sectionNames[_testRailRunSelectorWindow.getSectionID()].replace("Section", "Run") + + "', 'suite_id': " + _suiteID + + ", 'include_all': False, 'case_ids': case_ids}\n"; + + stream << "run = client.send_post('add_run/" + _projectID + "', data)\n"; + + file.close(); + + if (QMessageBox::Yes == QMessageBox(QMessageBox::Information, "Python script has been created", + "Do you want to run the script and update TestRail?", + QMessageBox::Yes | QMessageBox::No).exec() + ) { + QProcess* process = new QProcess(); + connect(process, &QProcess::started, this, [=]() { _busyWindow.exec(); }); + + connect(process, static_cast(&QProcess::finished), this, + [=](int exitCode, QProcess::ExitStatus exitStatus) { _busyWindow.hide(); }); + + QStringList parameters = QStringList() << _outputDirectory + "/addRun.py"; + process->start(_pythonCommand, parameters); + } +} +void TestRailInterface::updateRunWithResults() { + QString filename = _outputDirectory + "/updateRunWithResults.py"; + if (QFile::exists(filename)) { + QFile::remove(filename); + } + QFile file(filename); + + if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { + QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), + "Could not create " + filename); + exit(-1); + } + + QTextStream stream(&file); + + // Code to access TestRail + stream << "from testrail import *\n"; + stream << "client = APIClient('" << _url.toStdString().c_str() << "')\n"; + stream << "client.user = '" << _user << "'\n"; + stream << "client.password = '" << _password << "'\n\n"; + + // It is assumed that all the tests that haven't failed have passed + // The failed tests are read, formatted and inserted into a set + // A failure named 'Failure_1--tests.content.entity.material.apply.avatars.00000' is formatted to 'content/entity/material/apply/avatars' + // This is the name of the test in TestRail + stream << "from os import listdir\n"; + + stream << "failed_tests = set()\n"; + + stream << "for entry in listdir('" + _outputDirectory + "/" + tempName + "'):\n"; + stream << "\tparts = entry.split('--tests.')[1].split('.')\n"; + stream << "\tfailed_test = parts[0]\n"; + stream << "\tfor i in range(1, len(parts) - 1):\n"; + stream << "\t\tfailed_test = failed_test + '/' + parts[i]\n"; + + stream << "\tfailed_tests.add(failed_test)\n\n"; + + // Initialize the array of results that will be eventually used to update TestRail + stream << "status_ids = []\n"; + stream << "case_ids = []\n"; + + int runID = _runIDs[_testRailResultsSelectorWindow.getRunID()]; + stream << "tests = client.send_get('get_tests/" + QString::number(runID) + "')\n\n"; + stream << "for test in tests:\n"; + + // status_id's: 1 - Passed + // 2 - Blocked + // 3 - Untested + // 4 - Retest + // 5 - Failed + stream << "\tstatus_id = 1\n"; + stream << "\tif test['title'] in failed_tests:\n"; + stream << "\t\tstatus_id = 5\n"; + stream << "\tcase_ids.append(test['case_id'])\n"; + stream << "\tstatus_ids.append(status_id)\n\n"; + + // We can now update the test (note that all tests need to be updated) + // An example request is as follows: + // + // "results" : [ + // { 'case_id': 1, 'status_id': 5 }, + // { 'case_id': 2, 'status_id': 1 } + // ] + // + stream << "results = []\n"; + stream << "for i in range(len(case_ids)):\n"; + stream << "\tresults.append({'case_id': case_ids[i], 'status_id': status_ids[i] })\n\n"; + + stream << "data = { 'results': results }\n"; + stream << "section = client.send_post('add_results_for_cases/' + str(" << runID << "), data)\n"; + + file.close(); + + if (QMessageBox::Yes == QMessageBox(QMessageBox::Information, "Python script has been created", + "Do you want to run the script and update TestRail?", + QMessageBox::Yes | QMessageBox::No).exec() + ) { + QProcess* process = new QProcess(); + connect(process, &QProcess::started, this, [=]() { _busyWindow.exec(); }); + + connect(process, static_cast(&QProcess::finished), this, + [=](int exitCode, QProcess::ExitStatus exitStatus) { _busyWindow.hide(); }); + + QStringList parameters = QStringList() << _outputDirectory + "/updateRunWithResults.py"; + process->start(_pythonCommand, parameters); + } +} + +void TestRailInterface::updateSectionsComboData(int exitCode, QProcess::ExitStatus exitStatus) { + // Quit if user has previously cancelled + if (_testRailRunSelectorWindow.getUserCancelled()) { + return; + } + + // Check if process completed successfully + if (exitStatus != QProcess::NormalExit) { + QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), + "Could not get sections from TestRail"); + exit(-1); + } + + // Create map of sections from the file created by the process + _sectionNames.clear(); + + QString filename = _outputDirectory + "/sections.txt"; + if (!QFile::exists(filename)) { + QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), + "Could not find sections.txt in " + _outputDirectory); + exit(-1); + } + + QFile file(filename); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), + "Could not open " + filename); + exit(-1); + } + + QTextStream in(&file); + QString line = in.readLine(); + while (!line.isNull()) { + // The section name is all the words except for the last + // The id is the last word + QString section = line.left(line.lastIndexOf(" ")); + QString id = line.right(line.length() - line.lastIndexOf(" ") - 1); + + _sectionIDs.push_back(id.toInt()); + _sectionNames << section; + + line = in.readLine(); + } + + file.close(); + + // Update the combo + _testRailRunSelectorWindow.updateSectionsComboBoxData(_sectionNames); + + _testRailRunSelectorWindow.exec(); + + if (_testRailRunSelectorWindow.getUserCancelled()) { + return; + } + + // The test cases are now read from TestRail + // When this is complete, the Run can be created + addRun(); +} + +void TestRailInterface::updateRunsComboData(int exitCode, QProcess::ExitStatus exitStatus) { + // Quit if user has previously cancelled + if (_testRailRunSelectorWindow.getUserCancelled()) { + return; + } + + // Check if process completed successfully + if (exitStatus != QProcess::NormalExit) { + QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), + "Could not get runs from TestRail"); + exit(-1); + } + + // Create map of sections from the file created by the process + _runNames.clear(); + + QString filename = _outputDirectory + "/runs.txt"; + if (!QFile::exists(filename)) { + QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), + "Could not find runs.txt in " + _outputDirectory); + exit(-1); + } + + QFile file(filename); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), + "Could not open " + filename); + exit(-1); + } + + QTextStream in(&file); + QString line = in.readLine(); + while (!line.isNull()) { + // The run name is all the words except for the last + // The id is the last word + QString section = line.left(line.lastIndexOf(" ")); + QString id = line.right(line.length() - line.lastIndexOf(" ") - 1); + + _runIDs.push_back(id.toInt()); + _runNames << section; + + line = in.readLine(); + } + + file.close(); + + // Update the combo + _testRailResultsSelectorWindow.updateRunsComboBoxData(_runNames); + + _testRailResultsSelectorWindow.exec(); + + if (_testRailResultsSelectorWindow.getUserCancelled()) { + return; + } + + // The test cases are now read from TestRail + // When this is complete, the Run can be updated + updateRunWithResults(); +} + +void TestRailInterface::getReleasesFromTestRail() { + QString filename = _outputDirectory + "/getReleases.py"; + if (QFile::exists(filename)) { + QFile::remove(filename); + } + QFile file(filename); + + if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { + QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), + "Could not create 'getReleases.py'"); + exit(-1); + } + + QTextStream stream(&file); + + // Code to access TestRail + stream << "from testrail import *\n"; + stream << "client = APIClient('" << _url.toStdString().c_str() << "')\n"; + stream << "client.user = '" << _user << "'\n"; + stream << "client.password = '" << _password << "'\n\n"; + + // Print the list of releases + stream << "case_fields = client.send_get('get_case_fields/" + _projectID + "')\n"; + stream << "for case_field in case_fields:\n"; + stream << "\tif case_field['name'] == 'added_to_release':\n"; + stream << "\t\trelease_string = case_field['configs'][0]['options']['items']\n\n"; + + // The list read from TestRail looks like this: + // '0,< RC65\n1,RC65\n2,RC66\n3,RC67\n4,RC68\n5,RC69\n6,v0.70.0\n7,v0.71.0\n8,v0.72.0\n9,v0.73.0\n10,v0.74.0\n11,v0.75.0\n12,v0.76.0\n13,v0.77.0\n14,v0.78.0\n15,v0.79.0' + // Splitting on newline gives an array: + // ['0,< RC65', '1,RC65', '2,RC66', '3,RC67', '4,RC68', '5,RC69', '6,v0.70.0', '7,v0.71.0', '8,v0.72.0', '9,v0.73.0', '10,v0.74.0', '11,v0.75.0', '12,v0.76.0', '13,v0.77.0', '14,v0.78.0', '15,v0.79.0'] + // Each element consists of an index and a string, separated by a comma. + // We just need the strings + stream << "file = open('" + _outputDirectory + "/releases.txt', 'w')\n\n"; + stream << "releases = release_string.split('\\n')\n"; + stream << "for release in releases:\n"; + stream << "\twords = release.split(',')\n"; + stream << "\tfile.write(words[1] + '\\n')\n\n"; + + stream << "file.close()\n"; + + file.close(); + + QProcess* process = new QProcess(); + connect(process, static_cast(&QProcess::finished), this, + [=](int exitCode, QProcess::ExitStatus exitStatus) { updateReleasesComboData(exitCode, exitStatus); }); + + QStringList parameters = QStringList() << filename; + process->start(_pythonCommand, parameters); +} + +void TestRailInterface::createTestSuitePython(const QString& testDirectory, + const QString& outputDirectory, + const QString& userGitHub, + const QString& branchGitHub) { + _testDirectory = testDirectory; + _outputDirectory = outputDirectory; + _userGitHub = userGitHub; + _branchGitHub = branchGitHub; + + if (!setPythonCommand()) { + return; + } + + if (!requestTestRailTestCasesDataFromUser()) { + return; + } + + createTestRailDotPyScript(); + createStackDotPyScript(); + + // TestRail will be updated after the process initiated by getReleasesFromTestRail has completed + getReleasesFromTestRail(); +} + +void TestRailInterface::createTestSuiteXML(const QString& testDirectory, + const QString& outputDirectory, + const QString& userGitHub, + const QString& branchGitHub) { + _outputDirectory = outputDirectory; + + QDomProcessingInstruction instruction = _document.createProcessingInstruction("xml", "version='1.0' encoding='UTF-8'"); + _document.appendChild(instruction); + + // We create a single section, within sections + QDomElement root = _document.createElement("sections"); + _document.appendChild(root); + + QDomElement topLevelSection = _document.createElement("section"); + + QDomElement suiteName = _document.createElement("name"); + suiteName.appendChild( + _document.createTextNode("Test Suite - " + QDateTime::currentDateTime().toString("yyyy-MM-ddTHH:mm"))); + topLevelSection.appendChild(suiteName); + + // This is the first call to 'process'. This is then called recursively to build the full XML tree + QDomElement secondLevelSections = _document.createElement("sections"); + topLevelSection.appendChild(processDirectoryXML(testDirectory, userGitHub, branchGitHub, secondLevelSections)); + + topLevelSection.appendChild(secondLevelSections); + root.appendChild(topLevelSection); + + // Write to file + const QString testRailsFilename{ _outputDirectory + "/TestRailSuite.xml" }; + QFile file(testRailsFilename); + if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { + QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), + "Could not create XML file"); + exit(-1); + } + + QTextStream stream(&file); + stream << _document.toString(); + + file.close(); + + QMessageBox::information(0, "Success", "TestRail XML file has been created"); +} + +QDomElement TestRailInterface::processDirectoryXML(const QString& directory, + const QString& userGitHub, + const QString& branchGitHub, + const QDomElement& element) { + QDomElement result = element; + + // Loop over all entries in directory + QDirIterator it(directory.toStdString().c_str()); + while (it.hasNext()) { + QString nextDirectory = it.next(); + + // The object name appears after the last slash (we are assured there is at least 1). + QString objectName = getObject(nextDirectory); + + // Only process directories + if (isAValidTestDirectory(nextDirectory)) { + // Create a section and process it + QDomElement sectionElement = _document.createElement("section"); + + QDomElement sectionElementName = _document.createElement("name"); + sectionElementName.appendChild(_document.createTextNode(objectName)); + sectionElement.appendChild(sectionElementName); + + QDomElement testsElement = _document.createElement("sections"); + sectionElement.appendChild(processDirectoryXML(nextDirectory, userGitHub, branchGitHub, testsElement)); + + result.appendChild(sectionElement); + } else if (objectName == "test.js" || objectName == "testStory.js") { + QDomElement sectionElement = _document.createElement("section"); + QDomElement sectionElementName = _document.createElement("name"); + sectionElementName.appendChild(_document.createTextNode("all")); + sectionElement.appendChild(sectionElementName); + sectionElement.appendChild( + processTestXML(nextDirectory, objectName, userGitHub, branchGitHub, _document.createElement("cases"))); + + result.appendChild(sectionElement); + } + } + + return result; +} + +QDomElement TestRailInterface::processTestXML(const QString& fullDirectory, + const QString& test, + const QString& userGitHub, + const QString& branchGitHub, + const QDomElement& element) { + QDomElement result = element; + + QDomElement caseElement = _document.createElement("case"); + + caseElement.appendChild(_document.createElement("id")); + + // The name of the test is derived from the full path. + // The first term is the first word after "tests" + // The last word is the penultimate word + QStringList words = fullDirectory.split('/'); + int i = 0; + while (words[i] != "tests") { + ++i; + if (i >= words.length() - 1) { + QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), + "Folder \"tests\" not found in " + fullDirectory); + exit(-1); + } + } + + ++i; + QString title{ words[i] }; + for (++i; i < words.length() - 1; ++i) { + title += " / " + words[i]; + } + + QDomElement titleElement = _document.createElement("title"); + titleElement.appendChild(_document.createTextNode(title)); + caseElement.appendChild(titleElement); + + QDomElement templateElement = _document.createElement("template"); + templateElement.appendChild(_document.createTextNode("Test Case (Steps)")); + caseElement.appendChild(templateElement); + + QDomElement typeElement = _document.createElement("type"); + typeElement.appendChild(_document.createTextNode("3 - Regression")); + caseElement.appendChild(typeElement); + + QDomElement priorityElement = _document.createElement("priority"); + priorityElement.appendChild(_document.createTextNode("Medium")); + caseElement.appendChild(priorityElement); + + QDomElement estimateElementName = _document.createElement("estimate"); + estimateElementName.appendChild(_document.createTextNode("60")); + caseElement.appendChild(estimateElementName); + + caseElement.appendChild(_document.createElement("references")); + + QDomElement customElement = _document.createElement("custom"); + + QDomElement tester_countElement = _document.createElement("tester_count"); + tester_countElement.appendChild(_document.createTextNode("1")); + customElement.appendChild(tester_countElement); + + QDomElement domain_bot_loadElement = _document.createElement("domain_bot_load"); + QDomElement domain_bot_loadElementId = _document.createElement("id"); + domain_bot_loadElementId.appendChild(_document.createTextNode("1")); + domain_bot_loadElement.appendChild(domain_bot_loadElementId); + QDomElement domain_bot_loadElementValue = _document.createElement("value"); + domain_bot_loadElementValue.appendChild( + _document.createTextNode(" Without Bots (hifiqa-rc / hifi-qa-stable / hifiqa-master)")); + domain_bot_loadElement.appendChild(domain_bot_loadElementValue); + customElement.appendChild(domain_bot_loadElement); + + QDomElement automation_typeElement = _document.createElement("automation_type"); + QDomElement automation_typeElementId = _document.createElement("id"); + automation_typeElementId.appendChild(_document.createTextNode("0")); + automation_typeElement.appendChild(automation_typeElementId); + QDomElement automation_typeElementValue = _document.createElement("value"); + automation_typeElementValue.appendChild(_document.createTextNode("None")); + automation_typeElement.appendChild(automation_typeElementValue); + customElement.appendChild(automation_typeElement); + + QDomElement added_to_releaseElement = _document.createElement("added_to_release"); + QDomElement added_to_releaseElementId = _document.createElement("id"); + added_to_releaseElementId.appendChild(_document.createTextNode("4")); + added_to_releaseElement.appendChild(added_to_releaseElementId); + QDomElement added_to_releaseElementValue = _document.createElement("value"); + added_to_releaseElementValue.appendChild(_document.createTextNode(branchGitHub)); + added_to_releaseElement.appendChild(added_to_releaseElementValue); + customElement.appendChild(added_to_releaseElement); + + QDomElement precondsElement = _document.createElement("preconds"); + precondsElement.appendChild(_document.createTextNode( + "Tester is in an empty region of a domain in which they have edit rights\n\n*Note: Press 'n' to advance test script")); + customElement.appendChild(precondsElement); + + QString testMDName = QString("https://github.com/") + userGitHub + "/hifi_tests/blob/" + branchGitHub + + "/tests/content/entity/light/point/create/test.md"; + + QDomElement steps_seperatedElement = _document.createElement("steps_separated"); + QDomElement stepElement = _document.createElement("step"); + QDomElement stepIndexElement = _document.createElement("index"); + stepIndexElement.appendChild(_document.createTextNode("1")); + stepElement.appendChild(stepIndexElement); + QDomElement stepContentElement = _document.createElement("content"); + stepContentElement.appendChild( + _document.createTextNode(QString("Execute instructions in [THIS TEST](") + testMDName + ")")); + stepElement.appendChild(stepContentElement); + QDomElement stepExpectedElement = _document.createElement("expected"); + stepExpectedElement.appendChild(_document.createTextNode("Refer to the expected result in the linked description.")); + stepElement.appendChild(stepExpectedElement); + steps_seperatedElement.appendChild(stepElement); + customElement.appendChild(steps_seperatedElement); + + QDomElement notesElement = _document.createElement("notes"); + notesElement.appendChild(_document.createTextNode(testMDName)); + customElement.appendChild(notesElement); + + caseElement.appendChild(customElement); + + result.appendChild(caseElement); + + return result; +} + +void TestRailInterface::processTestPython(const QString& fullDirectory, + QTextStream& stream, + const QString& userGitHub, + const QString& branchGitHub) { + // The name of the test is derived from the full path. + // The first term is the first word after "tests" + // The last word is the penultimate word + QStringList words = fullDirectory.split('/'); + int i = 0; + while (words[i] != "tests") { + ++i; + if (i >= words.length() - 1) { + QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), + "Folder \"tests\" not found in " + fullDirectory); + exit(-1); + } + } + + ++i; + QString title{ words[i] }; + for (++i; i < words.length() - 1; ++i) { + title += " / " + words[i]; + } + + // To create the path to test.md, prefix by tests, and remove blanks + QString pathToTestMD = QString("/tests/") + title.remove(" "); + + stream << "section_id = parent_ids.peek()\n"; + + QString testMDName = + QString("https://github.com/") + userGitHub + "/hifi_tests/blob/" + branchGitHub + pathToTestMD + "/test.md "; + + QString testContent = QString("Execute instructions in [THIS TEST](") + testMDName + ")"; + QString testExpected = QString("Refer to the expected result in the linked description."); + + + stream << "data = {\n" + << "\t'title': '" << title << "',\n" + << "\t'template_id': 2,\n" + << "\t'custom_added_to_release': " << _testRailTestCasesSelectorWindow.getReleaseID() << ",\n" + << "\t'custom_tester_count': 1,\n" + << "\t'custom_domain_bot_load': 1,\n" + << "\t'custom_preconds': " + << "'Tester is in an empty region of a domain in which they have edit rights\\n\\n*Note: Press \\'n\\' to advance " + "test script',\n" + << "\t'custom_steps_separated': " + << "[\n\t\t{\n\t\t\t'content': '" << testContent << "',\n\t\t\t'expected': '" << testExpected << "'\n\t\t}\n\t]\n" + << "}\n"; + + stream << "case = client.send_post('add_case/' + str(section_id), data)\n"; +} + +void TestRailInterface::getTestSectionsFromTestRail() { + QString filename = _outputDirectory + "/getSections.py"; + if (QFile::exists(filename)) { + QFile::remove(filename); + } + QFile file(filename); + + if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { + QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), + "Could not create 'getSections.py'"); + exit(-1); + } + + QTextStream stream(&file); + + // Code to access TestRail + stream << "from testrail import *\n"; + stream << "client = APIClient('" << _url.toStdString().c_str() << "')\n"; + stream << "client.user = '" << _user << "'\n"; + stream << "client.password = '" << _password << "'\n\n"; + + // Print the list of sections without parents + stream << "sections = client.send_get('get_sections/" + _projectID + "&suite_id=" + _suiteID + "')\n\n"; + stream << "file = open('" + _outputDirectory + "/sections.txt', 'w')\n\n"; + stream << "for section in sections:\n"; + stream << "\tif section['parent_id'] == None:\n"; + stream << "\t\tfile.write(section['name'] + ' ' + str(section['id']) + '\\n')\n\n"; + stream << "file.close()\n"; + + file.close(); + + QProcess* process = new QProcess(); + connect(process, static_cast(&QProcess::finished), this, + [=](int exitCode, QProcess::ExitStatus exitStatus) { updateSectionsComboData(exitCode, exitStatus); }); + + QStringList parameters = QStringList() << filename; + process->start(_pythonCommand, parameters); +} + +void TestRailInterface::getRunsFromTestRail() { + QString filename = _outputDirectory + "/getRuns.py"; + if (QFile::exists(filename)) { + QFile::remove(filename); + } + QFile file(filename); + + if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { + QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), + "Could not create " + filename); + exit(-1); + } + + QTextStream stream(&file); + + // Code to access TestRail + stream << "from testrail import *\n"; + stream << "client = APIClient('" << _url.toStdString().c_str() << "')\n"; + stream << "client.user = '" << _user << "'\n"; + stream << "client.password = '" << _password << "'\n\n"; + + // Print the list of runs + stream << "runs = client.send_get('get_runs/" + _projectID + "')\n\n"; + stream << "file = open('" + _outputDirectory + "/runs.txt', 'w')\n\n"; + stream << "for run in runs:\n"; + stream << "\tfile.write(run['name'] + ' ' + str(run['id']) + '\\n')\n\n"; + stream << "file.close()\n"; + + file.close(); + + QProcess* process = new QProcess(); + connect(process, static_cast(&QProcess::finished), this, + [=](int exitCode, QProcess::ExitStatus exitStatus) { updateRunsComboData(exitCode, exitStatus); }); + + QStringList parameters = QStringList() << filename; + + process->start(_pythonCommand, parameters); +} + +void TestRailInterface::createTestRailRun(const QString& outputDirectory) { + _outputDirectory = outputDirectory; + + if (!setPythonCommand()) { + return; + } + + if (!requestTestRailRunDataFromUser()) { + return; + } + + createTestRailDotPyScript(); + createStackDotPyScript(); + + // TestRail will be updated after the process initiated by getTestCasesFromTestRail has completed + getTestSectionsFromTestRail(); +} + +void TestRailInterface::updateTestRailRunResults(const QString& testResults, const QString& tempDirectory) { + _outputDirectory = tempDirectory; + + if (!setPythonCommand()) { + return; + } + + if (!requestTestRailResultsDataFromUser()) { + return; + } + + // This is needed to access TestRail + createTestRailDotPyScript(); + + // Extract test failures from zipped folder + QString tempSubDirectory = tempDirectory + "/" + tempName; + QDir dir = tempSubDirectory; + dir.mkdir(tempSubDirectory); + JlCompress::extractDir(testResults, tempSubDirectory); + + // TestRail will be updated after the process initiated by getTestRunFromTestRail has completed + getRunsFromTestRail(); +} \ No newline at end of file diff --git a/tools/auto-tester/src/TestRailInterface.h b/tools/auto-tester/src/TestRailInterface.h new file mode 100644 index 0000000000..6f250dfbba --- /dev/null +++ b/tools/auto-tester/src/TestRailInterface.h @@ -0,0 +1,132 @@ +// +// TestRailInterface.h +// +// Created by Nissim Hadar on 6 Jul 2018. +// Copyright 2013 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_test_testrail_interface_h +#define hifi_test_testrail_interface_h + +#include "ui/BusyWindow.h" + +#include "ui/TestRailTestCasesSelectorWindow.h" +#include "ui/TestRailRunSelectorWindow.h" +#include "ui/TestRailResultsSelectorWindow.h" + +#include +#include +#include +#include + +class TestRailInterface : public QObject{ + Q_OBJECT + +public: + TestRailInterface(); + + void createTestSuiteXML(const QString& testDirectory, + const QString& outputDirectory, + const QString& userGitHub, + const QString& branchGitHub); + + void createTestSuitePython(const QString& testDirectory, + const QString& outputDirectory, + const QString& userGitHub, + const QString& branchGitHub); + + QDomElement processDirectoryXML(const QString& directory, + const QString& useGitHubr, + const QString& branchGitHub, + const QDomElement& element); + + QDomElement processTestXML(const QString& fullDirectory, + const QString& test, + const QString& userGitHub, + const QString& branchGitHub, + const QDomElement& element); + + void processTestPython(const QString& fullDirectory, + QTextStream& stream, + const QString& userGitHub, + const QString& branchGitHub); + + void getReleasesFromTestRail(); + void getTestSectionsFromTestRail(); + void getRunsFromTestRail(); + + void createTestRailDotPyScript(); + void createStackDotPyScript(); + + bool requestTestRailTestCasesDataFromUser(); + bool requestTestRailRunDataFromUser(); + bool requestTestRailResultsDataFromUser(); + + void createAddTestCasesPythonScript(const QString& testDirectory, + const QString& userGitHub, + const QString& branchGitHub); + + void processDirectoryPython(const QString& directory, + QTextStream& stream, + const QString& userGitHub, + const QString& branchGitHub); + + bool isAValidTestDirectory(const QString& directory); + + QString getObject(const QString& path); + + void updateReleasesComboData(int exitCode, QProcess::ExitStatus exitStatus); + void updateSectionsComboData(int exitCode, QProcess::ExitStatus exitStatus); + void updateRunsComboData(int exitCode, QProcess::ExitStatus exitStatus); + + void createTestRailRun(const QString& outputDirectory); + void updateTestRailRunResults(const QString& testResults, const QString& tempDirectory); + + void addRun(); + void updateRunWithResults(); + + bool setPythonCommand(); + +private: + // HighFidelity Interface project ID in TestRail + const int INTERFACE_PROJECT_ID{ 24 }; + + // Rendering suite ID + const int INTERFACE_SUITE_ID{ 1147 }; + + QDomDocument _document; + + BusyWindow _busyWindow; + TestRailTestCasesSelectorWindow _testRailTestCasesSelectorWindow; + TestRailRunSelectorWindow _testRailRunSelectorWindow; + TestRailResultsSelectorWindow _testRailResultsSelectorWindow; + + QString _url; + QString _user; + QString _password; + QString _projectID; + QString _suiteID; + + QString _testDirectory; + QString _outputDirectory; + QString _userGitHub; + QString _branchGitHub; + + const QString pythonExe{ "python.exe" }; + QString _pythonCommand; + + QStringList _releaseNames; + + QStringList _sectionNames; + std::vector _sectionIDs; + + QStringList _runNames; + std::vector _runIDs; + + QString tempName{ "fgadhcUDHSFaidsfh3478JJJFSDFIUSOEIrf" }; +}; + +#endif \ No newline at end of file diff --git a/tools/auto-tester/src/ui/AutoTester.cpp b/tools/auto-tester/src/ui/AutoTester.cpp index 079fa63a9d..13bda4853f 100644 --- a/tools/auto-tester/src/ui/AutoTester.cpp +++ b/tools/auto-tester/src/ui/AutoTester.cpp @@ -15,57 +15,83 @@ #include #endif -AutoTester::AutoTester(QWidget *parent) : QMainWindow(parent) { - ui.setupUi(this); - ui.checkBoxInteractiveMode->setChecked(true); - ui.progressBar->setVisible(false); +AutoTester::AutoTester(QWidget* parent) : QMainWindow(parent) { + _ui.setupUi(this); - signalMapper = new QSignalMapper(); + _ui.checkBoxInteractiveMode->setChecked(true); + _ui.progressBar->setVisible(false); + _ui.tabWidget->setCurrentIndex(0); - connect(ui.actionClose, &QAction::triggered, this, &AutoTester::on_closeButton_clicked); - connect(ui.actionAbout, &QAction::triggered, this, &AutoTester::about); + _signalMapper = new QSignalMapper(); + + connect(_ui.actionClose, &QAction::triggered, this, &AutoTester::on_closeButton_clicked); + connect(_ui.actionAbout, &QAction::triggered, this, &AutoTester::about); + connect(_ui.actionContent, &QAction::triggered, this, &AutoTester::content); #ifndef Q_OS_WIN - ui.hideTaskbarButton->setVisible(false); - ui.showTaskbarButton->setVisible(false); + _ui.tabWidget->setTabEnabled(3, false); #endif + + // helpWindow.textBrowser->setText() } void AutoTester::setup() { - test = new Test(); + _test = new Test(); } void AutoTester::runFromCommandLine(const QString& testFolder, const QString& branch, const QString& user) { - isRunningFromCommandline = true; - test->startTestsEvaluation(testFolder, branch, user); + _isRunningFromCommandline = true; + _test->startTestsEvaluation(testFolder, branch, user); +} + +void AutoTester::on_tabWidget_currentChanged(int index) { + if (index == 1 || index == 2) { + _ui.userTextEdit->setDisabled(false); + _ui.branchTextEdit->setDisabled(false); + } else { + _ui.userTextEdit->setDisabled(true); + _ui.branchTextEdit->setDisabled(true); + } } void AutoTester::on_evaluateTestsButton_clicked() { - test->startTestsEvaluation(); + _test->startTestsEvaluation(); } void AutoTester::on_createRecursiveScriptButton_clicked() { - test->createRecursiveScript(); + _test->createRecursiveScript(); } void AutoTester::on_createAllRecursiveScriptsButton_clicked() { - test->createAllRecursiveScripts(); + _test->createAllRecursiveScripts(); } void AutoTester::on_createTestsButton_clicked() { - test->createTests(); + _test->createTests(); } void AutoTester::on_createMDFileButton_clicked() { - test->createMDFile(); + _test->createMDFile(); } void AutoTester::on_createAllMDFilesButton_clicked() { - test->createAllMDFiles(); + _test->createAllMDFiles(); } void AutoTester::on_createTestsOutlineButton_clicked() { - test->createTestsOutline(); + _test->createTestsOutline(); +} + +void AutoTester::on_createTestRailTestCasesButton_clicked() { + _test->createTestRailTestCases(); +} + +void AutoTester::on_createTestRailRunButton_clicked() { + _test->createTestRailRun(); +} + +void AutoTester::on_updateTestRailRunResultsButton_clicked() { + _test->updateTestRailRunResult(); } // To toggle between show and hide @@ -96,11 +122,19 @@ void AutoTester::on_closeButton_clicked() { exit(0); } -void AutoTester::downloadImage(const QUrl& url) { - downloaders.emplace_back(new Downloader(url, this)); - connect(downloaders[_index], SIGNAL (downloaded()), signalMapper, SLOT (map())); +void AutoTester::on_createPythonScriptRadioButton_clicked() { + _test->setTestRailCreateMode(PYTHON); +} - signalMapper->setMapping(downloaders[_index], _index); +void AutoTester::on_createXMLScriptRadioButton_clicked() { + _test->setTestRailCreateMode(XML); +} + +void AutoTester::downloadImage(const QUrl& url) { + _downloaders.emplace_back(new Downloader(url, this)); + connect(_downloaders[_index], SIGNAL(downloaded()), _signalMapper, SLOT(map())); + + _signalMapper->setMapping(_downloaders[_index], _index); ++_index; } @@ -113,39 +147,39 @@ void AutoTester::downloadImages(const QStringList& URLs, const QString& director _numberOfImagesDownloaded = 0; _index = 0; - ui.progressBar->setMinimum(0); - ui.progressBar->setMaximum(_numberOfImagesToDownload - 1); - ui.progressBar->setValue(0); - ui.progressBar->setVisible(true); + _ui.progressBar->setMinimum(0); + _ui.progressBar->setMaximum(_numberOfImagesToDownload - 1); + _ui.progressBar->setValue(0); + _ui.progressBar->setVisible(true); - downloaders.clear(); + _downloaders.clear(); for (int i = 0; i < _numberOfImagesToDownload; ++i) { QUrl imageURL(URLs[i]); downloadImage(imageURL); } - connect(signalMapper, SIGNAL (mapped(int)), this, SLOT (saveImage(int))); + connect(_signalMapper, SIGNAL(mapped(int)), this, SLOT(saveImage(int))); } void AutoTester::saveImage(int index) { try { QFile file(_directoryName + "/" + _filenames[index]); file.open(QIODevice::WriteOnly); - file.write(downloaders[index]->downloadedData()); + file.write(_downloaders[index]->downloadedData()); file.close(); } catch (...) { QMessageBox::information(0, "Test Aborted", "Failed to save image: " + _filenames[index]); - ui.progressBar->setVisible(false); + _ui.progressBar->setVisible(false); return; } ++_numberOfImagesDownloaded; if (_numberOfImagesDownloaded == _numberOfImagesToDownload) { - disconnect(signalMapper, SIGNAL (mapped(int)), this, SLOT (saveImage(int))); - test->finishTestsEvaluation(isRunningFromCommandline, ui.checkBoxInteractiveMode->isChecked(), ui.progressBar); + disconnect(_signalMapper, SIGNAL(mapped(int)), this, SLOT(saveImage(int))); + _test->finishTestsEvaluation(_isRunningFromCommandline, _ui.checkBoxInteractiveMode->isChecked(), _ui.progressBar); } else { - ui.progressBar->setValue(_numberOfImagesDownloaded); + _ui.progressBar->setValue(_numberOfImagesDownloaded); } } @@ -153,19 +187,22 @@ void AutoTester::about() { QMessageBox::information(0, "About", QString("Built ") + __DATE__ + " : " + __TIME__); } -void AutoTester::setUserText(const QString& user) { - ui.userTextEdit->setText(user); +void AutoTester::content() { + helpWindow.show(); } -QString AutoTester::getSelectedUser() -{ - return ui.userTextEdit->toPlainText(); +void AutoTester::setUserText(const QString& user) { + _ui.userTextEdit->setText(user); +} + +QString AutoTester::getSelectedUser() { + return _ui.userTextEdit->toPlainText(); } void AutoTester::setBranchText(const QString& branch) { - ui.branchTextEdit->setText(branch); + _ui.branchTextEdit->setText(branch); } QString AutoTester::getSelectedBranch() { - return ui.branchTextEdit->toPlainText(); + return _ui.branchTextEdit->toPlainText(); } diff --git a/tools/auto-tester/src/ui/AutoTester.h b/tools/auto-tester/src/ui/AutoTester.h index d47c4929c4..e29da5b716 100644 --- a/tools/auto-tester/src/ui/AutoTester.h +++ b/tools/auto-tester/src/ui/AutoTester.h @@ -18,6 +18,8 @@ #include "../Downloader.h" #include "../Test.h" +#include "HelpWindow.h" + class AutoTester : public QMainWindow { Q_OBJECT @@ -38,41 +40,55 @@ public: QString getSelectedBranch(); private slots: + void on_tabWidget_currentChanged(int index); + void on_evaluateTestsButton_clicked(); void on_createRecursiveScriptButton_clicked(); void on_createAllRecursiveScriptsButton_clicked(); void on_createTestsButton_clicked(); + void on_createMDFileButton_clicked(); void on_createAllMDFilesButton_clicked(); + void on_createTestsOutlineButton_clicked(); + void on_createTestRailTestCasesButton_clicked(); + void on_createTestRailRunButton_clicked(); + void on_updateTestRailRunResultsButton_clicked(); + void on_hideTaskbarButton_clicked(); void on_showTaskbarButton_clicked(); + void on_createPythonScriptRadioButton_clicked(); + void on_createXMLScriptRadioButton_clicked(); + void on_closeButton_clicked(); void saveImage(int index); void about(); + void content(); private: - Ui::AutoTesterClass ui; - Test* test; + Ui::AutoTesterClass _ui; + Test* _test; - std::vector downloaders; + std::vector _downloaders; // local storage for parameters - folder to store downloaded files in, and a list of their names QString _directoryName; QStringList _filenames; // Used to enable passing a parameter to slots - QSignalMapper* signalMapper; + QSignalMapper* _signalMapper; int _numberOfImagesToDownload { 0 }; int _numberOfImagesDownloaded { 0 }; int _index { 0 }; - bool isRunningFromCommandline { false }; + bool _isRunningFromCommandline { false }; + + HelpWindow helpWindow; }; #endif // hifi_AutoTester_h \ No newline at end of file diff --git a/tools/auto-tester/src/ui/AutoTester.ui b/tools/auto-tester/src/ui/AutoTester.ui index e12fc70e3f..ac8fcf5e86 100644 --- a/tools/auto-tester/src/ui/AutoTester.ui +++ b/tools/auto-tester/src/ui/AutoTester.ui @@ -6,10 +6,16 @@ 0 0 - 612 - 537 + 432 + 734 + + + 0 + 0 + + AutoTester @@ -17,9 +23,9 @@ - 380 - 430 - 101 + 166 + 610 + 100 40 @@ -27,157 +33,258 @@ Close - + - 20 - 30 - 220 - 40 + 12 + 140 + 408 + 461 - - Create Tests + + 0 + + + Create + + + + + 96 + 20 + 220 + 40 + + + + Create Tests + + + + + + 96 + 100 + 220 + 40 + + + + Create MD file + + + + + + 96 + 150 + 220 + 40 + + + + Create all MD files + + + + + + 96 + 230 + 220 + 40 + + + + Create Tests Outline + + + + + + 96 + 310 + 220 + 40 + + + + Create Recursive Script + + + + + + 96 + 360 + 220 + 40 + + + + Create all Recursive Scripts + + + + + + Evaluate + + + + + 90 + 100 + 255 + 23 + + + + 24 + + + + + + 90 + 50 + 131 + 20 + + + + <html><head/><body><p>If unchecked, will not show results during evaluation</p></body></html> + + + Interactive Mode + + + + + + 200 + 40 + 101 + 40 + + + + Evaluate Test + + + + + + TestRail + + + + + 180 + 160 + 161 + 40 + + + + Update Run Results + + + + + + 80 + 40 + 95 + 20 + + + + Python + + + true + + + + + + 180 + 100 + 161 + 40 + + + + Create Run + + + + + + 180 + 40 + 161 + 40 + + + + Create Test Cases + + + + + + 80 + 60 + 95 + 20 + + + + XML + + + + + + Windows + + + + + 100 + 100 + 211 + 40 + + + + Hide Windows Taskbar + + + + + + 100 + 170 + 211 + 40 + + + + Show Windows Taskbar + + + - + - 430 - 270 - 101 - 40 - - - - Evaluate Test - - - - - - 330 - 110 - 220 - 40 - - - - Create Recursive Script - - - - - - 320 - 280 - 131 - 20 - - - - <html><head/><body><p>If unchecked, will not show results during evaluation</p></body></html> - - - Interactive Mode - - - - - - 320 - 330 - 255 - 23 - - - - 24 - - - - - - 330 - 170 - 220 - 40 - - - - Create all Recursive Scripts - - - - - - 20 - 110 - 220 - 40 - - - - Create MD file - - - - - - 20 - 160 - 220 - 40 - - - - Create all MD files - - - - - - 20 - 250 - 220 - 40 - - - - Create Tests Outline - - - - - - 10 - 440 - 211 - 40 - - - - Show Windows Taskbar - - - - - - 10 - 390 - 211 - 40 - - - - Hide Windows Taskbar - - - - - - 330 - 55 + 110 + 90 81 16 @@ -191,11 +298,31 @@ GitHub Branch - + - 330 - 15 + 200 + 85 + 140 + 24 + + + + + + + 200 + 47 + 140 + 24 + + + + + + + 110 + 50 81 16 @@ -209,33 +336,13 @@ GitHub User - - - - 420 - 12 - 140 - 24 - - - - - - - 420 - 50 - 140 - 24 - - - 0 0 - 612 + 432 21 @@ -250,6 +357,7 @@ Help + @@ -273,6 +381,11 @@ About + + + Content + + diff --git a/tools/auto-tester/src/ui/BusyWindow.cpp b/tools/auto-tester/src/ui/BusyWindow.cpp new file mode 100644 index 0000000000..8066d576f8 --- /dev/null +++ b/tools/auto-tester/src/ui/BusyWindow.cpp @@ -0,0 +1,14 @@ +// +// BusyWindow.cpp +// +// Created by Nissim Hadar on 26 Jul 2017. +// Copyright 2013 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#include "BusyWindow.h" + +BusyWindow::BusyWindow(QWidget *parent) { + setupUi(this); +} diff --git a/tools/auto-tester/src/ui/BusyWindow.h b/tools/auto-tester/src/ui/BusyWindow.h new file mode 100644 index 0000000000..62f2df7e04 --- /dev/null +++ b/tools/auto-tester/src/ui/BusyWindow.h @@ -0,0 +1,22 @@ +// +// BusyWindow.h +// +// Created by Nissim Hadar on 29 Jul 2017. +// Copyright 2013 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#ifndef hifi_BusyWindow_h +#define hifi_BusyWindow_h + +#include "ui_BusyWindow.h" + +class BusyWindow : public QDialog, public Ui::BusyWindow { + Q_OBJECT + +public: + BusyWindow(QWidget* parent = Q_NULLPTR); +}; + +#endif \ No newline at end of file diff --git a/tools/auto-tester/src/ui/BusyWindow.ui b/tools/auto-tester/src/ui/BusyWindow.ui new file mode 100644 index 0000000000..c237566a5e --- /dev/null +++ b/tools/auto-tester/src/ui/BusyWindow.ui @@ -0,0 +1,75 @@ + + + BusyWindow + + + Qt::ApplicationModal + + + + 0 + 0 + 542 + 189 + + + + Updating TestRail - please wait + + + + + 30 + 850 + 500 + 28 + + + + + 12 + + + + similarity + + + + + + 40 + 40 + 481 + 101 + + + + 0 + + + 0 + + + + + + 50 + 60 + 431 + 61 + + + + + 20 + + + + Please wait for this window to close + + + + + + + diff --git a/tools/auto-tester/src/ui/HelpWindow.cpp b/tools/auto-tester/src/ui/HelpWindow.cpp new file mode 100644 index 0000000000..21c5d9d375 --- /dev/null +++ b/tools/auto-tester/src/ui/HelpWindow.cpp @@ -0,0 +1,14 @@ +// +// HelpWindow.cpp +// +// Created by Nissim Hadar on 8 Aug 2017. +// Copyright 2013 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#include "HelpWindow.h" + +HelpWindow::HelpWindow(QWidget *parent) { + setupUi(this); +} diff --git a/tools/auto-tester/src/ui/HelpWindow.h b/tools/auto-tester/src/ui/HelpWindow.h new file mode 100644 index 0000000000..5ce91b360d --- /dev/null +++ b/tools/auto-tester/src/ui/HelpWindow.h @@ -0,0 +1,22 @@ +// +// HelpWindow.h +// +// Created by Nissim Hadar on 8 Aug 2017. +// Copyright 2013 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#ifndef hifi_HelpWindow_h +#define hifi_HelpWindow_h + +#include "ui_HelpWindow.h" + +class HelpWindow : public QDialog, public Ui::HelpWindow { + Q_OBJECT + +public: + HelpWindow(QWidget* parent = Q_NULLPTR); +}; + +#endif \ No newline at end of file diff --git a/tools/auto-tester/src/ui/HelpWindow.ui b/tools/auto-tester/src/ui/HelpWindow.ui new file mode 100644 index 0000000000..d2aa0da0d4 --- /dev/null +++ b/tools/auto-tester/src/ui/HelpWindow.ui @@ -0,0 +1,46 @@ + + + HelpWindow + + + Qt::ApplicationModal + + + + 0 + 0 + 696 + 546 + + + + AutoTester Help + + + + + 50 + 50 + 581 + 381 + + + + + + + 300 + 460 + 93 + 28 + + + + Close + + + + + + + diff --git a/tools/auto-tester/src/ui/MismatchWindow.cpp b/tools/auto-tester/src/ui/MismatchWindow.cpp index d880a1abdc..79d2ce9f61 100644 --- a/tools/auto-tester/src/ui/MismatchWindow.cpp +++ b/tools/auto-tester/src/ui/MismatchWindow.cpp @@ -66,14 +66,14 @@ void MismatchWindow::setTestFailure(TestFailure testFailure) { QPixmap expectedPixmap = QPixmap(testFailure._pathname + testFailure._expectedImageFilename); QPixmap actualPixmap = QPixmap(testFailure._pathname + testFailure._actualImageFilename); - diffPixmap = computeDiffPixmap( + _diffPixmap = computeDiffPixmap( QImage(testFailure._pathname + testFailure._expectedImageFilename), QImage(testFailure._pathname + testFailure._actualImageFilename) ); expectedImage->setPixmap(expectedPixmap); resultImage->setPixmap(actualPixmap); - diffImage->setPixmap(diffPixmap); + diffImage->setPixmap(_diffPixmap); } void MismatchWindow::on_passTestButton_clicked() { @@ -92,5 +92,5 @@ void MismatchWindow::on_abortTestsButton_clicked() { } QPixmap MismatchWindow::getComparisonImage() { - return diffPixmap; + return _diffPixmap; } diff --git a/tools/auto-tester/src/ui/MismatchWindow.h b/tools/auto-tester/src/ui/MismatchWindow.h index cdbdcb4098..f203a2be6a 100644 --- a/tools/auto-tester/src/ui/MismatchWindow.h +++ b/tools/auto-tester/src/ui/MismatchWindow.h @@ -14,8 +14,7 @@ #include "../common.h" -class MismatchWindow : public QDialog, public Ui::MismatchWindow -{ +class MismatchWindow : public QDialog, public Ui::MismatchWindow { Q_OBJECT public: @@ -36,7 +35,7 @@ private slots: private: UserResponse _userResponse{ USER_RESPONSE_INVALID }; - QPixmap diffPixmap; + QPixmap _diffPixmap; }; diff --git a/tools/auto-tester/src/ui/MismatchWindow.ui b/tools/auto-tester/src/ui/MismatchWindow.ui index 72f86261ab..8a174989d4 100644 --- a/tools/auto-tester/src/ui/MismatchWindow.ui +++ b/tools/auto-tester/src/ui/MismatchWindow.ui @@ -2,6 +2,9 @@ MismatchWindow + + Qt::ApplicationModal + 0 @@ -193,4 +196,4 @@ - \ No newline at end of file + diff --git a/tools/auto-tester/src/ui/TestRailResultsSelectorWindow.cpp b/tools/auto-tester/src/ui/TestRailResultsSelectorWindow.cpp new file mode 100644 index 0000000000..414e4fca79 --- /dev/null +++ b/tools/auto-tester/src/ui/TestRailResultsSelectorWindow.cpp @@ -0,0 +1,104 @@ +// +// TestRailResultsSelectorWindow.cpp +// +// Created by Nissim Hadar on 2 Aug 2017. +// Copyright 2013 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#include "TestRailResultsSelectorWindow.h" + +#include + +#include + +TestRailResultsSelectorWindow::TestRailResultsSelectorWindow(QWidget *parent) { + setupUi(this); + + projectIDLineEdit->setValidator(new QIntValidator(1, 999, this)); +} + + +void TestRailResultsSelectorWindow::reset() { + urlLineEdit->setDisabled(false); + userLineEdit->setDisabled(false); + passwordLineEdit->setDisabled(false); + projectIDLineEdit->setDisabled(false); + + OKButton->setDisabled(true); + + runsLabel->setDisabled(true); + runsComboBox->setDisabled(true); +} + +void TestRailResultsSelectorWindow::on_acceptButton_clicked() { + urlLineEdit->setDisabled(true); + userLineEdit->setDisabled(true); + passwordLineEdit->setDisabled(true); + projectIDLineEdit->setDisabled(true); + + OKButton->setDisabled(false); + + runsLabel->setDisabled(false); + runsComboBox->setDisabled(false); + close(); +} + +void TestRailResultsSelectorWindow::on_OKButton_clicked() { + userCancelled = false; + close(); +} + +void TestRailResultsSelectorWindow::on_cancelButton_clicked() { + userCancelled = true; + close(); +} + +bool TestRailResultsSelectorWindow::getUserCancelled() { + return userCancelled; +} + +void TestRailResultsSelectorWindow::setURL(const QString& user) { + urlLineEdit->setText(user); +} + +QString TestRailResultsSelectorWindow::getURL() { + return urlLineEdit->text(); +} + +void TestRailResultsSelectorWindow::setUser(const QString& user) { + userLineEdit->setText(user); +} + +QString TestRailResultsSelectorWindow::getUser() { + return userLineEdit->text(); +} + +QString TestRailResultsSelectorWindow::getPassword() { + return passwordLineEdit->text(); +} + +void TestRailResultsSelectorWindow::setProjectID(const int project) { + projectIDLineEdit->setText(QString::number(project)); +} + +int TestRailResultsSelectorWindow::getProjectID() { + return projectIDLineEdit->text().toInt(); +} + +void TestRailResultsSelectorWindow::setSuiteID(const int project) { + suiteIDLineEdit->setText(QString::number(project)); +} + +int TestRailResultsSelectorWindow::getSuiteID() { + return suiteIDLineEdit->text().toInt(); +} + +void TestRailResultsSelectorWindow::updateRunsComboBoxData(QStringList data) { + runsComboBox->insertItems(0, data); +} + +int TestRailResultsSelectorWindow::getRunID() { + return runsComboBox->currentIndex(); +} \ No newline at end of file diff --git a/tools/auto-tester/src/ui/TestRailResultsSelectorWindow.h b/tools/auto-tester/src/ui/TestRailResultsSelectorWindow.h new file mode 100644 index 0000000000..51059d6127 --- /dev/null +++ b/tools/auto-tester/src/ui/TestRailResultsSelectorWindow.h @@ -0,0 +1,50 @@ +// +// TestRailResultsSelectorWindow.h +// +// Created by Nissim Hadar on 2 Aug 2017. +// Copyright 2013 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#ifndef hifi_TestRailResultsSelectorWindow_h +#define hifi_TestRailResultsSelectorWindow_h + +#include "ui_TestRailResultsSelectorWindow.h" + +class TestRailResultsSelectorWindow : public QDialog, public Ui::TestRailResultsSelectorWindow { + Q_OBJECT + +public: + TestRailResultsSelectorWindow(QWidget* parent = Q_NULLPTR); + + void reset(); + + bool getUserCancelled(); + + void setURL(const QString& user); + QString getURL(); + + void setUser(const QString& user); + QString getUser(); + + QString getPassword(); + + void setProjectID(const int project); + int getProjectID(); + + void setSuiteID(const int project); + int getSuiteID(); + + bool userCancelled{ false }; + + void updateRunsComboBoxData(QStringList data); + int getRunID(); + +private slots: + void on_acceptButton_clicked(); + void on_OKButton_clicked(); + void on_cancelButton_clicked(); +}; + +#endif \ No newline at end of file diff --git a/tools/auto-tester/src/ui/TestRailResultsSelectorWindow.ui b/tools/auto-tester/src/ui/TestRailResultsSelectorWindow.ui new file mode 100644 index 0000000000..983b95ee79 --- /dev/null +++ b/tools/auto-tester/src/ui/TestRailResultsSelectorWindow.ui @@ -0,0 +1,280 @@ + + + TestRailResultsSelectorWindow + + + + 0 + 0 + 533 + 474 + + + + TestRail Test Case Selector Window + + + + + 30 + 850 + 500 + 28 + + + + + 12 + + + + similarity + + + + + + 70 + 125 + 121 + 20 + + + + + 10 + + + + TestRail Password + + + + + + 70 + 25 + 121 + 20 + + + + + 10 + + + + TestRail URL + + + + + false + + + + 120 + 420 + 93 + 28 + + + + OK + + + + + + 280 + 420 + 93 + 28 + + + + Cancel + + + + + + 200 + 120 + 231 + 24 + + + + QLineEdit::Password + + + + + + 70 + 75 + 121 + 20 + + + + + 10 + + + + TestRail User + + + + + + 200 + 170 + 231 + 24 + + + + QLineEdit::Normal + + + + + + 70 + 175 + 121 + 20 + + + + + 10 + + + + TestRail Project ID + + + + + + 200 + 270 + 231 + 28 + + + + Accept + + + + + false + + + + 160 + 350 + 271 + 22 + + + + + + true + + + + 80 + 350 + 71 + 20 + + + + + 10 + + + + TestRail Run + + + + + + 200 + 20 + 231 + 24 + + + + QLineEdit::Normal + + + + + + 200 + 70 + 231 + 24 + + + + QLineEdit::Normal + + + + + + 200 + 215 + 231 + 24 + + + + QLineEdit::Normal + + + + + + 70 + 220 + 121 + 20 + + + + + 10 + + + + TestRail Suite ID + + + + + + urlLineEdit + userLineEdit + passwordLineEdit + projectIDLineEdit + suiteIDLineEdit + acceptButton + runsComboBox + OKButton + cancelButton + + + + diff --git a/tools/auto-tester/src/ui/TestRailRunSelectorWindow.cpp b/tools/auto-tester/src/ui/TestRailRunSelectorWindow.cpp new file mode 100644 index 0000000000..54a3985a8b --- /dev/null +++ b/tools/auto-tester/src/ui/TestRailRunSelectorWindow.cpp @@ -0,0 +1,100 @@ +// +// TestRailRunSelectorWindow.cpp +// +// Created by Nissim Hadar on 31 Jul 2017. +// Copyright 2013 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#include "TestRailRunSelectorWindow.h" + +#include + +#include + +TestRailRunSelectorWindow::TestRailRunSelectorWindow(QWidget *parent) { + setupUi(this); + + projectIDLineEdit->setValidator(new QIntValidator(1, 999, this)); +} + + +void TestRailRunSelectorWindow::reset() { + urlLineEdit->setDisabled(false); + userLineEdit->setDisabled(false); + passwordLineEdit->setDisabled(false); + projectIDLineEdit->setDisabled(false); + + OKButton->setDisabled(true); + sectionsComboBox->setDisabled(true); +} + +void TestRailRunSelectorWindow::on_acceptButton_clicked() { + urlLineEdit->setDisabled(true); + userLineEdit->setDisabled(true); + passwordLineEdit->setDisabled(true); + projectIDLineEdit->setDisabled(true); + + OKButton->setDisabled(false); + sectionsComboBox->setDisabled(false); + close(); +} + +void TestRailRunSelectorWindow::on_OKButton_clicked() { + userCancelled = false; + close(); +} + +void TestRailRunSelectorWindow::on_cancelButton_clicked() { + userCancelled = true; + close(); +} + +bool TestRailRunSelectorWindow::getUserCancelled() { + return userCancelled; +} + +void TestRailRunSelectorWindow::setURL(const QString& user) { + urlLineEdit->setText(user); +} + +QString TestRailRunSelectorWindow::getURL() { + return urlLineEdit->text(); +} + +void TestRailRunSelectorWindow::setUser(const QString& user) { + userLineEdit->setText(user); +} + +QString TestRailRunSelectorWindow::getUser() { + return userLineEdit->text(); +} + +QString TestRailRunSelectorWindow::getPassword() { + return passwordLineEdit->text(); +} + +void TestRailRunSelectorWindow::setProjectID(const int project) { + projectIDLineEdit->setText(QString::number(project)); +} + +int TestRailRunSelectorWindow::getProjectID() { + return projectIDLineEdit->text().toInt(); +} + +void TestRailRunSelectorWindow::setSuiteID(const int project) { + suiteIDLineEdit->setText(QString::number(project)); +} + +int TestRailRunSelectorWindow::getSuiteID() { + return suiteIDLineEdit->text().toInt(); +} + +void TestRailRunSelectorWindow::updateSectionsComboBoxData(QStringList data) { + sectionsComboBox->insertItems(0, data); +} + +int TestRailRunSelectorWindow::getSectionID() { + return sectionsComboBox->currentIndex(); +} \ No newline at end of file diff --git a/tools/auto-tester/src/ui/TestRailRunSelectorWindow.h b/tools/auto-tester/src/ui/TestRailRunSelectorWindow.h new file mode 100644 index 0000000000..d6428bb476 --- /dev/null +++ b/tools/auto-tester/src/ui/TestRailRunSelectorWindow.h @@ -0,0 +1,50 @@ +// +// TestRailRunSelectorWindow.h +// +// Created by Nissim Hadar on 31 Jul 2017. +// Copyright 2013 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#ifndef hifi_TestRailRunSelectorWindow_h +#define hifi_TestRailRunSelectorWindow_h + +#include "ui_TestRailRunSelectorWindow.h" + +class TestRailRunSelectorWindow : public QDialog, public Ui::TestRailRunSelectorWindow { + Q_OBJECT + +public: + TestRailRunSelectorWindow(QWidget* parent = Q_NULLPTR); + + void reset(); + + bool getUserCancelled(); + + void setURL(const QString& user); + QString getURL(); + + void setUser(const QString& user); + QString getUser(); + + QString getPassword(); + + void setProjectID(const int project); + int getProjectID(); + + void setSuiteID(const int project); + int getSuiteID(); + + bool userCancelled{ false }; + + void updateSectionsComboBoxData(QStringList data); + int getSectionID(); + +private slots: + void on_acceptButton_clicked(); + void on_OKButton_clicked(); + void on_cancelButton_clicked(); +}; + +#endif \ No newline at end of file diff --git a/tools/auto-tester/src/ui/TestRailRunSelectorWindow.ui b/tools/auto-tester/src/ui/TestRailRunSelectorWindow.ui new file mode 100644 index 0000000000..ad39b5cc64 --- /dev/null +++ b/tools/auto-tester/src/ui/TestRailRunSelectorWindow.ui @@ -0,0 +1,283 @@ + + + TestRailRunSelectorWindow + + + Qt::ApplicationModal + + + + 0 + 0 + 489 + 474 + + + + TestRail Run Selector Window + + + + + 30 + 850 + 500 + 28 + + + + + 12 + + + + similarity + + + + + + 70 + 125 + 121 + 20 + + + + + 10 + + + + TestRail Password + + + + + + 70 + 25 + 121 + 20 + + + + + 10 + + + + TestRail URL + + + + + false + + + + 120 + 420 + 93 + 28 + + + + OK + + + + + + 280 + 420 + 93 + 28 + + + + Cancel + + + + + + 200 + 120 + 231 + 24 + + + + QLineEdit::Password + + + + + + 70 + 75 + 121 + 20 + + + + + 10 + + + + TestRail User + + + + + + 200 + 170 + 231 + 24 + + + + QLineEdit::Normal + + + + + + 70 + 175 + 121 + 20 + + + + + 10 + + + + TestRail Project ID + + + + + + 200 + 270 + 231 + 28 + + + + Accept + + + + + false + + + + 140 + 350 + 311 + 22 + + + + + + true + + + + 20 + 350 + 121 + 20 + + + + + 10 + + + + TestRail Sections + + + + + + 200 + 20 + 231 + 24 + + + + QLineEdit::Normal + + + + + + 200 + 70 + 231 + 24 + + + + QLineEdit::Normal + + + + + + 200 + 215 + 231 + 24 + + + + QLineEdit::Normal + + + + + + 70 + 220 + 121 + 20 + + + + + 10 + + + + TestRail Suite ID + + + + + + urlLineEdit + userLineEdit + passwordLineEdit + projectIDLineEdit + suiteIDLineEdit + acceptButton + sectionsComboBox + OKButton + cancelButton + + + + diff --git a/tools/auto-tester/src/ui/TestRailTestCasesSelectorWindow.cpp b/tools/auto-tester/src/ui/TestRailTestCasesSelectorWindow.cpp new file mode 100644 index 0000000000..abb873ea14 --- /dev/null +++ b/tools/auto-tester/src/ui/TestRailTestCasesSelectorWindow.cpp @@ -0,0 +1,104 @@ +// +// TestRailTestCasesSelectorWindow.cpp +// +// Created by Nissim Hadar on 26 Jul 2017. +// Copyright 2013 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#include "TestRailTestCasesSelectorWindow.h" + +#include + +#include + +TestRailTestCasesSelectorWindow::TestRailTestCasesSelectorWindow(QWidget *parent) { + setupUi(this); + + projectIDLineEdit->setValidator(new QIntValidator(1, 999, this)); +} + + +void TestRailTestCasesSelectorWindow::reset() { + urlLineEdit->setDisabled(false); + userLineEdit->setDisabled(false); + passwordLineEdit->setDisabled(false); + projectIDLineEdit->setDisabled(false); + + OKButton->setDisabled(true); + + releasesLabel->setDisabled(true); + releasesComboBox->setDisabled(true); +} + +void TestRailTestCasesSelectorWindow::on_acceptButton_clicked() { + urlLineEdit->setDisabled(true); + userLineEdit->setDisabled(true); + passwordLineEdit->setDisabled(true); + projectIDLineEdit->setDisabled(true); + + OKButton->setDisabled(false); + + releasesLabel->setDisabled(false); + releasesComboBox->setDisabled(false); + close(); +} + +void TestRailTestCasesSelectorWindow::on_OKButton_clicked() { + userCancelled = false; + close(); +} + +void TestRailTestCasesSelectorWindow::on_cancelButton_clicked() { + userCancelled = true; + close(); +} + +bool TestRailTestCasesSelectorWindow::getUserCancelled() { + return userCancelled; +} + +void TestRailTestCasesSelectorWindow::setURL(const QString& user) { + urlLineEdit->setText(user); +} + +QString TestRailTestCasesSelectorWindow::getURL() { + return urlLineEdit->text(); +} + +void TestRailTestCasesSelectorWindow::setUser(const QString& user) { + userLineEdit->setText(user); +} + +QString TestRailTestCasesSelectorWindow::getUser() { + return userLineEdit->text(); +} + +QString TestRailTestCasesSelectorWindow::getPassword() { + return passwordLineEdit->text(); +} + +void TestRailTestCasesSelectorWindow::setProjectID(const int project) { + projectIDLineEdit->setText(QString::number(project)); +} + +int TestRailTestCasesSelectorWindow::getProjectID() { + return projectIDLineEdit->text().toInt(); +} + +void TestRailTestCasesSelectorWindow::setSuiteID(const int project) { + suiteIDLineEdit->setText(QString::number(project)); +} + +int TestRailTestCasesSelectorWindow::getSuiteID() { + return suiteIDLineEdit->text().toInt(); +} + +void TestRailTestCasesSelectorWindow::updateReleasesComboBoxData(QStringList data) { + releasesComboBox->insertItems(0, data); +} + +int TestRailTestCasesSelectorWindow::getReleaseID() { + return releasesComboBox->currentIndex(); +} \ No newline at end of file diff --git a/tools/auto-tester/src/ui/TestRailTestCasesSelectorWindow.h b/tools/auto-tester/src/ui/TestRailTestCasesSelectorWindow.h new file mode 100644 index 0000000000..9153b003fa --- /dev/null +++ b/tools/auto-tester/src/ui/TestRailTestCasesSelectorWindow.h @@ -0,0 +1,50 @@ +// +// TestRailTestCasesSelectorWindow.h +// +// Created by Nissim Hadar on 26 Jul 2017. +// Copyright 2013 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#ifndef hifi_TestRailTestCasesSelectorWindow_h +#define hifi_TestRailTestCasesSelectorWindow_h + +#include "ui_TestRailTestCasesSelectorWindow.h" + +class TestRailTestCasesSelectorWindow : public QDialog, public Ui::TestRailTestCasesSelectorWindow { + Q_OBJECT + +public: + TestRailTestCasesSelectorWindow(QWidget* parent = Q_NULLPTR); + + void reset(); + + bool getUserCancelled(); + + void setURL(const QString& user); + QString getURL(); + + void setUser(const QString& user); + QString getUser(); + + QString getPassword(); + + void setProjectID(const int project); + int getProjectID(); + + void setSuiteID(const int project); + int getSuiteID(); + + bool userCancelled{ false }; + + void updateReleasesComboBoxData(QStringList data); + int getReleaseID(); + +private slots: + void on_acceptButton_clicked(); + void on_OKButton_clicked(); + void on_cancelButton_clicked(); +}; + +#endif \ No newline at end of file diff --git a/tools/auto-tester/src/ui/TestRailTestCasesSelectorWindow.ui b/tools/auto-tester/src/ui/TestRailTestCasesSelectorWindow.ui new file mode 100644 index 0000000000..41ff2943d5 --- /dev/null +++ b/tools/auto-tester/src/ui/TestRailTestCasesSelectorWindow.ui @@ -0,0 +1,280 @@ + + + TestRailTestCasesSelectorWindow + + + + 0 + 0 + 489 + 474 + + + + TestRail Test Case Selector Window + + + + + 30 + 850 + 500 + 28 + + + + + 12 + + + + similarity + + + + + + 70 + 125 + 121 + 20 + + + + + 10 + + + + TestRail Password + + + + + + 70 + 25 + 121 + 20 + + + + + 10 + + + + TestRail URL + + + + + false + + + + 120 + 420 + 93 + 28 + + + + OK + + + + + + 280 + 420 + 93 + 28 + + + + Cancel + + + + + + 200 + 120 + 231 + 24 + + + + QLineEdit::Password + + + + + + 70 + 75 + 121 + 20 + + + + + 10 + + + + TestRail User + + + + + + 200 + 170 + 231 + 24 + + + + QLineEdit::Normal + + + + + + 70 + 175 + 121 + 20 + + + + + 10 + + + + TestRail Project ID + + + + + + 200 + 270 + 231 + 28 + + + + Accept + + + + + false + + + + 270 + 350 + 161 + 22 + + + + + + true + + + + 80 + 350 + 181 + 20 + + + + + 10 + + + + TestRail Added for Release + + + + + + 200 + 20 + 231 + 24 + + + + QLineEdit::Normal + + + + + + 200 + 70 + 231 + 24 + + + + QLineEdit::Normal + + + + + + 200 + 215 + 231 + 24 + + + + QLineEdit::Normal + + + + + + 70 + 220 + 121 + 20 + + + + + 10 + + + + TestRail Suite ID + + + + + + urlLineEdit + userLineEdit + passwordLineEdit + projectIDLineEdit + suiteIDLineEdit + acceptButton + releasesComboBox + OKButton + cancelButton + + + + diff --git a/tools/dissectors/1-hfudt.lua b/tools/dissectors/1-hfudt.lua index 137bee659b..9ad89462f9 100644 --- a/tools/dissectors/1-hfudt.lua +++ b/tools/dissectors/1-hfudt.lua @@ -26,9 +26,6 @@ local f_hmac_hash = ProtoField.bytes("hfudt.hmac_hash", "HMAC Hash") local f_control_type = ProtoField.uint16("hfudt.control_type", "Control Type", base.DEC) local f_control_type_text = ProtoField.string("hfudt.control_type_text", "Control Type Text", base.ASCII) local f_ack_sequence_number = ProtoField.uint32("hfudt.ack_sequence_number", "ACKed Sequence Number", base.DEC) -local f_control_sub_sequence = ProtoField.uint32("hfudt.control_sub_sequence", "Control Sub-Sequence Number", base.DEC) -local f_nak_sequence_number = ProtoField.uint32("hfudt.nak_sequence_number", "NAKed Sequence Number", base.DEC) -local f_nak_range_end = ProtoField.uint32("hfudt.nak_range_end", "NAK Range End", base.DEC) local SEQUENCE_NUMBER_MASK = 0x07FFFFFF @@ -37,19 +34,13 @@ p_hfudt.fields = { f_control_bit, f_reliable_bit, f_message_bit, f_sequence_number, f_type, f_type_text, f_version, f_sender_id, f_hmac_hash, f_message_position, f_message_number, f_message_part_number, f_obfuscation_level, - f_control_type, f_control_type_text, f_control_sub_sequence, f_ack_sequence_number, f_nak_sequence_number, f_nak_range_end, - f_data + f_control_type, f_control_type_text, f_ack_sequence_number, f_data } local control_types = { [0] = { "ACK", "Acknowledgement" }, - [1] = { "ACK2", "Acknowledgement of acknowledgement" }, - [2] = { "LightACK", "Light Acknowledgement" }, - [3] = { "NAK", "Loss report (NAK)" }, - [4] = { "TimeoutNAK", "Loss report re-transmission (TimeoutNAK)" }, [5] = { "Handshake", "Handshake" }, [6] = { "HandshakeACK", "Acknowledgement of Handshake" }, - [7] = { "ProbeTail", "Probe tail" }, [8] = { "HandshakeRequest", "Request a Handshake" } } @@ -86,12 +77,12 @@ local packet_types = { [22] = "ICEServerPeerInformation", [23] = "ICEServerQuery", [24] = "OctreeStats", - [25] = "Jurisdiction", + [25] = "UNUSED_PACKET_TYPE_1", [26] = "AvatarIdentityRequest", [27] = "AssignmentClientStatus", [28] = "NoisyMute", [29] = "AvatarIdentity", - [30] = "AvatarBillboard", + [30] = "NodeIgnoreRequest", [31] = "DomainConnectRequest", [32] = "DomainServerRequireDTLS", [33] = "NodeJsonStats", @@ -115,7 +106,52 @@ local packet_types = { [51] = "AssetUpload", [52] = "AssetUploadReply", [53] = "AssetGetInfo", - [54] = "AssetGetInfoReply" + [54] = "AssetGetInfoReply", + [55] = "DomainDisconnectRequest", + [56] = "DomainServerRemovedNode", + [57] = "MessagesData", + [58] = "MessagesSubscribe", + [59] = "MessagesUnsubscribe", + [60] = "ICEServerHeartbeatDenied", + [61] = "AssetMappingOperation", + [62] = "AssetMappingOperationReply", + [63] = "ICEServerHeartbeatACK", + [64] = "NegotiateAudioFormat", + [65] = "SelectedAudioFormat", + [66] = "MoreEntityShapes", + [67] = "NodeKickRequest", + [68] = "NodeMuteRequest", + [69] = "RadiusIgnoreRequest", + [70] = "UsernameFromIDRequest", + [71] = "UsernameFromIDReply", + [72] = "AvatarQuery", + [73] = "RequestsDomainListData", + [74] = "PerAvatarGainSet", + [75] = "EntityScriptGetStatus", + [76] = "EntityScriptGetStatusReply", + [77] = "ReloadEntityServerScript", + [78] = "EntityPhysics", + [79] = "EntityServerScriptLog", + [80] = "AdjustAvatarSorting", + [81] = "OctreeFileReplacement", + [82] = "CollisionEventChanges", + [83] = "ReplicatedMicrophoneAudioNoEcho", + [84] = "ReplicatedMicrophoneAudioWithEcho", + [85] = "ReplicatedInjectAudio", + [86] = "ReplicatedSilentAudioFrame", + [87] = "ReplicatedAvatarIdentity", + [88] = "ReplicatedKillAvatar", + [89] = "ReplicatedBulkAvatarData", + [90] = "DomainContentReplacementFromUrl", + [91] = "ChallengeOwnership", + [92] = "EntityScriptCallMethod", + [93] = "ChallengeOwnershipRequest", + [94] = "ChallengeOwnershipReply", + [95] = "OctreeDataFileRequest", + [96] = "OctreeDataFileReply", + [97] = "OctreeDataPersist", + [98] = "EntityClone", + [99] = "EntityQueryInitialResultsComplete" } local unsourced_packet_types = { @@ -160,51 +196,18 @@ function p_hfudt.dissector(buf, pinfo, tree) subtree:add(f_control_type_text, control_types[shifted_type][1]) end - if shifted_type == 0 or shifted_type == 1 then + if shifted_type == 0 then + local data_index = 4 - -- this has a sub-sequence number - local second_word = buf(4, 4):le_uint() - subtree:add(f_control_sub_sequence, bit32.band(second_word, SEQUENCE_NUMBER_MASK)) - - local data_index = 8 - - if shifted_type == 0 then - -- if this is an ACK let's read out the sequence number - local sequence_number = buf(8, 4):le_uint() - subtree:add(f_ack_sequence_number, bit32.band(sequence_number, SEQUENCE_NUMBER_MASK)) - - data_index = data_index + 4 - end + -- This is an ACK let's read out the sequence number + local sequence_number = buf(data_index, 4):le_uint() + subtree:add(f_ack_sequence_number, bit32.band(sequence_number, SEQUENCE_NUMBER_MASK)) + data_index = data_index + 4 data_length = buf:len() - data_index -- set the data from whatever is left in the packet subtree:add(f_data, buf(data_index, data_length)) - - elseif shifted_type == 2 then - -- this is a Light ACK let's read out the sequence number - local sequence_number = buf(4, 4):le_uint() - subtree:add(f_ack_sequence_number, bit32.band(sequence_number, SEQUENCE_NUMBER_MASK)) - - data_length = buf:len() - 4 - - -- set the data from whatever is left in the packet - subtree:add(f_data, buf(4, data_length)) - elseif shifted_type == 3 or shifted_type == 4 then - if buf:len() <= 12 then - -- this is a NAK pull the sequence number or range - local sequence_number = buf(4, 4):le_uint() - subtree:add(f_nak_sequence_number, bit32.band(sequence_number, SEQUENCE_NUMBER_MASK)) - - data_length = buf:len() - 4 - - if buf:len() > 8 then - local range_end = buf(8, 4):le_uint() - subtree:add(f_nak_range_end, bit32.band(range_end, SEQUENCE_NUMBER_MASK)) - - data_length = data_length - 4 - end - end else data_length = buf:len() - 4 diff --git a/tools/dissectors/3-hf-avatar.lua b/tools/dissectors/3-hf-avatar.lua index af648ed5b9..0fa551c6f8 100644 --- a/tools/dissectors/3-hf-avatar.lua +++ b/tools/dissectors/3-hf-avatar.lua @@ -19,6 +19,8 @@ local f_avatar_data_valid_rotations = ProtoField.string("hf_avatar.avatar_data_v local f_avatar_data_valid_translations = ProtoField.string("hf_avatar.avatar_data_valid_translations", "Valid Translations") local f_avatar_data_default_rotations = ProtoField.string("hf_avatar.avatar_data_default_rotations", "Valid Default") local f_avatar_data_default_translations = ProtoField.string("hf_avatar.avatar_data_default_translations", "Valid Default") +local f_avatar_data_sizes = ProtoField.string("hf_avatar.avatar_sizes", "Sizes") + p_hf_avatar.fields = { f_avatar_id, f_avatar_data_parent_id @@ -110,6 +112,9 @@ function add_avatar_data_subtrees(avatar_data) if avatar_data["default_translations"] then avatar_subtree:add(f_avatar_data_default_translations, avatar_data["default_translations"]) end + if avatar_data["sizes"] then + avatar_subtree:add(f_avatar_data_sizes, avatar_data["sizes"]) + end end function decode_vec3(buf) @@ -154,6 +159,8 @@ function decode_avatar_data_packet(buf) local i = 0 local result = {} + result["sizes"] = "" + -- uint16 has_flags local has_flags = buf(i, 2):le_uint() i = i + 2 @@ -258,6 +265,8 @@ function decode_avatar_data_packet(buf) if has_joint_data then + local joint_poses_start = i + local num_joints = buf(i, 1):uint() i = i + 1 local num_validity_bytes = math.ceil(num_joints / 8) @@ -279,6 +288,8 @@ function decode_avatar_data_packet(buf) -- TODO: skip hand controller data i = i + 24 + result["sizes"] = result["sizes"] .. " Poses: " .. (i - joint_poses_start) + end if has_joint_default_pose_flags then @@ -295,5 +306,7 @@ function decode_avatar_data_packet(buf) result["default_translations"] = "Default Translations: " .. string.format("(%d/%d) {", #indices, num_joints) .. table.concat(indices, ", ") .. "}" end + result["sizes"] = result["sizes"] .. " Total: " .. i + return result end diff --git a/tools/dissectors/README.md b/tools/dissectors/README.md new file mode 100644 index 0000000000..1e618a7b4c --- /dev/null +++ b/tools/dissectors/README.md @@ -0,0 +1,14 @@ +High Fidelity Wireshark Plugins +--------------------------------- + +Install wireshark 2.4.6 or higher. + +Copy these lua files into c:\Users\username\AppData\Roaming\Wireshark\Plugins + +After a capture any detected High Fidelity Packets should be easily identifiable by one of the following protocols + +* HF-AUDIO - Streaming audio packets +* HF-AVATAR - Streaming avatar mixer packets +* HF-ENTITY - Entity server traffic +* HF-DOMAIN - Domain server traffic +* HFUDT - All other UDP traffic diff --git a/tools/shreflect/CMakeLists.txt b/tools/shreflect/CMakeLists.txt new file mode 100644 index 0000000000..0748f59d31 --- /dev/null +++ b/tools/shreflect/CMakeLists.txt @@ -0,0 +1,10 @@ +set(TARGET_NAME shreflect) + +# don't use the setup_hifi_project macro as we don't want Qt or GLM dependencies +file(GLOB TARGET_SRCS src/*) +add_executable(${TARGET_NAME} ${TARGET_SRCS}) +target_json() + +if (WIN32) + set_property(TARGET ${TARGET_NAME} APPEND_STRING PROPERTY LINK_FLAGS_DEBUG "/OPT:NOREF /OPT:NOICF") +endif() diff --git a/tools/shreflect/src/main.cpp b/tools/shreflect/src/main.cpp new file mode 100644 index 0000000000..e13f937102 --- /dev/null +++ b/tools/shreflect/src/main.cpp @@ -0,0 +1,204 @@ +// +// Bradley Austin Davis on 2018/05/24 +// Copyright 2013 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using json = nlohmann::json; + +std::vector splitStringIntoLines(const std::string& s) { + std::stringstream ss(s); + std::vector result; + std::string line; + while (std::getline(ss, line, '\n')) { + result.push_back(line); + } + return result; +} + +std::string readFile(const std::string& file) { + std::ifstream t(file); + std::string str((std::istreambuf_iterator(t)), + std::istreambuf_iterator()); + return str; +} + +void writeFile(const std::string& file, const std::string& out) { + std::ofstream t(file, std::ios::trunc); + t << out; + t.close(); +} + +// Convert a Perl style multi-line commented regex into a C++ style regex +// All whitespace will be removed and lines with '#' comments will have the comments removed +std::string getUnformattedRegex(const std::string& formatted) { + static const std::regex WHITESPACE = std::regex("\\s+"); + static const std::string EMPTY; + std::string result; + result.reserve(formatted.size()); + auto lines = splitStringIntoLines(formatted); + for (auto line : lines) { + auto commentStart = line.find('#'); + if (std::string::npos != commentStart) { + line = line.substr(0, commentStart); + } + line = std::regex_replace(line, WHITESPACE, EMPTY); + result += line; + } + + return result; +} + +static std::string LAYOUT_REGEX_STRING{ R"REGEX( +^layout\( # BEGIN layout declaration block + (\s*std140\s*,\s*)? # Optional std140 marker + (binding|location) # binding / location + \s*=\s* + (?: + (\b\d+\b) # literal numeric binding like binding=1 + | + (\b[A-Z_0-9]+\b) # Preprocessor macro binding like binding=GPU_TEXTURE_FOO + ) +\)\s* # END layout declaration block +(?: + ( # Texture or simple uniform like `layout(binding=0) uniform sampler2D originalTexture;` + uniform\s+ + (\b\w+\b)\s+ + (\b\w+\b)\s* + (?:\[\d*\])? + (?:\s*=.*)? + \s*;.*$ + ) + | + ( # UBO or SSBO like `layout(std140, binding=GPU_STORAGE_TRANSFORM_OBJECT) buffer transformObjectBuffer {` + \b(uniform|buffer)\b\s+ + \b(\w+\b) + \s*\{.*$ + ) + | + ( # Input or output attribute like `layout(location=GPU_ATTR_POSITION) in vec4 inPosition;` + \b(in|out)\b\s+ + \b(\w+)\b\s+ + \b(\w+)\b\s*;\s*$ + ) +) +)REGEX" }; + +enum Groups { + STD140 = 1, + LOCATION_TYPE = 2, + LOCATION_LITERAL = 3, + LOCATION_DEFINE = 4, + DECL_SIMPLE = 5, + SIMPLE_TYPE = 6, + SIMPLE_NAME = 7, + DECL_STRUCT = 8, + STRUCT_TYPE = 9, + STRUCT_NAME = 10, + DECL_INOUT = 11, + INOUT_DIRECTION = 12, + INOUT_TYPE = 13, + INOUT_NAME = 14, +}; + +json reflectShader(const std::string& shaderPath) { + static const std::regex DEFINE("^#define\\s+([_A-Z0-9]+)\\s+(\\d+)\\s*$"); + static const std::regex LAYOUT_QUALIFIER{ getUnformattedRegex(LAYOUT_REGEX_STRING) }; + + + auto shaderSource = readFile(shaderPath); + std::vector lines = splitStringIntoLines(shaderSource); + using Map = std::unordered_map; + + json inputs; + json outputs; + json textures; + json textureTypes; + json uniforms; + json storageBuffers; + json uniformBuffers; + Map locationDefines; + for (const auto& line : lines) { + std::cmatch m; + if (std::regex_match(line.c_str(), m, DEFINE)) { + locationDefines[m[1].str()] = std::stoi(m[2].first); + } else if (std::regex_match(line.c_str(), m, LAYOUT_QUALIFIER)) { + int binding = -1; + if (m[LOCATION_LITERAL].matched) { + binding = std::stoi(m[LOCATION_LITERAL].str()); + } else { + binding = locationDefines[m[LOCATION_DEFINE].str()]; + } + if (m[DECL_SIMPLE].matched) { + auto name = m[SIMPLE_NAME].str(); + auto type = m[SIMPLE_TYPE].str(); + bool isTexture = 0 == type.find("sampler"); + auto& map = isTexture ? textures : uniforms; + map[name] = binding; + if (isTexture) { + textureTypes[name] = type; + } + } else if (m[DECL_STRUCT].matched) { + auto name = m[STRUCT_NAME].str(); + auto type = m[STRUCT_TYPE].str(); + auto& map = (type == "buffer") ? storageBuffers : uniformBuffers; + map[name] = binding; + } else if (m[DECL_INOUT].matched) { + auto name = m[INOUT_NAME].str(); + auto& map = (m[INOUT_DIRECTION].str() == "in") ? inputs : outputs; + map[name] = binding; + } + } + } + + json result; + if (!inputs.empty()) { + result["inputs"] = inputs; + } + if (!outputs.empty()) { + result["outputs"] = outputs; + } + if (!textures.empty()) { + result["textures"] = textures; + } + if (!textureTypes.empty()) { + result["texturesTypes"] = textureTypes; + } + if (!uniforms.empty()) { + result["uniforms"] = uniforms; + } + if (!storageBuffers.empty()) { + result["storageBuffers"] = storageBuffers; + } + if (!uniformBuffers.empty()) { + result["uniformBuffers"] = uniformBuffers; + } + + result["name"] = shaderPath; + + return result; +} + +int main (int argc, char** argv) { + auto path = std::string(argv[1]); + auto shaderReflection = reflectShader(path); + writeFile(path + ".json", shaderReflection.dump(4)); + return 0; +} diff --git a/tools/udt-test/src/UDTTest.cpp b/tools/udt-test/src/UDTTest.cpp index ce89f04ce5..46e7ed0be0 100644 --- a/tools/udt-test/src/UDTTest.cpp +++ b/tools/udt-test/src/UDTTest.cpp @@ -58,14 +58,12 @@ const QCommandLineOption STATS_INTERVAL { const QStringList CLIENT_STATS_TABLE_HEADERS { "Send (Mb/s)", "Est. Max (Mb/s)", "RTT (ms)", "CW (P)", "Period (us)", - "Recv ACK", "Procd ACK", "Recv LACK", "Recv NAK", "Recv TNAK", - "Sent ACK2", "Sent Packets", "Re-sent Packets" + "Recv ACK", "Procd ACK", "Sent Packets", "Re-sent Packets" }; const QStringList SERVER_STATS_TABLE_HEADERS { " Mb/s ", "Recv Mb/s", "Est. Max (Mb/s)", "RTT (ms)", "CW (P)", - "Sent ACK", "Sent LACK", "Sent NAK", "Sent TNAK", - "Recv ACK2", "Duplicates (P)" + "Sent ACK", "Duplicates (P)" }; UDTTest::UDTTest(int& argc, char** argv) : @@ -387,10 +385,6 @@ void UDTTest::sampleStats() { QString::number(stats.packetSendPeriod).rightJustified(CLIENT_STATS_TABLE_HEADERS[++headerIndex].size()), QString::number(stats.events[udt::ConnectionStats::Stats::ReceivedACK]).rightJustified(CLIENT_STATS_TABLE_HEADERS[++headerIndex].size()), QString::number(stats.events[udt::ConnectionStats::Stats::ProcessedACK]).rightJustified(CLIENT_STATS_TABLE_HEADERS[++headerIndex].size()), - QString::number(stats.events[udt::ConnectionStats::Stats::ReceivedLightACK]).rightJustified(CLIENT_STATS_TABLE_HEADERS[++headerIndex].size()), - QString::number(stats.events[udt::ConnectionStats::Stats::ReceivedNAK]).rightJustified(CLIENT_STATS_TABLE_HEADERS[++headerIndex].size()), - QString::number(stats.events[udt::ConnectionStats::Stats::ReceivedTimeoutNAK]).rightJustified(CLIENT_STATS_TABLE_HEADERS[++headerIndex].size()), - QString::number(stats.events[udt::ConnectionStats::Stats::SentACK2]).rightJustified(CLIENT_STATS_TABLE_HEADERS[++headerIndex].size()), QString::number(stats.sentPackets).rightJustified(CLIENT_STATS_TABLE_HEADERS[++headerIndex].size()), QString::number(stats.events[udt::ConnectionStats::Stats::Retransmission]).rightJustified(CLIENT_STATS_TABLE_HEADERS[++headerIndex].size()) }; @@ -420,10 +414,6 @@ void UDTTest::sampleStats() { QString::number(stats.rtt / USECS_PER_MSEC, 'f', 2).rightJustified(SERVER_STATS_TABLE_HEADERS[++headerIndex].size()), QString::number(stats.congestionWindowSize).rightJustified(SERVER_STATS_TABLE_HEADERS[++headerIndex].size()), QString::number(stats.events[udt::ConnectionStats::Stats::SentACK]).rightJustified(SERVER_STATS_TABLE_HEADERS[++headerIndex].size()), - QString::number(stats.events[udt::ConnectionStats::Stats::SentLightACK]).rightJustified(SERVER_STATS_TABLE_HEADERS[++headerIndex].size()), - QString::number(stats.events[udt::ConnectionStats::Stats::SentNAK]).rightJustified(SERVER_STATS_TABLE_HEADERS[++headerIndex].size()), - QString::number(stats.events[udt::ConnectionStats::Stats::SentTimeoutNAK]).rightJustified(SERVER_STATS_TABLE_HEADERS[++headerIndex].size()), - QString::number(stats.events[udt::ConnectionStats::Stats::ReceivedACK2]).rightJustified(SERVER_STATS_TABLE_HEADERS[++headerIndex].size()), QString::number(stats.events[udt::ConnectionStats::Stats::Duplicate]).rightJustified(SERVER_STATS_TABLE_HEADERS[++headerIndex].size()) }; diff --git a/tools/udt-test/src/main.cpp b/tools/udt-test/src/main.cpp index d88218c0d0..13953c91cc 100644 --- a/tools/udt-test/src/main.cpp +++ b/tools/udt-test/src/main.cpp @@ -15,7 +15,7 @@ #include "UDTTest.h" int main(int argc, char* argv[]) { - setupHifiApplication("UDT Test); + setupHifiApplication("UDT Test"); UDTTest app(argc, argv); return app.exec();