diff --git a/BUILD.md b/BUILD.md index ba38f4b51d..4a0274cea6 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. @@ -80,6 +80,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/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 82fd86b3dd..ce5af01f29 100644 --- a/android/app/src/main/cpp/native.cpp +++ b/android/app/src/main/cpp/native.cpp @@ -209,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) { } @@ -285,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..2a008d7950 --- /dev/null +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/FriendsFragment.java @@ -0,0 +1,193 @@ +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() { + mSwipeRefreshLayout.setRefreshing(false); + } + + @Override + public void onNonEmptyAdapter() { + mSwipeRefreshLayout.setRefreshing(false); + } + + @Override + public void onError(Exception e, String message) { + mSwipeRefreshLayout.setRefreshing(false); + } + }); + + mUsersView.setAdapter(mUsersAdapter); + + 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/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/UserListAdapter.java b/android/app/src/main/java/io/highfidelity/hifiinterface/view/UserListAdapter.java new file mode 100644 index 0000000000..9f62b21250 --- /dev/null +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/view/UserListAdapter.java @@ -0,0 +1,247 @@ +package io.highfidelity.hifiinterface.view; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.drawable.BitmapDrawable; +import android.net.Uri; +import android.support.v4.content.ContextCompat; +import android.support.v4.graphics.drawable.RoundedBitmapDrawable; +import android.support.v4.graphics.drawable.RoundedBitmapDrawableFactory; +import android.support.v7.widget.RecyclerView; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import com.squareup.picasso.Callback; +import com.squareup.picasso.Picasso; + +import java.util.ArrayList; +import java.util.List; + +import io.highfidelity.hifiinterface.R; +import io.highfidelity.hifiinterface.provider.UsersProvider; + +/** + * Created by cduarte on 6/13/18. + */ + +public class UserListAdapter extends RecyclerView.Adapter { + + private UsersProvider mProvider; + private LayoutInflater mInflater; + private Context mContext; + private List mUsers = new ArrayList<>(); + private ItemClickListener mClickListener; + private AdapterListener mAdapterListener; + + public UserListAdapter(Context c, UsersProvider usersProvider) { + mContext = c; + mInflater = LayoutInflater.from(mContext); + mProvider = usersProvider; + loadUsers(); + } + + public void setListener(AdapterListener adapterListener) { + mAdapterListener = adapterListener; + } + + public void loadUsers() { + mProvider.retrieve(new UsersProvider.UsersCallback() { + @Override + public void retrieveOk(List users) { + mUsers = new ArrayList<>(users); + notifyDataSetChanged(); + if (mAdapterListener != null) { + if (mUsers.isEmpty()) { + mAdapterListener.onEmptyAdapter(); + } else { + mAdapterListener.onNonEmptyAdapter(); + } + } + } + + @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(); + void onNonEmptyAdapter(); + 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 d61a1a49b7..f534be9346 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -19,6 +19,7 @@ #include #include +#include #include #include #include @@ -32,6 +33,7 @@ #include #include #include +#include #include #include #include @@ -50,6 +52,7 @@ #include "entities/AssignmentParentFinder.h" #include "RecordingScriptingInterface.h" #include "AbstractAudioInterface.h" +#include "AgentScriptingInterface.h" static const int RECEIVED_AUDIO_STREAM_CAPACITY_FRAMES = 10; @@ -70,6 +73,7 @@ Agent::Agent(ReceivedMessage& message) : DependencyManager::set(); DependencyManager::set(); + DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); @@ -78,8 +82,6 @@ Agent::Agent(ReceivedMessage& message) : DependencyManager::set(); DependencyManager::set(); - DependencyManager::set(ScriptEngine::AGENT_SCRIPT); - DependencyManager::set(); DependencyManager::set(); @@ -158,6 +160,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(); @@ -364,7 +368,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()) { @@ -450,10 +453,10 @@ void Agent::executeScript() { packetReceiver.registerListener(PacketType::AvatarIdentity, avatarHashMap.data(), "processAvatarIdentityPacket"); // register ourselves to the script engine - _scriptEngine->registerGlobalObject("Agent", this); + _scriptEngine->registerGlobalObject("Agent", new AgentScriptingInterface(this)); - _scriptEngine->registerGlobalObject("SoundCache", DependencyManager::get().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); @@ -494,7 +497,6 @@ void Agent::executeScript() { Frame::clearFrameHandler(AVATAR_FRAME_TYPE); DependencyManager::destroy(); - setFinished(true); } @@ -513,7 +515,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) { @@ -823,10 +825,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); @@ -839,16 +837,16 @@ 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(); QMetaObject::invokeMethod(&_avatarAudioTimer, "stop"); // cleanup codec & encoder @@ -857,3 +855,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..7d47c8e713 100644 --- a/assignment-client/src/Agent.h +++ b/assignment-client/src/Agent.h @@ -33,19 +33,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 +60,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..d761699285 100644 --- a/assignment-client/src/AssignmentClient.cpp +++ b/assignment-client/src/AssignmentClient.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -63,6 +64,7 @@ AssignmentClient::AssignmentClient(Assignment::Type requestAssignmentType, QStri auto nodeList = DependencyManager::set(NodeType::Unassigned, listenPort); auto animationCache = DependencyManager::set(); + DependencyManager::set(); auto entityScriptingInterface = DependencyManager::set(false); DependencyManager::registerInheritance(); 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/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index 772c8643b3..8f1df9c321 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -447,18 +447,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) { @@ -643,6 +646,7 @@ 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()) { @@ -650,17 +654,22 @@ void AvatarMixer::handleNodeIgnoreRequestPacket(QSharedPointer QUuid ignoredUUID = QUuid::fromRfc4122(message->readWithoutCopy(NUM_BYTES_RFC4122_UUID)); 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); + 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); + } + // 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); + } } if (addToIgnore) { diff --git a/assignment-client/src/avatars/AvatarMixerClientData.cpp b/assignment-client/src/avatars/AvatarMixerClientData.cpp index e185fe9167..9aa3e88b52 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.cpp +++ b/assignment-client/src/avatars/AvatarMixerClientData.cpp @@ -108,7 +108,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 +116,7 @@ void AvatarMixerClientData::ignoreOther(SharedNodePointer self, SharedNodePointe killPacket->writePrimitive(KillAvatarReason::YourAvatarEnteredTheirBubble); } setLastBroadcastTime(other->getUUID(), 0); - DependencyManager::get()->sendUnreliablePacket(*killPacket, *self); + DependencyManager::get()->sendPacket(std::move(killPacket), *self); } } diff --git a/assignment-client/src/entities/EntityTreeSendThread.cpp b/assignment-client/src/entities/EntityTreeSendThread.cpp index f008ef9925..f7ca05fbf2 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() - 1U)); + 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..0e1126cebe 100644 --- a/assignment-client/src/scripts/EntityScriptServer.cpp +++ b/assignment-client/src/scripts/EntityScriptServer.cpp @@ -26,7 +26,7 @@ #include #include #include -#include +#include #include #include @@ -66,6 +66,7 @@ EntityScriptServer::EntityScriptServer(ReceivedMessage& message) : ThreadedAssig DependencyManager::set(); DependencyManager::set(); + DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); @@ -438,7 +439,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(); 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..b4cacd8f90 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-RC72.zip + URL_MD5 b1d8faf9266bfbff88274a484911eb99 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 07f1eb7e5e..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 } ] }, diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index 59cc8d10f6..2307b1be3b 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,7 +460,7 @@ 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 @@ -1009,7 +1011,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/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..2e6e909312 100644 --- a/interface/resources/qml/Stats.qml +++ b/interface/resources/qml/Stats.qml @@ -173,6 +173,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, " + 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/SpinBox.qml b/interface/resources/qml/controls-uit/SpinBox.qml index 5a4ba70080..f3ae3c5d6e 100644 --- a/interface/resources/qml/controls-uit/SpinBox.qml +++ b/interface/resources/qml/controls-uit/SpinBox.qml @@ -124,6 +124,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 +143,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/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 a88a584c63..b7e1adda70 100644 --- a/interface/resources/qml/hifi/AvatarApp.qml +++ b/interface/resources/qml/hifi/AvatarApp.qml @@ -244,7 +244,7 @@ Rectangle { var avatarSettings = { dominantHand : settings.dominantHandIsLeft ? 'left' : 'right', collisionsEnabled : settings.avatarCollisionsOn, - animGraphUrl : settings.avatarAnimationJSON, + animGraphOverrideUrl : settings.avatarAnimationOverrideJSON, collisionSoundUrl : settings.avatarCollisionSoundUrl }; @@ -476,17 +476,13 @@ Rectangle { anchors.verticalCenter: avatarNameLabel.verticalCenter glyphText: "." glyphSize: 22 - - MouseArea { - anchors.fill: parent - onClicked: { - popup.showSpecifyAvatarUrl(currentAvatar.avatarUrl, 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); + }); } } @@ -496,12 +492,8 @@ Rectangle { glyphText: "\ue02e" visible: avatarWearablesCount !== 0 - - MouseArea { - anchors.fill: parent - onClicked: { - adjustWearables.open(currentAvatar); - } + onClicked: { + adjustWearables.open(currentAvatar); } } @@ -682,6 +674,14 @@ Rectangle { PropertyChanges { target: container; y: -5 } PropertyChanges { target: favoriteAvatarImage; dropShadowRadius: 10 } PropertyChanges { target: favoriteAvatarImage; dropShadowVerticalOffset: 6 } + }, + State { + name: "getMoreAvatarsHovered" + when: getMoreAvatarsMouseArea.containsMouse; + PropertyChanges { target: getMoreAvatarsMouseArea; anchors.bottomMargin: -5 } + PropertyChanges { target: container; y: -5 } + PropertyChanges { target: getMoreAvatarsImage; dropShadowRadius: 10 } + PropertyChanges { target: getMoreAvatarsImage; dropShadowVerticalOffset: 6 } } ] @@ -741,6 +741,7 @@ Rectangle { } ShadowRectangle { + id: getMoreAvatarsImage width: 92 height: 92 radius: 5 @@ -756,7 +757,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..abcceae295 100644 --- a/interface/resources/qml/hifi/NameCard.qml +++ b/interface/resources/qml/hifi/NameCard.qml @@ -526,11 +526,13 @@ 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 + Component.onCompleted: { + value = Users.getAvatarGain(uuid); + } onValueChanged: { updateGainFromQML(uuid, value, false); } @@ -587,9 +589,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..cbab83ea28 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 @@ -1027,12 +1046,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 +1229,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..39344b04bc 100644 --- a/interface/resources/qml/hifi/avatarapp/AdjustWearables.qml +++ b/interface/resources/qml/hifi/avatarapp/AdjustWearables.qml @@ -326,7 +326,7 @@ Rectangle { height: 40 anchors.right: parent.right color: hifi.buttons.red; - colorScheme: hifi.colorSchemes.dark; + colorScheme: hifi.colorSchemes.light; text: "TAKE IT OFF" onClicked: wearableDeleted(root.avatarName, getCurrentWearable().id); enabled: wearablesCombobox.model.count !== 0 diff --git a/interface/resources/qml/hifi/avatarapp/MessageBox.qml b/interface/resources/qml/hifi/avatarapp/MessageBox.qml index f2df0b5199..e4aa0847c5 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 = ''; diff --git a/interface/resources/qml/hifi/avatarapp/MessageBoxes.qml b/interface/resources/qml/hifi/avatarapp/MessageBoxes.qml index fd65d014a0..bdb0462f42 100644 --- a/interface/resources/qml/hifi/avatarapp/MessageBoxes.qml +++ b/interface/resources/qml/hifi/avatarapp/MessageBoxes.qml @@ -37,11 +37,12 @@ MessageBox { function showGetWearables(callback, linkCallback) { popup.button2text = 'AvatarIsland' + popup.dialogButtons.yesButton.fontCapitalization = Font.MixedCase; popup.button1text = 'CANCEL' popup.titleText = 'Get Wearables' - popup.bodyText = 'Buy wearables from Marketplace' + '
' + - 'Wear wearables from My Purchases' + '
' + - 'You can visit the domain “AvatarIsland” to get wearables' + popup.bodyText = 'Buy wearables from Marketplace.' + '
' + + 'Use wearables in My Purchases.' + '
' + '
' + + 'Visit “AvatarIsland” to get wearables.' popup.imageSource = getWearablesUrl; popup.onButton2Clicked = function() { @@ -96,12 +97,13 @@ MessageBox { function showBuyAvatars(callback, linkCallback) { popup.button2text = 'BodyMart' + popup.dialogButtons.yesButton.fontCapitalization = Font.MixedCase; popup.button1text = 'CANCEL' popup.titleText = 'Get Avatars' - popup.bodyText = 'Buy avatars from 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() { diff --git a/interface/resources/qml/hifi/avatarapp/Settings.qml b/interface/resources/qml/hifi/avatarapp/Settings.qml index f996bdfd03..e1b55866c2 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; @@ -45,6 +46,7 @@ Rectangle { } avatarAnimationJSON = settings.animGraphUrl; + avatarAnimationOverrideJSON = settings.animGraphOverrideUrl; avatarCollisionSoundUrl = settings.collisionSoundUrl; visible = true; 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/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/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/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/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/ControllerSettings.qml b/interface/resources/qml/hifi/tablet/ControllerSettings.qml index 0a45feb61f..135c1379e2 100644 --- a/interface/resources/qml/hifi/tablet/ControllerSettings.qml +++ b/interface/resources/qml/hifi/tablet/ControllerSettings.qml @@ -299,7 +299,7 @@ Item { anchors.fill: stackView id: controllerPrefereneces objectName: "TabletControllerPreferences" - showCategories: [( (HMD.active) ? "VR Movement" : "Movement"), "Game Controller", "Sixense Controllers", "Perception Neuron", "Leap Motion"] + showCategories: ["VR Movement", "Game Controller", "Sixense Controllers", "Perception Neuron", "Leap Motion"] categoryProperties: { "VR Movement" : { "User real-world height (meters)" : { "anchors.right" : "undefined" }, 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/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 26ec3c1444..3f74ad6cbb 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 @@ -313,6 +317,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); @@ -805,16 +810,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(); @@ -1069,8 +1078,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(); } }); @@ -1080,33 +1091,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(); @@ -1177,7 +1189,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); @@ -1205,27 +1217,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; @@ -1295,10 +1309,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(); @@ -1627,23 +1642,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 @@ -1693,13 +1711,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); @@ -1756,13 +1777,13 @@ 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) { + connect(getEntities()->getTree().get(), &EntityTree::deletingEntity, [](const EntityItemID& entityItemID) { auto avatarManager = DependencyManager::get(); auto myAvatar = avatarManager ? avatarManager->getMyAvatar() : nullptr; if (myAvatar) { @@ -1770,7 +1791,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo } }); - EntityTree::setAddMaterialToEntityOperator([&](const QUuid& entityID, graphics::MaterialLayer material, const std::string& parentMaterialName) { + EntityTree::setAddMaterialToEntityOperator([this](const QUuid& entityID, graphics::MaterialLayer material, const std::string& parentMaterialName) { // try to find the renderable auto renderable = getEntities()->renderableForEntityId(entityID); if (renderable) { @@ -1785,7 +1806,7 @@ 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) { // try to find the renderable auto renderable = getEntities()->renderableForEntityId(entityID); if (renderable) { @@ -1820,7 +1841,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); @@ -1828,7 +1849,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); @@ -1839,13 +1860,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); }); @@ -2114,23 +2135,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); }); } @@ -2157,8 +2177,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); @@ -2168,7 +2188,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); @@ -2184,7 +2204,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); }); @@ -2384,21 +2404,35 @@ 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(); _displayPlugin.reset(); @@ -2436,6 +2470,8 @@ void Application::cleanupBeforeQuit() { DependencyManager::destroy(); #endif + DependencyManager::destroy(); // Must be destroyed before TabletScriptingInterface + // stop QML DependencyManager::destroy(); DependencyManager::destroy(); @@ -2447,10 +2483,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(); @@ -2491,9 +2523,6 @@ Application::~Application() { _entityClipboard->eraseAllOctreeElements(); _entityClipboard.reset(); - EntityTreePointer tree = getEntities()->getTree(); - tree->setSimulation(nullptr); - _octreeProcessor.terminate(); _entityEditSender.terminate(); @@ -2504,12 +2533,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(); @@ -2602,44 +2637,12 @@ 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(); -*/ + // 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 - - //// Requires the window context, because that's what's used in the actual rendering - //// and the GPU backend will make things like the VAO which cannot be shared across - //// 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(); - _graphicsEngine.initializeGPU(_glWidget); } @@ -2661,6 +2664,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 @@ -2675,8 +2682,6 @@ void Application::initializeDisplayPlugins() { // Submit a default frame to render until the engine starts up updateRenderArgs(0.0f); - _graphicsEngine._offscreenContext->makeCurrent(); - #define ENABLE_SPLASH_FRAME 0 #if ENABLE_SPLASH_FRAME { @@ -2718,24 +2723,10 @@ 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([&] { _graphicsEngine.initializeRender(DISABLE_DEFERRED); - /* // Set up the render engine - render::CullFunctor cullFunctor = LODManager::shouldRender; - _renderEngine->addJob("UpdateScene"); -#ifndef Q_OS_ANDROID - _renderEngine->addJob("SecondaryCameraJob", cullFunctor, !DISABLE_DEFERRED); -#endif - _renderEngine->addJob("RenderMainView", cullFunctor, !DISABLE_DEFERRED, render::ItemKey::TAG_BITS_0, render::ItemKey::TAG_BITS_0); - _renderEngine->load(); - _renderEngine->registerScene(_main3DScene); - - // Now that OpenGL is initialized, we are sure we have a valid context and can create the various pipeline shaders with success. - DependencyManager::get()->initializeShapePipelines(); - */ }); + }); } extern void setupPreferences(); @@ -2852,6 +2843,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); @@ -2934,10 +2934,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 @@ -3192,8 +3193,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()); @@ -3506,6 +3506,10 @@ static void dumpEventQueue(QThread* thread) { bool Application::event(QEvent* event) { + if (_aboutToQuit) { + return false; + } + if (!Menu::getInstance()) { return false; } @@ -3623,7 +3627,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 @@ -3834,7 +3841,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) { @@ -3870,11 +3879,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 { @@ -3902,10 +3914,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(); @@ -3970,11 +3978,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 @@ -4011,14 +4017,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; @@ -4037,10 +4040,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 @@ -4183,37 +4184,6 @@ bool Application::acceptSnapshot(const QString& urlString) { } return true; } -// -//bool Application::shouldPaint() const { -// if (_aboutToQuit || _window->isMinimized()) { -// return false; -// } -// -// -// auto displayPlugin = getActiveDisplayPlugin(); -// -//#ifdef DEBUG_PAINT_DELAY -// static uint64_t paintDelaySamples{ 0 }; -// static uint64_t paintDelayUsecs{ 0 }; -// -// paintDelayUsecs += displayPlugin->getPaintDelayUsecs(); -// -// static const int PAINT_DELAY_THROTTLE = 1000; -// if (++paintDelaySamples % PAINT_DELAY_THROTTLE == 0) { -// qCDebug(interfaceapp).nospace() << -// "Paint delay (" << paintDelaySamples << " samples): " << -// (float)paintDelaySamples / paintDelayUsecs << "us"; -// } -//#endif -// -// // Throttle if requested -// //if (displayPlugin->isThrottled() && (_graphicsEngine._renderEventHandler->_lastTimeRendered.elapsed() < THROTTLED_SIM_FRAME_PERIOD_MS)) { -// if (displayPlugin->isThrottled() && !_graphicsEngine.shouldPaint()) { -// return false; -// } -// -// return true; -//} #ifdef Q_OS_WIN #include @@ -4458,7 +4428,6 @@ void Application::idle() { if (offscreenUi->size() != fromGlm(uiSize)) { qCDebug(interfaceapp) << "Device pixel ratio changed, triggering resize to " << uiSize; offscreenUi->resize(fromGlm(uiSize)); - _graphicsEngine._offscreenContext->makeCurrent(); } } @@ -4496,11 +4465,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(); @@ -4513,10 +4485,6 @@ void Application::idle() { bool showWarnings = getLogger()->extraDebugging(); PerformanceWarning warn(showWarnings, "idle()"); - if (!_graphicsEngine._offscreenContext->makeCurrent()) { - qFatal("Unable to make main thread context current"); - } - { _gameWorkload.updateViews(_viewFrustum, getMyAvatar()->getHeadPosition()); _gameWorkload._engine->run(); @@ -4665,7 +4633,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) { @@ -4844,7 +4812,6 @@ QVector Application::pasteEntities(float x, float y, float z) { } void Application::init() { - _graphicsEngine._offscreenContext->makeCurrent(); // Make sure Login state is up to date DependencyManager::get()->toggleLoginDialog(); // if (!DISABLE_DEFERRED) { @@ -5193,6 +5160,7 @@ void Application::resetPhysicsReadyInformation() { _nearbyEntitiesCountAtLastPhysicsCheck = 0; _nearbyEntitiesStabilityCount = 0; _physicsEnabled = false; + _octreeProcessor.startEntitySequence(); } @@ -5206,6 +5174,7 @@ void Application::reloadResourceCaches() { queryOctree(NodeType::EntityServer, PacketType::EntityQuery); DependencyManager::get()->clearCache(); + DependencyManager::get()->clearCache(); DependencyManager::get()->refreshAll(); DependencyManager::get()->refreshAll(); @@ -5233,7 +5202,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); } @@ -5413,6 +5382,10 @@ static bool domainLoadingInProgress = false; void Application::update(float deltaTime) { PROFILE_RANGE_EX(app, __FUNCTION__, 0xffff0000, (uint64_t)_graphicsEngine._renderFrameCount + 1); + if (_aboutToQuit) { + return; + } + if (!_physicsEnabled) { if (!domainLoadingInProgress) { PROFILE_ASYNC_BEGIN(app, "Scene Loading", ""); @@ -5422,17 +5395,19 @@ 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) { + // Check for flagged EntityData having arrived. + auto entityTreeRenderer = getEntities(); + 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(); } @@ -5679,15 +5654,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); } { @@ -5705,11 +5678,9 @@ 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(); @@ -6083,11 +6054,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(); @@ -6269,7 +6252,6 @@ void Application::domainURLChanged(QUrl domainURL) { void Application::resettingDomain() { _notifiedPacketVersionMismatchThisDomain = false; - auto nodeList = DependencyManager::get(); clearDomainOctreeDetails(); } @@ -6533,11 +6515,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()); @@ -6555,10 +6538,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); @@ -6619,19 +6602,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); } @@ -6939,10 +6919,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) { @@ -7195,6 +7174,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()); @@ -7237,7 +7218,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); @@ -7281,7 +7261,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); } @@ -7506,7 +7487,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)")); @@ -8229,7 +8209,7 @@ QOpenGLContext* Application::getPrimaryContext() { } bool Application::makeRenderingContextCurrent() { - return _graphicsEngine._offscreenContext->makeCurrent(); + return true; } bool Application::isForeground() const { diff --git a/interface/src/Application.h b/interface/src/Application.h index 6d57ca9d8d..3adf771c1d 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; @@ -560,7 +559,6 @@ private: bool _previousSessionCrashed; - // OffscreenGLCanvas* _offscreenContext { nullptr }; DisplayPluginPointer _displayPlugin; QMetaObject::Connection _displayPluginPresentConnection; mutable std::mutex _displayPluginLock; @@ -623,7 +621,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 3e0e643bd8..6afab71c90 100644 --- a/interface/src/AvatarBookmarks.cpp +++ b/interface/src/AvatarBookmarks.cpp @@ -64,7 +64,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()) { @@ -171,6 +171,13 @@ 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(); auto treeRenderer = DependencyManager::get(); diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 130c2c0b89..2c364154f7 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -147,9 +147,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, @@ -620,8 +622,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 @@ -655,10 +660,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..."); @@ -667,16 +671,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 >>> @@ -756,10 +758,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... @@ -767,11 +768,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..a4fc283d54 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -76,6 +76,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"; diff --git a/interface/src/SecondaryCamera.cpp b/interface/src/SecondaryCamera.cpp index 184762632f..e71602b271 100644 --- a/interface/src/SecondaryCamera.cpp +++ b/interface/src/SecondaryCamera.cpp @@ -207,7 +207,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..09fa6dc573 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -79,7 +79,7 @@ AvatarManager::AvatarManager(QObject* parent) : // 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); } @@ -278,20 +278,8 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { } 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); } - _numAvatarsUpdated = numAvatarsUpdated; _numAvatarsNotUpdated = numAVatarsNotUpdated; @@ -333,16 +321,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) { @@ -365,8 +358,6 @@ void AvatarManager::simulateAvatarFades(float deltaTime) { } avatarItr = _avatarsToFade.erase(avatarItr); } else { - const bool inView = true; // HACK - avatar->simulate(deltaTime, inView); ++avatarItr; } } @@ -412,9 +403,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 { @@ -618,6 +606,8 @@ RayToAvatarIntersectionResult AvatarManager::findRayIntersectionVector(const Pic result.intersects = true; result.avatarID = avatar->getID(); result.distance = distance; + result.face = face; + result.surfaceNormal = surfaceNormal; result.extraInfo = extraInfo; } } @@ -629,6 +619,79 @@ RayToAvatarIntersectionResult AvatarManager::findRayIntersectionVector(const Pic 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; + } + + 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 parabolicDistance; + BoxFace face; + glm::vec3 surfaceNormal; + + SkeletonModelPointer avatarModel = avatar->getSkeletonModel(); + + // It's better to intersect the parabola against the avatar's actual mesh, but this is currently difficult to + // do, because the transformed mesh data only exists over in GPU-land. As a compromise, this code + // intersects against the avatars capsule and then against the (T-pose) mesh. The end effect is that picking + // against the avatar is sort-of right, but you likely wont be able to pick against the arms. + + // TODO -- find a way to extract transformed avatar mesh data from the rendering engine. + + // if we weren't picking against the capsule, we would want to pick against the avatarBounds... + // AABox avatarBounds = avatarModel->getRenderableMeshBound(); + // if (!avatarBounds.findParabolaIntersection(pick.origin, pick.velocity, pick.acceleration, parabolicDistance, face, surfaceNormal)) { + // // parabola doesn't intersect avatar's bounding-box + // continue; + // } + + glm::vec3 start; + glm::vec3 end; + float radius; + avatar->getCapsule(start, end, radius); + bool intersects = findParabolaCapsuleIntersection(pick.origin, pick.velocity, pick.acceleration, start, end, radius, avatar->getWorldOrientation(), parabolicDistance); + if (!intersects) { + // ray doesn't intersect avatar's capsule + continue; + } + + QVariantMap extraInfo; + intersects = avatarModel->findParabolaIntersectionAgainstSubMeshes(pick.origin, pick.velocity, pick.acceleration, + parabolicDistance, face, surfaceNormal, extraInfo, true); + + if (intersects && (!result.intersects || parabolicDistance < result.parabolicDistance)) { + result.intersects = true; + result.avatarID = avatar->getID(); + result.parabolicDistance = parabolicDistance; + result.face = face; + result.surfaceNormal = surfaceNormal; + result.extraInfo = extraInfo; + } + } + + if (result.intersects) { + result.intersection = pick.origin + pick.velocity * result.parabolicDistance + 0.5f * pick.acceleration * result.parabolicDistance * result.parabolicDistance; + result.distance = glm::distance(pick.origin, result.intersection); + } + + return result; +} + // HACK float AvatarManager::getAvatarSortCoefficient(const QString& name) { if (name == "size") { diff --git a/interface/src/avatar/AvatarManager.h b/interface/src/avatar/AvatarManager.h index 53461146e5..ecf9a2d735 100644 --- a/interface/src/avatar/AvatarManager.h +++ b/interface/src/avatar/AvatarManager.h @@ -142,6 +142,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 diff --git a/interface/src/avatar/AvatarMotionState.cpp b/interface/src/avatar/AvatarMotionState.cpp index 50c715b14a..07e6b3f6b0 100644 --- a/interface/src/avatar/AvatarMotionState.cpp +++ b/interface/src/avatar/AvatarMotionState.cpp @@ -19,6 +19,7 @@ AvatarMotionState::AvatarMotionState(AvatarSharedPointer avatar, const btCollisionShape* shape) : ObjectMotionState(shape), _avatar(avatar) { assert(_avatar); _type = MOTIONSTATE_TYPE_AVATAR; + cacheShapeDiameter(); } void AvatarMotionState::handleEasyChanges(uint32_t& flags) { @@ -57,9 +58,6 @@ PhysicsMotionType AvatarMotionState::computePhysicsMotionType() const { 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); 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,7 +143,10 @@ 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 @@ -174,3 +179,28 @@ float AvatarMotionState::getMass() const { return std::static_pointer_cast(_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..a458704b1a 100644 --- a/interface/src/avatar/AvatarMotionState.h +++ b/interface/src/avatar/AvatarMotionState.h @@ -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(); diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index e87dcc85cd..41855e7973 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -139,6 +139,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 +160,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(); @@ -492,14 +498,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))); @@ -1135,9 +1141,7 @@ void MyAvatar::saveData() { settings.setValue("collisionSoundURL", _collisionSoundURL); settings.setValue("useSnapTurn", _useSnapTurn); settings.setValue("userHeight", getUserHeight()); - settings.setValue("flyingDesktop", getFlyingDesktopPref()); settings.setValue("flyingHMD", getFlyingHMDPref()); - settings.setValue("enabledFlying", getFlyingEnabled()); settings.endGroup(); } @@ -1251,7 +1255,6 @@ void MyAvatar::loadData() { useFullAvatarURL(_fullAvatarURLFromPreferences, _fullAvatarModelName); - QVector attachmentData; int attachmentCount = settings.beginReadArray("attachmentData"); for (int i = 0; i < attachmentCount; i++) { settings.setArrayIndex(i); @@ -1268,10 +1271,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++) { @@ -1290,7 +1293,6 @@ void MyAvatar::loadData() { // Flying preferences must be loaded before calling setFlyingEnabled() Setting::Handle firstRunVal { Settings::firstRun, true }; - setFlyingDesktopPref(firstRunVal.get() ? true : settings.value("flyingDesktop").toBool()); setFlyingHMDPref(firstRunVal.get() ? false : settings.value("flyingHMD").toBool()); setFlyingEnabled(getFlyingEnabled()); @@ -1497,50 +1499,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); }); } @@ -1550,8 +1628,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); }); } @@ -1561,8 +1638,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); }); } @@ -1572,7 +1648,7 @@ void MyAvatar::clearJointData(const QString& name) { return; } writeLockWithNamedJointIndex(name, [&](int index) { - _skeletonModel->getRig().clearJointAnimationPriority(index); + clearJointData(index); }); } @@ -1581,6 +1657,9 @@ void MyAvatar::clearJointsData() { QMetaObject::invokeMethod(this, "clearJointsData"); return; } + _farGrabRightMatrixCache.invalidate(); + _farGrabLeftMatrixCache.invalidate(); + _farGrabMouseMatrixCache.invalidate(); _skeletonModel->getRig().clearJointStates(); } @@ -1691,16 +1770,6 @@ void MyAvatar::useFullAvatarURL(const QUrl& fullAvatarURL, const QString& modelN 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 { CameraMode mode = qApp->getCamera().getMode(); if (mode == CAMERA_MODE_THIRD_PERSON || mode == CAMERA_MODE_INDEPENDENT) { @@ -1971,20 +2040,164 @@ 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); + } + removeAvatarEntities(); + 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() { @@ -2062,6 +2275,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())); @@ -2337,6 +2552,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) @@ -2398,7 +2630,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; @@ -2624,6 +2859,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) { @@ -3407,18 +3685,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; diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index d36e43ca25..f2c52624d9 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -138,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. @@ -233,6 +235,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"; @@ -865,8 +869,6 @@ public: void resetFullAvatarURL(); - virtual void setAttachmentData(const QVector& attachmentData) override; - MyCharacterController* getCharacterController() { return &_characterController; } const MyCharacterController* getCharacterController() const { return &_characterController; } @@ -982,7 +984,7 @@ public: /**jsdoc * @function MyAvatar.getAvatarScale - * @returns {number} + * @returns {number} */ Q_INVOKABLE float getAvatarScale(); @@ -1074,6 +1076,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(); @@ -1082,6 +1088,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 @@ -1133,6 +1145,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 @@ -1506,11 +1532,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); @@ -1731,6 +1767,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 diff --git a/interface/src/avatar/OtherAvatar.cpp b/interface/src/avatar/OtherAvatar.cpp index 5e51658128..2061df6004 100644 --- a/interface/src/avatar/OtherAvatar.cpp +++ b/interface/src/avatar/OtherAvatar.cpp @@ -48,7 +48,7 @@ void OtherAvatar::createOrb() { _otherAvatarOrbMeshPlaceholder->setPulseMin(0.5); _otherAvatarOrbMeshPlaceholder->setPulseMax(1.0); _otherAvatarOrbMeshPlaceholder->setColorPulse(1.0); - _otherAvatarOrbMeshPlaceholder->setIgnoreRayIntersection(true); + _otherAvatarOrbMeshPlaceholder->setIgnorePickIntersection(true); _otherAvatarOrbMeshPlaceholder->setDrawInFront(false); _otherAvatarOrbMeshPlaceholderID = qApp->getOverlays().addOverlay(_otherAvatarOrbMeshPlaceholder); // Position focus diff --git a/interface/src/commerce/QmlCommerce.cpp b/interface/src/commerce/QmlCommerce.cpp index 1f44343bdc..1c6600cf3f 100644 --- a/interface/src/commerce/QmlCommerce.cpp +++ b/interface/src/commerce/QmlCommerce.cpp @@ -352,8 +352,7 @@ 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)) { diff --git a/interface/src/commerce/Wallet.cpp b/interface/src/commerce/Wallet.cpp index 991f1ebf3f..ef6b4654f5 100644 --- a/interface/src/commerce/Wallet.cpp +++ b/interface/src/commerce/Wallet.cpp @@ -328,7 +328,7 @@ 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; diff --git a/interface/src/graphics/GraphicsEngine.cpp b/interface/src/graphics/GraphicsEngine.cpp index d115d8914c..56cfbf6fee 100644 --- a/interface/src/graphics/GraphicsEngine.cpp +++ b/interface/src/graphics/GraphicsEngine.cpp @@ -41,40 +41,20 @@ GraphicsEngine::~GraphicsEngine() { void GraphicsEngine::initializeGPU(GLWidget* glwidget) { - // 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( - glwidget->qglContext(), [this]() { return this->shouldPaint(); }, [this]() { this->render_performFrame(); } ); - if (!_offscreenContext->makeCurrent()) { - qFatal("Unable to make offscreen context current"); - } - // Requires the window context, because that's what's used in the actual rendering // and the GPU backend will make things like the VAO which cannot be shared across // 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(); } void GraphicsEngine::initializeRender(bool disableDeferred) { @@ -91,8 +71,6 @@ void GraphicsEngine::initializeRender(bool disableDeferred) { // Now that OpenGL is initialized, we are sure we have a valid context and can create the various pipeline shaders with success. DependencyManager::get()->initializeShapePipelines(); - - } void GraphicsEngine::startup() { diff --git a/interface/src/graphics/GraphicsEngine.h b/interface/src/graphics/GraphicsEngine.h index bc2121429a..6efddce51a 100644 --- a/interface/src/graphics/GraphicsEngine.h +++ b/interface/src/graphics/GraphicsEngine.h @@ -88,8 +88,6 @@ protected: QObject* _renderEventHandler{ nullptr }; friend class RenderEventHandler; - OffscreenGLCanvas* _offscreenContext{ nullptr }; - FrameTimingsScriptingInterface _frameTimingsScriptingInterface; friend class Application; diff --git a/interface/src/graphics/RenderEventHandler.cpp b/interface/src/graphics/RenderEventHandler.cpp index 5b587ab171..213264725e 100644 --- a/interface/src/graphics/RenderEventHandler.cpp +++ b/interface/src/graphics/RenderEventHandler.cpp @@ -19,16 +19,8 @@ RenderEventHandler::RenderEventHandler(QOpenGLContext* context, CheckCall checkC _checkCall(checkCall), _renderCall(renderCall) { - _renderContext = new OffscreenGLCanvas(); - _renderContext->setObjectName("RenderContext"); - _renderContext->create(context); - if (!_renderContext->makeCurrent()) { - qFatal("Unable to make rendering context current"); - } - _renderContext->doneCurrent(); - // Deleting the object with automatically shutdown the thread - connect(qApp, &QCoreApplication::aboutToQuit, this, &QObject::deleteLater); + // connect(qApp, &QCoreApplication::aboutToQuit, this, &QObject::deleteLater); // Transfer to a new thread moveToNewNamedThread(this, "RenderThread", [this](QThread* renderThread) { @@ -42,10 +34,6 @@ void RenderEventHandler::initialize() { 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 RenderEventHandler::resumeThread() { diff --git a/interface/src/graphics/RenderEventHandler.h b/interface/src/graphics/RenderEventHandler.h index 43b16a76bd..c566202b5b 100644 --- a/interface/src/graphics/RenderEventHandler.h +++ b/interface/src/graphics/RenderEventHandler.h @@ -34,7 +34,7 @@ public: CheckCall _checkCall; RenderCall _renderCall; - RenderEventHandler(QOpenGLContext* context, CheckCall checkCall, RenderCall renderCall); + RenderEventHandler(CheckCall checkCall, RenderCall renderCall); QElapsedTimer _lastTimeRendered; std::atomic _pendingRenderEvent{ true }; @@ -47,8 +47,6 @@ private: void render(); bool event(QEvent* event) override; - - OffscreenGLCanvas* _renderContext{ nullptr }; }; #endif // #include hifi_RenderEventHandler_h \ No newline at end of file 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..31106457fb --- /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); + if (sequenceSize == 0 || + (startIter != _sequenceNumbers.end() + && endIter != _sequenceNumbers.end() + && distance(startIter, endIter) == sequenceSize) ) { + _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..210dfbac25 --- /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); + 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..9f2e6da2e8 --- /dev/null +++ b/interface/src/raypick/CollisionPick.cpp @@ -0,0 +1,386 @@ +// +// 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(); + if (newCollisionResult->loadState == LOAD_STATE_NOT_LOADED || loadState == LOAD_STATE_UNKNOWN) { + loadState = (LoadState)newCollisionResult->loadState; + } + + 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["loaded"] = (loadState == LOAD_STATE_LOADED); + variantMap["collisionRegion"] = pickVariant; + + return variantMap; +} + +bool CollisionPick::isShapeInfoReady() { + if (_mathPick.shouldComputeShapeInfo()) { + if (_cachedResource && _cachedResource->isLoaded()) { + computeShapeInfo(_mathPick, *_mathPick.shapeInfo, _cachedResource); + return true; + } + + return false; + } + + return true; +} + +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()); + } +} + +CollisionRegion CollisionPick::getMathematicalPick() const { + 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 (!isShapeInfoReady()) { + // Cannot compute result + return std::make_shared(pick.toVariantMap(), CollisionPickResult::LOAD_STATE_NOT_LOADED, std::vector(), std::vector()); + } + + auto entityIntersections = _physicsEngine->contactTest(USER_COLLISION_MASK_ENTITIES, *pick.shapeInfo, pick.transform); + filterIntersections(entityIntersections); + return std::make_shared(pick, CollisionPickResult::LOAD_STATE_LOADED, entityIntersections, std::vector()); +} + +PickResultPointer CollisionPick::getOverlayIntersection(const CollisionRegion& pick) { + return std::make_shared(pick.toVariantMap(), isShapeInfoReady() ? CollisionPickResult::LOAD_STATE_LOADED : CollisionPickResult::LOAD_STATE_NOT_LOADED, std::vector(), std::vector()); +} + +PickResultPointer CollisionPick::getAvatarIntersection(const CollisionRegion& pick) { + if (!isShapeInfoReady()) { + // Cannot compute result + return std::make_shared(pick.toVariantMap(), CollisionPickResult::LOAD_STATE_NOT_LOADED, std::vector(), std::vector()); + } + + auto avatarIntersections = _physicsEngine->contactTest(USER_COLLISION_MASK_AVATARS, *pick.shapeInfo, pick.transform); + filterIntersections(avatarIntersections); + return std::make_shared(pick, CollisionPickResult::LOAD_STATE_LOADED, std::vector(), avatarIntersections); +} + +PickResultPointer CollisionPick::getHUDIntersection(const CollisionRegion& pick) { + return std::make_shared(pick.toVariantMap(), isShapeInfoReady() ? CollisionPickResult::LOAD_STATE_LOADED : CollisionPickResult::LOAD_STATE_NOT_LOADED, std::vector(), std::vector()); +} \ 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..6631238737 --- /dev/null +++ b/interface/src/raypick/CollisionPick.h @@ -0,0 +1,86 @@ +// +// 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 + +class CollisionPickResult : public PickResult { +public: + enum LoadState { + LOAD_STATE_UNKNOWN, + LOAD_STATE_NOT_LOADED, + LOAD_STATE_LOADED + }; + + CollisionPickResult() {} + CollisionPickResult(const QVariantMap& pickVariant) : PickResult(pickVariant) {} + + CollisionPickResult(const CollisionRegion& searchRegion, LoadState loadState, const std::vector& entityIntersections, const std::vector& avatarIntersections) : + PickResult(searchRegion.toVariantMap()), + loadState(loadState), + 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; + loadState = collisionPickResult.loadState; + } + + LoadState loadState { LOAD_STATE_UNKNOWN }; + 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) : + Pick(filter, maxDistance, enabled), + _mathPick(collisionRegion), + _physicsEngine(physicsEngine) { + if (collisionRegion.shouldComputeShapeInfo()) { + _cachedResource = DependencyManager::get()->getCollisionGeometryResource(collisionRegion.modelURL); + } + } + + CollisionRegion getMathematicalPick() const override; + PickResultPointer getDefaultResult(const QVariantMap& pickVariant) const override { + return std::make_shared(pickVariant, CollisionPickResult::LOAD_STATE_UNKNOWN, 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; + +protected: + // Returns true if pick.shapeInfo is valid. Otherwise, attempts to get the shapeInfo ready for use. + bool isShapeInfoReady(); + void computeShapeInfo(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..2382a95105 100644 --- a/interface/src/raypick/LaserPointer.cpp +++ b/interface/src/raypick/LaserPointer.cpp @@ -14,315 +14,114 @@ #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()); - } - }); -} - -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); + renderState->setLineWidth(lineWidth.toFloat()); } } - return visualPickResult; } -void LaserPointer::updateRenderStateOverlay(const OverlayID& id, const QVariant& props) { - if (!id.isNull() && props.isValid()) { - QVariantMap propMap = props.toMap(); - propMap.remove("visible"); - qApp->getOverlays().editOverlay(id, propMap); +glm::vec3 LaserPointer::getPickOrigin(const PickResultPointer& pickResult) const { + auto rayPickResult = std::static_pointer_cast(pickResult); + return (rayPickResult ? vec3FromVariant(rayPickResult->pickVariant["origin"]) : glm::vec3(0.0f)); +} + +glm::vec3 LaserPointer::getPickEnd(const PickResultPointer& pickResult, float distance) const { + auto rayPickResult = std::static_pointer_cast(pickResult); + if (distance > 0.0f) { + PickRay pick = PickRay(rayPickResult->pickVariant); + return pick.origin + distance * pick.direction; + } else { + return rayPickResult->intersection; } } -void LaserPointer::updateRenderState(const RenderState& renderState, const IntersectionType type, float distance, const QUuid& objectID, const PickRay& pickRay) { - if (!renderState.getStartID().isNull()) { - QVariantMap startProps; - startProps.insert("position", vec3toVariant(pickRay.origin)); - startProps.insert("visible", true); - startProps.insert("ignoreRayIntersection", renderState.doesStartIgnoreRays()); - qApp->getOverlays().editOverlay(renderState.getStartID(), startProps); - } - glm::vec3 endVec = pickRay.origin + pickRay.direction * distance; - - QVariant end = vec3toVariant(endVec); - if (!renderState.getPathID().isNull()) { - QVariantMap pathProps; - pathProps.insert("start", vec3toVariant(pickRay.origin)); - pathProps.insert("end", end); - pathProps.insert("visible", true); - pathProps.insert("ignoreRayIntersection", renderState.doesPathIgnoreRays()); - if (_scaleWithAvatar) { - pathProps.insert("lineWidth", renderState.getLineWidth() * DependencyManager::get()->getMyAvatar()->getSensorToWorldScale()); - } - qApp->getOverlays().editOverlay(renderState.getPathID(), pathProps); - } - 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); - } +glm::vec3 LaserPointer::getPickedObjectNormal(const PickResultPointer& pickResult) const { + auto rayPickResult = std::static_pointer_cast(pickResult); + return (rayPickResult ? rayPickResult->surfaceNormal : glm::vec3(0.0f)); } -void LaserPointer::disableRenderState(const RenderState& renderState) { - if (!renderState.getStartID().isNull()) { - QVariantMap startProps; - startProps.insert("visible", false); - startProps.insert("ignoreRayIntersection", true); - qApp->getOverlays().editOverlay(renderState.getStartID(), startProps); - } - if (!renderState.getPathID().isNull()) { - QVariantMap pathProps; - pathProps.insert("visible", false); - pathProps.insert("ignoreRayIntersection", true); - qApp->getOverlays().editOverlay(renderState.getPathID(), pathProps); - } - if (!renderState.getEndID().isNull()) { - QVariantMap endProps; - endProps.insert("visible", false); - endProps.insert("ignoreRayIntersection", true); - qApp->getOverlays().editOverlay(renderState.getEndID(), endProps); - } +IntersectionType LaserPointer::getPickedObjectType(const PickResultPointer& pickResult) const { + auto rayPickResult = std::static_pointer_cast(pickResult); + return (rayPickResult ? rayPickResult->type : IntersectionType::NONE); } -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); - } +QUuid LaserPointer::getPickedObjectID(const PickResultPointer& pickResult) const { + auto rayPickResult = std::static_pointer_cast(pickResult); + return (rayPickResult ? rayPickResult->objectID : QUuid()); } -Pointer::PickedObject LaserPointer::getHoveredObject(const PickResultPointer& pickResult) { - auto rayPickResult = std::static_pointer_cast(pickResult); - if (!rayPickResult) { - return PickedObject(); - } - return PickedObject(rayPickResult->objectID, rayPickResult->type); -} - -Pointer::Buttons LaserPointer::getPressedButtons(const PickResultPointer& pickResult) { - std::unordered_set toReturn; - auto rayPickResult = std::static_pointer_cast(pickResult); - +void LaserPointer::setVisualPickResultInternal(PickResultPointer pickResult, IntersectionType type, const QUuid& id, + const glm::vec3& intersection, float distance, const glm::vec3& surfaceNormal) { + auto rayPickResult = std::static_pointer_cast(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 +134,7 @@ RenderState LaserPointer::buildRenderState(const QVariantMap& propMap) { QUuid pathID; if (propMap["path"].isValid()) { QVariantMap pathMap = propMap["path"].toMap(); - // right now paths must be line3ds + // laser paths must be line3ds if (pathMap["type"].isValid() && pathMap["type"].toString() == "line3d") { pathMap.remove("visible"); pathID = qApp->getOverlays().addOverlay(pathMap["type"].toString(), pathMap); @@ -351,7 +150,7 @@ RenderState LaserPointer::buildRenderState(const QVariantMap& propMap) { } } - return RenderState(startID, pathID, endID); + return std::make_shared(startID, pathID, endID); } PointerEvent LaserPointer::buildPointerEvent(const PickedObject& target, const PickResultPointer& pickResult, const std::string& button, bool hover) { @@ -391,24 +190,11 @@ PointerEvent LaserPointer::buildPointerEvent(const PickedObject& target, const P glm::vec3 LaserPointer::findIntersection(const PickedObject& pickedObject, const glm::vec3& origin, const glm::vec3& direction) { switch (pickedObject.type) { - case ENTITY: - return RayPick::intersectRayWithEntityXYPlane(pickedObject.objectID, origin, direction); - case OVERLAY: - return RayPick::intersectRayWithOverlayXYPlane(pickedObject.objectID, origin, direction); - default: - return glm::vec3(NAN); - } -} - -glm::vec2 LaserPointer::findPos2D(const PickedObject& pickedObject, const glm::vec3& origin) { - switch (pickedObject.type) { - case ENTITY: - return RayPick::projectOntoEntityXYPlane(pickedObject.objectID, origin); - case OVERLAY: - return RayPick::projectOntoOverlayXYPlane(pickedObject.objectID, origin); - case HUD: - return DependencyManager::get()->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..95a5bccc6c 100644 --- a/interface/src/raypick/LaserPointer.h +++ b/interface/src/raypick/LaserPointer.h @@ -11,117 +11,54 @@ #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); + + 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/ParabolaPick.cpp b/interface/src/raypick/ParabolaPick.cpp new file mode 100644 index 0000000000..b3e3f16345 --- /dev/null +++ b/interface/src/raypick/ParabolaPick.cpp @@ -0,0 +1,70 @@ +// +// Created by Sam Gondelman 7/2/2018 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#include "ParabolaPick.h" + +#include "Application.h" +#include "EntityScriptingInterface.h" +#include "ui/overlays/Overlays.h" +#include "avatar/AvatarManager.h" +#include "scripting/HMDScriptingInterface.h" +#include "DependencyManager.h" + +PickResultPointer ParabolaPick::getEntityIntersection(const PickParabola& pick) { + if (glm::length2(pick.acceleration) > EPSILON && glm::length2(pick.velocity) > EPSILON) { + ParabolaToEntityIntersectionResult entityRes = + DependencyManager::get()->findParabolaIntersectionVector(pick, !getFilter().doesPickCoarse(), + 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) { + ParabolaToOverlayIntersectionResult overlayRes = + qApp->getOverlays().findParabolaIntersectionVector(pick, !getFilter().doesPickCoarse(), + 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; +} \ 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..99a42a5380 --- /dev/null +++ b/interface/src/raypick/ParabolaPick.h @@ -0,0 +1,97 @@ +// +// Created by Sam Gondelman 7/2/2018 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#ifndef hifi_ParabolaPick_h +#define hifi_ParabolaPick_h + +#include +#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; + +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..097c98340c --- /dev/null +++ b/interface/src/raypick/ParabolaPointer.cpp @@ -0,0 +1,424 @@ +// +// 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); + } +} + +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 (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..93f9a7b055 --- /dev/null +++ b/interface/src/raypick/ParabolaPointer.h @@ -0,0 +1,126 @@ +// +// 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); + + 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..685611d77b --- /dev/null +++ b/interface/src/raypick/PathPointer.cpp @@ -0,0 +1,353 @@ +// +// Created by Sam Gondelman 7/17/2018 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#include "PathPointer.h" + +#include "Application.h" +#include "avatar/AvatarManager.h" + +#include +#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) { + if (_renderStates.find(_currentRenderState) != _renderStates.end()) { + _renderStates[_currentRenderState]->disable(); + } + if (_defaultRenderStates.find(_currentRenderState) != _defaultRenderStates.end()) { + _defaultRenderStates[_currentRenderState].second->disable(); + } + } + _currentRenderState = state; + }); +} + +void PathPointer::setLength(float length) { + withWriteLock([&] { + _pathLength = length; + }); +} + +void PathPointer::setLockEndUUID(const QUuid& objectID, const bool isOverlay, const glm::mat4& offsetMat) { + withWriteLock([&] { + _lockEndObject.id = objectID; + _lockEndObject.isOverlay = isOverlay; + _lockEndObject.offsetMat = offsetMat; + }); +} + +PickResultPointer PathPointer::getVisualPickResult(const PickResultPointer& pickResult) { + PickResultPointer visualPickResult = pickResult; + glm::vec3 origin = getPickOrigin(pickResult); + IntersectionType type = getPickedObjectType(pickResult); + QUuid id; + glm::vec3 intersection; + float distance; + glm::vec3 surfaceNormal; + + if (type != IntersectionType::HUD) { + glm::vec3 endVec; + if (!_lockEndObject.id.isNull()) { + glm::vec3 pos; + glm::quat rot; + glm::vec3 dim; + glm::vec3 registrationPoint; + if (_lockEndObject.isOverlay) { + pos = vec3FromVariant(qApp->getOverlays().getProperty(_lockEndObject.id, "position").value); + rot = quatFromVariant(qApp->getOverlays().getProperty(_lockEndObject.id, "rotation").value); + dim = vec3FromVariant(qApp->getOverlays().getProperty(_lockEndObject.id, "dimensions").value); + registrationPoint = glm::vec3(0.5f); + } else { + EntityItemProperties props = DependencyManager::get()->getEntityProperties(_lockEndObject.id); + glm::mat4 entityMat = createMatFromQuatAndPos(props.getRotation(), props.getPosition()); + glm::mat4 finalPosAndRotMat = entityMat * _lockEndObject.offsetMat; + pos = extractTranslation(finalPosAndRotMat); + rot = glmExtractRotation(finalPosAndRotMat); + dim = props.getDimensions(); + registrationPoint = props.getRegistrationPoint(); + } + const glm::vec3 DEFAULT_REGISTRATION_POINT = glm::vec3(0.5f); + endVec = pos + rot * (dim * (DEFAULT_REGISTRATION_POINT - registrationPoint)); + glm::vec3 direction = endVec - origin; + distance = glm::distance(origin, endVec); + glm::vec3 normalizedDirection = glm::normalize(direction); + + type = _lockEndObject.isOverlay ? IntersectionType::OVERLAY : IntersectionType::ENTITY; + id = _lockEndObject.id; + intersection = endVec; + surfaceNormal = -normalizedDirection; + setVisualPickResultInternal(visualPickResult, type, id, intersection, distance, surfaceNormal); + } else if (type != IntersectionType::NONE && _lockEnd) { + id = getPickedObjectID(pickResult); + if (type == IntersectionType::ENTITY) { + endVec = DependencyManager::get()->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); + if (_enabled && !_currentRenderState.empty() && _renderStates.find(_currentRenderState) != _renderStates.end() && + (type != IntersectionType::NONE || _pathLength > 0.0f)) { + glm::vec3 origin = getPickOrigin(pickResult); + glm::vec3 end = getPickEnd(pickResult, _pathLength); + glm::vec3 surfaceNormal = getPickedObjectNormal(pickResult); + _renderStates[_currentRenderState]->update(origin, end, surfaceNormal, _scaleWithAvatar, _distanceScaleEnd, _centerEndY, _faceAvatar, + _followNormal, _followNormalStrength, _pathLength, pickResult); + if (_defaultRenderStates.find(_currentRenderState) != _defaultRenderStates.end()) { + _defaultRenderStates[_currentRenderState].second->disable(); + } + } else if (_enabled && !_currentRenderState.empty() && _defaultRenderStates.find(_currentRenderState) != _defaultRenderStates.end()) { + if (_renderStates.find(_currentRenderState) != _renderStates.end()) { + _renderStates[_currentRenderState]->disable(); + } + glm::vec3 origin = getPickOrigin(pickResult); + glm::vec3 end = getPickEnd(pickResult, _defaultRenderStates[_currentRenderState].first); + _defaultRenderStates[_currentRenderState].second->update(origin, end, Vectors::UP, _scaleWithAvatar, _distanceScaleEnd, _centerEndY, + _faceAvatar, _followNormal, _followNormalStrength, _defaultRenderStates[_currentRenderState].first, pickResult); + } else if (!_currentRenderState.empty()) { + if (_renderStates.find(_currentRenderState) != _renderStates.end()) { + _renderStates[_currentRenderState]->disable(); + } + if (_defaultRenderStates.find(_currentRenderState) != _defaultRenderStates.end()) { + _defaultRenderStates[_currentRenderState].second->disable(); + } + } +} + +void PathPointer::editRenderState(const std::string& state, const QVariant& startProps, const QVariant& pathProps, const QVariant& endProps) { + withWriteLock([&] { + updateRenderStateOverlay(_renderStates[state]->getStartID(), startProps); + updateRenderStateOverlay(_renderStates[state]->getEndID(), endProps); + QVariant startDim = startProps.toMap()["dimensions"]; + if (startDim.isValid()) { + _renderStates[state]->setStartDim(vec3FromVariant(startDim)); + } + QVariant endDim = endProps.toMap()["dimensions"]; + if (endDim.isValid()) { + _renderStates[state]->setEndDim(vec3FromVariant(endDim)); + } + QVariant rotation = endProps.toMap()["rotation"]; + if (rotation.isValid()) { + _renderStates[state]->setEndRot(quatFromVariant(rotation)); + } + + editRenderStatePath(state, pathProps); + }); +} + +void PathPointer::updateRenderStateOverlay(const OverlayID& id, const QVariant& props) { + if (!id.isNull() && props.isValid()) { + QVariantMap propMap = props.toMap(); + propMap.remove("visible"); + qApp->getOverlays().editOverlay(id, propMap); + } +} + +Pointer::PickedObject PathPointer::getHoveredObject(const PickResultPointer& pickResult) { + return PickedObject(getPickedObjectID(pickResult), getPickedObjectType(pickResult)); +} + +Pointer::Buttons PathPointer::getPressedButtons(const PickResultPointer& pickResult) { + std::unordered_set toReturn; + + for (const PointerTrigger& trigger : _triggers) { + std::string button = trigger.getButton(); + TriggerState& state = _states[button]; + // TODO: right now, LaserPointers don't support axes, only on/off buttons + if (trigger.getEndpoint()->peek() >= 1.0f) { + toReturn.insert(button); + + if (_previousButtons.find(button) == _previousButtons.end()) { + // start triggering for buttons that were just pressed + state.triggeredObject = PickedObject(getPickedObjectID(pickResult), getPickedObjectType(pickResult)); + state.intersection = getPickEnd(pickResult); + state.triggerPos2D = findPos2D(state.triggeredObject, state.intersection); + state.triggerStartTime = usecTimestampNow(); + state.surfaceNormal = getPickedObjectNormal(pickResult); + state.deadspotExpired = false; + state.wasTriggering = true; + state.triggering = true; + _latestState = state; + } + } else { + // stop triggering for buttons that aren't pressed + state.wasTriggering = state.triggering; + state.triggering = false; + _latestState = state; + } + } + _previousButtons = toReturn; + return toReturn; +} + +StartEndRenderState::StartEndRenderState(const OverlayID& startID, const OverlayID& endID) : + _startID(startID), _endID(endID) { + if (!_startID.isNull()) { + _startDim = vec3FromVariant(qApp->getOverlays().getProperty(_startID, "dimensions").value); + _startIgnoreRays = qApp->getOverlays().getProperty(_startID, "ignoreRayIntersection").value.toBool(); + } + if (!_endID.isNull()) { + _endDim = vec3FromVariant(qApp->getOverlays().getProperty(_endID, "dimensions").value); + _endRot = quatFromVariant(qApp->getOverlays().getProperty(_endID, "rotation").value); + _endIgnoreRays = qApp->getOverlays().getProperty(_endID, "ignoreRayIntersection").value.toBool(); + } +} + +void StartEndRenderState::cleanup() { + if (!_startID.isNull()) { + qApp->getOverlays().deleteOverlay(_startID); + } + if (!_endID.isNull()) { + qApp->getOverlays().deleteOverlay(_endID); + } +} + +void StartEndRenderState::disable() { + if (!getStartID().isNull()) { + QVariantMap startProps; + startProps.insert("visible", false); + startProps.insert("ignoreRayIntersection", true); + qApp->getOverlays().editOverlay(getStartID(), startProps); + } + if (!getEndID().isNull()) { + QVariantMap endProps; + endProps.insert("visible", false); + endProps.insert("ignoreRayIntersection", true); + qApp->getOverlays().editOverlay(getEndID(), endProps); + } +} + +void StartEndRenderState::update(const glm::vec3& origin, const glm::vec3& end, const glm::vec3& surfaceNormal, bool scaleWithAvatar, bool distanceScaleEnd, bool centerEndY, + bool faceAvatar, bool followNormal, float followNormalStrength, float distance, const PickResultPointer& pickResult) { + if (!getStartID().isNull()) { + QVariantMap startProps; + startProps.insert("position", vec3toVariant(origin)); + startProps.insert("visible", true); + if (scaleWithAvatar) { + startProps.insert("dimensions", vec3toVariant(getStartDim() * DependencyManager::get()->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); + } +} + +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); + } +} \ No newline at end of file diff --git a/interface/src/raypick/PathPointer.h b/interface/src/raypick/PathPointer.h new file mode 100644 index 0000000000..44c1b7f82b --- /dev/null +++ b/interface/src/raypick/PathPointer.h @@ -0,0 +1,135 @@ +// +// Created by Sam Gondelman 7/17/2018 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#ifndef hifi_PathPointer_h +#define hifi_PathPointer_h + +#include +#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); + +protected: + OverlayID _startID; + OverlayID _endID; + bool _startIgnoreRays; + bool _endIgnoreRays; + + glm::vec3 _startDim; + glm::vec3 _endDim; + glm::quat _endRot; + + glm::quat _avgEndRot; + bool _avgEndRotInitialized { false }; +}; + +typedef std::unordered_map> 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..0ed35e5589 100644 --- a/interface/src/raypick/PickScriptingInterface.cpp +++ b/interface/src/raypick/PickScriptingInterface.cpp @@ -11,12 +11,17 @@ #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 @@ -26,6 +31,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 +143,143 @@ 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); + + return DependencyManager::get()->addPick(PickQuery::Collision, std::make_shared(filter, maxDistance, enabled, collisionRegion, qApp->getPhysicsEngine())); +} + void PickScriptingInterface::enablePick(unsigned int uid) { DependencyManager::get()->enablePick(uid); } diff --git a/interface/src/raypick/PickScriptingInterface.h b/interface/src/raypick/PickScriptingInterface.h index 0ee091716d..1cad4abf7c 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,55 @@ 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. + */ + + // TODO: Add this to the CollisionPickResult jsdoc once model collision picks are working + //* @property {boolean} loaded If the CollisionRegion was successfully loaded (may be false if a model was used) + + /**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 +209,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 +217,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 +225,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); diff --git a/interface/src/raypick/PointerScriptingInterface.cpp b/interface/src/raypick/PointerScriptingInterface.cpp index 4e953a5cb8..a9b9b6e9ea 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; } @@ -84,27 +87,22 @@ unsigned int PointerScriptingInterface::createStylus(const QVariant& properties) * @property {Overlays.OverlayProperties} [end] All of the properties you would normally pass to {@link Overlays.addOverlay}, plus the type (as a 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". - */ /**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[]} [renderStates] A list of different visual states to switch between. + * @property {Pointers.DefaultRayPointerRenderState[]} [defaultRenderStates] A list of different visual states to use if there is no intersection. * @property {boolean} [hover=false] If this Pointer should generate hover events. - * @property {Pointers.Trigger[]} [triggers] Ray Pointers only. A list of different triggers mechanisms that control this Pointer's click event generation. + * @property {Pointers.Trigger[]} [triggers] A list of different triggers mechanisms that control this Pointer's click event generation. */ unsigned int PointerScriptingInterface::createLaserPointer(const QVariant& properties) const { QVariantMap propertyMap = properties.toMap(); @@ -134,12 +132,21 @@ unsigned int PointerScriptingInterface::createLaserPointer(const QVariant& prope scaleWithAvatar = propertyMap["scaleWithAvatar"].toBool(); } + bool followNormal = false; + if (propertyMap["followNormal"].isValid()) { + followNormal = propertyMap["followNormal"].toBool(); + } + float followNormalStrength = 0.0f; + if (propertyMap["followNormalStrength"].isValid()) { + followNormalStrength = propertyMap["followNormalStrength"].toFloat(); + } + bool enabled = false; if (propertyMap["enabled"].isValid()) { enabled = propertyMap["enabled"].toBool(); } - LaserPointer::RenderStateMap renderStates; + RenderStateMap renderStates; if (propertyMap["renderStates"].isValid()) { QList renderStateVariants = propertyMap["renderStates"].toList(); for (const QVariant& renderStateVariant : renderStateVariants) { @@ -153,7 +160,7 @@ unsigned int PointerScriptingInterface::createLaserPointer(const QVariant& prope } } - LaserPointer::DefaultRenderStateMap defaultRenderStates; + DefaultRenderStateMap defaultRenderStates; if (propertyMap["defaultRenderStates"].isValid()) { QList renderStateVariants = propertyMap["defaultRenderStates"].toList(); for (const QVariant& renderStateVariant : renderStateVariants) { @@ -162,7 +169,7 @@ unsigned int PointerScriptingInterface::createLaserPointer(const QVariant& prope if (renderStateMap["name"].isValid() && renderStateMap["distance"].isValid()) { std::string name = renderStateMap["name"].toString().toStdString(); float distance = renderStateMap["distance"].toFloat(); - defaultRenderStates[name] = std::pair(distance, LaserPointer::buildRenderState(renderStateMap)); + defaultRenderStates[name] = std::pair>(distance, LaserPointer::buildRenderState(renderStateMap)); } } } @@ -192,7 +199,152 @@ 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 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 Parabola Pointer, if desired. +* @property {Pointers.ParabolaProperties} [path] The rendering properties of the parabolic path defined by the Parabola Pointer. +* @property {Overlays.OverlayProperties} [end] All of the properties you would normally pass to {@link Overlays.addOverlay}, plus the type (as a type field). +* An overlay to represent the end of the Parabola Pointer, if desired. +*/ +/**jsdoc +* A set of properties that can be passed to {@link Pointers.createPointer} to create a new Pointer. Contains the relevant {@link Picks.PickProperties} to define the underlying Pick. +* @typedef {object} Pointers.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[]} [renderStates] A list of different visual states to switch between. +* @property {Pointers.DefaultParabolaPointerRenderState[]} [defaultRenderStates] A list of different visual states to use if there is no intersection. +* @property {boolean} [hover=false] If this Pointer should generate hover events. +* @property {Pointers.Trigger[]} [triggers] A list of different triggers mechanisms that control this Pointer's click event generation. +*/ +unsigned int PointerScriptingInterface::createParabolaPointer(const QVariant& properties) const { + QVariantMap propertyMap = properties.toMap(); + + bool faceAvatar = false; + if (propertyMap["faceAvatar"].isValid()) { + faceAvatar = propertyMap["faceAvatar"].toBool(); + } + + bool centerEndY = true; + if (propertyMap["centerEndY"].isValid()) { + centerEndY = propertyMap["centerEndY"].toBool(); + } + + bool lockEnd = false; + if (propertyMap["lockEnd"].isValid()) { + lockEnd = propertyMap["lockEnd"].toBool(); + } + + bool distanceScaleEnd = false; + if (propertyMap["distanceScaleEnd"].isValid()) { + distanceScaleEnd = propertyMap["distanceScaleEnd"].toBool(); + } + + bool scaleWithAvatar = false; + if (propertyMap["scaleWithAvatar"].isValid()) { + scaleWithAvatar = propertyMap["scaleWithAvatar"].toBool(); + } + + bool followNormal = false; + if (propertyMap["followNormal"].isValid()) { + followNormal = propertyMap["followNormal"].toBool(); + } + float followNormalStrength = 0.0f; + if (propertyMap["followNormalStrength"].isValid()) { + followNormalStrength = propertyMap["followNormalStrength"].toFloat(); + } + + bool enabled = false; + if (propertyMap["enabled"].isValid()) { + enabled = propertyMap["enabled"].toBool(); + } + + RenderStateMap renderStates; + if (propertyMap["renderStates"].isValid()) { + QList 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 { diff --git a/interface/src/raypick/PointerScriptingInterface.h b/interface/src/raypick/PointerScriptingInterface.h index 628af84790..9fe05182c7 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. * diff --git a/interface/src/raypick/RayPick.cpp b/interface/src/raypick/RayPick.cpp index 75b5e77fd8..96b41dcc72 100644 --- a/interface/src/raypick/RayPick.cpp +++ b/interface/src/raypick/RayPick.cpp @@ -39,7 +39,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()); } diff --git a/interface/src/raypick/RayPick.h b/interface/src/raypick/RayPick.h index 6bdc2cb5b0..9410d39c1a 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; 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/scripting/Audio.cpp b/interface/src/scripting/Audio.cpp index bcb7a6c10f..524170c7a5 100644 --- a/interface/src/scripting/Audio.cpp +++ b/interface/src/scripting/Audio.cpp @@ -60,23 +60,20 @@ Audio::Audio() : _devices(_contextIsHMD) { } 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/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 50a4d17cae..e2037d54d0 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); } @@ -163,8 +162,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 +218,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 +239,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,60 +265,24 @@ void setupPreferences() { preferences->addPreference(new SliderPreference(FACE_TRACKING, "Eye Deflection", getter, setter)); } - static const QString MOVEMENT{ "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"), - 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); - QStringList items; - items << "Snap turn" << "Smooth turn"; - preference->setItems(items); - preferences->addPreference(preference); - } - { - 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); - preference->setMin(1.0f); - preference->setMax(2.2f); - preference->setDecimals(3); - preference->setStep(0.001f); - preferences->addPreference(preference); - } - { - auto preference = new ButtonPreference(MOVEMENT, "RESET SENSORS", [] { - qApp->resetSensors(); - }); - preferences->addPreference(preference); - } - 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); }; + auto getter = [myAvatar]()->bool { return myAvatar->useAdvancedMovementControls(); }; + auto setter = [myAvatar](bool value) { myAvatar->setUseAdvancedMovementControls(value); }; preferences->addPreference(new CheckPreference(VR_MOVEMENT, QStringLiteral("Advanced movement for hand controllers"), getter, setter)); } { - auto getter = [=]()->bool { return myAvatar->getFlyingHMDPref(); }; - auto setter = [=](bool value) { myAvatar->setFlyingHMDPref(value); }; - preferences->addPreference(new CheckPreference(VR_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 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"; @@ -345,8 +308,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); @@ -355,8 +318,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); @@ -417,12 +380,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..ce1cd51de1 100644 --- a/interface/src/ui/Stats.cpp +++ b/interface/src/ui/Stats.cpp @@ -174,7 +174,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 +191,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 +219,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 +259,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(); @@ -346,7 +353,6 @@ 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())); @@ -431,7 +437,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 +485,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 +502,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..cf624b54c3 100644 --- a/interface/src/ui/Stats.h +++ b/interface/src/ui/Stats.h @@ -135,6 +135,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 @@ -282,6 +283,7 @@ 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) public: static Stats* getInstance(); @@ -306,6 +308,7 @@ public: } QStringList downloadUrls () { return _downloadUrls; } + QStringList animStackNames() { return _animStackNames; } public slots: void forceUpdateStats() { updateStats(true); } @@ -999,6 +1002,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 @@ -1244,6 +1254,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..6bce9d9283 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. @@ -260,8 +261,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 +283,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); diff --git a/interface/src/ui/overlays/Base3DOverlay.h b/interface/src/ui/overlays/Base3DOverlay.h index 7c5f551e6a..d44c193055 100644 --- a/interface/src/ui/overlays/Base3DOverlay.h +++ b/interface/src/ui/overlays/Base3DOverlay.h @@ -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 }; 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..789b1f9969 100644 --- a/interface/src/ui/overlays/ContextOverlayInterface.cpp +++ b/interface/src/ui/overlays/ContextOverlayInterface.cpp @@ -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 641ad8a152..eee8222051 100644 --- a/interface/src/ui/overlays/ModelOverlay.cpp +++ b/interface/src/ui/overlays/ModelOverlay.cpp @@ -381,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. @@ -518,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 32500576e2..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; 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..c87650a77b 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, 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..29feb639f4 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,7 @@ 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().withoutShadowCaster().withTagBits(render::ItemKey::TAG_BITS_0 | render::ItemKey::TAG_BITS_1)) { } render::ItemKey GameWorkloadRenderItem::getKey() const { @@ -149,14 +145,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 +162,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 +173,7 @@ const gpu::PipelinePointer GameWorkloadRenderItem::getViewsPipeline() { state->setCullMode(gpu::State::CULL_NONE); _drawAllViewsPipeline = gpu::Pipeline::create(program, state); } + return _drawAllViewsPipeline; } 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..6313d4cbe9 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]); @@ -109,7 +158,7 @@ void AnimBlendLinearMove::setFrameAndPhase(float dt, float alpha, int prevPoseIn // 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 87e33ed95d..c3e679096b 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); @@ -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,7 +1244,7 @@ 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, @@ -1305,7 +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) { @@ -1364,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"); @@ -1396,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"); @@ -1427,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; } } @@ -1475,23 +1512,12 @@ 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. @@ -1510,19 +1536,21 @@ bool Rig::calculateElbowPoleVector(int handIndex, int elbowIndex, int armIndex, glm::vec3 backVector = oppositeArmPose.trans() - armPose.trans(); glm::vec3 backCenter = armPose.trans() + 0.5f * backVector; - - const float OVER_BACK_HEAD_PERCENTAGE = 0.2f; - glm::vec3 headCenter = backCenter + glm::vec3(0, OVER_BACK_HEAD_PERCENTAGE * backVector.length(), 0); - glm::vec3 frontVector = glm::normalize(glm::cross(backVector, glm::vec3(0, 1, 0))); + glm::vec3 frontVector = glm::normalize(glm::cross(backVector, Vectors::UNIT_Y)); + glm::vec3 topVector = glm::normalize(glm::cross(frontVector, backVector)); + + glm::vec3 centerToHand = handPose.trans() - backCenter; + glm::vec3 headCenter = backCenter + glm::length(backVector) * topVector; + // Make sure is pointing forward frontVector = frontVector.z < 0 ? -frontVector : frontVector; - float horizontalModule = glm::dot(armToHand, glm::vec3(0, -1, 0)); - glm::vec3 headForward = headCenter + horizontalModule * frontVector; + float horizontalModule = glm::dot(centerToHand, -topVector); + glm::vec3 headForward = headCenter + glm::max(0.0f, horizontalModule) * frontVector; glm::vec3 armToHead = headForward - armPose.trans(); - + float armToHandDistance = glm::length(armToHand); float armToElbowDistance = glm::length(armToElbow); float elbowToHandDistance = glm::length(elbowToHand); @@ -1533,9 +1561,11 @@ bool Rig::calculateElbowPoleVector(int handIndex, int elbowIndex, int armIndex, // How much the hand is reaching for the opposite side float oppositeProjection = glm::dot(armToHandDir, glm::normalize(backVector)); - - // Don't use pole vector when the hands are behind - if (glm::dot(frontVector, armToHand) < 0 && oppositeProjection < 0.5f * armTotalDistance) { + + 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; } @@ -1549,10 +1579,10 @@ bool Rig::calculateElbowPoleVector(int handIndex, int elbowIndex, int armIndex, glm::vec3 correctionVector = glm::vec3(0, 0, 0); const float FORWARD_TRIGGER_PERCENTAGE = 0.2f; - const float FORWARD_CORRECTOR_WEIGHT = 3.0f; + 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; @@ -1561,31 +1591,18 @@ bool Rig::calculateElbowPoleVector(int handIndex, int elbowIndex, int armIndex, 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) { @@ -1596,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; @@ -1608,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 1a1337fa84..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]; @@ -217,6 +221,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,12 +236,13 @@ 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); @@ -293,6 +301,7 @@ protected: std::shared_ptr _animSkeleton; std::unique_ptr _animLoader; AnimVariantMap _animVars; + enum class RigRole { Idle = 0, Turn, @@ -378,6 +387,7 @@ protected: bool _smoothPoleVectors { false }; int _rigId; + bool _headEnabled { false }; }; #endif /* defined(__hifi__Rig__) */ diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index c57360b09f..6a9363f309 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -53,6 +53,7 @@ #include "AudioHelpers.h" #if defined(Q_OS_ANDROID) +#define VOICE_RECOGNITION "voicerecognition" #include #endif @@ -273,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) { @@ -465,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(); } @@ -486,15 +479,6 @@ bool nativeFormatForAudioDevice(const QAudioDeviceInfo& audioDevice, audioFormat.setSampleType(QAudioFormat::SignedInt); audioFormat.setByteOrder(QAudioFormat::LittleEndian); -#if defined(Q_OS_ANDROID) - // Using the HW sample rate (AUDIO_INPUT_FLAG_FAST) in some samsung phones causes a low volume at input stream - // Changing the sample rate forces a resampling that (in samsung) amplifies +18 dB - QAndroidJniObject brand = QAndroidJniObject::getStaticObjectField("android/os/Build", "BRAND"); - if (audioDevice == QAudioDeviceInfo::defaultInputDevice() && brand.toString().contains("samsung", Qt::CaseInsensitive)) { - audioFormat.setSampleRate(24000); - } -#endif - if (!audioDevice.isFormatSupported(audioFormat)) { qCWarning(audioclient) << "The native format is" << audioFormat << "but isFormatSupported() failed."; return false; @@ -1848,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..23adcde0c5 100644 --- a/libraries/audio/src/AudioHRTF.cpp +++ b/libraries/audio/src/AudioHRTF.cpp @@ -840,7 +840,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 +919,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; } 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/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..0b43fd5433 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -1184,6 +1184,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 +1233,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); @@ -1568,6 +1586,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 +1595,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; diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h index 157f7b2ec6..c6e8ac59f1 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h @@ -249,6 +249,12 @@ public: 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; diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index fc72f34304..62c7a7053f 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -62,7 +62,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 +73,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 +228,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 +286,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 +311,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 +355,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 +694,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 +907,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(); @@ -1195,6 +1269,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 +1363,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 +1422,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 +1450,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 +1465,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 +1477,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 +1551,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 +1589,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 +1602,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 +1719,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; } @@ -1908,7 +2069,7 @@ 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) { @@ -2555,15 +2716,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 +2737,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..fcc63fdc98 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -138,6 +138,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 +274,7 @@ namespace AvatarDataPacket { SixByteTrans rightHandControllerTranslation; }; */ - size_t maxJointDataSize(size_t numJoints); + size_t maxJointDataSize(size_t numJoints, bool hasGrabJoints); /* struct JointDefaultPoseFlags { @@ -283,6 +284,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 @@ -347,6 +359,7 @@ public: RateCounter<> faceTrackerRate; RateCounter<> jointDataRate; RateCounter<> jointDefaultPoseFlagsRate; + RateCounter<> farGrabJointRate; }; class AvatarPriority { @@ -363,7 +376,7 @@ class AvatarData : public QObject, public SpatiallyNestable { // 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) @@ -895,14 +908,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 @@ -969,7 +982,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 +1053,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 +1062,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)); } @@ -1317,6 +1330,7 @@ protected: bool _firstSkeletonCheck { true }; QUrl _skeletonFBXURL; QVector _attachmentData; + QVector _oldAttachmentData; QString _displayName; QString _sessionDisplayName { }; bool _lookAtSnappingEnabled { true }; @@ -1369,6 +1383,7 @@ protected: RateCounter<> _faceTrackerRate; RateCounter<> _jointDataRate; RateCounter<> _jointDefaultPoseFlagsRate; + RateCounter<> _farGrabJointRate; // Some rate data for incoming data updates RateCounter<> _parseBufferUpdateRate; @@ -1385,6 +1400,7 @@ protected: RateCounter<> _faceTrackerUpdateRate; RateCounter<> _jointDataUpdateRate; RateCounter<> _jointDefaultPoseFlagsUpdateRate; + RateCounter<> _farGrabJointUpdateRate; // Some rate data for outgoing data AvatarDataRate _outboundDataRate; @@ -1403,6 +1419,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 }; @@ -1524,19 +1544,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 { 0.0f }; + 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 +1580,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/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/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 b18f9f988b..2acc96c4e7 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 { @@ -400,10 +373,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); @@ -411,10 +381,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); @@ -422,10 +389,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, @@ -435,10 +401,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, @@ -448,10 +413,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..34d8dbbaef 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); @@ -643,6 +642,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 3f3c751943..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); diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.h b/libraries/entities-renderer/src/RenderableModelEntityItem.h index 7a6c882c5a..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; @@ -142,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; diff --git a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp index 73f46245c4..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); @@ -346,7 +334,7 @@ void ParticleEffectEntityRenderer::doRender(RenderArgs* args) { 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)); 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..c11ccb70a0 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 @@ -605,7 +600,6 @@ bool RenderablePolyVoxEntityItem::findDetailedRayIntersection(const glm::vec3& o voxelBox += result3 + Vectors::HALF; float voxelDistance; - bool hit = voxelBox.findRayIntersection(glm::vec3(originInVoxel), glm::vec3(directionInVoxel), voxelDistance, face, surfaceNormal); @@ -615,6 +609,87 @@ bool RenderablePolyVoxEntityItem::findDetailedRayIntersection(const glm::vec3& o return hit; } +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 +1560,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 +1567,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 +1583,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 +1718,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/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 22254c0ab0..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. // @@ -43,15 +43,15 @@ struct ParticleUniforms { 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; +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 @@ -149,7 +149,7 @@ void main(void) { vec3 UP = vec3(0, 1, 0); vec3 modelUpWorld; <$transformModelToWorldDir(cam, obj, UP, modelUpWorld)$> - vec3 upWorld = mix(UP, normalize(modelUpWorld), particle.rotateWithEntity); + 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)); diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index 81cb18dee4..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; } 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 2d0fcd0496..27fe55673f 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -891,28 +891,30 @@ 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. @@ -3741,6 +3743,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/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index 09d9823728..c080fbbb88 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()) { @@ -871,6 +874,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 +1052,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 +1070,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 +1252,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..377e192bb1 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -63,6 +63,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) @@ -820,8 +841,7 @@ EntityItemID EntityTree::findRayIntersection(const glm::vec3& origin, const glm: 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() }; + visibleOnly, collidableOnly, precisionPicking, element, distance, face, surfaceNormal, extraInfo, EntityItemID() }; distance = FLT_MAX; bool requireLock = lockType == Octree::Lock; @@ -836,6 +856,47 @@ 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, keepSearching, + 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; + } + return keepSearching; +} + +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([&] { + recurseTreeWithOperation(findParabolaIntersectionOp, &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 +1435,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..5974fce6c5 100644 --- a/libraries/entities/src/EntityTreeElement.cpp +++ b/libraries/entities/src/EntityTreeElement.cpp @@ -159,7 +159,7 @@ EntityItemID EntityTreeElement::findRayIntersection(const glm::vec3& origin, con } // by default, we only allow intersections with leaves with content - if (!canRayIntersect()) { + if (!canPickIntersect()) { return result; // we don't intersect with non-leaves, and we keep searching } @@ -168,7 +168,7 @@ EntityItemID EntityTreeElement::findRayIntersection(const glm::vec3& origin, con 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; @@ -232,7 +232,7 @@ EntityItemID EntityTreeElement::findDetailedRayIntersection(const glm::vec3& ori 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 +250,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 +288,144 @@ 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, bool& keepSearching, OctreeElementPointer& element, float& parabolicDistance, + BoxFace& face, glm::vec3& surfaceNormal, const QVector& entityIdsToInclude, + const QVector& entityIdsToDiscard, bool visibleOnly, bool collidableOnly, + QVariantMap& extraInfo, bool precisionPicking) { + + EntityItemID result; + float distanceToElementCube = std::numeric_limits::max(); + BoxFace localFace; + glm::vec3 localSurfaceNormal; + + // if the parabola doesn't intersect with our cube OR the distance to element is less than current best distance + // we can stop searching! + bool hit = _cube.findParabolaIntersection(origin, velocity, acceleration, distanceToElementCube, localFace, localSurfaceNormal); + if (!hit || (!_cube.contains(origin) && distanceToElementCube > parabolicDistance)) { + keepSearching = false; // no point in continuing to search + return result; // we did not intersect + } + + // by default, we only allow intersections with leaves with content + if (!canPickIntersect()) { + return result; // we don't intersect with non-leaves, and we keep searching + } + + // if the distance to the element cube is not less than the current best distance, then it's not possible + // for any details inside the cube to be closer so we don't need to consider them. + QVariantMap localExtraInfo; + float distanceToElementDetails = parabolicDistance; + // We can precompute the world-space parabola normal and reuse it for the parabola plane intersects AABox sphere check + 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..d6f9db08d6 100644 --- a/libraries/entities/src/EntityTreeElement.h +++ b/libraries/entities/src/EntityTreeElement.h @@ -134,9 +134,9 @@ 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, + bool& keepSearching, OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, const QVector& entityIdsToInclude, const QVector& entityIdsToDiscard, bool visibleOnly, bool collidableOnly, QVariantMap& extraInfo, bool precisionPicking = false); @@ -148,6 +148,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, bool& keepSearching, 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.h b/libraries/entities/src/ParticleEffectEntityItem.h index 480b78ffcc..02284768ce 100644 --- a/libraries/entities/src/ParticleEffectEntityItem.h +++ b/libraries/entities/src/ParticleEffectEntityItem.h @@ -340,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..e4ea1470c1 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; } @@ -273,6 +273,7 @@ bool ShapeEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3 hitAt = glm::vec3(entityToWorldMatrix * glm::vec4(entityFrameHitAt, 1.0f)); distance = glm::distance(origin, hitAt); bool success; + // FIXME: this is only correct for uniformly scaled spheres surfaceNormal = glm::normalize(hitAt - getCenterPosition(success)); if (!success) { return false; @@ -282,6 +283,30 @@ bool ShapeEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const 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; + // FIXME: this is only correct for uniformly scaled spheres + surfaceNormal = glm::normalize((origin + velocity * parabolicDistance + 0.5f * acceleration * parabolicDistance * parabolicDistance) - getCenterPosition(success)); + if (!success) { + return false; + } + return true; + } + return false; +} + void ShapeEntityItem::debugDump() const { quint64 now = usecTimestampNow(); qCDebug(entities) << "SHAPE EntityItem id:" << getEntityItemID() << "---------------------------------------------"; 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..f2550e5d3c 100644 --- a/libraries/entities/src/ZoneEntityItem.cpp +++ b/libraries/entities/src/ZoneEntityItem.cpp @@ -294,10 +294,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; } diff --git a/libraries/entities/src/ZoneEntityItem.h b/libraries/entities/src/ZoneEntityItem.h index 3a9c7cb1e6..0aaa32a57a 100644 --- a/libraries/entities/src/ZoneEntityItem.h +++ b/libraries/entities/src/ZoneEntityItem.h @@ -102,11 +102,15 @@ public: 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; 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..01424c2ac6 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)); 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..88d2e8609f 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,16 @@ 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)) { + 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 +233,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 +241,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"; + 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 +445,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..eaee054b7d 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()); 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 864a106350..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; 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/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/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/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/NodeList.cpp b/libraries/networking/src/NodeList.cpp index 2ce734dd26..dd351ef940 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -665,6 +665,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/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/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 d0bd7fc872..8b50e37699 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -27,7 +27,7 @@ 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: @@ -40,7 +40,7 @@ PacketVersion versionForPacketType(PacketType packetType) { case PacketType::AvatarData: case PacketType::BulkAvatarData: case PacketType::KillAvatar: - return static_cast(AvatarMixerPacketVersion::ProceduralFaceMovementFlagsAndBlendshapes); + return static_cast(AvatarMixerPacketVersion::FarGrabJoints); case PacketType::MessagesData: return static_cast(MessageDataVersion::TextOrBinaryData); // ICE packets @@ -95,7 +95,7 @@ PacketVersion versionForPacketType(PacketType packetType) { case PacketType::AvatarIdentityRequest: return 22; default: - return 21; + return 22; } } diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index 2ffadfef4b..126dac7c8f 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -132,6 +132,7 @@ public: OctreeDataPersist, EntityClone, + EntityQueryInitialResultsComplete, NUM_PACKET_TYPE }; @@ -287,7 +288,8 @@ enum class AvatarMixerPacketVersion : PacketVersion { AvatarJointDefaultPoseFlags, FBXReaderNodeReparenting, FixMannequinDefaultAvatarFeet, - ProceduralFaceMovementFlagsAndBlendshapes + ProceduralFaceMovementFlagsAndBlendshapes, + FarGrabJoints }; enum class DomainConnectRequestVersion : PacketVersion { @@ -314,7 +316,8 @@ enum class DomainListVersion : PacketVersion { PrePermissionsGrid = 18, PermissionsGrid, GetUsernameFromUUIDSupport, - GetMachineFingerprintFromUUIDSupport + GetMachineFingerprintFromUUIDSupport, + AuthenticationOptional }; enum class AudioVersion : PacketVersion { 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 a068a806ec..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))); @@ -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(); } 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/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/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/ObjectMotionState.cpp b/libraries/physics/src/ObjectMotionState.cpp index 310cf7cec1..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() { 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..163005bc81 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) { @@ -840,3 +842,87 @@ 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) : + btCollisionWorld::ContactResultCallback(), + collisionObject(), + contacts(), + myAvatarCollisionObject(myAvatarCollisionObject) { + 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 addSingleResult(btManifoldPoint& cp, const btCollisionObjectWrapper* colObj0, int partId0, int index0, const btCollisionObjectWrapper* colObj1, int partId1, int index1) override { + 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) 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); + _dynamicsWorld->contactTest(&contactCallback.collisionObject, contactCallback); + + return contactCallback.contacts; +} + diff --git a/libraries/physics/src/PhysicsEngine.h b/libraries/physics/src/PhysicsEngine.h index 2ac195956a..c6e165632b 100644 --- a/libraries/physics/src/PhysicsEngine.h +++ b/libraries/physics/src/PhysicsEngine.h @@ -43,6 +43,28 @@ 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; @@ -103,6 +125,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) const; + private: QList removeDynamicsForBody(btRigidBody* body); void addObjectToDynamicsWorld(ObjectMotionState* motionState); diff --git a/libraries/pointers/src/Pick.h b/libraries/pointers/src/Pick.h index 53606b154f..fc09064bd1 100644 --- a/libraries/pointers/src/Pick.h +++ b/libraries/pointers/src/Pick.h @@ -145,6 +145,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 +155,7 @@ public: * * * + * * *
{@link PickType(0)|PickType.Ray}
{@link PickType(0)|PickType.Stylus}
{@link PickType(0)|PickType.Parabola}
* @typedef {number} PickType @@ -161,7 +163,8 @@ public: enum PickType { Ray = 0, Stylus, - + Parabola, + Collision, NUM_PICK_TYPES }; Q_ENUM(PickType) diff --git a/libraries/pointers/src/PickManager.cpp b/libraries/pointers/src/PickManager.cpp index ba8fa814f0..470d88a46c 100644 --- a/libraries/pointers/src/PickManager.cpp +++ b/libraries/pointers/src/PickManager.cpp @@ -100,6 +100,8 @@ void PickManager::update() { // and the rayPicks updae will ALWAYS update at least one ray even when there is no budget _stylusPickCacheOptimizer.update(cachedPicks[PickQuery::Stylus], _nextPickToUpdate[PickQuery::Stylus], expiry, false); _rayPickCacheOptimizer.update(cachedPicks[PickQuery::Ray], _nextPickToUpdate[PickQuery::Ray], expiry, shouldPickHUD); + _parabolaPickCacheOptimizer.update(cachedPicks[PickQuery::Parabola], _nextPickToUpdate[PickQuery::Parabola], expiry, shouldPickHUD); + _collisionPickCacheOptimizer.update(cachedPicks[PickQuery::Collision], _nextPickToUpdate[PickQuery::Collision], expiry, false); } bool PickManager::isLeftHand(unsigned int uid) { diff --git a/libraries/pointers/src/PickManager.h b/libraries/pointers/src/PickManager.h index 3b466be2bc..242550d837 100644 --- a/libraries/pointers/src/PickManager.h +++ b/libraries/pointers/src/PickManager.h @@ -59,14 +59,16 @@ protected: 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/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 5be05da505..6f8b4808db 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 93d2b5177b..34e2bb4fa8 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 @@ -61,19 +60,8 @@ 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); } @@ -90,8 +78,8 @@ 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); }); @@ -124,17 +112,7 @@ void BloomApply::run(const render::RenderContextPointer& renderContext, const In 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); @@ -161,7 +139,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(PARAMETERS_SLOT, _parameters); batch.draw(gpu::TRIANGLE_STRIP, 4); }); } @@ -178,13 +156,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, @@ -237,14 +209,7 @@ void DebugBloom::run(const render::RenderContextPointer& renderContext, const In 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); @@ -318,7 +283,7 @@ float BloomConfig::getIntensity() const { void BloomConfig::setSize(float value) { std::string blurName{ "BloomBlurN" }; auto sigma = 0.5f+value*3.5f; - auto task = static_cast(_task); + auto task = static_cast(_task); for (auto i = 0; i < BLOOM_BLUR_LEVEL_COUNT; i++) { blurName.back() = '0' + i; @@ -328,9 +293,9 @@ void BloomConfig::setSize(float value) { auto gaussianBlurParams = gaussianBlur.getParameters(); gaussianBlurParams->setFilterGaussianTaps(9, sigma); } - auto blurJobIt = task->getJob("BloomApply"); - assert(blurJobIt != task->_jobs.end()); - blurJobIt->getConfiguration()->setProperty("sigma", sigma); + auto blurJobIt = task->getJob("BloomApply"); + assert(blurJobIt != task->_jobs.end()); + blurJobIt->getConfiguration()->setProperty("sigma", sigma); } Bloom::Bloom() { 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/DebugDeferredBuffer.cpp b/libraries/render-utils/src/DebugDeferredBuffer.cpp index 8575df399e..cab95e50be 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,6 +38,7 @@ void DebugDeferredBufferConfig::setMode(int newMode) { emit dirty(); } +#if 0 enum TextureSlot { Albedo = 0, Normal, @@ -63,29 +63,30 @@ enum ParamSlot { ShadowTransform, DebugParametersBuffer }; +#endif -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 +95,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 +104,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 +119,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 +131,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,7 +176,7 @@ 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);" "}" @@ -194,7 +197,7 @@ static const std::string DEFAULT_HALF_NORMAL_SHADER{ 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 +205,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);" " }" }; @@ -238,7 +241,7 @@ 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(vec3(texture(scatteringMap, uv).xyz), 1.0);" " }" }; @@ -261,7 +264,7 @@ static const std::string DEFAULT_VELOCITY_SHADER{ " }" }; -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 +275,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 +365,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 +401,7 @@ const gpu::PipelinePointer& DebugDeferredBuffer::getPipeline(Mode mode, std::str _customPipelines[customFile].pipeline = pipeline; } } - + if (mode != CustomMode) { return _pipelines[mode]; } else { @@ -471,23 +448,27 @@ 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()); + batch.setResourceTexture(Textures::DebugVelocity, velocityFramebuffer->getVelocityTexture()); } - batch.setUniformBuffer(DebugParametersBuffer, _parameters); + // FIXME can't find the corresponding buffer + // batch.setUniformBuffer(UBOs:: DebugParametersBuffer, _parameters); auto lightStage = renderContext->_scene->getStage(); assert(lightStage); @@ -495,46 +476,48 @@ void DebugDeferredBuffer::run(const RenderContextPointer& renderContext, const I 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()); } if (linearDepthTarget) { - batch.setResourceTexture(LinearDepth, linearDepthTarget->getLinearDepthTexture()); - batch.setResourceTexture(HalfLinearDepth, linearDepthTarget->getHalfLinearDepthTexture()); - batch.setResourceTexture(HalfNormal, linearDepthTarget->getHalfNormalTexture()); + batch.setResourceTexture(Textures::DebugDepth, linearDepthTarget->getLinearDepthTexture()); + batch.setResourceTexture(Textures::DebugHalfDepth, linearDepthTarget->getHalfLinearDepthTexture()); + batch.setResourceTexture(Textures::DebugHalfNormal, 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()); + batch.setResourceTexture(Textures::DebugOcclusion, ambientOcclusionFramebuffer->getOcclusionTexture()); + batch.setResourceTexture(Textures::DebugOcclusionBlurred, + 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::DeferredLighting, nullptr); + batch.setResourceTexture(Textures::Shadow, nullptr); + batch.setResourceTexture(Textures::DebugDepth, nullptr); + batch.setResourceTexture(Textures::DebugHalfDepth, nullptr); + batch.setResourceTexture(Textures::DebugHalfNormal, nullptr); - batch.setResourceTexture(Curvature, nullptr); - batch.setResourceTexture(DiffusedCurvature, nullptr); + batch.setResourceTexture(Textures::DeferredCurvature, nullptr); + batch.setResourceTexture(Textures::DeferredDiffusedCurvature, nullptr); - batch.setResourceTexture(AmbientOcclusion, nullptr); - batch.setResourceTexture(AmbientOcclusionBlurred, nullptr); + batch.setResourceTexture(Textures::DebugOcclusion, nullptr); + batch.setResourceTexture(Textures::DebugOcclusionBlurred, nullptr); - batch.setResourceTexture(Velocity, nullptr); + batch.setResourceTexture(Textures::DebugVelocity, 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 48ddfbf5cd..676e8f94a2 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,10 +489,8 @@ 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); @@ -602,22 +498,22 @@ void RenderDeferredSetup::run(const render::RenderContextPointer& renderContext, // 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)); + batch._glUniform4fv(ru::Uniform::TexcoordTransform, 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); } } } @@ -669,20 +565,18 @@ void RenderDeferredLocals::run(const render::RenderContextPointer& renderContext 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._glUniform4fv(ru::Uniform::TexcoordTransform, 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._glUniform4fv(ru::Uniform::TexcoordTransform, 1, reinterpret_cast(&textureFrameTransform)); batch.draw(gpu::TRIANGLE_STRIP, 4); } @@ -695,25 +589,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 fc4abb28c4..27454321e8 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/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..ca6e736a65 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! { @@ -395,7 +399,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 +419,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..ba0d714f7a 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -353,31 +353,27 @@ 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)) { QMutexLocker locker(&_mutex); @@ -395,7 +391,7 @@ bool Model::findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const g } 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)); @@ -405,11 +401,10 @@ bool Model::findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const g for (auto& meshTriangleSets : _modelSpaceMeshTriangleSets) { int partIndex = 0; for (auto &partTriangleSet : meshTriangleSets) { - float triangleSetDistance = 0.0f; + float triangleSetDistance; BoxFace triangleSetFace; Triangle triangleSetTriangle; if (partTriangleSet.findRayIntersection(meshFrameOrigin, meshFrameDirection, triangleSetDistance, triangleSetFace, triangleSetTriangle, pickAgainstTriangles, allowBackface)) { - glm::vec3 meshIntersectionPoint = meshFrameOrigin + (meshFrameDirection * triangleSetDistance); glm::vec3 worldIntersectionPoint = glm::vec3(meshToWorldMatrix * glm::vec4(meshIntersectionPoint, 1.0f)); float worldDistance = glm::distance(origin, worldIntersectionPoint); @@ -457,6 +452,111 @@ bool Model::findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const g 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; + Triangle bestModelTriangle; + Triangle bestWorldTriangle; + 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 = 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; + for (auto& meshTriangleSets : _modelSpaceMeshTriangleSets) { + int partIndex = 0; + for (auto &partTriangleSet : meshTriangleSets) { + float triangleSetDistance; + BoxFace triangleSetFace; + Triangle triangleSetTriangle; + if (partTriangleSet.findParabolaIntersection(meshFrameOrigin, meshFrameVelocity, meshFrameAcceleration, + triangleSetDistance, triangleSetFace, triangleSetTriangle, pickAgainstTriangles, allowBackface)) { + if (triangleSetDistance < bestDistance) { + bestDistance = triangleSetDistance; + intersectedSomething = true; + face = triangleSetFace; + bestModelTriangle = triangleSetTriangle; + bestWorldTriangle = triangleSetTriangle * meshToWorldMatrix; + glm::vec3 meshIntersectionPoint = meshFrameOrigin + meshFrameVelocity * triangleSetDistance + + 0.5f * meshFrameAcceleration * triangleSetDistance * triangleSetDistance; + glm::vec3 worldIntersectionPoint = origin + velocity * triangleSetDistance + + 0.5f * acceleration * triangleSetDistance * triangleSetDistance; + extraInfo["worldIntersectionPoint"] = vec3toVariant(worldIntersectionPoint); + extraInfo["meshIntersectionPoint"] = vec3toVariant(meshIntersectionPoint); + extraInfo["partIndex"] = partIndex; + extraInfo["shapeID"] = shapeID; + bestSubMeshIndex = subMeshIndex; + } + } + partIndex++; + shapeID++; + } + subMeshIndex++; + } + + if (intersectedSomething) { + parabolicDistance = bestDistance; + surfaceNormal = bestWorldTriangle.getNormal(); + 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::convexHullContains(glm::vec3 point) { // if we aren't active, we can't compute that yet... if (!isActive()) { @@ -594,7 +694,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) { diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index 0bddae6a38..627e5fddab 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -178,6 +178,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; } diff --git a/libraries/render-utils/src/RenderCommonTask.cpp b/libraries/render-utils/src/RenderCommonTask.cpp index c2181b7613..9aee0e57a4 100644 --- a/libraries/render-utils/src/RenderCommonTask.cpp +++ b/libraries/render-utils/src/RenderCommonTask.cpp @@ -9,10 +9,23 @@ #include "RenderCommonTask.h" #include +#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..9ea1669cc0 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" @@ -56,6 +60,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 +98,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 @@ -211,6 +228,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); @@ -241,13 +261,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 +373,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 +402,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 +438,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 +460,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 d9ae0caade..500bcfa562 100644 --- a/libraries/render-utils/src/RenderShadowTask.cpp +++ b/libraries/render-utils/src/RenderShadowTask.cpp @@ -33,182 +33,10 @@ using namespace render; extern void initZPassPipelines(ShapePlumber& plumber, gpu::StatePointer state); -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]; - auto clippedTriangleCount = clipTriangleWithPlanes(triangle, shadowClipPlanes, 4, clippedTriangles, MAX_TRIANGLE_COUNT); - - for (auto i = 0; i < clippedTriangleCount; i++) { - const auto& clippedTriangle = clippedTriangles[i]; - - near = glm::min(near, -clippedTriangle.v0.z); - near = glm::min(near, -clippedTriangle.v1.z); - near = glm::min(near, -clippedTriangle.v2.z); - - far = glm::max(far, -clippedTriangle.v0.z); - far = glm::max(far, -clippedTriangle.v1.z); - far = glm::max(far, -clippedTriangle.v2.z); - } -} - -static void computeNearFar(const glm::vec3 sceneBoundVertices[8], const Plane shadowClipPlanes[4], float& near, float& far) { - // This code is inspired from Microsoft's CascadedShadowMaps11 sample which is under MIT licence. - // See https://code.msdn.microsoft.com/windowsdesktop/Direct3D-Shadow-Win32-2d72a4f2/sourcecode?fileId=121915&pathId=1645833187 - // Basically it decomposes the object bounding box in triangles and clips each triangle with the shadow - // frustum planes. Finally it computes the minimum and maximum depth of the clipped triangle vertices - // in shadow space to extract the near and far distances of the shadow frustum. - static const std::array boxQuadVertexIndices = { { - { TOP_LEFT_FAR, BOTTOM_LEFT_FAR, BOTTOM_RIGHT_FAR, TOP_RIGHT_FAR }, - { TOP_LEFT_NEAR, BOTTOM_LEFT_NEAR, BOTTOM_RIGHT_NEAR, TOP_RIGHT_NEAR }, - { TOP_RIGHT_FAR, BOTTOM_RIGHT_FAR, BOTTOM_RIGHT_NEAR, TOP_RIGHT_NEAR }, - { TOP_LEFT_FAR, BOTTOM_LEFT_FAR, BOTTOM_LEFT_NEAR, TOP_LEFT_NEAR }, - { BOTTOM_LEFT_FAR, BOTTOM_RIGHT_FAR, BOTTOM_RIGHT_NEAR, BOTTOM_LEFT_NEAR }, - { TOP_LEFT_FAR, TOP_RIGHT_FAR, TOP_RIGHT_NEAR, TOP_LEFT_NEAR } - } }; - Triangle triangle; - - for (auto quadVertexIndices : boxQuadVertexIndices) { - triangle.v0 = sceneBoundVertices[quadVertexIndices[0]]; - triangle.v1 = sceneBoundVertices[quadVertexIndices[1]]; - triangle.v2 = sceneBoundVertices[quadVertexIndices[2]]; - computeNearFar(triangle, shadowClipPlanes, near, far); - triangle.v1 = sceneBoundVertices[quadVertexIndices[3]]; - computeNearFar(triangle, shadowClipPlanes, near, far); - } -} - -static void adjustNearFar(const AABox& inShapeBounds, ViewFrustum& shadowFrustum) { - 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; - - // 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()); - - 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) { - assert(renderContext->args); - assert(renderContext->args->hasViewFrustum()); - - const auto& inShapes = inputs.get0(); - const auto& inShapeBounds = inputs.get1(); - - auto lightStage = renderContext->_scene->getStage(); - assert(lightStage); - - auto shadow = lightStage->getCurrentKeyShadow(); - if (!shadow || _cascadeIndex >= shadow->getCascadeCount()) { - return; - } - - auto& cascade = shadow->getCascade(_cascadeIndex); - auto& fbo = cascade.framebuffer; - - RenderArgs* args = renderContext->args; - ShapeKey::Builder defaultKeyBuilder; - auto adjustedShadowFrustum = args->getViewFrustum(); - - // Adjust the frustum near and far depths based on the rendered items bounding box to have - // the minimal Z range. - adjustNearFar(inShapeBounds, adjustedShadowFrustum); - // Reapply the frustum as it has been adjusted - shadow->setCascadeFrustum(_cascadeIndex, adjustedShadowFrustum); - args->popViewFrustum(); - args->pushViewFrustum(adjustedShadowFrustum); - - gpu::doInBatch("RenderShadowMap::run", args->_context, [&](gpu::Batch& batch) { - args->_batch = &batch; - batch.enableStereo(false); - - glm::ivec4 viewport{0, 0, fbo->getWidth(), fbo->getHeight()}; - batch.setViewportTransform(viewport); - batch.setStateScissorRect(viewport); - - batch.setFramebuffer(fbo); - batch.clearDepthFramebuffer(1.0, false); - - glm::mat4 projMat; - Transform viewMat; - args->getViewFrustum().evalProjectionMatrix(projMat); - args->getViewFrustum().evalViewTransform(viewMat); - - 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()); - - 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); - } else { - skinnedShapeKeys.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 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)); - } - - args->_batch = nullptr; - }); +void RenderShadowTask::configure(const Config& configuration) { + DependencyManager::get()->setShadowMapEnabled(configuration.isEnabled()); + // 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) { @@ -256,35 +84,221 @@ void RenderShadowTask::build(JobModel& task, const render::Varying& input, rende #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 shadowRenderFilter = cascadeSetupOutput.getN(0); - const auto shadowBoundsFilter = cascadeSetupOutput.getN(1); + const auto shadowFilter = cascadeSetupOutput.getN(0); auto antiFrustum = render::Varying(ViewFrustumPointer()); - cascadeFrustums[i] = cascadeSetupOutput.getN(2); + cascadeFrustums[i] = cascadeSetupOutput.getN(1); 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); + 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); - task.addJob("ShadowCascadeTeardown", shadowRenderFilter); + sprintf(jobName, "ShadowCascadeTeardown%d", i); + task.addJob(jobName, shadowFilter); + + cascadeSceneBBoxes[i] = culledShadowItemsAndBounds.getN(1); } + output = render::Varying(cascadeSceneBBoxes); + task.addJob("ShadowTeardown", setupOutput); } -void RenderShadowTask::configure(const Config& configuration) { - DependencyManager::get()->setShadowMapEnabled(configuration.isEnabled()); - // This is a task, so must still propogate configure() to its Jobs -// Task::configure(configuration); +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]; + auto clippedTriangleCount = clipTriangleWithPlanes(triangle, shadowClipPlanes, 4, clippedTriangles, MAX_TRIANGLE_COUNT); + + for (auto i = 0; i < clippedTriangleCount; i++) { + const auto& clippedTriangle = clippedTriangles[i]; + + near = glm::min(near, -clippedTriangle.v0.z); + near = glm::min(near, -clippedTriangle.v1.z); + near = glm::min(near, -clippedTriangle.v2.z); + + far = glm::max(far, -clippedTriangle.v0.z); + far = glm::max(far, -clippedTriangle.v1.z); + far = glm::max(far, -clippedTriangle.v2.z); + } +} + +static void computeNearFar(const glm::vec3 sceneBoundVertices[8], const Plane shadowClipPlanes[4], float& near, float& far) { + // This code is inspired from Microsoft's CascadedShadowMaps11 sample which is under MIT licence. + // See https://code.msdn.microsoft.com/windowsdesktop/Direct3D-Shadow-Win32-2d72a4f2/sourcecode?fileId=121915&pathId=1645833187 + // Basically it decomposes the object bounding box in triangles and clips each triangle with the shadow + // frustum planes. Finally it computes the minimum and maximum depth of the clipped triangle vertices + // in shadow space to extract the near and far distances of the shadow frustum. + static const std::array boxQuadVertexIndices = { { + { TOP_LEFT_FAR, BOTTOM_LEFT_FAR, BOTTOM_RIGHT_FAR, TOP_RIGHT_FAR }, + { TOP_LEFT_NEAR, BOTTOM_LEFT_NEAR, BOTTOM_RIGHT_NEAR, TOP_RIGHT_NEAR }, + { TOP_RIGHT_FAR, BOTTOM_RIGHT_FAR, BOTTOM_RIGHT_NEAR, TOP_RIGHT_NEAR }, + { TOP_LEFT_FAR, BOTTOM_LEFT_FAR, BOTTOM_LEFT_NEAR, TOP_LEFT_NEAR }, + { BOTTOM_LEFT_FAR, BOTTOM_RIGHT_FAR, BOTTOM_RIGHT_NEAR, BOTTOM_LEFT_NEAR }, + { TOP_LEFT_FAR, TOP_RIGHT_FAR, TOP_RIGHT_NEAR, TOP_LEFT_NEAR } + } }; + Triangle triangle; + + for (auto quadVertexIndices : boxQuadVertexIndices) { + triangle.v0 = sceneBoundVertices[quadVertexIndices[0]]; + triangle.v1 = sceneBoundVertices[quadVertexIndices[1]]; + triangle.v2 = sceneBoundVertices[quadVertexIndices[2]]; + computeNearFar(triangle, shadowClipPlanes, near, far); + triangle.v1 = sceneBoundVertices[quadVertexIndices[3]]; + computeNearFar(triangle, shadowClipPlanes, near, far); + } +} + +static void adjustNearFar(const AABox& inShapeBounds, ViewFrustum& shadowFrustum) { + 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; + + // 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(); + } +} + +void RenderShadowMap::run(const render::RenderContextPointer& renderContext, const Inputs& inputs) { + assert(renderContext->args); + assert(renderContext->args->hasViewFrustum()); + + const auto& inShapes = inputs.get0(); + const auto& inShapeBounds = inputs.get1(); + + auto lightStage = renderContext->_scene->getStage(); + assert(lightStage); + + auto shadow = lightStage->getCurrentKeyShadow(); + if (!shadow || _cascadeIndex >= shadow->getCascadeCount()) { + return; + } + + auto& cascade = shadow->getCascade(_cascadeIndex); + auto& fbo = cascade.framebuffer; + + RenderArgs* args = renderContext->args; + ShapeKey::Builder defaultKeyBuilder; + auto adjustedShadowFrustum = args->getViewFrustum(); + + // Adjust the frustum near and far depths based on the rendered items bounding box to have + // the minimal Z range. + adjustNearFar(inShapeBounds, adjustedShadowFrustum); + // Reapply the frustum as it has been adjusted + shadow->setCascadeFrustum(_cascadeIndex, adjustedShadowFrustum); + args->popViewFrustum(); + args->pushViewFrustum(adjustedShadowFrustum); + + gpu::doInBatch("RenderShadowMap::run", args->_context, [&](gpu::Batch& batch) { + args->_batch = &batch; + batch.enableStereo(false); + + glm::ivec4 viewport{0, 0, fbo->getWidth(), fbo->getHeight()}; + batch.setViewportTransform(viewport); + batch.setStateScissorRect(viewport); + + batch.setFramebuffer(fbo); + batch.clearDepthFramebuffer(1.0, false); + + if (!inShapeBounds.isNull()) { + glm::mat4 projMat; + Transform viewMat; + args->getViewFrustum().evalProjectionMatrix(projMat); + args->getViewFrustum().evalViewTransform(viewMat); + + 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()); + + 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); + } else { + skinnedShapeKeys.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 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)); + } + } + + args->_batch = nullptr; + }); } RenderShadowSetup::RenderShadowSetup() : @@ -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 1c6291b1e0..929fa79a12 100644 --- a/libraries/render-utils/src/RenderShadowTask.h +++ b/libraries/render-utils/src/RenderShadowTask.h @@ -45,8 +45,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); @@ -117,7 +120,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) : @@ -146,4 +149,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/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/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/ZoneRenderer.cpp b/libraries/render-utils/src/ZoneRenderer.cpp index 51939efd4f..3299b0c41c 100644 --- a/libraries/render-utils/src/ZoneRenderer.cpp +++ b/libraries/render-utils/src/ZoneRenderer.cpp @@ -10,20 +10,31 @@ // #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" + +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; @@ -77,16 +88,7 @@ void SetupZones::run(const RenderContextPointer& context, const Inputs& inputs) 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 +100,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 +111,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 +162,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 +170,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 +181,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 +195,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..aee04cba90 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,14 @@ <@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_TEXTURE_DEBUG_DEPTH) uniform sampler2D linearDepthMap; +layout(binding=RENDER_UTILS_TEXTURE_DEBUG_HALF_DEPTH) uniform sampler2D halfLinearDepthMap; +layout(binding=RENDER_UTILS_TEXTURE_DEBUG_HALF_NORMAL) uniform sampler2D halfNormalMap; +layout(binding=RENDER_UTILS_TEXTURE_DEBUG_OCCLUSION) uniform sampler2D occlusionMap; +layout(binding=RENDER_UTILS_TEXTURE_DEBUG_OCCLUSION_BLURRED) uniform sampler2D occlusionBlurredMap; +layout(binding=RENDER_UTILS_TEXTURE_DEBUG_SCATTERING) uniform sampler2D scatteringMap; +layout(binding=RENDER_UTILS_TEXTURE_DEBUG_VELOCITY) uniform sampler2D velocityMap; +layout(binding=RENDER_UTILS_TEXTURE_DEBUG_SHADOWS) uniform sampler2DArrayShadow shadowMaps; <@include ShadowCore.slh@> @@ -39,8 +39,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..654e9a69b2 100644 --- a/libraries/render-utils/src/deferred_light.slv +++ b/libraries/render-utils/src/deferred_light.slv @@ -12,9 +12,11 @@ // 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; + +layout(location=RENDER_UTILS_UNIFORM_LIGHT_TEXCOORD_TRANSFORM) uniform vec4 texcoordFrameTransform; void main(void) { const float depth = 1.0; @@ -26,10 +28,10 @@ void main(void) { ); vec4 pos = UNIT_QUAD[gl_VertexID]; - _texCoord0 = (pos.xy + 1.0) * 0.5; + _texCoord01.xy = (pos.xy + 1.0) * 0.5; - _texCoord0 *= texcoordFrameTransform.zw; - _texCoord0 += texcoordFrameTransform.xy; + _texCoord01.xy *= texcoordFrameTransform.zw; + _texCoord01.xy += texcoordFrameTransform.xy; 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.slv b/libraries/render-utils/src/drawWorkloadProxy.slv index 64fb335fd6..ec007e1a78 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. @@ -18,7 +18,7 @@ <@include gpu/Color.slh@> <$declareColorWheel()$> -uniform vec4 inColor; +layout(location=0) uniform vec4 inColor; struct WorkloadProxy { @@ -27,7 +27,7 @@ struct WorkloadProxy { }; #if defined(GPU_GL410) -uniform samplerBuffer workloadProxiesBuffer; +layout(binding=0) uniform samplerBuffer workloadProxiesBuffer; WorkloadProxy getWorkloadProxy(int i) { int offset = 2 * i; WorkloadProxy proxy; @@ -36,7 +36,7 @@ WorkloadProxy getWorkloadProxy(int i) { return proxy; } #else -layout(std140) buffer workloadProxiesBuffer { +layout(std140, binding=0) buffer workloadProxiesBuffer { WorkloadProxy _proxies[]; }; WorkloadProxy getWorkloadProxy(int i) { diff --git a/libraries/render-utils/src/drawWorkloadView.slf b/libraries/render-utils/src/drawWorkloadView.slf index 1304e68c7f..946b2ce6b7 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. diff --git a/libraries/render-utils/src/drawWorkloadView.slv b/libraries/render-utils/src/drawWorkloadView.slv index f5497d250c..294c3bfa99 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. @@ -18,7 +18,7 @@ <@include gpu/Color.slh@> <$declareColorWheel()$> -uniform vec4 inColor; +layout(location=0) uniform vec4 inColor; struct WorkloadView { @@ -30,16 +30,16 @@ struct WorkloadView { }; #if defined(GPU_GL410) -uniform samplerBuffer workloadViewsBuffer; +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); + //view.radiuses = texelFetch(workloadViewsBuffer, offset + 1); return view; } #else -layout(std140) buffer workloadViewsBuffer { +layout(std140,binding=1) buffer workloadViewsBuffer { WorkloadView _views[]; }; WorkloadView getWorkloadView(int i) { @@ -61,11 +61,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 +79,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); 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..8863f37083 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; +layout(location=0) in vec4 _color; + +layout(location=0) out vec4 _fragColor; void main(void) { - // pass-through to set z-buffer - _fragColor = vec4(1.0, 1.0, 1.0, 0.0); + _fragColor = _color; } 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..afb195c240 --- /dev/null +++ b/libraries/render-utils/src/render-utils/ShaderConstants.h @@ -0,0 +1,239 @@ +// + +// <@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_TEXTURE_DEBUG_DEPTH 11 +#define RENDER_UTILS_TEXTURE_DEBUG_HALF_DEPTH 12 +#define RENDER_UTILS_TEXTURE_DEBUG_OCCLUSION 13 +#define RENDER_UTILS_TEXTURE_DEBUG_OCCLUSION_BLURRED 14 +#define RENDER_UTILS_TEXTURE_DEBUG_VELOCITY 15 +#define RENDER_UTILS_TEXTURE_DEBUG_SHADOWS 16 +#define RENDER_UTILS_TEXTURE_DEBUG_HALF_NORMAL 17 +#define RENDER_UTILS_TEXTURE_DEBUG_SCATTERING 18 + +// + +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, +}; +} // 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, + DebugDepth = RENDER_UTILS_TEXTURE_DEBUG_DEPTH, + DebugHalfDepth = RENDER_UTILS_TEXTURE_DEBUG_HALF_DEPTH, + DebugOcclusion = RENDER_UTILS_TEXTURE_DEBUG_OCCLUSION, + DebugOcclusionBlurred = RENDER_UTILS_TEXTURE_DEBUG_OCCLUSION_BLURRED, + DebugVelocity = RENDER_UTILS_TEXTURE_DEBUG_VELOCITY, + DebugShadows = RENDER_UTILS_TEXTURE_DEBUG_SHADOWS, + DebugHalfNormal = RENDER_UTILS_TEXTURE_DEBUG_HALF_NORMAL, + DebugScattering = RENDER_UTILS_TEXTURE_DEBUG_SCATTERING, +}; +} // 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/BlurTask.cpp b/libraries/render/src/render/BlurTask.cpp index 896482eb2a..b76793ea2d 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; @@ -209,15 +203,7 @@ BlurGaussian::BlurGaussian(bool generateOutputFramebuffer, unsigned int downsamp 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 +217,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 @@ -323,16 +301,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 +315,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.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..b5f1718f6c 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) { @@ -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) { diff --git a/libraries/render/src/render/CullTask.h b/libraries/render/src/render/CullTask.h index 47abe8a960..2ef9e92eaa 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; 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 97cf45ebcf..593efcddb9 100644 --- a/libraries/render/src/render/DrawSceneOctree.h +++ b/libraries/render/src/render/DrawSceneOctree.h @@ -50,15 +50,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 @@ -110,11 +103,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 b1e7620b19..fcb5eadab1 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/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..105742db35 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" @@ -219,6 +220,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 +763,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 +2200,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..1791360a45 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 }; 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..994df551fe 100644 --- a/libraries/shared/src/AABox.cpp +++ b/libraries/shared/src/AABox.cpp @@ -109,19 +109,12 @@ glm::vec3 AABox::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 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 +168,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)) { @@ -225,66 +194,12 @@ bool AABox::expandedIntersectsSegment(const glm::vec3& start, const glm::vec3& e 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; - } + return findRayAABoxIntersection(origin, direction, _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 +211,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); diff --git a/libraries/shared/src/AABox.h b/libraries/shared/src/AABox.h index cf79cf9d04..fbc90cff47 100644 --- a/libraries/shared/src/AABox.h +++ b/libraries/shared/src/AABox.h @@ -70,8 +70,11 @@ 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; + 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; @@ -136,6 +139,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..dc1003215d 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)) { @@ -220,67 +189,12 @@ bool AACube::expandedIntersectsSegment(const glm::vec3& start, const glm::vec3& 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)) { + return findRayAABoxIntersection(origin, direction, _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..72aed31999 100644 --- a/libraries/shared/src/AACube.h +++ b/libraries/shared/src/AACube.h @@ -58,6 +58,8 @@ public: 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 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 d9b26927e2..39117de7ef 100644 --- a/libraries/shared/src/AvatarConstants.h +++ b/libraries/shared/src/AvatarConstants.h @@ -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/GeometryUtil.cpp b/libraries/shared/src/GeometryUtil.cpp index 0742a5625b..6fb06eb624 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,94 @@ 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; +} + +bool findRayAABoxIntersection(const glm::vec3& origin, const glm::vec3& direction, const glm::vec3& corner, const glm::vec3& scale, float& distance, + BoxFace& face, glm::vec3& surfaceNormal) { + // handle the trivial case where the box contains the origin + if (aaBoxContains(origin, corner, scale)) { + // We still want to calculate the distance from the origin to the inside out plane + float axisDistance; + if ((findInsideOutIntersection(origin.x, direction.x, corner.x, scale.x, axisDistance) && axisDistance >= 0 && + isWithin(origin.y + axisDistance * direction.y, corner.y, scale.y) && + isWithin(origin.z + axisDistance * direction.z, corner.z, scale.z))) { + distance = axisDistance; + face = direction.x > 0 ? MAX_X_FACE : MIN_X_FACE; + surfaceNormal = glm::vec3(direction.x > 0 ? 1.0f : -1.0f, 0.0f, 0.0f); + return true; + } + if ((findInsideOutIntersection(origin.y, direction.y, corner.y, scale.y, axisDistance) && axisDistance >= 0 && + isWithin(origin.x + axisDistance * direction.x, corner.x, scale.x) && + isWithin(origin.z + axisDistance * direction.z, corner.z, scale.z))) { + distance = axisDistance; + face = direction.y > 0 ? MAX_Y_FACE : MIN_Y_FACE; + surfaceNormal = glm::vec3(0.0f, direction.y > 0 ? 1.0f : -1.0f, 0.0f); + return true; + } + if ((findInsideOutIntersection(origin.z, direction.z, corner.z, scale.z, axisDistance) && axisDistance >= 0 && + isWithin(origin.y + axisDistance * direction.y, corner.y, scale.y) && + isWithin(origin.x + axisDistance * direction.x, corner.x, scale.x))) { + distance = axisDistance; + face = direction.z > 0 ? MAX_Z_FACE : MIN_Z_FACE; + surfaceNormal = glm::vec3(0.0f, 0.0f, direction.z > 0 ? 1.0f : -1.0f); + return true; + } + // This case is unexpected, but mimics the previous behavior for inside out intersections + distance = 0; + return true; + } + + // 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 findRaySphereIntersection(const glm::vec3& origin, const glm::vec3& direction, const glm::vec3& center, float radius, float& distance) { glm::vec3 relativeOrigin = origin - center; @@ -711,6 +802,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 +1684,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..54f9062469 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& 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/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/RegisteredMetaTypes.h b/libraries/shared/src/RegisteredMetaTypes.h index 467d6374a5..e78dbafd75 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,128 @@ 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; + } +}; + +/**jsdoc +* A CollisionPick defines a volume for checking collisions in the physics simulation. + +* @typedef {object} CollisionPick +* @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. +*/ +class CollisionRegion : public MathPick { +public: + CollisionRegion() { } + CollisionRegion(const QVariantMap& pickVariant) { + 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["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["position"] = vec3toVariant(transform.getTranslation()); + collisionRegion["orientation"] = quatToVariant(transform.getRotation()); + + return collisionRegion; + } + + operator bool() const override { + return !(glm::any(glm::isnan(transform.getTranslation())) || + glm::any(glm::isnan(transform.getRotation())) || + shapeInfo->getType() == SHAPE_TYPE_NONE); + } + + bool operator==(const CollisionRegion& other) const { + return 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 + 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; +}; namespace std { inline void hash_combine(std::size_t& seed) { } @@ -255,6 +379,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 +406,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/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/TriangleSet.cpp b/libraries/shared/src/TriangleSet.cpp index d7f685f8d3..cde9c20cab 100644 --- a/libraries/shared/src/TriangleSet.cpp +++ b/libraries/shared/src/TriangleSet.cpp @@ -51,6 +51,26 @@ bool TriangleSet::findRayIntersection(const glm::vec3& origin, const glm::vec3& return result; } +bool TriangleSet::findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + float& parabolicDistance, BoxFace& face, Triangle& triangle, bool precision, bool allowBackface) { + // reset our distance to be the max possible, lower level tests will store best distance here + parabolicDistance = FLT_MAX; + + if (!_isBalanced) { + balanceOctree(); + } + + int trianglesTouched = 0; + auto result = _triangleOctree.findParabolaIntersection(origin, velocity, acceleration, parabolicDistance, face, triangle, precision, trianglesTouched, allowBackface); + +#if WANT_DEBUGGING + if (precision) { + qDebug() << "trianglesTouched :" << trianglesTouched << "out of:" << _triangleOctree._population << "_triangles.size:" << _triangles.size(); + } +#endif + return result; +} + bool TriangleSet::convexHullContains(const glm::vec3& point) const { if (!_bounds.contains(point)) { return false; @@ -95,40 +115,63 @@ void TriangleSet::balanceOctree() { // 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) { - + float& distance, BoxFace& face, Triangle& triangle, bool precision, + int& trianglesTouched, bool allowBackface) { bool intersectedSomething = false; - float boxDistance = distance; - float bestDistance = distance; - glm::vec3 surfaceNormal; + float bestDistance = FLT_MAX; - 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; - } + 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; } } - } else { - intersectedSomething = true; - distance = boxDistance; } + } else { + intersectedSomething = true; + bestDistance = distance; + } + + if (intersectedSomething) { + distance = bestDistance; + } + + return intersectedSomething; +} + +bool TriangleSet::TriangleOctreeCell::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; + + 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; + intersectedSomething = true; + triangle = thisTriangle; + } + } + } + } else { + intersectedSomething = true; + bestDistance = parabolicDistance; + } + + if (intersectedSomething) { + parabolicDistance = bestDistance; } return intersectedSomething; @@ -204,45 +247,42 @@ 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::TriangleOctreeCell::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, + BoxFace& face, Triangle& triangle, bool precision, int& trianglesTouched, + bool allowBackface) { if (_population < 1) { return false; // no triangles below here, so we can't intersect } - float bestLocalDistance = 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)) { - + float boxDistance = FLT_MAX; + // if the pick intersects our bounding box, then continue + if (getBounds().findRayIntersection(origin, direction, boxDistance, bestLocalFace, bestLocalNormal)) { // if the intersection with our bounding box, is greater than the current best distance (the distance passed in) // then we know that none of our triangles can represent a better intersection and we can return - - if (bestLocalDistance > distance) { + if (boxDistance > distance) { return false; } - bestLocalDistance = distance; - - 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) { + float bestChildDistance = FLT_MAX; for (auto& child : _children) { // check each child, if there's an intersection, it will return some distance that we need // to compare against the other results, because there might be multiple intersections and // we will always choose the best (shortest) intersection + float childDistance = bestChildDistance; + BoxFace childFace; + Triangle childTriangle; if (child.second.findRayIntersection(origin, direction, childDistance, childFace, childTriangle, precision, trianglesTouched)) { if (childDistance < bestLocalDistance) { bestLocalDistance = childDistance; + bestChildDistance = childDistance; bestLocalFace = childFace; bestLocalTriangle = childTriangle; intersects = true; @@ -251,11 +291,14 @@ bool TriangleSet::TriangleOctreeCell::findRayIntersection(const glm::vec3& origi } } // 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; + float internalDistance = boxDistance; + BoxFace internalFace; + Triangle internalTriangle; + if (findRayIntersectionInternal(origin, direction, internalDistance, internalFace, internalTriangle, precision, trianglesTouched, allowBackface)) { + if (internalDistance < bestLocalDistance) { + bestLocalDistance = internalDistance; + bestLocalFace = internalFace; + bestLocalTriangle = internalTriangle; intersects = true; } } @@ -267,3 +310,68 @@ bool TriangleSet::TriangleOctreeCell::findRayIntersection(const glm::vec3& origi } return intersects; } + +bool TriangleSet::TriangleOctreeCell::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; + glm::vec3 bestLocalNormal; + bool intersects = false; + + float boxDistance = FLT_MAX; + // if the pick intersects our bounding box, then continue + if (getBounds().findParabolaIntersection(origin, velocity, acceleration, boxDistance, bestLocalFace, bestLocalNormal)) { + // if the intersection with our bounding box, is greater than the current best distance (the distance passed in) + // then we know that none of our triangles can represent a better intersection and we can return + if (boxDistance > parabolicDistance) { + return false; + } + + // if we're not yet at the max depth, then check which child the triangle fits in + if (_depth < MAX_DEPTH) { + float bestChildDistance = FLT_MAX; + for (auto& child : _children) { + // check each child, if there's an intersection, it will return some distance that we need + // to compare against the other results, because there might be multiple intersections and + // we will always choose the best (shortest) intersection + float childDistance = bestChildDistance; + BoxFace childFace; + Triangle childTriangle; + if (child.second.findParabolaIntersection(origin, velocity, acceleration, childDistance, childFace, childTriangle, precision, trianglesTouched)) { + if (childDistance < bestLocalDistance) { + bestLocalDistance = childDistance; + bestChildDistance = childDistance; + bestLocalFace = childFace; + bestLocalTriangle = childTriangle; + intersects = true; + } + } + } + } + // also check our local triangle set + float internalDistance = boxDistance; + BoxFace internalFace; + Triangle internalTriangle; + if (findParabolaIntersectionInternal(origin, velocity, acceleration, internalDistance, internalFace, internalTriangle, precision, trianglesTouched, allowBackface)) { + if (internalDistance < bestLocalDistance) { + bestLocalDistance = internalDistance; + bestLocalFace = internalFace; + bestLocalTriangle = internalTriangle; + intersects = true; + } + } + } + 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..0b0d0a9ac5 100644 --- a/libraries/shared/src/TriangleSet.h +++ b/libraries/shared/src/TriangleSet.h @@ -31,6 +31,9 @@ class TriangleSet { bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, 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; } @@ -43,6 +46,9 @@ class TriangleSet { 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::vector& _allTriangles; std::map _children; @@ -65,6 +71,8 @@ public: bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, 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(); @@ -72,12 +80,6 @@ public: 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. 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/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/OffscreenUi.cpp b/libraries/ui/src/OffscreenUi.cpp index 25f0652496..a5ef1457db 100644 --- a/libraries/ui/src/OffscreenUi.cpp +++ b/libraries/ui/src/OffscreenUi.cpp @@ -96,10 +96,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; } 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..4e920e430b 100644 --- a/libraries/ui/src/ui/TabletScriptingInterface.cpp +++ b/libraries/ui/src/ui/TabletScriptingInterface.cpp @@ -334,6 +334,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) { 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/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/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/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/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/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 5c7cb93aa6..03b7b3969d 100644 --- a/scripts/system/avatarapp.js +++ b/scripts/system/avatarapp.js @@ -67,7 +67,8 @@ function getMyAvatarSettings() { dominantHand: MyAvatar.getDominantHand(), collisionsEnabled : MyAvatar.getCollisionsEnabled(), collisionSoundUrl : MyAvatar.collisionSoundURL, - animGraphUrl : MyAvatar.getAnimGraphUrl(), + animGraphUrl: MyAvatar.getAnimGraphUrl(), + animGraphOverrideUrl : MyAvatar.getAnimGraphOverrideUrl(), } } @@ -142,9 +143,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 }) + } } } @@ -282,7 +288,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; 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/farActionGrabEntityDynOnly.js b/scripts/system/controllers/controllerModules/farActionGrabEntityDynOnly.js new file mode 100644 index 0000000000..a080e75325 --- /dev/null +++ b/scripts/system/controllers/controllerModules/farActionGrabEntityDynOnly.js @@ -0,0 +1,603 @@ +"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 +*/ + +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" + ]); + 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; + } + 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..439b5e5f51 --- /dev/null +++ b/scripts/system/controllers/controllerModules/farParentGrabEntity.js @@ -0,0 +1,646 @@ +"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 +*/ + +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" + ]); + 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; + } + 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 377167d7bf..d590545532 100644 --- a/scripts/system/controllers/controllerModules/inEditMode.js +++ b/scripts/system/controllers/controllerModules/inEditMode.js @@ -19,7 +19,6 @@ Script.include("/~/system/libraries/utils.js"); (function () { var MARGIN = 25; - var TABLET_MATERIAL_ENTITY_NAME = 'Tablet-Material-Entity'; function InEditMode(hand) { this.hand = hand; this.triggerClicked = false; @@ -66,25 +65,27 @@ 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) { - if (!this.isTabletMaterialEntity(this.selectedTarget.objectID)) { - Messages.sendLocalMessage("entityToolUpdates", JSON.stringify({ - method: "selectEntity", - entityID: this.selectedTarget.objectID - })); - } + Messages.sendLocalMessage("entityToolUpdates", JSON.stringify({ + method: "selectEntity", + 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 })); } @@ -94,12 +95,6 @@ Script.include("/~/system/libraries/utils.js"); this.sendPointingAtData(controllerData); }; - - this.isTabletMaterialEntity = function(entityID) { - return ((entityID === HMD.homeButtonHighlightMaterialID) || - (entityID === HMD.homeButtonUnhighlightMaterialID)); - }; - 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 6564c91d8a..db95f6b09b 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 = 12.0; + 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,7 +310,7 @@ 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'); @@ -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 4b8abff84b..adee7c6236 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -314,12 +314,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 +350,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); @@ -801,6 +804,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); } @@ -858,6 +869,7 @@ var toolBar = (function () { } UserActivityLogger.enabledEdit(); entityListTool.setVisible(true); + entityListTool.sendUpdate(); gridTool.setVisible(true); grid.setEnabled(true); propertiesTool.setVisible(true); @@ -963,13 +975,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]); + } } } } @@ -1590,7 +1604,7 @@ function deleteSelectedEntities() { Entities.deleteEntity(entityID); } } - + if (savedProperties.length > 0) { SelectionManager.clearSelections(); pushCommandForSelections([], savedProperties); @@ -1880,12 +1894,14 @@ Controller.keyReleaseEvent.connect(keyReleaseEvent); Controller.keyPressEvent.connect(keyPressEvent); 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]); + } } } @@ -1895,16 +1911,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; @@ -1933,31 +1955,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 }); @@ -2015,10 +2045,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() { @@ -2071,8 +2107,7 @@ var PropertiesTool = function (opts) { } data.selections = selections; - webView.emitScriptEvent(JSON.stringify(data)); - createToolsWindow.emitScriptEvent(JSON.stringify(data)); + emitScriptEvent(data); } selectionManager.addEventListener(updateSelections); @@ -2229,7 +2264,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..9614f8b8fe 100644 --- a/scripts/system/html/entityProperties.html +++ b/scripts/system/html/entityProperties.html @@ -126,8 +126,8 @@

- - + +
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..31984d2d01 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"); @@ -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"); @@ -906,10 +916,206 @@ 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 = ""; + 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); + + // 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 +1243,7 @@ function loaded() { elGrabbable.checked = properties.dynamic; - elWantsTrigger.checked = false; + elTriggerable.checked = false; elIgnoreIK.checked = true; elCloneable.checked = properties.cloneable; @@ -1060,10 +1266,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 +1284,7 @@ function loaded() { } if (!grabbablesSet) { elGrabbable.checked = true; - elWantsTrigger.checked = false; + elTriggerable.checked = false; elIgnoreIK.checked = true; elCloneable.checked = false; } @@ -1184,10 +1392,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 +1472,12 @@ 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); elShapeType.value = properties.shapeType; elCompoundShapeURL.value = properties.compoundShapeURL; @@ -1447,8 +1658,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 +1673,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 +2059,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')); @@ -1936,7 +2147,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 f83f961438..c201a251d0 100644 --- a/scripts/system/libraries/WebTablet.js +++ b/scripts/system/libraries/WebTablet.js @@ -39,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; @@ -87,8 +85,7 @@ cleanUpOldMaterialEntities = function() { for (var entityID in avatarEntityData) { var entityName = Entities.getEntityProperties(entityID, ["name"]).name; - if (entityName === TABLET_MATERIAL_ENTITY_NAME && entityID !== HMD.homeButtonHighlightMaterialID && - entityID !== HMD.homeButtonUnhighlightMaterialID) { + if (entityName === TABLET_MATERIAL_ENTITY_NAME) { Entities.deleteEntity(entityID); } } @@ -194,45 +191,21 @@ WebTablet = function (url, width, dpi, hand, clientOnly, location, visible) { parentJointIndex: -1 }); - this.homeButtonUnhighlightMaterial = Entities.addEntity({ - type: "Material", - name: TABLET_MATERIAL_ENTITY_NAME, - 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", - name: TABLET_MATERIAL_ENTITY_NAME, - 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) { @@ -387,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); @@ -482,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 }); } }; @@ -627,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 = { @@ -634,6 +618,8 @@ WebTablet.prototype.mouseMoveEvent = function (event) { y: event.y }; this.scheduleMouseMoveProcessor(); + } else { + this.handleHomeButtonHover(event.x, event.y); } }; @@ -660,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..a386dcf5b4 100644 --- a/scripts/system/libraries/controllerDispatcherUtils.js +++ b/scripts/system/libraries/controllerDispatcherUtils.js @@ -5,7 +5,6 @@ // 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, Selection, Uuid, MSECS_PER_SEC:true , LEFT_HAND:true, RIGHT_HAND:true, FORBIDDEN_GRAB_TYPES:true, @@ -58,7 +57,7 @@ entityIsFarGrabbedByOther:true, highlightTargetEntity:true, clearHighlightedEntities:true, - unhighlightTargetEntity:true + unhighlightTargetEntity:true, distanceBetweenEntityLocalPositionAndBoundingBox: true */ @@ -95,7 +94,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 +202,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 +236,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 +258,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 +303,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 +327,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 +348,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 +416,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); 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 24b90e3a44..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) { @@ -111,11 +115,6 @@ EntityListTool = function(shouldUseEditTabletApp) { return value !== undefined ? value : ""; } - function filterEntity(entityID) { - return ((entityID === HMD.homeButtonHighlightMaterialID) || - (entityID === HMD.homeButtonUnhighlightMaterialID)); - } - that.sendUpdate = function() { var entities = []; @@ -126,10 +125,6 @@ EntityListTool = function(shouldUseEditTabletApp) { ids = Entities.findEntities(MyAvatar.position, searchRadius); } - ids = ids.filter(function(id) { - return !filterEntity(id); - }); - var cameraPosition = Camera.position; for (var i = 0; i < ids.length; i++) { var id = ids[i]; @@ -253,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 091d431502..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; @@ -271,7 +339,7 @@ SelectionManager = (function() { SelectionDisplay.setSpaceMode(SPACE_LOCAL); } } else { - properties = Entities.getEntityProperties(that.selections[0]); + properties = Entities.getEntityProperties(that.selections[0], ['type', 'boundingBox']); that.entityType = properties.type; @@ -279,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); @@ -356,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 @@ -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, @@ -751,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 && @@ -774,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) { @@ -833,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; } @@ -1079,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({}); @@ -1092,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); @@ -1716,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(); @@ -1745,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) <- ======================="); @@ -1929,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 }; @@ -1955,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; }, @@ -2243,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/tablet-ui/tabletUI.js b/scripts/system/tablet-ui/tabletUI.js index bd6a9c69d5..f339475f72 100644 --- a/scripts/system/tablet-ui/tabletUI.js +++ b/scripts/system/tablet-ui/tabletUI.js @@ -112,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); @@ -139,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); @@ -159,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 }); } @@ -179,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"); @@ -331,9 +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/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..9238e0dc1c 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, 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/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..95a4d7f9f0 100644 --- a/tests/shared/src/AACubeTests.cpp +++ b/tests/shared/src/AACubeTests.cpp @@ -152,3 +152,47 @@ 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)); + float distance; + BoxFace face; + glm::vec3 normal; + auto start = std::chrono::high_resolution_clock::now(); + for (auto& cube : cubes) { + if (cube.findRayIntersection(origin, direction, distance, face, normal)) { + 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/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();