Merge branch 'master' into M17428-c

This commit is contained in:
David Rowe 2018-08-21 16:52:30 +12:00
commit 4b12e18c27
180 changed files with 1802 additions and 616 deletions

View file

@ -145,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')
}

View file

@ -209,6 +209,11 @@ JNIEXPORT void Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeGotoUr
DependencyManager::get<AddressManager>()->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<AddressManager>()->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<AccountManager>();
return accountManager->isLoggedIn();
}
JNIEXPORT jstring JNICALL
Java_io_highfidelity_hifiinterface_fragment_FriendsFragment_nativeGetAccessToken(JNIEnv *env, jobject instance) {
auto accountManager = DependencyManager::get<AccountManager>();
return env->NewStringUTF(accountManager->getAccountInfo().getAccessToken().token.toLatin1().data());
}
JNIEXPORT void JNICALL
Java_io_highfidelity_hifiinterface_SplashActivity_registerLoadCompleteListener(JNIEnv *env,
jobject instance) {

View file

@ -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));
}
}

View file

@ -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();
}
}

View file

@ -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);
}
}

View file

@ -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<UsersResponse> friendsCall = mEndpointUsersProviderService.getUsers(
CONNECTION_FILTER_CONNECTIONS,
400,
null);
friendsCall.enqueue(new Callback<UsersResponse>() {
@Override
public void onResponse(Call<UsersResponse> call, retrofit2.Response<UsersResponse> response) {
if (!response.isSuccessful()) {
usersCallback.retrieveError(new Exception("Error calling Users API"), "Error calling Users API");
return;
}
UsersResponse usersResponse = response.body();
List<UserListAdapter.User> 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<UsersResponse> call, Throwable t) {
usersCallback.retrieveError(new Exception(t), "Error calling Users API");
}
});
}
public class UserActionRetrofitCallback implements Callback<UsersResponse> {
UserActionCallback callback;
public UserActionRetrofitCallback(UserActionCallback callback) {
this.callback = callback;
}
@Override
public void onResponse(Call<UsersResponse> call, retrofit2.Response<UsersResponse> 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<UsersResponse> call, Throwable t) {
callback.requestError(new Exception(t), t.getMessage());
}
}
@Override
public void addFriend(String friendUserName, UserActionCallback callback) {
Call<UsersResponse> friendCall = mEndpointUsersProviderService.addFriend(new BodyAddFriend(friendUserName));
friendCall.enqueue(new UserActionRetrofitCallback(callback));
}
@Override
public void removeFriend(String friendUserName, UserActionCallback callback) {
Call<UsersResponse> friendCall = mEndpointUsersProviderService.removeFriend(friendUserName);
friendCall.enqueue(new UserActionRetrofitCallback(callback));
}
@Override
public void removeConnection(String connectionUserName, UserActionCallback callback) {
Call<UsersResponse> connectionCall = mEndpointUsersProviderService.removeConnection(connectionUserName);
connectionCall.enqueue(new UserActionRetrofitCallback(callback));
}
public interface EndpointUsersProviderService {
@GET("api/v1/users")
Call<UsersResponse> getUsers(@Query("filter") String filter,
@Query("per_page") int perPage,
@Query("online") Boolean online);
@DELETE("api/v1/user/connections/{connectionUserName}")
Call<UsersResponse> removeConnection(@Path("connectionUserName") String connectionUserName);
@DELETE("api/v1/user/friends/{friendUserName}")
Call<UsersResponse> removeFriend(@Path("friendUserName") String friendUserName);
@POST("api/v1/user/friends")
Call<UsersResponse> 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<User> 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;
}
}

View file

@ -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<UserListAdapter.User> 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);
}
}

View file

@ -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<UserListAdapter.ViewHolder> {
private UsersProvider mProvider;
private LayoutInflater mInflater;
private Context mContext;
private List<User> 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<User> 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);
}
}

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2V7H6v12zM19,4h-3.5l-1,-1h-5l-1,1H5v2h14V4z"/>
</vector>

View file

@ -0,0 +1,4 @@
<vector android:height="31dp" android:viewportHeight="25.0"
android:viewportWidth="27.0" android:width="31dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FBD92A" android:pathData="M12.549,0.927C12.848,0.006 14.152,0.006 14.451,0.927L16.756,8.019C16.889,8.431 17.273,8.71 17.706,8.71H25.164C26.132,8.71 26.535,9.95 25.751,10.519L19.719,14.903C19.368,15.157 19.221,15.608 19.355,16.021L21.66,23.113C21.959,24.034 20.904,24.8 20.121,24.231L14.088,19.847C13.737,19.593 13.263,19.593 12.912,19.847L6.879,24.231C6.096,24.8 5.041,24.034 5.34,23.113L7.645,16.021C7.779,15.608 7.632,15.157 7.282,14.903L1.249,10.519C0.465,9.95 0.868,8.71 1.836,8.71H9.293C9.727,8.71 10.111,8.431 10.245,8.019L12.549,0.927Z"/>
</vector>

View file

@ -0,0 +1,31 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M0.6,3h22.8v18.7h-22.8z"
android:fillAlpha="0"/>
<path
android:fillColor="#FF000000"
android:pathData="M0.6,3h16.3v18.7h-16.3z"
android:fillAlpha="0"/>
<path
android:fillColor="#FF000000"
android:pathData="M0.6,3h16.3v18.7h-16.3z"
android:fillAlpha="0"/>
<path
android:fillColor="#FF000000"
android:pathData="M13.8,9.9h9.6v7.8h-9.6z"
android:fillAlpha="0"/>
<path
android:pathData="M11.9,16.9c-0.2,-0.9 -0.3,-2.3 -0.4,-3.4c-0.1,-0.7 -0.1,-1.3 -0.2,-1.7c0,-0.1 -0.1,-0.3 0.3,-0.4c0.1,0 0.1,0 0.2,-0.1l4.4,-1.7c0.3,-0.1 0.5,-0.4 0.6,-0.7c0.1,-0.3 0.1,-0.7 -0.2,-0.9L16.6,8c-0.2,-0.2 -0.5,-0.3 -0.8,-0.3c-0.1,0 -4.8,0.7 -6.8,0.7c-0.1,0 -0.1,0 -0.1,0c-2,0 -6.9,-0.8 -7,-0.8c-0.4,-0.1 -0.8,0.1 -1,0.4L0.7,8.3C0.6,8.5 0.6,8.8 0.6,9.1c0.1,0.3 0.3,0.5 0.5,0.6C2,10 5,11.2 5.9,11.3c0.2,0 0.4,0.1 0.5,0.6c0.1,0.6 -0.2,3.6 -0.6,5c-0.4,1.4 -1,3.2 -1,3.2c-0.2,0.5 0.1,1 0.6,1.2l0.6,0.2c0.2,0.1 0.5,0.1 0.7,-0.1c0.2,-0.1 0.4,-0.3 0.5,-0.6l1.7,-5l1.6,5.1c0.1,0.3 0.3,0.5 0.5,0.6c0.1,0.1 0.3,0.1 0.4,0.1c0.1,0 0.2,0 0.3,-0.1l0.5,-0.2c0.4,-0.2 0.7,-0.6 0.6,-1.1C12.8,20.3 12.3,18.5 11.9,16.9z"
android:fillColor="#FFFFFF"/>
<path
android:pathData="M8.9,7.5c1.3,0 2.3,-1 2.3,-2.3S10.2,3 8.9,3S6.6,4 6.6,5.3S7.7,7.5 8.9,7.5z"
android:fillColor="#FFFFFF"/>
<path
android:pathData="M23,13.4L22.6,13c0,0 0,0 0,0l-2.9,-2.8c-0.2,-0.2 -0.5,-0.2 -0.7,0l-0.7,0.7c-0.2,0.2 -0.2,0.5 0,0.7l1.2,1.2h-5.2c-0.3,0 -0.5,0.2 -0.5,0.5v0.9c0,0.3 0.2,0.5 0.5,0.5h5.1l-1.2,1.1c-0.2,0.2 -0.2,0.5 0,0.7l0.7,0.7c0.2,0.2 0.5,0.2 0.7,0l3.3,-3.2C23.2,13.9 23.2,13.6 23,13.4z"
android:fillColor="#FFFFFF"/>
</vector>

View file

@ -0,0 +1,88 @@
<?xml version="1.0" encoding="utf-8"?>
<com.sothree.slidinguppanel.SlidingUpPanelLayout
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/sliding_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="bottom"
app:umanoFadeColor="@color/slidingUpPanelFadeColor"
app:umanoShadowHeight="4dp"
android:background="@color/backgroundLight">
<android.support.v4.widget.SwipeRefreshLayout
android:id="@+id/swipeRefreshLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:id="@+id/rvUsers"
android:paddingTop="@dimen/list_vertical_padding"
android:paddingBottom="@dimen/list_vertical_padding"
android:clipToPadding="false"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</android.support.v4.widget.SwipeRefreshLayout>
<LinearLayout
android:id="@+id/userActionsLayout"
android:layout_width="match_parent"
android:layout_height="270dp"
android:orientation="vertical"
android:background="@color/backgroundDark">
<android.support.constraint.ConstraintLayout
android:id="@+id/userActionVisit"
android:layout_width="match_parent"
android:layout_height="56dp"
android:clickable="true"
android:focusable="true"
android:background="?attr/selectableItemBackground">
<ImageView android:id="@+id/userActionVisitIcon"
android:layout_width="16dp"
android:layout_height="16dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginStart="@dimen/activity_horizontal_margin"
android:src="@drawable/ic_teleporticon"
android:tint="@color/white_opaque" />
<TextView android:id="@+id/userActionVisitText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Visit In-World"
android:fontFamily="@font/raleway"
android:textColor="@color/white_opaque"
app:layout_constraintStart_toEndOf="@id/userActionVisitIcon"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginStart="32dp" />
</android.support.constraint.ConstraintLayout>
<android.support.constraint.ConstraintLayout
android:id="@+id/userActionDelete"
android:layout_width="match_parent"
android:layout_height="56dp"
android:clickable="true"
android:focusable="true"
android:background="?attr/selectableItemBackground">
<ImageView android:id="@+id/userActionDeleteIcon"
android:layout_width="16dp"
android:layout_height="16dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginStart="@dimen/activity_horizontal_margin"
android:src="@drawable/ic_delete_black_24dp"
android:tint="@color/white_opaque" />
<TextView android:id="@+id/userActionDeleteText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Remove from People"
android:fontFamily="@font/raleway"
android:textColor="@color/white_opaque"
app:layout_constraintStart_toEndOf="@id/userActionDeleteIcon"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginStart="32dp" />
</android.support.constraint.ConstraintLayout>
</LinearLayout>
</com.sothree.slidinguppanel.SlidingUpPanelLayout>

View file

@ -0,0 +1,69 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="56dp"
android:background="?attr/selectableItemBackground"
android:clickable="true">
<ImageView
android:id="@+id/userImage"
android:layout_width="40dp"
android:layout_height="40dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginStart="@dimen/activity_horizontal_margin"
app:layout_constraintStart_toStartOf="parent"/>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginStart="@dimen/activity_horizontal_margin"
app:layout_constraintStart_toEndOf="@id/userImage"
android:orientation="vertical">
<TextView
android:id="@+id/userName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/raleway"
android:textColor="@color/menuOption"/>
<LinearLayout android:id="@+id/userOnlineInfo"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView
android:id="@+id/userOnline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/online"
android:fontFamily="@font/raleway"
android:textColor="@color/hifiAquamarine" />
<TextView
android:id="@+id/userLocation"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="3dp"
android:fontFamily="@font/raleway_italic"
android:textColor="@color/menuOption"/>
</LinearLayout>
</LinearLayout>
<RelativeLayout android:id="@+id/userFav"
android:layout_width="48dp"
android:layout_height="48dp"
android:clickable="true"
android:focusable="true"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginEnd="5.5dp">
<ImageView android:id="@+id/userFavImage"
android:layout_width="27dp"
android:layout_height="27dp"
android:src="@drawable/ic_star"
android:tint="@color/starUnselectedTint"
android:background="?attr/selectableItemBackgroundBorderless"
android:layout_centerInParent="true"
android:layout_marginEnd="0dp" />
</RelativeLayout>
</android.support.constraint.ConstraintLayout>

View file

@ -5,4 +5,8 @@
android:id="@+id/action_home"
android:title="@string/home"
/>
<item
android:id="@+id/action_people"
android:title="@string/people"
/>
</menu>

View file

@ -18,4 +18,8 @@
<color name="black_060">#99000000</color>
<color name="statusbar_color">#292929</color>
<color name="hifiLogoColor">#23B2E7</color>
<color name="hifiAquamarine">#62D5C6</color>
<color name="starSelectedTint">#FBD92A</color>
<color name="starUnselectedTint">#8A8A8A</color>
<color name="slidingUpPanelFadeColor">#40000000</color>
</resources>

View file

@ -37,4 +37,6 @@
<dimen name="header_hifi_height">101dp</dimen>
<dimen name="header_hifi_width">425dp</dimen>
<dimen name="list_vertical_padding">8dp</dimen>
</resources>

View file

@ -1,6 +1,7 @@
<resources>
<string name="app_name" translatable="false">Interface</string>
<string name="home">Home</string>
<string name="people">People</string>
<string name="web_view_action_open_in_browser" translatable="false">Open in browser</string>
<string name="web_view_action_share" translatable="false">Share link</string>
<string name="web_view_action_share_subject" translatable="false">Shared a link</string>
@ -21,5 +22,11 @@
<string name="search_no_results">No places exist with that name</string>
<string name="privacyPolicy">Privacy Policy</string>
<string name="your_last_location">Your Last Location</string>
<string name="online">Online</string>
<!-- tags -->
<string name="tagFragmentHome">tagFragmentHome</string>
<string name="tagFragmentLogin">tagFragmentLogin</string>
<string name="tagFragmentPolicy">tagFragmentPolicy</string>
<string name="tagFragmentPeople">tagFragmentPeople</string>
</resources>

View file

@ -28,6 +28,7 @@ allprojects {
repositories {
jcenter()
google()
mavenCentral()
}
}

View file

@ -4,8 +4,8 @@ set(EXTERNAL_NAME serverless-content)
ExternalProject_Add(
${EXTERNAL_NAME}
URL http://cdn.highfidelity.com/content-sets/serverless-tutorial-RC70v2.zip
URL_MD5 35fcc8e635e71d0b00a08455a2582448
URL http://cdn.highfidelity.com/content-sets/serverless-tutorial-RC72.zip
URL_MD5 b1d8faf9266bfbff88274a484911eb99
CONFIGURE_COMMAND ""
BUILD_COMMAND ""
INSTALL_COMMAND ""

View file

@ -8,290 +8,224 @@
# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
#
function(global_append varName varValue)
get_property(LOCAL_LIST GLOBAL PROPERTY ${varName})
list(APPEND LOCAL_LIST ${varValue})
set_property(GLOBAL PROPERTY ${varName} ${LOCAL_LIST})
endfunction()
function(AUTOSCRIBE_SHADER SHADER_FILE)
# 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()
#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)
#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()
# 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()
file(MAKE_DIRECTORY "${SHADERS_DIR}/${SHADER_LIB}")
set(SHADER_TARGET "${SHADERS_DIR}/${SHADER_LIB}/${SHADER_TARGET}.${SHADER_TYPE}")
set(SCRIBE_COMMAND scribe)
# 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 -T ${SHADER_TYPE} -D GLPROFILE ${GLPROFILE} ${SCRIBE_INCLUDES} -o ${SHADER_TARGET} ${SHADER_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} PARENT_SCOPE)
endfunction()
macro(AUTOSCRIBE_APPEND_SHADER_SOURCES)
if (NOT("${ARGN}" STREQUAL ""))
set_property(GLOBAL PROPERTY ${TARGET_NAME}_SHADER_SOURCES "${ARGN}")
global_append(GLOBAL_SHADER_LIBS ${TARGET_NAME})
global_append(GLOBAL_SHADER_SOURCES "${ARGN}")
endif()
endmacro()
macro(AUTOSCRIBE_SHADER_LIB)
unset(HIFI_LIBRARIES_SHADER_INCLUDE_FILES)
set(SRC_FOLDER "${CMAKE_SOURCE_DIR}/libraries/${TARGET_NAME}/src")
file(GLOB_RECURSE SHADER_INCLUDE_FILES ${SRC_FOLDER}/*.slh)
file(GLOB_RECURSE SHADER_VERTEX_FILES ${SRC_FOLDER}/*.slv)
file(GLOB_RECURSE SHADER_FRAGMENT_FILES ${SRC_FOLDER}/*.slf)
file(GLOB_RECURSE SHADER_GEOMETRY_FILES ${SRC_FOLDER}/*.slg)
file(GLOB_RECURSE SHADER_COMPUTE_FILES ${SRC_FOLDER}/*.slc)
unset(SHADER_SOURCE_FILES)
list(APPEND SHADER_SOURCE_FILES ${SHADER_VERTEX_FILES})
list(APPEND SHADER_SOURCE_FILES ${SHADER_FRAGMENT_FILES})
list(APPEND SHADER_SOURCE_FILES ${SHADER_GEOMETRY_FILES})
list(APPEND SHADER_SOURCE_FILES ${SHADER_COMPUTE_FILES})
unset(LOCAL_SHADER_SOURCES)
list(APPEND LOCAL_SHADER_SOURCES ${SHADER_SOURCE_FILES})
list(APPEND LOCAL_SHADER_SOURCES ${SHADER_INCLUDE_FILES})
AUTOSCRIBE_APPEND_SHADER_SOURCES(${LOCAL_SHADER_SOURCES})
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()
endmacro()
macro(AUTOSCRIBE_PROGRAM)
set(oneValueArgs NAME VERTEX FRAGMENT GEOMETRY COMPUTE)
cmake_parse_arguments(AUTOSCRIBE_PROGRAM "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
if (NOT (DEFINED AUTOSCRIBE_PROGRAM_NAME))
message(FATAL_ERROR "Programs must have a name and both a vertex and fragment shader")
endif()
if (NOT (DEFINED AUTOSCRIBE_PROGRAM_VERTEX))
set(AUTOSCRIBE_PROGRAM_VERTEX ${AUTOSCRIBE_PROGRAM_NAME})
endif()
if (NOT (DEFINED AUTOSCRIBE_PROGRAM_FRAGMENT))
set(AUTOSCRIBE_PROGRAM_FRAGMENT ${AUTOSCRIBE_PROGRAM_NAME})
endif()
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()
unset(AUTOSCRIBE_PROGRAM_MAP)
list(APPEND AUTOSCRIBE_PROGRAM_MAP AUTOSCRIBE_PROGRAM_VERTEX)
list(APPEND AUTOSCRIBE_PROGRAM_MAP ${AUTOSCRIBE_PROGRAM_VERTEX})
list(APPEND AUTOSCRIBE_PROGRAM_MAP AUTOSCRIBE_PROGRAM_FRAGMENT)
list(APPEND AUTOSCRIBE_PROGRAM_MAP ${AUTOSCRIBE_PROGRAM_FRAGMENT})
global_append(${TARGET_NAME}_PROGRAMS ${AUTOSCRIBE_PROGRAM_NAME})
set_property(GLOBAL PROPERTY ${AUTOSCRIBE_PROGRAM_NAME} "${AUTOSCRIBE_PROGRAM_MAP}")
endmacro()
macro(unpack_map)
set(MAP_VAR "${ARGN}")
list(LENGTH MAP_VAR MAP_SIZE)
MATH(EXPR MAP_ENTRY_RANGE "(${MAP_SIZE} / 2) - 1")
foreach(INDEX RANGE ${MAP_ENTRY_RANGE})
MATH(EXPR INDEX_NAME "${INDEX} * 2")
MATH(EXPR INDEX_VAL "${INDEX_NAME} + 1")
list(GET MAP_VAR ${INDEX_NAME} VAR_NAME)
list(GET MAP_VAR ${INDEX_VAL} VAR_VAL)
set(${VAR_NAME} ${VAR_VAL})
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()
endmacro()
macro(PROCESS_SHADER_FILE)
AUTOSCRIBE_SHADER(${SHADER} ${ALL_SHADER_HEADERS} ${HIFI_LIBRARIES_SHADER_INCLUDE_FILES})
file(TO_CMAKE_PATH "${AUTOSCRIBE_SHADER_RETURN}" AUTOSCRIBE_GENERATED_FILE)
set_property(SOURCE ${AUTOSCRIBE_GENERATED_FILE} PROPERTY SKIP_AUTOMOC ON)
source_group("Compiled/${SHADER_LIB}" FILES ${AUTOSCRIBE_GENERATED_FILE})
set(REFLECTED_SHADER "${AUTOSCRIBE_GENERATED_FILE}.json")
foreach(SHADER_INCLUDE ${SHADER_INCLUDE_FILES})
get_filename_component(INCLUDE_DIR ${SHADER_INCLUDE} PATH)
list(APPEND SHADER_INCLUDES_PATHS ${INCLUDE_DIR})
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})
# 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_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(SCRIBE_ARGS -T ${SHADER_TYPE} -D GLPROFILE ${GLPROFILE} ${SCRIBE_INCLUDES} -o ${SHADER_TARGET} ${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})
# 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})
#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})
source_group("Reflected/${SHADER_LIB}" FILES ${REFLECTED_SHADER})
list(APPEND COMPILED_SHADERS ${AUTOSCRIBE_GENERATED_FILE})
get_filename_component(ENUM_NAME ${SHADER} NAME_WE)
string(CONCAT SHADER_ENUMS "${SHADER_ENUMS}" "${ENUM_NAME} = ${SHADER_COUNT},\n")
list(APPEND REFLECTED_SHADERS ${REFLECTED_SHADER})
string(CONCAT SHADER_QRC "${SHADER_QRC}" "<file alias=\"${SHADER_COUNT}\">${COMPILED_SHADER}</file>\n")
string(CONCAT SHADER_QRC "${SHADER_QRC}" "<file alias=\"${SHADER_COUNT}_reflection\">${REFLECTED_SHADER}</file>\n")
string(CONCAT SHADER_ENUMS "${SHADER_ENUMS}" "${SHADER_NAME} = ${SHADER_COUNT},\n")
MATH(EXPR SHADER_COUNT "${SHADER_COUNT}+1")
endmacro()
macro(AUTOSCRIBE_SHADER_FINISH)
get_property(GLOBAL_SHADER_LIBS GLOBAL PROPERTY GLOBAL_SHADER_LIBS)
list(REMOVE_DUPLICATES GLOBAL_SHADER_LIBS)
set(SHADER_COUNT 0)
set(PROGRAM_COUNT 0)
macro(AUTOSCRIBE_SHADER_LIB)
if (NOT ("${TARGET_NAME}" STREQUAL "shaders"))
message(FATAL_ERROR "AUTOSCRIBE_SHADER_LIB can only be used by the shaders library")
endif()
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")
# 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()
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()
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)
# 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})
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()
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()
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})
unset(COMPILED_SHADERS)
foreach(SHADER_LIB ${GLOBAL_SHADER_LIBS})
get_property(LIB_SHADER_SOURCES GLOBAL PROPERTY ${SHADER_LIB}_SHADER_SOURCES)
get_property(LIB_PROGRAMS GLOBAL PROPERTY ${SHADER_LIB}_PROGRAMS)
list(REMOVE_DUPLICATES LIB_SHADER_SOURCES)
string(REGEX REPLACE "[-]" "_" SHADER_NAMESPACE ${SHADER_LIB})
list(APPEND HIFI_LIBRARIES_SHADER_INCLUDE_FILES "${CMAKE_SOURCE_DIR}/libraries/${SHADER_LIB}/src")
unset(VERTEX_SHADERS)
unset(FRAGMENT_SHADERS)
unset(SHADER_HEADERS)
foreach(SHADER_FILE ${LIB_SHADER_SOURCES})
if (SHADER_FILE MATCHES ".*\\.slv")
list(APPEND VERTEX_SHADERS ${SHADER_FILE})
elseif (SHADER_FILE MATCHES ".*\\.slf")
list(APPEND FRAGMENT_SHADERS ${SHADER_FILE})
elseif (SHADER_FILE MATCHES ".*\\.slh")
list(APPEND SHADER_HEADERS ${SHADER_FILE})
endif()
endforeach()
if (DEFINED SHADER_HEADERS)
list(REMOVE_DUPLICATES SHADER_HEADERS)
source_group("${SHADER_LIB}/Headers" FILES ${SHADER_HEADERS})
list(APPEND ALL_SHADER_HEADERS ${SHADER_HEADERS})
list(APPEND ALL_SCRIBE_SHADERS ${SHADER_HEADERS})
endif()
string(CONCAT SHADER_ENUMS "${SHADER_ENUMS}" "namespace ${SHADER_NAMESPACE} {\n")
if (DEFINED VERTEX_SHADERS)
list(REMOVE_DUPLICATES VERTEX_SHADERS)
source_group("${SHADER_LIB}/Vertex" FILES ${VERTEX_SHADERS})
list(APPEND ALL_SCRIBE_SHADERS ${VERTEX_SHADERS})
string(CONCAT SHADER_ENUMS "${SHADER_ENUMS}" "namespace vertex { enum {\n")
foreach(SHADER ${VERTEX_SHADERS})
process_shader_file()
endforeach()
string(CONCAT SHADER_ENUMS "${SHADER_ENUMS}" "}; } // vertex \n")
endif()
if (DEFINED FRAGMENT_SHADERS)
list(REMOVE_DUPLICATES FRAGMENT_SHADERS)
source_group("${SHADER_LIB}/Fragment" FILES ${FRAGMENT_SHADERS})
list(APPEND ALL_SCRIBE_SHADERS ${FRAGMENT_SHADERS})
string(CONCAT SHADER_ENUMS "${SHADER_ENUMS}" "namespace fragment { enum {\n")
foreach(SHADER ${FRAGMENT_SHADERS})
process_shader_file()
endforeach()
string(CONCAT SHADER_ENUMS "${SHADER_ENUMS}" "}; } // fragment \n")
endif()
if (DEFINED LIB_PROGRAMS)
string(CONCAT SHADER_ENUMS "${SHADER_ENUMS}" "namespace program { enum {\n")
foreach(LIB_PROGRAM ${LIB_PROGRAMS})
get_property(LIB_PROGRAM_MAP GLOBAL PROPERTY ${LIB_PROGRAM})
unpack_map(${LIB_PROGRAM_MAP})
string(CONCAT SHADER_ENUMS "${SHADER_ENUMS}" "${LIB_PROGRAM} = (${AUTOSCRIBE_PROGRAM_VERTEX} << 16) | ${AUTOSCRIBE_PROGRAM_FRAGMENT},\n")
MATH(EXPR PROGRAM_COUNT "${PROGRAM_COUNT}+1")
list(APPEND SHADER_ALL_PROGRAMS "${SHADER_NAMESPACE}::program::${LIB_PROGRAM}")
endforeach()
string(CONCAT SHADER_ENUMS "${SHADER_ENUMS}" "}; } // program \n")
endif()
string(CONCAT SHADER_ENUMS "${SHADER_ENUMS}" "} // namespace ${SHADER_NAMESPACE}\n")
#
# Scribe generation & program defintiion
#
foreach(SHADER_LIB ${ARGN})
AUTOSCRIBE_SHADER_LIB(${SHADER_LIB})
endforeach()
set(SHADER_PROGRAMS_ARRAY "")
foreach(SHADER_PROGRAM ${SHADER_ALL_PROGRAMS})
string(CONCAT SHADER_PROGRAMS_ARRAY "${SHADER_PROGRAMS_ARRAY}" " ${SHADER_PROGRAM},\n")
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(SHREFLECT_COMMAND shreflect)
set(SHREFLECT_DEPENDENCY shreflect)
if (ANDROID)
set(SHREFLECT_COMMAND ${NATIVE_SHREFLECT})
unset(SHREFLECT_DEPENDENCY)
endif()
set(SHADER_COUNT 0)
foreach(COMPILED_SHADER ${COMPILED_SHADERS})
set(REFLECTED_SHADER "${COMPILED_SHADER}.json")
list(APPEND COMPILED_SHADER_REFLECTIONS ${REFLECTED_SHADER})
string(CONCAT SHADER_QRC "${SHADER_QRC}" "<file alias=\"${SHADER_COUNT}\">${COMPILED_SHADER}</file>\n")
string(CONCAT SHADER_QRC "${SHADER_QRC}" "<file alias=\"${SHADER_COUNT}_reflection\">${REFLECTED_SHADER}</file>\n")
MATH(EXPR SHADER_COUNT "${SHADER_COUNT}+1")
add_custom_command(
OUTPUT ${REFLECTED_SHADER}
COMMAND ${SHREFLECT_COMMAND} ${COMPILED_SHADER}
DEPENDS ${SHREFLECT_DEPENDENCY} ${COMPILED_SHADER}
)
endforeach()
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 ${COMPILED_SHADER_REFLECTIONS})
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")
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")
list(APPEND AUTOSCRIBE_SHADER_LIB_SRC ${COMPILED_SHADERS})
set(QT_RESOURCES_FILE ${CMAKE_CURRENT_BINARY_DIR}/shaders.qrc)
get_property(GLOBAL_SHADER_SOURCES GLOBAL PROPERTY GLOBAL_SHADER_SOURCES)
list(REMOVE_DUPLICATES GLOBAL_SHADER_SOURCES)
endmacro()

View file

@ -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)

View file

@ -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@")

View file

@ -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.

View file

@ -2439,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);

View file

@ -266,19 +266,10 @@ class RenderEventHandler : public QObject {
using Parent = QObject;
Q_OBJECT
public:
RenderEventHandler(QOpenGLContext* context) {
_renderContext = new OffscreenGLCanvas();
_renderContext->setObjectName("RenderContext");
_renderContext->create(context);
if (!_renderContext->makeCurrent()) {
qFatal("Unable to make rendering context current");
}
_renderContext->doneCurrent();
RenderEventHandler() {
// Transfer to a new thread
moveToNewNamedThread(this, "RenderThread", [this](QThread* renderThread) {
hifi::qt::addBlockingForbiddenThread("Render", renderThread);
_renderContext->moveToThreadWithContext(renderThread);
qApp->_lastTimeRendered.start();
}, std::bind(&RenderEventHandler::initialize, this), QThread::HighestPriority);
}
@ -288,9 +279,6 @@ private:
setObjectName("Render");
PROFILE_SET_THREAD_NAME("Render");
setCrashAnnotation("render_thread_id", std::to_string((size_t)QThread::currentThreadId()));
if (!_renderContext->makeCurrent()) {
qFatal("Unable to make rendering context current on render thread");
}
}
void render() {
@ -311,8 +299,6 @@ private:
}
return Parent::event(event);
}
OffscreenGLCanvas* _renderContext { nullptr };
};
@ -2695,26 +2681,14 @@ void Application::initializeGL() {
}
}
// Build an offscreen GL context for the main thread.
_offscreenContext = new OffscreenGLCanvas();
_offscreenContext->setObjectName("MainThreadContext");
_offscreenContext->create(_glWidget->qglContext());
if (!_offscreenContext->makeCurrent()) {
qFatal("Unable to make offscreen context current");
}
_offscreenContext->doneCurrent();
_offscreenContext->setThreadContext();
_renderEventHandler = new RenderEventHandler();
// Build an offscreen GL context for the main thread.
_glWidget->makeCurrent();
glClearColor(0.2f, 0.2f, 0.2f, 1);
glClear(GL_COLOR_BUFFER_BIT);
_glWidget->swapBuffers();
// Move the GL widget context to the render event handler thread
_renderEventHandler = new RenderEventHandler(_glWidget->qglContext());
if (!_offscreenContext->makeCurrent()) {
qFatal("Unable to make offscreen context current");
}
// Create the GPU backend
@ -2727,9 +2701,6 @@ void Application::initializeGL() {
_gpuContext = std::make_shared<gpu::Context>();
DependencyManager::get<TextureCache>()->setGPUContext(_gpuContext);
// Restore the default main thread context
_offscreenContext->makeCurrent();
}
static const QString SPLASH_SKYBOX{ "{\"ProceduralEntity\":{ \"version\":2, \"shaderUrl\":\"qrc:///shaders/splashSkybox.frag\" } }" };
@ -2768,8 +2739,6 @@ void Application::initializeDisplayPlugins() {
// Submit a default frame to render until the engine starts up
updateRenderArgs(0.0f);
_offscreenContext->makeCurrent();
#define ENABLE_SPLASH_FRAME 0
#if ENABLE_SPLASH_FRAME
{
@ -2811,8 +2780,6 @@ void Application::initializeDisplayPlugins() {
}
void Application::initializeRenderEngine() {
_offscreenContext->makeCurrent();
// FIXME: on low end systems os the shaders take up to 1 minute to compile, so we pause the deadlock watchdog thread.
DeadlockWatchdogThread::withPause([&] {
// Set up the render engine
@ -4562,7 +4529,6 @@ void Application::idle() {
if (offscreenUi->size() != fromGlm(uiSize)) {
qCDebug(interfaceapp) << "Device pixel ratio changed, triggering resize to " << uiSize;
offscreenUi->resize(fromGlm(uiSize));
_offscreenContext->makeCurrent();
}
}
@ -4620,10 +4586,6 @@ void Application::idle() {
bool showWarnings = getLogger()->extraDebugging();
PerformanceWarning warn(showWarnings, "idle()");
if (!_offscreenContext->makeCurrent()) {
qFatal("Unable to make main thread context current");
}
{
_gameWorkload.updateViews(_viewFrustum, getMyAvatar()->getHeadPosition());
_gameWorkload._engine->run();
@ -4951,7 +4913,6 @@ QVector<EntityItemID> Application::pasteEntities(float x, float y, float z) {
}
void Application::init() {
_offscreenContext->makeCurrent();
// Make sure Login state is up to date
DependencyManager::get<DialogsManager>()->toggleLoginDialog();
if (!DISABLE_DEFERRED) {
@ -5300,6 +5261,7 @@ void Application::resetPhysicsReadyInformation() {
_nearbyEntitiesCountAtLastPhysicsCheck = 0;
_nearbyEntitiesStabilityCount = 0;
_physicsEnabled = false;
_octreeProcessor.startEntitySequence();
}
@ -5537,7 +5499,7 @@ void Application::update(float deltaTime) {
// Check for flagged EntityData having arrived.
auto entityTreeRenderer = getEntities();
if (isServerlessMode() ||
(entityTreeRenderer && _octreeProcessor.octreeSequenceIsComplete(entityTreeRenderer->getLastOctreeMessageSequence()) )) {
(_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;
@ -5546,7 +5508,7 @@ void Application::update(float deltaTime) {
// 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();
}
@ -6350,7 +6312,6 @@ void Application::clearDomainOctreeDetails() {
_octreeServerSceneStats.clear();
});
_octreeProcessor.resetCompletionSequenceNumber();
// reset the model renderer
getEntities()->clear();
@ -8342,7 +8303,7 @@ QOpenGLContext* Application::getPrimaryContext() {
}
bool Application::makeRenderingContextCurrent() {
return _offscreenContext->makeCurrent();
return true;
}
bool Application::isForeground() const {

View file

@ -81,7 +81,6 @@
#include "Sound.h"
class OffscreenGLCanvas;
class GLCanvas;
class FaceTracker;
class MainWindow;
@ -554,7 +553,6 @@ private:
bool _previousSessionCrashed;
OffscreenGLCanvas* _offscreenContext { nullptr };
DisplayPluginPointer _displayPlugin;
QMetaObject::Connection _displayPluginPresentConnection;
mutable std::mutex _displayPluginLock;

View file

@ -277,6 +277,9 @@ void AvatarManager::updateOtherAvatars(float deltaTime) {
sortedAvatars.pop();
}
if (_shouldRender) {
qApp->getMain3DScene()->enqueueTransaction(transaction);
}
_numAvatarsUpdated = numAvatarsUpdated;
_numAvatarsNotUpdated = numAVatarsNotUpdated;

View file

@ -16,8 +16,11 @@
#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<NodeList>()->getPacketReceiver();
@ -26,6 +29,8 @@ OctreePacketProcessor::OctreePacketProcessor() {
packetReceiver.registerDirectListenerForTypes(octreePackets, this, "handleOctreePacket");
}
OctreePacketProcessor::~OctreePacketProcessor() { }
void OctreePacketProcessor::handleOctreePacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode) {
queueReceivedPacket(message, senderNode);
}
@ -107,6 +112,7 @@ void OctreePacketProcessor::processPacket(QSharedPointer<ReceivedMessage> messag
auto renderer = qApp->getEntities();
if (renderer) {
renderer->processDatagram(*message, sendingNode);
_safeLanding->noteReceivedsequenceNumber(renderer->getLastOctreeMessageSequence());
}
}
} break;
@ -115,8 +121,7 @@ void OctreePacketProcessor::processPacket(QSharedPointer<ReceivedMessage> messag
// Read sequence #
OCTREE_PACKET_SEQUENCE completionNumber;
message->readPrimitive(&completionNumber);
_completionSequenceNumber = completionNumber;
_safeLanding->setCompletionSequenceNumbers(0, completionNumber);
} break;
default: {
@ -125,22 +130,10 @@ void OctreePacketProcessor::processPacket(QSharedPointer<ReceivedMessage> messag
}
}
void OctreePacketProcessor::resetCompletionSequenceNumber() {
_completionSequenceNumber = INVALID_SEQUENCE;
void OctreePacketProcessor::startEntitySequence() {
_safeLanding->startEntitySequence(qApp->getEntities());
}
namespace {
template<typename T> bool lessThanWraparound(int a, int b) {
constexpr int MAX_T_VALUE = std::numeric_limits<T>::max();
if (b <= a) {
b += MAX_T_VALUE;
}
return (b - a) < (MAX_T_VALUE / 2);
}
}
bool OctreePacketProcessor::octreeSequenceIsComplete(int sequenceNumber) const {
// If we've received the flagged seq # and the current one is >= it.
return _completionSequenceNumber != INVALID_SEQUENCE &&
!lessThanWraparound<OCTREE_PACKET_SEQUENCE>(sequenceNumber, _completionSequenceNumber);
bool OctreePacketProcessor::isLoadSequenceComplete() const {
return _safeLanding->isLoadSequenceComplete();
}

View file

@ -15,21 +15,22 @@
#include <ReceivedPacketProcessor.h>
#include <ReceivedMessage.h>
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();
bool octreeSequenceIsComplete(int sequenceNumber) const;
void startEntitySequence();
bool isLoadSequenceComplete() const;
signals:
void packetVersionMismatch();
public slots:
void resetCompletionSequenceNumber();
protected:
virtual void processPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode) override;
@ -37,8 +38,6 @@ private slots:
void handleOctreePacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
private:
static constexpr int INVALID_SEQUENCE = -1;
std::atomic<int> _completionSequenceNumber { INVALID_SEQUENCE };
std::unique_ptr<SafeLanding> _safeLanding;
};
#endif // hifi_OctreePacketProcessor_h

View file

@ -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<OCTREE_PACKET_SEQUENCE>::max() + 1;
namespace {
template<typename T> bool lessThanWraparound(int a, int b) {
constexpr int MAX_T_VALUE = std::numeric_limits<T>::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<OCTREE_PACKET_SEQUENCE>(a, b);
}
void SafeLanding::startEntitySequence(QSharedPointer<EntityTreeRenderer> entityTreeRenderer) {
auto entityTree = entityTreeRenderer->getTree();
if (entityTree) {
Locker lock(_lock);
_entityTree = entityTree;
_trackedEntities.clear();
_trackingEntities = true;
connect(std::const_pointer_cast<EntityTree>(_entityTree).get(),
&EntityTree::addingEntity, this, &SafeLanding::addTrackedEntity);
connect(std::const_pointer_cast<EntityTree>(_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<ModelEntityItem>(entity).get();
static const std::set<ShapeType> 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;
}
}

View file

@ -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 <QtCore/QObject>
#include <QtCore/QSharedPointer>
#include "EntityItem.h"
class EntityTreeRenderer;
class EntityItemID;
class SafeLanding : public QObject {
public:
void startEntitySequence(QSharedPointer<EntityTreeRenderer> 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<std::mutex>;
bool _trackingEntities { false };
EntityTreePointer _entityTree;
using EntityMap = std::map<EntityItemID, EntityItemPointer>;
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<int, SequenceLessThan> _sequenceNumbers;
static float ElevatedPriority(const EntityItem& entityItem);
static float StandardPriority(const EntityItem&) { return 0.0f; }
static const int SEQUENCE_MODULO;
};
#endif // hifi_SafeLanding_h

View file

@ -12,9 +12,33 @@
#include <glm/gtx/transform.hpp>
#include "PhysicsCollisionGroups.h"
#include "ScriptEngineLogging.h"
#include "UUIDHasher.h"
PickResultPointer CollisionPickResult::compareAndProcessNewResult(const PickResultPointer& newRes) {
const std::shared_ptr<CollisionPickResult> newCollisionResult = std::static_pointer_cast<CollisionPickResult>(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<CollisionPickResult>(*this);
}
void buildObjectIntersectionsMap(IntersectionType intersectionType, const std::vector<ContactTestResult>& objectIntersections, std::unordered_map<QUuid, QVariantMap>& intersections, std::unordered_map<QUuid, QVariantList>& collisionPointPairs) {
for (auto& objectIntersection : objectIntersections) {
auto at = intersections.find(objectIntersection.foundID);
@ -308,20 +332,27 @@ CollisionRegion CollisionPick::getMathematicalPick() const {
return _mathPick;
}
const std::vector<ContactTestResult> CollisionPick::filterIntersections(const std::vector<ContactTestResult>& intersections) const {
std::vector<ContactTestResult> filteredIntersections;
void CollisionPick::filterIntersections(std::vector<ContactTestResult>& intersections) const {
const QVector<QUuid>& ignoreItems = getIgnoreItems();
const QVector<QUuid>& includeItems = getIncludeItems();
bool isWhitelist = includeItems.size();
for (const auto& intersection : intersections) {
bool isWhitelist = !includeItems.empty();
if (!isWhitelist && ignoreItems.empty()) {
return;
}
std::vector<ContactTestResult> 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);
}
}
return filteredIntersections;
intersections = filteredIntersections;
}
PickResultPointer CollisionPick::getEntityIntersection(const CollisionRegion& pick) {
@ -330,7 +361,8 @@ PickResultPointer CollisionPick::getEntityIntersection(const CollisionRegion& pi
return std::make_shared<CollisionPickResult>(pick.toVariantMap(), CollisionPickResult::LOAD_STATE_NOT_LOADED, std::vector<ContactTestResult>(), std::vector<ContactTestResult>());
}
const auto& entityIntersections = filterIntersections(_physicsEngine->getCollidingInRegion(MOTIONSTATE_TYPE_ENTITY, *pick.shapeInfo, pick.transform));
auto entityIntersections = _physicsEngine->contactTest(USER_COLLISION_MASK_ENTITIES, *pick.shapeInfo, pick.transform);
filterIntersections(entityIntersections);
return std::make_shared<CollisionPickResult>(pick, CollisionPickResult::LOAD_STATE_LOADED, entityIntersections, std::vector<ContactTestResult>());
}
@ -343,8 +375,9 @@ PickResultPointer CollisionPick::getAvatarIntersection(const CollisionRegion& pi
// Cannot compute result
return std::make_shared<CollisionPickResult>(pick.toVariantMap(), CollisionPickResult::LOAD_STATE_NOT_LOADED, std::vector<ContactTestResult>(), std::vector<ContactTestResult>());
}
const auto& avatarIntersections = filterIntersections(_physicsEngine->getCollidingInRegion(MOTIONSTATE_TYPE_AVATAR, *pick.shapeInfo, pick.transform));
auto avatarIntersections = _physicsEngine->contactTest(USER_COLLISION_MASK_AVATARS, *pick.shapeInfo, pick.transform);
filterIntersections(avatarIntersections);
return std::make_shared<CollisionPickResult>(pick, CollisionPickResult::LOAD_STATE_LOADED, std::vector<ContactTestResult>(), avatarIntersections);
}

View file

@ -49,23 +49,7 @@ public:
bool doesIntersect() const override { return intersects; }
bool checkOrFilterAgainstMaxDistance(float maxDistance) override { return true; }
PickResultPointer compareAndProcessNewResult(const PickResultPointer& newRes) override {
const std::shared_ptr<CollisionPickResult> newCollisionResult = std::static_pointer_cast<CollisionPickResult>(newRes);
for (ContactTestResult& entityIntersection : newCollisionResult->entityIntersections) {
entityIntersections.push_back(entityIntersection);
}
for (ContactTestResult& avatarIntersection : newCollisionResult->avatarIntersections) {
avatarIntersections.push_back(avatarIntersection);
}
intersects = entityIntersections.size() || avatarIntersections.size();
if (newCollisionResult->loadState == LOAD_STATE_NOT_LOADED || loadState == LOAD_STATE_UNKNOWN) {
loadState = (LoadState)newCollisionResult->loadState;
}
return std::make_shared<CollisionPickResult>(*this);
}
PickResultPointer compareAndProcessNewResult(const PickResultPointer& newRes) override;
};
class CollisionPick : public Pick<CollisionRegion> {
@ -92,7 +76,7 @@ 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<GeometryResource> resource);
const std::vector<ContactTestResult> filterIntersections(const std::vector<ContactTestResult>& intersections) const;
void filterIntersections(std::vector<ContactTestResult>& intersections) const;
CollisionRegion _mathPick;
PhysicsEnginePointer _physicsEngine;

View file

@ -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)

View file

@ -1,5 +1,4 @@
set(TARGET_NAME display-plugins)
AUTOSCRIBE_SHADER_LIB(gpu display-plugins)
setup_hifi_library(Gui)
link_hifi_libraries(shared shaders plugins ui-plugins gl ui render-utils ${PLATFORM_GL_BACKEND})
include_hifi_library_headers(gpu)

View file

@ -0,0 +1 @@
VERTEX gpu::vertex::DrawUnitQuadTexcoord

View file

@ -0,0 +1 @@
VERTEX gpu::vertex::DrawUnitQuadTexcoord

View file

@ -642,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<EntityTree>(_tree)->processEraseMessage(message, sourceNode);
}

View file

@ -0,0 +1,3 @@
VERTEX DrawTransformVertexPosition
FRAGMENT DrawColor
r

View file

@ -0,0 +1,2 @@
VERTEX DrawVertexPosition
FRAGMENT DrawNada

View file

@ -0,0 +1,2 @@
VERTEX DrawUnitQuadTexcoord
FRAGMENT DrawTexture

View file

@ -0,0 +1,2 @@
VERTEX DrawTexcoordRectTransformUnitQuad
FRAGMENT DrawTextureOpaque

View file

@ -0,0 +1,2 @@
VERTEX DrawTransformUnitQuad
FRAGMENT DrawTextureOpaque

View file

@ -0,0 +1,2 @@
VERTEX DrawUnitQuadTexcoord
FRAGMENT DrawTextureOpaque

View file

@ -28,24 +28,6 @@
#include "PhysicsLogging.h"
static bool flipNormalsMyAvatarVsBackfacingTriangles( btManifoldPoint& cp,
const btCollisionObjectWrapper* colObj0Wrap, int partId0, int index0,
const btCollisionObjectWrapper* colObj1Wrap, int partId1, int index1) {
if (colObj1Wrap->getCollisionShape()->getShapeType() == TRIANGLE_SHAPE_PROXYTYPE) {
auto triShape = static_cast<const btTriangleShape*>(colObj1Wrap->getCollisionShape());
const btVector3* v = triShape->m_vertices1;
btVector3 faceNormal = colObj1Wrap->getWorldTransform().getBasis() * btCross(v[1] - v[0], v[2] - v[0]);
float nDotF = btDot(faceNormal, cp.m_normalWorldOnB);
if (nDotF <= 0.0f) {
faceNormal.normalize();
// flip the contact normal to be aligned with the face normal
cp.m_normalWorldOnB += -2.0f * nDotF * faceNormal;
}
}
// return value is currently ignored but to be future-proof: return false when not modifying friction
return false;
}
PhysicsEngine::PhysicsEngine(const glm::vec3& offset) :
_originOffset(offset),
_myAvatarController(nullptr) {
@ -88,9 +70,6 @@ void PhysicsEngine::init() {
// in order for its broadphase collision queries to work correctly. Look at how we use
// _activeStaticBodies to track and update the Aabb's of moved static objects.
_dynamicsWorld->setForceUpdateAllAabbs(false);
// register contact filter to help MyAvatar pass through backfacing triangles
gContactAddedCallback = flipNormalsMyAvatarVsBackfacingTriangles;
}
}
@ -864,9 +843,8 @@ void PhysicsEngine::setShowBulletConstraintLimits(bool value) {
}
struct AllContactsCallback : public btCollisionWorld::ContactResultCallback {
AllContactsCallback(MotionStateType desiredObjectType, const ShapeInfo& shapeInfo, const Transform& transform, btCollisionObject* myAvatarCollisionObject) :
AllContactsCallback(int32_t mask, int32_t group, const ShapeInfo& shapeInfo, const Transform& transform, btCollisionObject* myAvatarCollisionObject) :
btCollisionWorld::ContactResultCallback(),
desiredObjectType(desiredObjectType),
collisionObject(),
contacts(),
myAvatarCollisionObject(myAvatarCollisionObject) {
@ -879,21 +857,19 @@ struct AllContactsCallback : public btCollisionWorld::ContactResultCallback {
bulletTransform.setRotation(glmToBullet(transform.getRotation()));
collisionObject.setWorldTransform(bulletTransform);
m_collisionFilterMask = mask;
m_collisionFilterGroup = group;
}
~AllContactsCallback() {
ObjectMotionState::getShapeManager()->releaseShape(collisionObject.getCollisionShape());
}
MotionStateType desiredObjectType;
btCollisionObject collisionObject;
std::vector<ContactTestResult> contacts;
btCollisionObject* myAvatarCollisionObject;
bool needsCollision(btBroadphaseProxy* proxy) const override {
return true;
}
btScalar addSingleResult(btManifoldPoint& cp, const btCollisionObjectWrapper* colObj0, int partId0, int index0, const btCollisionObjectWrapper* colObj1, int partId1, int index1) override {
const btCollisionObject* otherBody;
btVector3 penetrationPoint;
@ -909,7 +885,7 @@ struct AllContactsCallback : public btCollisionWorld::ContactResultCallback {
}
// TODO: Give MyAvatar a motion state so we don't have to do this
if (desiredObjectType == MOTIONSTATE_TYPE_AVATAR && myAvatarCollisionObject && myAvatarCollisionObject == otherBody) {
if ((m_collisionFilterMask & BULLET_COLLISION_GROUP_MY_AVATAR) && myAvatarCollisionObject && myAvatarCollisionObject == otherBody) {
contacts.emplace_back(Physics::getSessionUUID(), bulletToGLM(penetrationPoint), bulletToGLM(otherPenetrationPoint));
return 0;
}
@ -921,7 +897,7 @@ struct AllContactsCallback : public btCollisionWorld::ContactResultCallback {
const btMotionState* motionStateCandidate = collisionCandidate->getMotionState();
const ObjectMotionState* candidate = dynamic_cast<const ObjectMotionState*>(motionStateCandidate);
if (!candidate || candidate->getType() != desiredObjectType) {
if (!candidate) {
return 0;
}
@ -937,14 +913,14 @@ protected:
}
};
const std::vector<ContactTestResult> PhysicsEngine::getCollidingInRegion(MotionStateType desiredObjectType, const ShapeInfo& regionShapeInfo, const Transform& regionTransform) const {
std::vector<ContactTestResult> 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 (desiredObjectType == MOTIONSTATE_TYPE_AVATAR && _myAvatarController) {
if ((mask & USER_COLLISION_GROUP_MY_AVATAR) && _myAvatarController) {
myAvatarCollisionObject = _myAvatarController->getCollisionObject();
}
auto contactCallback = AllContactsCallback(desiredObjectType, regionShapeInfo, regionTransform, myAvatarCollisionObject);
auto contactCallback = AllContactsCallback((int32_t)mask, (int32_t)group, regionShapeInfo, regionTransform, myAvatarCollisionObject);
_dynamicsWorld->contactTest(&contactCallback.collisionObject, contactCallback);
return contactCallback.contacts;

View file

@ -125,8 +125,9 @@ public:
void setShowBulletConstraints(bool value);
void setShowBulletConstraintLimits(bool value);
// Function for getting colliding ObjectMotionStates in the world of specified type
const std::vector<ContactTestResult> getCollidingInRegion(MotionStateType desiredObjectType, const ShapeInfo& regionShapeInfo, const Transform& regionTransform) const;
// Function for getting colliding objects in the world of specified type
// See PhysicsCollisionGroups.h for mask flags.
std::vector<ContactTestResult> contactTest(uint16_t mask, const ShapeInfo& regionShapeInfo, const Transform& regionTransform, uint16_t group = USER_COLLISION_GROUP_DYNAMIC) const;
private:
QList<EntityDynamicPointer> removeDynamicsForBody(btRigidBody* body);

View file

@ -0,0 +1,2 @@
VERTEX gpu::vertex::DrawTransformUnitQuad
FRAGMENT BloomApply

View file

@ -0,0 +1,2 @@
VERTEX gpu::vertex::DrawTransformUnitQuad
FRAGMENT BloomThreshold

View file

@ -0,0 +1 @@
VERTEX deferred_light

View file

@ -0,0 +1 @@
VERTEX deferred_light

View file

@ -0,0 +1 @@
VERTEX deferred_light

View file

@ -0,0 +1 @@
VERTEX deferred_light

View file

@ -0,0 +1 @@
VERTEX model

View file

@ -0,0 +1 @@
VERTEX model_normal_map

View file

@ -0,0 +1,2 @@
VERTEX model_normal_map
FRAGMENT forward_model_translucent

View file

@ -0,0 +1 @@
VERTEX model

View file

@ -0,0 +1 @@
VERTEX model

View file

@ -0,0 +1 @@
VERTEX simple

View file

@ -0,0 +1 @@
VERTEX simple

View file

@ -0,0 +1 @@
VERTEX simple

View file

@ -0,0 +1,2 @@
VERTEX skin_model
FRAGMENT forward_model

View file

@ -0,0 +1,2 @@
VERTEX skin_model_dq
FRAGMENT forward_model

View file

@ -0,0 +1,2 @@
VERTEX skin_model_normal_map
FRAGMENT forward_model_normal_map

View file

@ -0,0 +1,2 @@
VERTEX skin_model_normal_map_dq
FRAGMENT forward_model_normal_map

View file

@ -0,0 +1,2 @@
VERTEX skin_model
FRAGMENT forward_model_translucent

View file

@ -0,0 +1,2 @@
VERTEX skin_model_dq
FRAGMENT forward_model_translucent

View file

@ -0,0 +1,2 @@
VERTEX skin_model_normal_map
FRAGMENT forward_model_translucent

View file

@ -0,0 +1,2 @@
VERTEX skin_model_normal_map_dq
FRAGMENT forward_model_translucent

View file

@ -0,0 +1 @@
VERTEX gpu::vertex::DrawUnitQuadTexcoord

View file

@ -0,0 +1 @@
VERTEX standardTransformPNTC

View file

@ -0,0 +1,2 @@
VERTEX gpu::vertex::DrawViewportQuadTransformTexcoord
FRAGMENT Haze

View file

@ -0,0 +1,2 @@
VERTEX gpu::vertex::DrawViewportQuadTransformTexcoord
FRAGMENT Highlight

View file

@ -0,0 +1,2 @@
VERTEX Highlight_aabox
FRAGMENT nop

View file

@ -0,0 +1,2 @@
VERTEX gpu::vertex::DrawViewportQuadTransformTexcoord
FRAGMENT Highlight_filled

View file

@ -0,0 +1 @@
VERTEX gpu::vertex::DrawUnitQuadTexcoord

View file

@ -0,0 +1 @@
VERTEX gpu::vertex::DrawUnitQuadTexcoord

View file

@ -0,0 +1 @@
VERTEX deferred_light

View file

@ -0,0 +1 @@
VERTEX deferred_light

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