Merge branch 'master' into scriptvec3

This commit is contained in:
Sam Gondelman 2018-08-20 09:47:03 -07:00 committed by SamGondelman
commit f0986a7f5a
645 changed files with 17559 additions and 10355 deletions

View file

@ -1,3 +1,10 @@
### OS Specific Build Guides
* [BUILD_WIN.md](BUILD_WIN.md) - complete instructions for Windows.
* [BUILD_OSX.md](BUILD_OSX.md) - additional instructions for OS X.
* [BUILD_LINUX.md](BUILD_LINUX.md) - additional instructions for Linux.
* [BUILD_ANDROID.md](BUILD_ANDROID.md) - additional instructions for Android
### Dependencies
- [cmake](https://cmake.org/download/): 3.9
@ -27,14 +34,7 @@ These are not placed in your normal build tree when doing an out of source build
If you would like to use a specific install of a dependency instead of the version that would be grabbed as a CMake ExternalProject, you can pass -DUSE\_LOCAL\_$NAME=0 (where $NAME is the name of the subfolder in [cmake/externals](cmake/externals)) when you run CMake to tell it not to get that dependency as an external project.
### OS Specific Build Guides
* [BUILD_OSX.md](BUILD_OSX.md) - additional instructions for OS X.
* [BUILD_LINUX.md](BUILD_LINUX.md) - additional instructions for Linux.
* [BUILD_WIN.md](BUILD_WIN.md) - additional instructions for Windows.
* [BUILD_ANDROID.md](BUILD_ANDROID.md) - additional instructions for Android
### CMake
#### CMake
Hifi uses CMake to generate build files and project files for your platform.
@ -80,6 +80,7 @@ In the examples below the variable $NAME would be replaced by the name of the de
* $NAME_ROOT_DIR - set this variable in your ENV
* HIFI_LIB_DIR - set this variable in your ENV to your High Fidelity lib folder, should contain a folder '$name'
### Optional Components
#### Devices

View file

@ -15,6 +15,7 @@ include("cmake/compiler.cmake")
if (BUILD_SCRIBE_ONLY)
add_subdirectory(tools/scribe)
add_subdirectory(tools/shader_reflect)
return()
endif()

View file

@ -24,6 +24,7 @@ android {
'-DANDROID_STL=c++_shared',
'-DQT_CMAKE_PREFIX_PATH=' + HIFI_ANDROID_PRECOMPILED + '/qt/lib/cmake',
'-DNATIVE_SCRIBE=' + HIFI_ANDROID_PRECOMPILED + '/scribe' + EXEC_SUFFIX,
'-DNATIVE_SHREFLECT=' + HIFI_ANDROID_PRECOMPILED + '/shreflect' + EXEC_SUFFIX,
'-DHIFI_ANDROID_PRECOMPILED=' + HIFI_ANDROID_PRECOMPILED,
'-DRELEASE_NUMBER=' + RELEASE_NUMBER,
'-DRELEASE_TYPE=' + RELEASE_TYPE,
@ -144,5 +145,7 @@ dependencies {
compile 'com.squareup.retrofit2:converter-gson:2.4.0'
implementation 'com.squareup.picasso:picasso:2.71828'
compile 'com.sothree.slidinguppanel:library:3.4.0'
implementation fileTree(include: ['*.jar'], dir: 'libs')
}

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()
}
}
@ -164,18 +165,29 @@ def packages = [
def scribeLocalFile='scribe' + EXEC_SUFFIX
def scribeFile='scribe_linux_x86_64'
def scribeChecksum='ca4b904f52f4f993c29175ba96798fa6'
def scribeVersion='u_iTrJDaE95i2abTPXOpPZckGBIim53G'
def shreflectLocalFile='shreflect' + EXEC_SUFFIX
def shreflectFile='shreflect_linux_x86_64'
def shreflectChecksum='d6094a8580066c0b6f4e80b5adfb1d98'
def shreflectVersion='jnrpudh6fptIg6T2.Z6fgKP2ultAdKmE'
if (Os.isFamily(Os.FAMILY_MAC)) {
scribeFile = 'scribe_osx_x86_64'
scribeChecksum='72db9d32d4e1e50add755570ac5eb749'
scribeVersion='DAW0DmnjCRib4MD8x93bgc2Z2MpPojZC'
shreflectFile='shreflect_osx_x86_64'
shreflectChecksum='d613ef0703c21371fee93fd2e54b964f'
shreflectVersion='.rYNzjSFq6WtWDnE5KIKRIAGyJtr__ad'
} else if (Os.isFamily(Os.FAMILY_WINDOWS)) {
scribeFile = 'scribe_win32_x86_64.exe'
scribeChecksum='678e43d290c90fda670c6fefe038a06d'
scribeVersion='PuullrA_bPlO9kXZRt8rLe536X1UI.m7'
shreflectFile='shreflect_win32_x86_64.exe'
shreflectChecksum='6f4a77b8cceb3f1bbc655132c3665060'
shreflectVersion='iIyCyza1nelkbI7ihybF59bBlwrfAC3D'
}
def options = [
@ -450,11 +462,27 @@ task fixScribePermissions(type: Exec, dependsOn: verifyScribe) {
commandLine 'chmod', 'a+x', HIFI_ANDROID_PRECOMPILED + '/' + scribeLocalFile
}
task setupScribe(dependsOn: verifyScribe) { }
task downloadShreflect(type: Download) {
src baseUrl + shreflectFile + '?versionId=' + shreflectVersion
dest new File(baseFolder, shreflectLocalFile)
onlyIfNewer true
}
task verifyShreflect(type: Verify, dependsOn: downloadShreflect) {
src new File(baseFolder, shreflectLocalFile);
checksum shreflectChecksum
}
task fixShreflectPermissions(type: Exec, dependsOn: verifyShreflect) {
commandLine 'chmod', 'a+x', HIFI_ANDROID_PRECOMPILED + '/' + shreflectLocalFile
}
task setupScribe(dependsOn: [verifyScribe, verifyShreflect]) { }
// On Windows, we don't need to set the executable bit, but on OSX and Unix we do
if (!Os.isFamily(Os.FAMILY_WINDOWS)) {
setupScribe.dependsOn fixScribePermissions
setupScribe.dependsOn fixShreflectPermissions
}
task extractGvrBinaries(dependsOn: extractDependencies) {

View file

@ -82,8 +82,6 @@ Agent::Agent(ReceivedMessage& message) :
DependencyManager::set<recording::ClipCache>();
DependencyManager::set<ScriptCache>();
DependencyManager::set<ScriptEngines>(ScriptEngine::AGENT_SCRIPT);
DependencyManager::set<RecordingScriptingInterface>();
DependencyManager::set<UsersScriptingInterface>();
@ -162,6 +160,8 @@ void Agent::handleAudioPacket(QSharedPointer<ReceivedMessage> message) {
static const QString AGENT_LOGGING_NAME = "agent";
void Agent::run() {
// Create ScriptEngines on threaded-assignment thread then move to main thread.
DependencyManager::set<ScriptEngines>(ScriptEngine::AGENT_SCRIPT)->moveToThread(qApp->thread());
// make sure we request our script once the agent connects to the domain
auto nodeList = DependencyManager::get<NodeList>();
@ -368,7 +368,6 @@ void Agent::executeScript() {
// give scripts access to the Users object
_scriptEngine->registerGlobalObject("Users", DependencyManager::get<UsersScriptingInterface>().data());
auto player = DependencyManager::get<recording::Deck>();
connect(player.data(), &recording::Deck::playbackStateChanged, [=] {
if (player->isPlaying()) {
@ -498,7 +497,6 @@ void Agent::executeScript() {
Frame::clearFrameHandler(AVATAR_FRAME_TYPE);
DependencyManager::destroy<RecordingScriptingInterface>();
setFinished(true);
}
@ -517,7 +515,7 @@ void Agent::setIsListeningToAudioStream(bool isListeningToAudioStream) {
auto nodeList = DependencyManager::get<NodeList>();
nodeList->eachMatchingNode(
[&](const SharedNodePointer& node)->bool {
[](const SharedNodePointer& node)->bool {
return (node->getType() == NodeType::AudioMixer) && node->getActiveSocket();
},
[&](const SharedNodePointer& node) {
@ -848,7 +846,7 @@ void Agent::aboutToFinish() {
DependencyManager::destroy<recording::Deck>();
DependencyManager::destroy<recording::Recorder>();
DependencyManager::destroy<recording::ClipCache>();
DependencyManager::destroy<ScriptEngine>();
QMetaObject::invokeMethod(&_avatarAudioTimer, "stop");
// cleanup codec & encoder

View file

@ -11,6 +11,8 @@
#include "AssignmentClientApp.h"
#include <iostream>
#include <QtCore/QCommandLineParser>
#include <QtCore/QDir>
#include <QtCore/QStandardPaths>
@ -42,9 +44,8 @@ AssignmentClientApp::AssignmentClientApp(int argc, char* argv[]) :
// parse command-line
QCommandLineParser parser;
parser.setApplicationDescription("High Fidelity Assignment Client");
parser.addHelpOption();
const QCommandLineOption helpOption = parser.addHelpOption();
const QCommandLineOption versionOption = parser.addVersionOption();
QString typeDescription = "run single assignment client of given type\n# | Type\n============================";
for (Assignment::Type type = Assignment::FirstType;
@ -97,11 +98,16 @@ AssignmentClientApp::AssignmentClientApp(int argc, char* argv[]) :
parser.addOption(parentPIDOption);
if (!parser.parse(QCoreApplication::arguments())) {
qCritical() << parser.errorText() << endl;
std::cout << parser.errorText().toStdString() << std::endl; // Avoid Qt log spam
parser.showHelp();
Q_UNREACHABLE();
}
if (parser.isSet(versionOption)) {
parser.showVersion();
Q_UNREACHABLE();
}
if (parser.isSet(helpOption)) {
parser.showHelp();
Q_UNREACHABLE();

View file

@ -945,22 +945,14 @@ void AssetServer::sendStatsPacket() {
upstreamStats["2. Sent Packets"] = stat.second.sentPackets;
upstreamStats["3. Recvd ACK"] = events[Events::ReceivedACK];
upstreamStats["4. Procd ACK"] = events[Events::ProcessedACK];
upstreamStats["5. Recvd LACK"] = events[Events::ReceivedLightACK];
upstreamStats["6. Recvd NAK"] = events[Events::ReceivedNAK];
upstreamStats["7. Recvd TNAK"] = events[Events::ReceivedTimeoutNAK];
upstreamStats["8. Sent ACK2"] = events[Events::SentACK2];
upstreamStats["9. Retransmitted"] = events[Events::Retransmission];
upstreamStats["5. Retransmitted"] = events[Events::Retransmission];
nodeStats["Upstream Stats"] = upstreamStats;
QJsonObject downstreamStats;
downstreamStats["1. Recvd (P/s)"] = stat.second.receiveRate;
downstreamStats["2. Recvd Packets"] = stat.second.receivedPackets;
downstreamStats["3. Sent ACK"] = events[Events::SentACK];
downstreamStats["4. Sent LACK"] = events[Events::SentLightACK];
downstreamStats["5. Sent NAK"] = events[Events::SentNAK];
downstreamStats["6. Sent TNAK"] = events[Events::SentTimeoutNAK];
downstreamStats["7. Recvd ACK2"] = events[Events::ReceivedACK2];
downstreamStats["8. Duplicates"] = events[Events::Duplicate];
downstreamStats["4. Duplicates"] = events[Events::Duplicate];
nodeStats["Downstream Stats"] = downstreamStats;
QString uuid;

View file

@ -447,18 +447,21 @@ void AvatarMixer::handleAvatarKilled(SharedNodePointer avatarNode) {
// send a kill packet for it to our other nodes
nodeList->eachMatchingNode([&](const SharedNodePointer& node) {
// we relay avatar kill packets to agents that are not upstream
// and downstream avatar mixers, if the node that was just killed was being replicated
return (node->getType() == NodeType::Agent && !node->isUpstream()) ||
(avatarNode->isReplicated() && shouldReplicateTo(*avatarNode, *node));
// and downstream avatar mixers, if the node that was just killed was being replicatedConnectedAgent
return node->getActiveSocket() &&
((node->getType() == NodeType::Agent && !node->isUpstream()) ||
(avatarNode->isReplicated() && shouldReplicateTo(*avatarNode, *node)));
}, [&](const SharedNodePointer& node) {
if (node->getType() == NodeType::Agent) {
if (!killPacket) {
killPacket = NLPacket::create(PacketType::KillAvatar, NUM_BYTES_RFC4122_UUID + sizeof(KillAvatarReason));
killPacket = NLPacket::create(PacketType::KillAvatar, NUM_BYTES_RFC4122_UUID + sizeof(KillAvatarReason), true);
killPacket->write(avatarNode->getUUID().toRfc4122());
killPacket->writePrimitive(KillAvatarReason::AvatarDisconnected);
}
nodeList->sendUnreliablePacket(*killPacket, *node);
auto killPacketCopy = NLPacket::createCopy(*killPacket);
nodeList->sendPacket(std::move(killPacketCopy), *node);
} else {
// send a replicated kill packet to the downstream avatar mixer
if (!replicatedKillPacket) {
@ -643,6 +646,7 @@ void AvatarMixer::handleNodeIgnoreRequestPacket(QSharedPointer<ReceivedMessage>
auto start = usecTimestampNow();
auto nodeList = DependencyManager::get<NodeList>();
AvatarMixerClientData* nodeData = reinterpret_cast<AvatarMixerClientData*>(senderNode->getLinkedData());
bool addToIgnore;
message->readPrimitive(&addToIgnore);
while (message->getBytesLeftToRead()) {
@ -650,17 +654,22 @@ void AvatarMixer::handleNodeIgnoreRequestPacket(QSharedPointer<ReceivedMessage>
QUuid ignoredUUID = QUuid::fromRfc4122(message->readWithoutCopy(NUM_BYTES_RFC4122_UUID));
if (nodeList->nodeWithUUID(ignoredUUID)) {
// Reset the lastBroadcastTime for the ignored avatar to 0
// so the AvatarMixer knows it'll have to send identity data about the ignored avatar
// to the ignorer if the ignorer unignores.
nodeData->setLastBroadcastTime(ignoredUUID, 0);
if (nodeData) {
// Reset the lastBroadcastTime for the ignored avatar to 0
// so the AvatarMixer knows it'll have to send identity data about the ignored avatar
// to the ignorer if the ignorer unignores.
nodeData->setLastBroadcastTime(ignoredUUID, 0);
}
// Reset the lastBroadcastTime for the ignorer (FROM THE PERSPECTIVE OF THE IGNORED) to 0
// so the AvatarMixer knows it'll have to send identity data about the ignorer
// to the ignored if the ignorer unignores.
auto ignoredNode = nodeList->nodeWithUUID(ignoredUUID);
AvatarMixerClientData* ignoredNodeData = reinterpret_cast<AvatarMixerClientData*>(ignoredNode->getLinkedData());
ignoredNodeData->setLastBroadcastTime(senderNode->getUUID(), 0);
if (ignoredNodeData) {
ignoredNodeData->setLastBroadcastTime(senderNode->getUUID(), 0);
}
}
if (addToIgnore) {

View file

@ -108,7 +108,7 @@ uint16_t AvatarMixerClientData::getLastBroadcastSequenceNumber(const QUuid& node
void AvatarMixerClientData::ignoreOther(SharedNodePointer self, SharedNodePointer other) {
if (!isRadiusIgnoring(other->getUUID())) {
addToRadiusIgnoringSet(other->getUUID());
auto killPacket = NLPacket::create(PacketType::KillAvatar, NUM_BYTES_RFC4122_UUID + sizeof(KillAvatarReason));
auto killPacket = NLPacket::create(PacketType::KillAvatar, NUM_BYTES_RFC4122_UUID + sizeof(KillAvatarReason), true);
killPacket->write(other->getUUID().toRfc4122());
if (self->isIgnoreRadiusEnabled()) {
killPacket->writePrimitive(KillAvatarReason::TheirAvatarEnteredYourBubble);
@ -116,7 +116,7 @@ void AvatarMixerClientData::ignoreOther(SharedNodePointer self, SharedNodePointe
killPacket->writePrimitive(KillAvatarReason::YourAvatarEnteredTheirBubble);
}
setLastBroadcastTime(other->getUUID(), 0);
DependencyManager::get<NodeList>()->sendUnreliablePacket(*killPacket, *self);
DependencyManager::get<NodeList>()->sendPacket(std::move(killPacket), *self);
}
}

View file

@ -1,10 +1,12 @@
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DDEBUG")
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
if (NOT "${CMAKE_SIZEOF_VOID_P}" EQUAL "8")
message( FATAL_ERROR "Only 64 bit builds supported." )
endif()
if (WIN32)
if (NOT "${CMAKE_SIZEOF_VOID_P}" EQUAL "8")
message( FATAL_ERROR "Only 64 bit builds supported." )
endif()
add_definitions(-DNOMINMAX -D_CRT_SECURE_NO_WARNINGS)
if (NOT WINDOW_SDK_PATH)
@ -52,32 +54,27 @@ endif()
if (ANDROID)
# assume that the toolchain selected for android has C++11 support
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14")
elseif(APPLE)
set(CMAKE_XCODE_ATTRIBUTE_CLANG_CXX_LANGUAGE_STANDARD "c++14")
set(CMAKE_XCODE_ATTRIBUTE_CLANG_CXX_LIBRARY "libc++")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --stdlib=libc++")
if (CMAKE_GENERATOR STREQUAL "Xcode")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g")
set(CMAKE_XCODE_ATTRIBUTE_GCC_GENERATE_DEBUGGING_SYMBOLS[variant=Release] "YES")
set(CMAKE_XCODE_ATTRIBUTE_DEBUG_INFORMATION_FORMAT[variant=Release] "dwarf-with-dsym")
set(CMAKE_XCODE_ATTRIBUTE_DEPLOYMENT_POSTPROCESSING[variant=Release] "YES")
endif()
elseif ((NOT MSVC12) AND (NOT MSVC14))
include(CheckCXXCompilerFlag)
CHECK_CXX_COMPILER_FLAG("-std=c++11" COMPILER_SUPPORTS_CXX11)
CHECK_CXX_COMPILER_FLAG("-std=c++0x" COMPILER_SUPPORTS_CXX0X)
if (COMPILER_SUPPORTS_CXX11)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
elseif(COMPILER_SUPPORTS_CXX0X)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x")
CHECK_CXX_COMPILER_FLAG("-std=c++14" COMPILER_SUPPORTS_CXX14)
if (COMPILER_SUPPORTS_CXX14)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14")
else()
message(FATAL_ERROR "The compiler ${CMAKE_CXX_COMPILER} has no C++11 support. Please use a different C++ compiler.")
endif()
endif ()
if (CMAKE_GENERATOR STREQUAL "Xcode")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g")
set(CMAKE_XCODE_ATTRIBUTE_GCC_GENERATE_DEBUGGING_SYMBOLS[variant=Release] "YES")
set(CMAKE_XCODE_ATTRIBUTE_DEBUG_INFORMATION_FORMAT[variant=Release] "dwarf-with-dsym")
set(CMAKE_XCODE_ATTRIBUTE_DEPLOYMENT_POSTPROCESSING[variant=Release] "YES")
endif()
if (APPLE)
set(CMAKE_XCODE_ATTRIBUTE_CLANG_CXX_LANGUAGE_STANDARD "c++11")
set(CMAKE_XCODE_ATTRIBUTE_CLANG_CXX_LIBRARY "libc++")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --stdlib=libc++")
endif ()
if (NOT ANDROID_LIB_DIR)
set(ANDROID_LIB_DIR $ENV{ANDROID_LIB_DIR})
@ -110,4 +107,4 @@ if (APPLE)
# set that as the SDK to use
set(CMAKE_OSX_SYSROOT ${_OSX_DESIRED_SDK_PATH}/MacOSX${OSX_SDK}.sdk)
endif ()
endif ()
endif ()

22
cmake/externals/json/CMakeLists.txt vendored Normal file
View file

@ -0,0 +1,22 @@
set(EXTERNAL_NAME json)
include(ExternalProject)
ExternalProject_Add(
${EXTERNAL_NAME}
URL https://hifi-public.s3.amazonaws.com/dependencies/json_3.1.2.zip
URL_MD5 94dbf6ea25a7569ddc0ab6e20862cf16
BINARY_DIR ${EXTERNAL_PROJECT_PREFIX}/build
CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR> ${EXTERNAL_ARGS}
CONFIGURE_COMMAND ""
BUILD_COMMAND ""
INSTALL_COMMAND ""
LOG_DOWNLOAD 1
)
# Hide this external target (for ide users)
set_target_properties(${EXTERNAL_NAME} PROPERTIES FOLDER "hidden/externals")
ExternalProject_Get_Property(${EXTERNAL_NAME} SOURCE_DIR)
string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER)
set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${SOURCE_DIR} CACHE PATH "List of json include directories")

View file

@ -8,6 +8,12 @@
# 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})
@ -45,11 +51,8 @@ function(AUTOSCRIBE_SHADER SHADER_FILE)
elseif(${SHADER_EXT} STREQUAL .slg)
set(SHADER_TYPE geom)
endif()
set(SHADER_TARGET ${SHADER_TARGET}_${SHADER_TYPE})
set(SHADER_TARGET "${SHADERS_DIR}/${SHADER_TARGET}")
set(SHADER_TARGET_HEADER ${SHADER_TARGET}.h)
set(SHADER_TARGET_SOURCE ${SHADER_TARGET}.cpp)
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
@ -63,65 +66,232 @@ function(AUTOSCRIBE_SHADER SHADER_FILE)
else ()
set(GLPROFILE PC_GL)
endif()
set(SCRIBE_ARGS -c++ -T ${SHADER_TYPE} -D GLPROFILE ${GLPROFILE} ${SCRIBE_INCLUDES} -o ${SHADER_TARGET} ${SHADER_FILE})
set(SCRIBE_ARGS -T ${SHADER_TYPE} -D GLPROFILE ${GLPROFILE} ${SCRIBE_INCLUDES} -o ${SHADER_TARGET} ${SHADER_FILE})
add_custom_command(
OUTPUT ${SHADER_TARGET_HEADER} ${SHADER_TARGET_SOURCE}
OUTPUT ${SHADER_TARGET}
COMMAND ${SCRIBE_COMMAND} ${SCRIBE_ARGS}
DEPENDS ${SCRIBE_COMMAND} ${SHADER_INCLUDE_FILES} ${SHADER_FILE}
DEPENDS ${SHADER_FILE} ${SCRIBE_COMMAND} ${SHADER_INCLUDE_FILES}
)
#output the generated file name
set(AUTOSCRIBE_SHADER_RETURN ${SHADER_TARGET_HEADER} ${SHADER_TARGET_SOURCE} PARENT_SCOPE)
file(GLOB INCLUDE_FILES ${SHADER_TARGET_HEADER})
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)
set(HIFI_LIBRARIES_SHADER_INCLUDE_FILES "")
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})
#if (NOT TARGET ${HIFI_LIBRARY})
# file(GLOB_RECURSE HIFI_LIBRARIES_SHADER_INCLUDE_FILES ${RELATIVE_LIBRARY_DIR_PATH}/${HIFI_LIBRARY}/src/)
#endif ()
#file(GLOB_RECURSE HIFI_LIBRARIES_SHADER_INCLUDE_FILES ${HIFI_LIBRARY_DIR}/${HIFI_LIBRARY}/src/*.slh)
list(APPEND HIFI_LIBRARIES_SHADER_INCLUDE_FILES ${HIFI_LIBRARY_DIR}/${HIFI_LIBRARY}/src)
endforeach()
#message("${TARGET_NAME} ${HIFI_LIBRARIES_SHADER_INCLUDE_FILES}")
endmacro()
file(GLOB_RECURSE SHADER_INCLUDE_FILES src/*.slh)
file(GLOB_RECURSE SHADER_SOURCE_FILES src/*.slv src/*.slf src/*.slg)
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()
#make the shader folder
set(SHADERS_DIR "${CMAKE_CURRENT_BINARY_DIR}/shaders/${TARGET_NAME}")
file(MAKE_DIRECTORY ${SHADERS_DIR})
if (NOT (${AUTOSCRIBE_PROGRAM_VERTEX} MATCHES ".*::.*"))
set(AUTOSCRIBE_PROGRAM_VERTEX "vertex::${AUTOSCRIBE_PROGRAM_VERTEX}")
endif()
if (NOT (${AUTOSCRIBE_PROGRAM_FRAGMENT} MATCHES ".*::.*"))
set(AUTOSCRIBE_PROGRAM_FRAGMENT "fragment::${AUTOSCRIBE_PROGRAM_FRAGMENT}")
endif()
#message("${TARGET_NAME} ${SHADER_INCLUDE_FILES}")
set(AUTOSCRIBE_SHADER_SRC "")
foreach(SHADER_FILE ${SHADER_SOURCE_FILES})
AUTOSCRIBE_SHADER(${SHADER_FILE} ${SHADER_INCLUDE_FILES})
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})
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)
list(APPEND AUTOSCRIBE_SHADER_SRC ${AUTOSCRIBE_GENERATED_FILE})
endforeach()
#message(${TARGET_NAME} ${AUTOSCRIBE_SHADER_SRC})
if (WIN32)
source_group("Shaders" FILES ${SHADER_INCLUDE_FILES})
source_group("Shaders" FILES ${SHADER_SOURCE_FILES})
source_group("Shaders\\generated" FILES ${AUTOSCRIBE_SHADER_SRC})
endif()
list(APPEND AUTOSCRIBE_SHADER_LIB_SRC ${SHADER_INCLUDE_FILES})
list(APPEND AUTOSCRIBE_SHADER_LIB_SRC ${SHADER_SOURCE_FILES})
list(APPEND AUTOSCRIBE_SHADER_LIB_SRC ${AUTOSCRIBE_SHADER_SRC})
# Link library shaders, if they exist
include_directories("${SHADERS_DIR}")
# Add search directory to find gpu/Shader.h
include_directories("${HIFI_LIBRARY_DIR}/gpu/src")
source_group("Compiled/${SHADER_LIB}" FILES ${AUTOSCRIBE_GENERATED_FILE})
set(REFLECTED_SHADER "${AUTOSCRIBE_GENERATED_FILE}.json")
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")
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)
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")
endforeach()
set(SHADER_PROGRAMS_ARRAY "")
foreach(SHADER_PROGRAM ${SHADER_ALL_PROGRAMS})
string(CONCAT SHADER_PROGRAMS_ARRAY "${SHADER_PROGRAMS_ARRAY}" " ${SHADER_PROGRAM},\n")
endforeach()
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()
# 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})
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

@ -19,12 +19,8 @@ function(LINK_HIFI_LIBRARIES)
endforeach()
foreach(HIFI_LIBRARY ${LIBRARIES_TO_LINK})
include_directories("${HIFI_LIBRARY_DIR}/${HIFI_LIBRARY}/src")
include_directories("${CMAKE_BINARY_DIR}/libraries/${HIFI_LIBRARY}/shaders")
#add_dependencies(${TARGET_NAME} ${HIFI_LIBRARY})
include_directories("${CMAKE_BINARY_DIR}/libraries/${HIFI_LIBRARY}")
# link the actual library - it is static so don't bubble it up
target_link_libraries(${TARGET_NAME} ${HIFI_LIBRARY})
endforeach()

View file

@ -0,0 +1,13 @@
#
# Created by Bradley Austin Davis on 2018/07/22
# Copyright 2013-2018 High Fidelity, Inc.
#
# Distributed under the Apache License, Version 2.0.
# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
#
macro(TARGET_JSON)
add_dependency_external_projects(json)
find_package(JSON REQUIRED)
message("JSON_INCLUDE_DIRS ${JSON_INCLUDE_DIRS}")
target_include_directories(${TARGET_NAME} PUBLIC ${JSON_INCLUDE_DIRS})
endmacro()

View file

@ -0,0 +1,19 @@
#
# Created by Bradley Austin Davis on 2018/07/22
# Copyright 2013-2018 High Fidelity, Inc.
#
# Distributed under the Apache License, Version 2.0.
# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
#
# setup hints for JSON search
include("${MACRO_DIR}/HifiLibrarySearchHints.cmake")
hifi_library_search_hints("json")
# locate header
find_path(JSON_INCLUDE_DIRS "json/json.hpp" HINTS ${JSON_SEARCH_DIRS})
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(JSON DEFAULT_MSG JSON_INCLUDE_DIRS)
mark_as_advanced(JSON_INCLUDE_DIRS JSON_SEARCH_DIRS)

View file

@ -46,6 +46,14 @@
"default": "40102",
"type": "int",
"advanced": true
},
{
"name": "enable_packet_verification",
"label": "Enable Packet Verification",
"help": "Enable secure checksums on communication that uses the High Fidelity protocol. Increases security with possibly a small performance penalty.",
"default": true,
"type": "checkbox",
"advanced": true
}
]
},

View file

@ -264,7 +264,8 @@ void DomainGatekeeper::updateNodePermissions() {
QList<SharedNodePointer> nodesToKill;
auto limitedNodeList = DependencyManager::get<LimitedNodeList>();
limitedNodeList->eachNode([this, limitedNodeList, &nodesToKill](const SharedNodePointer& node){
QWeakPointer<LimitedNodeList> limitedNodeListWeak = limitedNodeList;
limitedNodeList->eachNode([this, limitedNodeListWeak, &nodesToKill](const SharedNodePointer& node){
// the id and the username in NodePermissions will often be the same, but id is set before
// authentication and verifiedUsername is only set once they user's key has been confirmed.
QString verifiedUsername = node->getPermissions().getVerifiedUserName();
@ -296,7 +297,8 @@ void DomainGatekeeper::updateNodePermissions() {
machineFingerprint = nodeData->getMachineFingerprint();
auto sendingAddress = nodeData->getSendingSockAddr().getAddress();
isLocalUser = (sendingAddress == limitedNodeList->getLocalSockAddr().getAddress() ||
auto nodeList = limitedNodeListWeak.lock();
isLocalUser = ((nodeList && sendingAddress == nodeList->getLocalSockAddr().getAddress()) ||
sendingAddress == QHostAddress::LocalHost);
}
@ -458,7 +460,7 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect
// in case this is a node that's failing to connect
// double check we don't have the same node whose sockets match exactly already in the list
limitedNodeList->eachNodeBreakable([&](const SharedNodePointer& node){
limitedNodeList->eachNodeBreakable([nodeConnection, username, &existingNodeID](const SharedNodePointer& node){
if (node->getPublicSocket() == nodeConnection.publicSockAddr && node->getLocalSocket() == nodeConnection.localSockAddr) {
// we have a node that already has these exact sockets - this can occur if a node
@ -1009,7 +1011,7 @@ void DomainGatekeeper::refreshGroupsCache() {
getDomainOwnerFriendsList();
auto nodeList = DependencyManager::get<LimitedNodeList>();
nodeList->eachNode([&](const SharedNodePointer& node) {
nodeList->eachNode([this](const SharedNodePointer& node) {
if (!node->getPermissions().isAssignment) {
// this node is an agent
const QString& verifiedUserName = node->getPermissions().getVerifiedUserName();

View file

@ -13,6 +13,7 @@
#include <memory>
#include <random>
#include <iostream>
#include <QDir>
#include <QJsonDocument>
@ -69,6 +70,14 @@ const QString ICE_SERVER_DEFAULT_HOSTNAME = "ice.highfidelity.com";
const QString ICE_SERVER_DEFAULT_HOSTNAME = "dev-ice.highfidelity.com";
#endif
QString DomainServer::_iceServerAddr { ICE_SERVER_DEFAULT_HOSTNAME };
int DomainServer::_iceServerPort { ICE_SERVER_DEFAULT_PORT };
bool DomainServer::_overrideDomainID { false };
QUuid DomainServer::_overridingDomainID;
bool DomainServer::_getTempName { false };
QString DomainServer::_userConfigFilename;
int DomainServer::_parentPID { -1 };
bool DomainServer::forwardMetaverseAPIRequest(HTTPConnection* connection,
const QString& metaversePath,
const QString& requestSubobjectKey,
@ -148,24 +157,13 @@ bool DomainServer::forwardMetaverseAPIRequest(HTTPConnection* connection,
DomainServer::DomainServer(int argc, char* argv[]) :
QCoreApplication(argc, argv),
_gatekeeper(this),
_httpManager(QHostAddress::AnyIPv4, DOMAIN_SERVER_HTTP_PORT, QString("%1/resources/web/").arg(QCoreApplication::applicationDirPath()), this),
_allAssignments(),
_unfulfilledAssignments(),
_isUsingDTLS(false),
_oauthProviderURL(),
_oauthClientID(),
_hostname(),
_ephemeralACScripts(),
_webAuthenticationStateSet(),
_cookieSessionHash(),
_automaticNetworkingSetting(),
_settingsManager(),
_iceServerAddr(ICE_SERVER_DEFAULT_HOSTNAME),
_iceServerPort(ICE_SERVER_DEFAULT_PORT)
_httpManager(QHostAddress::AnyIPv4, DOMAIN_SERVER_HTTP_PORT, QString("%1/resources/web/").arg(QCoreApplication::applicationDirPath()), this)
{
PathUtils::removeTemporaryApplicationDirs();
if (_parentPID != -1) {
watchParentProcess(_parentPID);
}
parseCommandLine();
PathUtils::removeTemporaryApplicationDirs();
DependencyManager::set<tracing::Tracer>();
DependencyManager::set<StatTracker>();
@ -185,9 +183,16 @@ DomainServer::DomainServer(int argc, char* argv[]) :
// (need this since domain-server can restart itself and maintain static variables)
DependencyManager::set<AccountManager>();
auto args = arguments();
_settingsManager.setupConfigMap(args);
// load the user config
QString userConfigFilename;
if (!_userConfigFilename.isEmpty()) {
userConfigFilename = _userConfigFilename;
} else {
// we weren't passed a user config path
static const QString USER_CONFIG_FILE_NAME = "config.json";
userConfigFilename = PathUtils::getAppDataFilePath(USER_CONFIG_FILE_NAME);
}
_settingsManager.setupConfigMap(userConfigFilename);
// setup a shutdown event listener to handle SIGTERM or WM_CLOSE for us
#ifdef _WIN32
@ -246,8 +251,7 @@ DomainServer::DomainServer(int argc, char* argv[]) :
}
// check for the temporary name parameter
const QString GET_TEMPORARY_NAME_SWITCH = "--get-temp-name";
if (args.contains(GET_TEMPORARY_NAME_SWITCH)) {
if (_getTempName) {
getTemporaryName();
}
@ -316,28 +320,45 @@ DomainServer::DomainServer(int argc, char* argv[]) :
connect(_contentManager.get(), &DomainContentBackupManager::recoveryCompleted, this, &DomainServer::restart);
}
void DomainServer::parseCommandLine() {
void DomainServer::parseCommandLine(int argc, char* argv[]) {
QCommandLineParser parser;
parser.setApplicationDescription("High Fidelity Domain Server");
parser.addHelpOption();
const QCommandLineOption versionOption = parser.addVersionOption();
const QCommandLineOption helpOption = parser.addHelpOption();
const QCommandLineOption iceServerAddressOption("i", "ice-server address", "IP:PORT or HOSTNAME:PORT");
parser.addOption(iceServerAddressOption);
const QCommandLineOption domainIDOption("d", "domain-server uuid");
const QCommandLineOption domainIDOption("d", "domain-server uuid", "uuid");
parser.addOption(domainIDOption);
const QCommandLineOption getTempNameOption("get-temp-name", "Request a temporary domain-name");
parser.addOption(getTempNameOption);
const QCommandLineOption masterConfigOption("master-config", "Deprecated config-file option");
parser.addOption(masterConfigOption);
const QCommandLineOption userConfigOption("user-config", "Pass user config file pass", "path");
parser.addOption(userConfigOption);
const QCommandLineOption parentPIDOption(PARENT_PID_OPTION, "PID of the parent process", "parent-pid");
parser.addOption(parentPIDOption);
if (!parser.parse(QCoreApplication::arguments())) {
qWarning() << parser.errorText() << endl;
QStringList arguments;
for (int i = 0; i < argc; ++i) {
arguments << argv[i];
}
if (!parser.parse(arguments)) {
std::cout << parser.errorText().toStdString() << std::endl; // Avoid Qt log spam
QCoreApplication mockApp(argc, argv); // required for call to showHelp()
parser.showHelp();
Q_UNREACHABLE();
}
if (parser.isSet(versionOption)) {
parser.showVersion();
Q_UNREACHABLE();
}
if (parser.isSet(helpOption)) {
QCoreApplication mockApp(argc, argv); // required for call to showHelp()
parser.showHelp();
Q_UNREACHABLE();
}
@ -354,7 +375,7 @@ void DomainServer::parseCommandLine() {
if (_iceServerAddr.isEmpty()) {
qWarning() << "Could not parse an IP address and port combination from" << hostnamePortString;
QMetaObject::invokeMethod(this, "quit", Qt::QueuedConnection);
::exit(0);
}
}
@ -364,14 +385,21 @@ void DomainServer::parseCommandLine() {
qDebug() << "domain-server ID is" << _overridingDomainID;
}
if (parser.isSet(getTempNameOption)) {
_getTempName = true;
}
if (parser.isSet(userConfigOption)) {
_userConfigFilename = parser.value(userConfigOption);
}
if (parser.isSet(parentPIDOption)) {
bool ok = false;
int parentPID = parser.value(parentPIDOption).toInt(&ok);
if (ok) {
qDebug() << "Parent process PID is" << parentPID;
watchParentProcess(parentPID);
_parentPID = parentPID;
qDebug() << "Parent process PID is" << _parentPID;
}
}
}
@ -630,6 +658,7 @@ bool DomainServer::isPacketVerified(const udt::Packet& packet) {
void DomainServer::setupNodeListAndAssignments() {
const QString CUSTOM_LOCAL_PORT_OPTION = "metaverse.local_port";
static const QString ENABLE_PACKET_AUTHENTICATION = "metaverse.enable_packet_verification";
QVariant localPortValue = _settingsManager.valueOrDefaultValueForKeyPath(CUSTOM_LOCAL_PORT_OPTION);
int domainServerPort = localPortValue.toInt();
@ -696,6 +725,9 @@ void DomainServer::setupNodeListAndAssignments() {
}
}
bool isAuthEnabled = _settingsManager.valueOrDefaultValueForKeyPath(ENABLE_PACKET_AUTHENTICATION).toBool();
nodeList->setAuthenticatePackets(isAuthEnabled);
connect(nodeList.data(), &LimitedNodeList::nodeAdded, this, &DomainServer::nodeAdded);
connect(nodeList.data(), &LimitedNodeList::nodeKilled, this, &DomainServer::nodeKilled);
@ -1046,7 +1078,7 @@ bool DomainServer::isInInterestSet(const SharedNodePointer& nodeA, const SharedN
unsigned int DomainServer::countConnectedUsers() {
unsigned int result = 0;
auto nodeList = DependencyManager::get<LimitedNodeList>();
nodeList->eachNode([&](const SharedNodePointer& node){
nodeList->eachNode([&result](const SharedNodePointer& node){
// only count unassigned agents (i.e., users)
if (node->getType() == NodeType::Agent) {
auto nodeData = static_cast<DomainServerNodeData*>(node->getLinkedData());
@ -1133,7 +1165,7 @@ void DomainServer::sendDomainListToNode(const SharedNodePointer& node, const Hif
extendedHeaderStream << node->getUUID();
extendedHeaderStream << node->getLocalID();
extendedHeaderStream << node->getPermissions();
extendedHeaderStream << limitedNodeList->getAuthenticatePackets();
auto domainListPackets = NLPacketList::create(PacketType::DomainList, extendedHeader);
// always send the node their own UUID back
@ -1149,7 +1181,7 @@ void DomainServer::sendDomainListToNode(const SharedNodePointer& node, const Hif
// DTLSServerSession* dtlsSession = _isUsingDTLS ? _dtlsSessions[senderSockAddr] : NULL;
if (nodeData->isAuthenticated()) {
// if this authenticated node has any interest types, send back those nodes as well
limitedNodeList->eachNode([&](const SharedNodePointer& otherNode) {
limitedNodeList->eachNode([this, node, &domainListPackets, &domainListStream](const SharedNodePointer& otherNode) {
if (otherNode->getUUID() != node->getUUID() && isInInterestSet(node, otherNode)) {
// since we're about to add a node to the packet we start a segment
domainListPackets->startSegment();
@ -1198,6 +1230,7 @@ QUuid DomainServer::connectionSecretForNodes(const SharedNodePointer& nodeA, con
void DomainServer::broadcastNewNode(const SharedNodePointer& addedNode) {
auto limitedNodeList = DependencyManager::get<LimitedNodeList>();
QWeakPointer<LimitedNodeList> limitedNodeListWeak = limitedNodeList;
auto addNodePacket = NLPacket::create(PacketType::DomainServerAddedNode);
@ -1209,7 +1242,7 @@ void DomainServer::broadcastNewNode(const SharedNodePointer& addedNode) {
int connectionSecretIndex = addNodePacket->pos();
limitedNodeList->eachMatchingNode(
[&](const SharedNodePointer& node)->bool {
[this, addedNode](const SharedNodePointer& node)->bool {
if (node->getLinkedData() && node->getActiveSocket() && node != addedNode) {
// is the added Node in this node's interest list?
return isInInterestSet(node, addedNode);
@ -1217,16 +1250,19 @@ void DomainServer::broadcastNewNode(const SharedNodePointer& addedNode) {
return false;
}
},
[&](const SharedNodePointer& node) {
addNodePacket->seek(connectionSecretIndex);
QByteArray rfcConnectionSecret = connectionSecretForNodes(node, addedNode).toRfc4122();
// replace the bytes at the end of the packet for the connection secret between these nodes
addNodePacket->write(rfcConnectionSecret);
[this, &addNodePacket, connectionSecretIndex, addedNode, limitedNodeListWeak](const SharedNodePointer& node) {
// send off this packet to the node
limitedNodeList->sendUnreliablePacket(*addNodePacket, *node);
auto limitedNodeList = limitedNodeListWeak.lock();
if (limitedNodeList) {
addNodePacket->seek(connectionSecretIndex);
QByteArray rfcConnectionSecret = connectionSecretForNodes(node, addedNode).toRfc4122();
// replace the bytes at the end of the packet for the connection secret between these nodes
addNodePacket->write(rfcConnectionSecret);
limitedNodeList->sendUnreliablePacket(*addNodePacket, *node);
}
}
);
}
@ -2832,7 +2868,7 @@ void DomainServer::updateReplicationNodes(ReplicationServerDirection direction)
auto serversSettings = replicationSettings.value(serversKey).toList();
std::vector<HifiSockAddr> knownReplicationNodes;
nodeList->eachNode([&](const SharedNodePointer& otherNode) {
nodeList->eachNode([direction, &knownReplicationNodes](const SharedNodePointer& otherNode) {
if ((direction == Upstream && NodeType::isUpstream(otherNode->getType()))
|| (direction == Downstream && NodeType::isDownstream(otherNode->getType()))) {
knownReplicationNodes.push_back(otherNode->getPublicSocket());
@ -2870,7 +2906,7 @@ void DomainServer::updateReplicationNodes(ReplicationServerDirection direction)
// collect them in a vector to separately remove them with handleKillNode (since eachNode has a read lock and
// we cannot recursively take the write lock required by handleKillNode)
std::vector<SharedNodePointer> nodesToKill;
nodeList->eachNode([&](const SharedNodePointer& otherNode) {
nodeList->eachNode([this, direction, replicationNodesInSettings, replicationDirection, &nodesToKill](const SharedNodePointer& otherNode) {
if ((direction == Upstream && NodeType::isUpstream(otherNode->getType()))
|| (direction == Downstream && NodeType::isDownstream(otherNode->getType()))) {
bool nodeInSettings = find(replicationNodesInSettings.cbegin(), replicationNodesInSettings.cend(),

View file

@ -59,6 +59,8 @@ public:
DomainServer(int argc, char* argv[]);
~DomainServer();
static void parseCommandLine(int argc, char* argv[]);
enum DomainType {
NonMetaverse,
MetaverseDomain,
@ -138,7 +140,6 @@ signals:
private:
QUuid getID();
void parseCommandLine();
QString getContentBackupDir();
QString getEntitiesDirPath();
@ -228,7 +229,7 @@ private:
QQueue<SharedAssignmentPointer> _unfulfilledAssignments;
TransactionHash _pendingAssignmentCredits;
bool _isUsingDTLS;
bool _isUsingDTLS { false };
QUrl _oauthProviderURL;
QString _oauthClientID;
@ -265,10 +266,13 @@ private:
friend class DomainGatekeeper;
friend class DomainMetadata;
QString _iceServerAddr;
int _iceServerPort;
bool _overrideDomainID { false }; // should we override the domain-id from settings?
QUuid _overridingDomainID { QUuid() }; // what should we override it with?
static QString _iceServerAddr;
static int _iceServerPort;
static bool _overrideDomainID; // should we override the domain-id from settings?
static QUuid _overridingDomainID; // what should we override it with?
static bool _getTempName;
static QString _userConfigFilename;
static int _parentPID;
bool _sendICEServerAddressToMetaverseAPIInProgress { false };
bool _sendICEServerAddressToMetaverseAPIRedo { false };

View file

@ -191,13 +191,12 @@ void DomainServerSettingsManager::processSettingsRequestPacket(QSharedPointer<Re
nodeList->sendPacketList(std::move(packetList), message->getSenderSockAddr());
}
void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList) {
void DomainServerSettingsManager::setupConfigMap(const QString& userConfigFilename) {
// since we're called from the DomainServerSettingsManager constructor, we don't take a write lock here
// even though we change the underlying config map
_argumentList = argumentList;
_configMap.loadConfig(_argumentList);
_configMap.setUserConfigFilename(userConfigFilename);
_configMap.loadConfig();
static const auto VERSION_SETTINGS_KEYPATH = "version";
QVariant* versionVariant = _configMap.valueForKeyPath(VERSION_SETTINGS_KEYPATH);
@ -1736,7 +1735,7 @@ void DomainServerSettingsManager::persistToFile() {
// failed to write, reload whatever the current config state is
// with a write lock since we're about to overwrite the config map
QWriteLocker locker(&_settingsLock);
_configMap.loadConfig(_argumentList);
_configMap.loadConfig();
}
}

View file

@ -49,7 +49,7 @@ public:
DomainServerSettingsManager();
bool handleAuthenticatedHTTPRequest(HTTPConnection* connection, const QUrl& url);
void setupConfigMap(const QStringList& argumentList);
void setupConfigMap(const QString& userConfigFilename);
// each of the three methods in this group takes a read lock of _settingsLock
// and cannot be called when the a write lock is held by the same thread
@ -144,8 +144,6 @@ private slots:
void processUsernameFromIDRequestPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode);
private:
QStringList _argumentList;
QJsonArray filteredDescriptionArray(bool isContentSettings);
void updateSetting(const QString& key, const QJsonValue& newValue, QVariantMap& settingMap,
const QJsonObject& settingDescription);

View file

@ -24,6 +24,8 @@
int main(int argc, char* argv[]) {
setupHifiApplication(BuildInfo::DOMAIN_SERVER_NAME);
DomainServer::parseCommandLine(argc, argv);
Setting::init();
int currentExitCode = 0;

View file

@ -214,6 +214,7 @@ link_hifi_libraries(
controllers plugins image trackers
ui-plugins display-plugins input-plugins
${PLATFORM_GL_BACKEND}
shaders
)
# include the binary directory of render-utils for shader includes

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -585,8 +585,9 @@
"states": [
{
"id": "idle",
"interpTarget": 10,
"interpDuration": 10,
"interpTarget": 0,
"interpDuration": 4,
"interpType": "snapshotPrev",
"transitions": [
{ "var": "isMovingForward", "state": "idleToWalkFwd" },
{ "var": "isMovingBackward", "state": "walkBwd" },
@ -598,13 +599,16 @@
{ "var": "isTakeoffStand", "state": "takeoffStand" },
{ "var": "isTakeoffRun", "state": "takeoffRun" },
{ "var": "isInAirStand", "state": "inAirStand" },
{ "var": "isInAirRun", "state": "inAirRun" }
{ "var": "isInAirRun", "state": "inAirRun" },
{ "var": "isMovingRightHmd", "state": "strafeRightHmd" },
{ "var": "isMovingLeftHmd", "state": "strafeLeftHmd" }
]
},
{
"id": "idleToWalkFwd",
"interpTarget": 3,
"interpDuration": 3,
"interpTarget": 10,
"interpDuration": 4,
"interpType": "snapshotPrev",
"transitions": [
{ "var": "idleToWalkFwdOnDone", "state": "walkFwd" },
{ "var": "isNotMoving", "state": "idle" },
@ -617,6 +621,30 @@
{ "var": "isTakeoffStand", "state": "takeoffStand" },
{ "var": "isTakeoffRun", "state": "takeoffRun" },
{ "var": "isInAirStand", "state": "inAirStand" },
{ "var": "isInAirRun", "state": "inAirRun" },
{ "var": "isMovingRightHmd", "state": "strafeRightHmd" },
{ "var": "isMovingLeftHmd", "state": "strafeLeftHmd" }
]
},
{
"id": "idleSettle",
"interpTarget": 10,
"interpDuration": 10,
"interpType": "snapshotPrev",
"transitions": [
{"var": "idleSettleOnDone", "state": "idle" },
{"var": "isMovingForward", "state": "idleToWalkFwd" },
{ "var": "isMovingBackward", "state": "walkBwd" },
{ "var": "isMovingRight", "state": "strafeRight" },
{ "var": "isMovingLeft", "state": "strafeLeft" },
{ "var": "isMovingRightHmd", "state": "strafeRightHmd" },
{ "var": "isMovingLeftHmd", "state": "strafeLeftHmd" },
{ "var": "isTurningRight", "state": "turnRight" },
{ "var": "isTurningLeft", "state": "turnLeft" },
{ "var": "isFlying", "state": "fly" },
{ "var": "isTakeoffStand", "state": "takeoffStand" },
{ "var": "isTakeoffRun", "state": "takeoffRun" },
{ "var": "isInAirStand", "state": "inAirStand" },
{ "var": "isInAirRun", "state": "inAirRun" }
]
},
@ -624,8 +652,9 @@
"id": "walkFwd",
"interpTarget": 16,
"interpDuration": 6,
"interpType": "snapshotPrev",
"transitions": [
{ "var": "isNotMoving", "state": "idle" },
{ "var": "isNotMoving", "state": "idleSettle" },
{ "var": "isMovingBackward", "state": "walkBwd" },
{ "var": "isMovingRight", "state": "strafeRight" },
{ "var": "isMovingLeft", "state": "strafeLeft" },
@ -635,15 +664,18 @@
{ "var": "isTakeoffStand", "state": "takeoffStand" },
{ "var": "isTakeoffRun", "state": "takeoffRun" },
{ "var": "isInAirStand", "state": "inAirStand" },
{ "var": "isInAirRun", "state": "inAirRun" }
{ "var": "isInAirRun", "state": "inAirRun" },
{ "var": "isMovingRightHmd", "state": "strafeRightHmd" },
{ "var": "isMovingLeftHmd", "state": "strafeLeftHmd" }
]
},
{
"id": "walkBwd",
"interpTarget": 6,
"interpTarget": 8,
"interpDuration": 6,
"interpType": "snapshotPrev",
"transitions": [
{ "var": "isNotMoving", "state": "idle" },
{ "var": "isNotMoving", "state": "idleSettle" },
{ "var": "isMovingForward", "state": "walkFwd" },
{ "var": "isMovingRight", "state": "strafeRight" },
{ "var": "isMovingLeft", "state": "strafeLeft" },
@ -653,15 +685,18 @@
{ "var": "isTakeoffStand", "state": "takeoffStand" },
{ "var": "isTakeoffRun", "state": "takeoffRun" },
{ "var": "isInAirStand", "state": "inAirStand" },
{ "var": "isInAirRun", "state": "inAirRun" }
{ "var": "isInAirRun", "state": "inAirRun" },
{ "var": "isMovingRightHmd", "state": "strafeRightHmd" },
{ "var": "isMovingLeftHmd", "state": "strafeLeftHmd" }
]
},
{
"id": "strafeRight",
"interpTarget": 6,
"interpDuration": 6,
"interpTarget": 5,
"interpDuration": 8,
"interpType": "snapshotPrev",
"transitions": [
{ "var": "isNotMoving", "state": "idle" },
{ "var": "isNotMoving", "state": "idleSettle" },
{ "var": "isMovingForward", "state": "walkFwd" },
{ "var": "isMovingBackward", "state": "walkBwd" },
{ "var": "isMovingLeft", "state": "strafeLeft" },
@ -671,18 +706,65 @@
{ "var": "isTakeoffStand", "state": "takeoffStand" },
{ "var": "isTakeoffRun", "state": "takeoffRun" },
{ "var": "isInAirStand", "state": "inAirStand" },
{ "var": "isInAirRun", "state": "inAirRun" },
{ "var": "isMovingRightHmd", "state": "strafeRightHmd" },
{ "var": "isMovingLeftHmd", "state": "strafeLeftHmd" }
]
},
{
"id": "strafeLeft",
"interpTarget": 5,
"interpDuration": 8,
"interpType": "snapshotPrev",
"transitions": [
{ "var": "isNotMoving", "state": "idleSettle" },
{ "var": "isMovingForward", "state": "walkFwd" },
{ "var": "isMovingBackward", "state": "walkBwd" },
{ "var": "isMovingRight", "state": "strafeRight" },
{ "var": "isTurningRight", "state": "turnRight" },
{ "var": "isTurningLeft", "state": "turnLeft" },
{ "var": "isFlying", "state": "fly" },
{ "var": "isTakeoffStand", "state": "takeoffStand" },
{ "var": "isTakeoffRun", "state": "takeoffRun" },
{ "var": "isInAirStand", "state": "inAirStand" },
{ "var": "isInAirRun", "state": "inAirRun" },
{ "var": "isMovingRightHmd", "state": "strafeRightHmd" },
{ "var": "isMovingLeftHmd", "state": "strafeLeftHmd" }
]
},
{
"id": "strafeRightHmd",
"interpTarget": 5,
"interpDuration": 8,
"interpType": "snapshotPrev",
"transitions": [
{ "var": "isNotMoving", "state": "idleSettle" },
{ "var": "isMovingForward", "state": "walkFwd" },
{ "var": "isMovingBackward", "state": "walkBwd" },
{ "var": "isMovingLeftHmd", "state": "strafeLeftHmd" },
{ "var": "isMovingRight", "state": "strafeRight" },
{ "var": "isMovingLeft", "state": "strafeLeft" },
{ "var": "isTurningRight", "state": "turnRight" },
{ "var": "isTurningLeft", "state": "turnLeft" },
{ "var": "isFlying", "state": "fly" },
{ "var": "isTakeoffStand", "state": "takeoffStand" },
{ "var": "isTakeoffRun", "state": "takeoffRun" },
{ "var": "isInAirStand", "state": "inAirStand" },
{ "var": "isInAirRun", "state": "inAirRun" }
]
},
{
"id": "strafeLeft",
"interpTarget": 6,
"interpDuration": 6,
"id": "strafeLeftHmd",
"interpTarget": 5,
"interpDuration": 8,
"interpType": "snapshotPrev",
"transitions": [
{ "var": "isNotMoving", "state": "idle" },
{ "var": "isNotMoving", "state": "idleSettle" },
{ "var": "isMovingForward", "state": "walkFwd" },
{ "var": "isMovingBackward", "state": "walkBwd" },
{ "var": "isMovingRightHmd", "state": "strafeRightHmd" },
{ "var": "isMovingRight", "state": "strafeRight" },
{ "var": "isMovingLeft", "state": "strafeLeft" },
{ "var": "isTurningRight", "state": "turnRight" },
{ "var": "isTurningLeft", "state": "turnLeft" },
{ "var": "isFlying", "state": "fly" },
@ -695,7 +777,8 @@
{
"id": "turnRight",
"interpTarget": 6,
"interpDuration": 6,
"interpDuration": 8,
"interpType": "snapshotPrev",
"transitions": [
{ "var": "isNotTurning", "state": "idle" },
{ "var": "isMovingForward", "state": "walkFwd" },
@ -707,13 +790,16 @@
{ "var": "isTakeoffStand", "state": "takeoffStand" },
{ "var": "isTakeoffRun", "state": "takeoffRun" },
{ "var": "isInAirStand", "state": "inAirStand" },
{ "var": "isInAirRun", "state": "inAirRun" }
{ "var": "isInAirRun", "state": "inAirRun" },
{ "var": "isMovingRightHmd", "state": "strafeRightHmd" },
{ "var": "isMovingLeftHmd", "state": "strafeLeftHmd" }
]
},
{
"id": "turnLeft",
"interpTarget": 6,
"interpDuration": 6,
"interpDuration": 8,
"interpType": "snapshotPrev",
"transitions": [
{ "var": "isNotTurning", "state": "idle" },
{ "var": "isMovingForward", "state": "walkFwd" },
@ -725,7 +811,9 @@
{ "var": "isTakeoffStand", "state": "takeoffStand" },
{ "var": "isTakeoffRun", "state": "takeoffRun" },
{ "var": "isInAirStand", "state": "inAirStand" },
{ "var": "isInAirRun", "state": "inAirRun" }
{ "var": "isInAirRun", "state": "inAirRun" },
{ "var": "isMovingRightHmd", "state": "strafeRightHmd" },
{ "var": "isMovingLeftHmd", "state": "strafeLeftHmd" }
]
},
{
@ -733,7 +821,7 @@
"interpTarget": 6,
"interpDuration": 6,
"transitions": [
{ "var": "isNotFlying", "state": "idle" }
{ "var": "isNotFlying", "state": "idleSettle" }
]
},
{
@ -797,7 +885,9 @@
{ "var": "isTakeoffRun", "state": "takeoffRun" },
{ "var": "isInAirStand", "state": "inAirStand" },
{ "var": "isInAirRun", "state": "inAirRun" },
{ "var": "landStandOnDone", "state": "idle" }
{ "var": "landStandOnDone", "state": "idle" },
{ "var": "isMovingRightHmd", "state": "strafeRightHmd" },
{ "var": "isMovingLeftHmd", "state": "strafeLeftHmd" }
]
},
{
@ -871,13 +961,13 @@
"data": {
"alpha": 0.0,
"desiredSpeed": 1.4,
"characteristicSpeeds": [0.5, 1.4, 4.5],
"characteristicSpeeds": [0.5, 1.5, 2.5, 3.2, 4.5],
"alphaVar": "moveForwardAlpha",
"desiredSpeedVar": "moveForwardSpeed"
},
"children": [
{
"id": "walkFwdShort",
"id": "walkFwdShort_c",
"type": "clip",
"data": {
"url": "qrc:///avatar/animations/walk_short_fwd.fbx",
@ -889,7 +979,7 @@
"children": []
},
{
"id": "walkFwdNormal",
"id": "walkFwdNormal_c",
"type": "clip",
"data": {
"url": "qrc:///avatar/animations/walk_fwd.fbx",
@ -901,7 +991,31 @@
"children": []
},
{
"id": "walkFwdRun",
"id": "walkFwdFast_c",
"type": "clip",
"data": {
"url": "qrc:///avatar/animations/walk_fwd_fast.fbx",
"startFrame": 0.0,
"endFrame": 25.0,
"timeScale": 1.0,
"loopFlag": true
},
"children": []
},
{
"id": "walkFwdJog_c",
"type": "clip",
"data": {
"url": "qrc:///avatar/animations/jog_fwd.fbx",
"startFrame": 0.0,
"endFrame": 25.0,
"timeScale": 1.0,
"loopFlag": true
},
"children": []
},
{
"id": "walkFwdRun_c",
"type": "clip",
"data": {
"url": "qrc:///avatar/animations/run_fwd.fbx",
@ -926,13 +1040,25 @@
},
"children": []
},
{
"id": "idleSettle",
"type": "clip",
"data": {
"url": "qrc:///avatar/animations/settle_to_idle.fbx",
"startFrame": 1.0,
"endFrame": 48.0,
"timeScale": 1.0,
"loopFlag": false
},
"children": []
},
{
"id": "walkBwd",
"type": "blendLinearMove",
"data": {
"alpha": 0.0,
"desiredSpeed": 1.4,
"characteristicSpeeds": [0.6, 1.45],
"characteristicSpeeds": [0.6, 1.7],
"alphaVar": "moveBackwardAlpha",
"desiredSpeedVar": "moveBackwardSpeed"
},
@ -953,9 +1079,9 @@
"id": "walkBwdNormal",
"type": "clip",
"data": {
"url": "qrc:///avatar/animations/walk_bwd.fbx",
"url": "qrc:///avatar/animations/walk_bwd_fast.fbx",
"startFrame": 0.0,
"endFrame": 36.0,
"endFrame": 27.0,
"timeScale": 1.0,
"loopFlag": true
},
@ -969,7 +1095,7 @@
"data": {
"url": "qrc:///avatar/animations/turn_left.fbx",
"startFrame": 0.0,
"endFrame": 28.0,
"endFrame": 32.0,
"timeScale": 1.0,
"loopFlag": true
},
@ -981,7 +1107,7 @@
"data": {
"url": "qrc:///avatar/animations/turn_left.fbx",
"startFrame": 0.0,
"endFrame": 30.0,
"endFrame": 32.0,
"timeScale": 1.0,
"loopFlag": true,
"mirrorFlag": true
@ -994,30 +1120,66 @@
"data": {
"alpha": 0.0,
"desiredSpeed": 1.4,
"characteristicSpeeds": [0.2, 0.65],
"characteristicSpeeds": [0, 0.5, 1.5, 2.6, 3.0],
"alphaVar": "moveLateralAlpha",
"desiredSpeedVar": "moveLateralSpeed"
},
"children": [
{
"id": "strafeLeftShort",
"id": "strafeLeftShort_c",
"type": "clip",
"data": {
"url": "qrc:///avatar/animations/side_step_short_left.fbx",
"startFrame": 0.0,
"endFrame": 28.0,
"endFrame": 29.0,
"timeScale": 1.0,
"loopFlag": true
},
"children": []
},
{
"id": "strafeLeftNormal",
"id": "strafeLeft_c",
"type": "clip",
"data": {
"url": "qrc:///avatar/animations/side_step_left.fbx",
"startFrame": 0.0,
"endFrame": 30.0,
"endFrame": 20.0,
"timeScale": 1.0,
"loopFlag": true
},
"children": []
},
{
"id": "strafeLeftAnim_c",
"type": "clip",
"data": {
"url": "qrc:///avatar/animations/walk_left.fbx",
"startFrame": 0.0,
"endFrame": 33.0,
"timeScale": 1.0,
"loopFlag": true
},
"children": []
},
{
"id": "strafeLeftFast_c",
"type": "clip",
"data": {
"url": "qrc:///avatar/animations/walk_left_fast.fbx",
"startFrame": 0.0,
"endFrame": 21.0,
"timeScale": 1.0,
"loopFlag": true
},
"children": []
},
{
"id": "strafeLeftJog_c",
"type": "clip",
"data": {
"url": "qrc:///avatar/animations/jog_left.fbx",
"startFrame": 0.0,
"endFrame": 24.0,
"timeScale": 1.0,
"loopFlag": true
},
@ -1031,34 +1193,175 @@
"data": {
"alpha": 0.0,
"desiredSpeed": 1.4,
"characteristicSpeeds": [0.2, 0.65],
"characteristicSpeeds": [0, 0.5, 1.5, 2.6, 3.0],
"alphaVar": "moveLateralAlpha",
"desiredSpeedVar": "moveLateralSpeed"
},
"children": [ {
"id": "stepRightShort_c",
"type": "clip",
"data": {
"url": "qrc:///avatar/animations/side_step_short_left.fbx",
"startFrame": 0.0,
"endFrame": 29.0,
"timeScale": 1.0,
"loopFlag": true,
"mirrorFlag": true
},
"children": []
},
{
"id": "stepRight_c",
"type": "clip",
"data": {
"url": "qrc:///avatar/animations/side_step_left.fbx",
"startFrame": 0.0,
"endFrame": 20.0,
"timeScale": 1.0,
"loopFlag": true,
"mirrorFlag": true
},
"children": []
},
{
"id": "strafeRight_c",
"type": "clip",
"data": {
"url": "qrc:///avatar/animations/walk_left.fbx",
"startFrame": 0.0,
"endFrame": 33.0,
"timeScale": 1.0,
"loopFlag": true,
"mirrorFlag": true
},
"children": []
},
{
"id": "strafeRightFast_c",
"type": "clip",
"data": {
"url": "qrc:///avatar/animations/walk_left_fast.fbx",
"startFrame": 0.0,
"endFrame": 21.0,
"timeScale": 1.0,
"loopFlag": true,
"mirrorFlag": true
},
"children": []
},
{
"id": "strafeRightJog_c",
"type": "clip",
"data": {
"url": "qrc:///avatar/animations/jog_left.fbx",
"startFrame": 0.0,
"endFrame": 24.0,
"timeScale": 1.0,
"loopFlag": true,
"mirrorFlag": true
},
"children": []
}
]
},
{
"id": "strafeLeftHmd",
"type": "blendLinearMove",
"data": {
"alpha": 0.0,
"desiredSpeed": 1.4,
"characteristicSpeeds": [0, 0.5, 2.5],
"alphaVar": "moveLateralAlpha",
"desiredSpeedVar": "moveLateralSpeed"
},
"children": [
{
"id": "strafeRightShort",
"id": "stepLeftShort_c",
"type": "clip",
"data": {
"url": "qrc:///avatar/animations/side_step_short_right.fbx",
"url": "qrc:///avatar/animations/side_step_short_left.fbx",
"startFrame": 0.0,
"endFrame": 28.0,
"endFrame": 29.0,
"timeScale": 1.0,
"loopFlag": true
},
"children": []
},
{
"id": "strafeRightNormal",
"id": "stepLeft_c",
"type": "clip",
"data": {
"url": "qrc:///avatar/animations/side_step_right.fbx",
"url": "qrc:///avatar/animations/side_step_left.fbx",
"startFrame": 0.0,
"endFrame": 30.0,
"endFrame": 20.0,
"timeScale": 1.0,
"loopFlag": true
},
"children": []
},
{
"id": "strafeLeftAnim_c",
"type": "clip",
"data": {
"url": "qrc:///avatar/animations/side_step_left_fast.fbx",
"startFrame": 0.0,
"endFrame": 16.0,
"timeScale": 1.0,
"loopFlag": true
},
"children": []
}
]
},
{
"id": "strafeRightHmd",
"type": "blendLinearMove",
"data": {
"alpha": 0.0,
"desiredSpeed": 1.4,
"characteristicSpeeds": [0, 0.5, 2.5],
"alphaVar": "moveLateralAlpha",
"desiredSpeedVar": "moveLateralSpeed"
},
"children": [
{
"id": "stepRightShort_c",
"type": "clip",
"data": {
"url": "qrc:///avatar/animations/side_step_short_left.fbx",
"startFrame": 0.0,
"endFrame": 29.0,
"timeScale": 1.0,
"loopFlag": true,
"mirrorFlag": true
},
"children": []
},
{
"id": "stepRight_c",
"type": "clip",
"data": {
"url": "qrc:///avatar/animations/side_step_left.fbx",
"startFrame": 0.0,
"endFrame": 20.0,
"timeScale": 1.0,
"loopFlag": true,
"mirrorFlag": true
},
"children": []
},
{
"id": "strafeRightAnim_c",
"type": "clip",
"data": {
"url": "qrc:///avatar/animations/side_step_left_fast.fbx",
"startFrame": 0.0,
"endFrame": 16.0,
"timeScale": 1.0,
"loopFlag": true,
"mirrorFlag": true
},
"children": []
}
]
},

View file

@ -380,15 +380,21 @@
{
"properties": {
"acceleration": {
"blue": 0,
"green": 0,
"red": 0,
"x": 0,
"y": 0,
"z": 0
},
"actionData": "",
"age": 14.011327743530273,
"ageAsText": "0 hours 0 minutes 14 seconds",
"age": 321.8835144042969,
"ageAsText": "0 hours 5 minutes 21 seconds",
"angularDamping": 0.39346998929977417,
"angularVelocity": {
"blue": 0,
"green": 0,
"red": 0,
"x": 0,
"y": 0,
"z": 0
@ -406,24 +412,36 @@
},
"boundingBox": {
"brn": {
"x": -0.20154684782028198,
"y": 0.03644842654466629,
"z": -0.2641940414905548
"blue": -0.03950843587517738,
"green": 0.20785385370254517,
"red": -0.04381325840950012,
"x": -0.04381325840950012,
"y": 0.20785385370254517,
"z": -0.03950843587517738
},
"center": {
"x": -0.030000001192092896,
"y": 0.12999820709228516,
"z": -0.07000023126602173
"blue": 0,
"green": 0.23000000417232513,
"red": 0,
"x": 0,
"y": 0.23000000417232513,
"z": 0
},
"dimensions": {
"x": 0.3430936932563782,
"y": 0.18709957599639893,
"z": 0.38838762044906616
"blue": 0.07901687175035477,
"green": 0.044292300939559937,
"red": 0.08762651681900024,
"x": 0.08762651681900024,
"y": 0.044292300939559937,
"z": 0.07901687175035477
},
"tfl": {
"x": 0.1415468454360962,
"y": 0.22354799509048462,
"z": 0.12419357895851135
"blue": 0.03950843587517738,
"green": 0.2521461546421051,
"red": 0.04381325840950012,
"x": 0.04381325840950012,
"y": 0.2521461546421051,
"z": 0.03950843587517738
}
},
"canCastShadow": true,
@ -441,189 +459,14 @@
"collisionless": false,
"collisionsWillMove": false,
"compoundShapeURL": "",
"created": "2018-06-06T17:25:42Z",
"damping": 0.39346998929977417,
"density": 1000,
"description": "",
"dimensions": {
"x": 0.33466479182243347,
"y": 0.16981728374958038,
"z": 0.38838762044906616
},
"dynamic": false,
"editionNumber": 19,
"entityInstanceNumber": 0,
"friction": 0.5,
"gravity": {
"x": 0,
"y": 0,
"z": 0
},
"href": "",
"id": "{6b0a2b08-e8e3-4d43-95cc-dfc4f7a4b0c9}",
"ignoreForCollisions": false,
"itemArtist": "jyoum",
"itemCategories": "Wearables",
"itemDescription": "A stylish and classic piece of headwear for your avatar.",
"itemLicense": "",
"itemName": "Fedora",
"jointRotations": [
],
"jointRotationsSet": [
],
"jointTranslations": [
],
"jointTranslationsSet": [
],
"lastEdited": 1528306032827319,
"lastEditedBy": "{4c770def-4abb-40c6-91a1-88da5247b2db}",
"lifetime": -1,
"limitedRun": 4294967295,
"localPosition": {
"x": -0.030000008642673492,
"y": 0.12999820709228516,
"z": -0.07000023126602173
},
"localRotation": {
"w": 0.9996573328971863,
"x": 0,
"y": 0,
"z": 0.026176949962973595
},
"locked": false,
"marketplaceID": "11c4208d-15d7-4449-9758-a08da6dbd3dc",
"modelURL": "http://mpassets.highfidelity.com/11c4208d-15d7-4449-9758-a08da6dbd3dc-v1/Fedora.fbx",
"name": "",
"naturalDimensions": {
"x": 0.2765824794769287,
"y": 0.14034485816955566,
"z": 0.320981502532959
},
"naturalPosition": {
"x": 0.000143393874168396,
"y": 1.7460365295410156,
"z": 0.022502630949020386
},
"originalTextures": "{\n \"file5\": \"http://mpassets.highfidelity.com/11c4208d-15d7-4449-9758-a08da6dbd3dc-v1/Fedora.fbx/Texture/Fedora_Hat1_Base_Color.png\",\n \"file7\": \"http://mpassets.highfidelity.com/11c4208d-15d7-4449-9758-a08da6dbd3dc-v1/Fedora.fbx/Texture/Fedora_Hat1_Roughness.png\"\n}\n",
"owningAvatarID": "{4c770def-4abb-40c6-91a1-88da5247b2db}",
"parentID": "{4c770def-4abb-40c6-91a1-88da5247b2db}",
"parentJointIndex": 64,
"position": {
"x": -0.030000008642673492,
"y": 0.12999820709228516,
"z": -0.07000023126602173
},
"queryAACube": {
"scale": 1.6202316284179688,
"x": -0.5601736903190613,
"y": -10.668098449707031,
"z": -0.8933582305908203
},
"registrationPoint": {
"x": 0.5,
"y": 0.5,
"z": 0.5
},
"relayParentJoints": false,
"renderInfo": {
"drawCalls": 1,
"hasTransparent": false,
"texturesCount": 2,
"texturesSize": 327680,
"verticesCount": 719
},
"restitution": 0.5,
"rotation": {
"w": 0.9996573328971863,
"x": 0,
"y": 0,
"z": 0.026176949962973595
},
"script": "",
"scriptTimestamp": 0,
"serverScripts": "",
"shapeType": "box",
"staticCertificateVersion": 0,
"textures": "",
"type": "Model",
"userData": "{\"Attachment\":{\"action\":\"attach\",\"joint\":\"HeadTop_End\",\"attached\":false,\"options\":{\"translation\":{\"x\":0,\"y\":0,\"z\":0},\"scale\":1}},\"grabbableKey\":{\"cloneable\":false,\"grabbable\":true}}",
"velocity": {
"x": 0,
"y": 0,
"z": 0
},
"visible": true
}
},
{
"properties": {
"acceleration": {
"x": 0,
"y": 0,
"z": 0
},
"actionData": "",
"age": 14.011027336120605,
"ageAsText": "0 hours 0 minutes 14 seconds",
"angularDamping": 0.39346998929977417,
"angularVelocity": {
"x": 0,
"y": 0,
"z": 0
},
"animation": {
"allowTranslation": true,
"currentFrame": 0,
"firstFrame": 0,
"fps": 30,
"hold": false,
"lastFrame": 100000,
"loop": true,
"running": false,
"url": ""
},
"boundingBox": {
"brn": {
"x": -0.04381517320871353,
"y": 0.20789726078510284,
"z": -0.0394962802529335
},
"center": {
"x": -1.9073486328125e-06,
"y": 0.2300434112548828,
"z": 1.2159347534179688e-05
},
"dimensions": {
"x": 0.08762653172016144,
"y": 0.04429228603839874,
"z": 0.07901687920093536
},
"tfl": {
"x": 0.043811358511447906,
"y": 0.2521895468235016,
"z": 0.03952059894800186
}
},
"canCastShadow": true,
"certificateID": "",
"clientOnly": true,
"cloneAvatarEntity": false,
"cloneDynamic": false,
"cloneLifetime": 300,
"cloneLimit": 0,
"cloneOriginID": "{00000000-0000-0000-0000-000000000000}",
"cloneable": false,
"collidesWith": "",
"collisionMask": 0,
"collisionSoundURL": "",
"collisionless": false,
"collisionsWillMove": false,
"compoundShapeURL": "",
"created": "2018-06-06T17:25:42Z",
"created": "2018-07-26T23:56:46Z",
"damping": 0.39346998929977417,
"density": 1000,
"description": "",
"dimensions": {
"blue": 0.07229919731616974,
"green": 0.06644226610660553,
"red": 0.03022606298327446,
"x": 0.03022606298327446,
"y": 0.06644226610660553,
"z": 0.07229919731616974
@ -633,12 +476,15 @@
"entityInstanceNumber": 0,
"friction": 0.5,
"gravity": {
"blue": 0,
"green": 0,
"red": 0,
"x": 0,
"y": 0,
"z": 0
},
"href": "",
"id": "{d018c6ea-b2f4-441e-85e1-d17373ae6f34}",
"id": "{03053239-bb37-4c51-a013-a1772baaeed5}",
"ignoreForCollisions": false,
"itemArtist": "jyoum",
"itemCategories": "Wearables",
@ -653,51 +499,66 @@
],
"jointTranslationsSet": [
],
"lastEdited": 1528306032505220,
"lastEditedBy": "{b46f9c9e-4cd3-4964-96d6-cf3954abb908}",
"lastEdited": 1532649569894305,
"lastEditedBy": "{042ac463-7879-40f0-8126-e2e56c4345ca}",
"lifetime": -1,
"limitedRun": 4294967295,
"localPosition": {
"x": -1.9073486328125e-06,
"y": 0.2300434112548828,
"z": 1.2159347534179688e-05
"blue": 0,
"green": 0.23000000417232513,
"red": 0,
"x": 0,
"y": 0.23000000417232513,
"z": 0
},
"localRotation": {
"w": 0.5910987257957458,
"x": -0.48726412653923035,
"y": -0.4088631868362427,
"z": 0.49599069356918335
"w": 0.5910986065864563,
"x": -0.48726415634155273,
"y": -0.4088630974292755,
"z": 0.49599072337150574
},
"locked": false,
"marketplaceID": "0685794d-fddb-4bad-a608-6d7789ceda90",
"modelURL": "http://mpassets.highfidelity.com/0685794d-fddb-4bad-a608-6d7789ceda90-v1/ScifiWatch.fbx",
"name": "Scifi Watch by Jimi",
"naturalDimensions": {
"blue": 0.055614765733480453,
"green": 0.0511094331741333,
"red": 0.023250818252563477,
"x": 0.023250818252563477,
"y": 0.0511094331741333,
"z": 0.055614765733480453
},
"naturalPosition": {
"blue": -0.06031447649002075,
"green": 1.4500460624694824,
"red": 0.6493338942527771,
"x": 0.6493338942527771,
"y": 1.4500460624694824,
"z": -0.06031447649002075
},
"originalTextures": "{\n \"file4\": \"http://mpassets.highfidelity.com/0685794d-fddb-4bad-a608-6d7789ceda90-v1/ScifiWatch.fbx/ScifiWatch/texture/lambert1_Base_Color.png\",\n \"file5\": \"http://mpassets.highfidelity.com/0685794d-fddb-4bad-a608-6d7789ceda90-v1/ScifiWatch.fbx/ScifiWatch/texture/lambert1_Normal_OpenGL.png\",\n \"file6\": \"http://mpassets.highfidelity.com/0685794d-fddb-4bad-a608-6d7789ceda90-v1/ScifiWatch.fbx/ScifiWatch/texture/lambert1_Metallic.png\",\n \"file7\": \"http://mpassets.highfidelity.com/0685794d-fddb-4bad-a608-6d7789ceda90-v1/ScifiWatch.fbx/ScifiWatch/texture/lambert1_Roughness.png\",\n \"file8\": \"http://mpassets.highfidelity.com/0685794d-fddb-4bad-a608-6d7789ceda90-v1/ScifiWatch.fbx/ScifiWatch/texture/lambert1_Emissive.png\"\n}\n",
"owningAvatarID": "{4c770def-4abb-40c6-91a1-88da5247b2db}",
"parentID": "{4c770def-4abb-40c6-91a1-88da5247b2db}",
"owningAvatarID": "{042ac463-7879-40f0-8126-e2e56c4345ca}",
"parentID": "{042ac463-7879-40f0-8126-e2e56c4345ca}",
"parentJointIndex": 16,
"position": {
"x": -1.9073486328125e-06,
"y": 0.2300434112548828,
"z": 1.2159347534179688e-05
"blue": 0,
"green": 0.23000000417232513,
"red": 0,
"x": 0,
"y": 0.23000000417232513,
"z": 0
},
"queryAACube": {
"scale": 0.3082179129123688,
"x": -0.19203892350196838,
"y": -10.429610252380371,
"z": -0.4076632857322693
"x": 495.7716979980469,
"y": 498.345703125,
"z": 498.52044677734375
},
"registrationPoint": {
"blue": 0.5,
"green": 0.5,
"red": 0.5,
"x": 0.5,
"y": 0.5,
"z": 0.5
@ -712,10 +573,10 @@
},
"restitution": 0.5,
"rotation": {
"w": 0.5910987257957458,
"x": -0.48726412653923035,
"y": -0.4088631868362427,
"z": 0.49599069356918335
"w": 0.5910986065864563,
"x": -0.48726415634155273,
"y": -0.4088630974292755,
"z": 0.49599072337150574
},
"script": "",
"scriptTimestamp": 0,
@ -726,6 +587,229 @@
"type": "Model",
"userData": "{\"Attachment\":{\"action\":\"attach\",\"joint\":\"[LR]ForeArm\",\"attached\":false,\"options\":{\"translation\":{\"x\":0,\"y\":0,\"z\":0},\"scale\":1}},\"grabbableKey\":{\"cloneable\":false,\"grabbable\":true}}",
"velocity": {
"blue": 0,
"green": 0,
"red": 0,
"x": 0,
"y": 0,
"z": 0
},
"visible": true
}
},
{
"properties": {
"acceleration": {
"blue": 0,
"green": 0,
"red": 0,
"x": 0,
"y": 0,
"z": 0
},
"actionData": "",
"age": 308.8044128417969,
"ageAsText": "0 hours 5 minutes 8 seconds",
"angularDamping": 0.39346998929977417,
"angularVelocity": {
"blue": 0,
"green": 0,
"red": 0,
"x": 0,
"y": 0,
"z": 0
},
"animation": {
"allowTranslation": true,
"currentFrame": 0,
"firstFrame": 0,
"fps": 30,
"hold": false,
"lastFrame": 100000,
"loop": true,
"running": false,
"url": ""
},
"boundingBox": {
"brn": {
"blue": -0.2340194433927536,
"green": -0.07067721337080002,
"red": -0.17002610862255096,
"x": -0.17002610862255096,
"y": -0.07067721337080002,
"z": -0.2340194433927536
},
"center": {
"blue": -0.039825439453125,
"green": 0.02001953125,
"red": 0.0001678466796875,
"x": 0.0001678466796875,
"y": 0.02001953125,
"z": -0.039825439453125
},
"dimensions": {
"blue": 0.3883880078792572,
"green": 0.18139348924160004,
"red": 0.34038791060447693,
"x": 0.34038791060447693,
"y": 0.18139348924160004,
"z": 0.3883880078792572
},
"tfl": {
"blue": 0.1543685644865036,
"green": 0.11071627587080002,
"red": 0.17036180198192596,
"x": 0.17036180198192596,
"y": 0.11071627587080002,
"z": 0.1543685644865036
}
},
"canCastShadow": true,
"certificateID": "",
"clientOnly": true,
"cloneAvatarEntity": false,
"cloneDynamic": false,
"cloneLifetime": 300,
"cloneLimit": 0,
"cloneOriginID": "{00000000-0000-0000-0000-000000000000}",
"cloneable": false,
"collidesWith": "",
"collisionMask": 0,
"collisionSoundURL": "",
"collisionless": false,
"collisionsWillMove": false,
"compoundShapeURL": "",
"created": "2018-07-26T23:56:46Z",
"damping": 0.39346998929977417,
"density": 1000,
"description": "",
"dimensions": {
"blue": 0.38838762044906616,
"green": 0.16981728374958038,
"red": 0.33466479182243347,
"x": 0.33466479182243347,
"y": 0.16981728374958038,
"z": 0.38838762044906616
},
"dynamic": false,
"editionNumber": 18,
"entityInstanceNumber": 0,
"friction": 0.5,
"gravity": {
"blue": 0,
"green": 0,
"red": 0,
"x": 0,
"y": 0,
"z": 0
},
"href": "",
"id": "{1bf231ce-3913-4c53-be3c-b1f4094dac51}",
"ignoreForCollisions": false,
"itemArtist": "jyoum",
"itemCategories": "Wearables",
"itemDescription": "A stylish and classic piece of headwear for your avatar.",
"itemLicense": "",
"itemName": "Fedora",
"jointRotations": [
],
"jointRotationsSet": [
],
"jointTranslations": [
],
"jointTranslationsSet": [
],
"lastEdited": 1532649698129709,
"lastEditedBy": "{042ac463-7879-40f0-8126-e2e56c4345ca}",
"lifetime": -1,
"limitedRun": 4294967295,
"localPosition": {
"blue": -0.039825439453125,
"green": 0.02001953125,
"red": 0.0001678466796875,
"x": 0.0001678466796875,
"y": 0.02001953125,
"z": -0.039825439453125
},
"localRotation": {
"w": 0.9998477101325989,
"x": -9.898545982878204e-09,
"y": 5.670873406415922e-07,
"z": 0.017452405765652657
},
"locked": false,
"marketplaceID": "11c4208d-15d7-4449-9758-a08da6dbd3dc",
"modelURL": "http://mpassets.highfidelity.com/11c4208d-15d7-4449-9758-a08da6dbd3dc-v1/Fedora.fbx",
"name": "",
"naturalDimensions": {
"blue": 0.320981502532959,
"green": 0.14034485816955566,
"red": 0.2765824794769287,
"x": 0.2765824794769287,
"y": 0.14034485816955566,
"z": 0.320981502532959
},
"naturalPosition": {
"blue": 0.022502630949020386,
"green": 1.7460365295410156,
"red": 0.000143393874168396,
"x": 0.000143393874168396,
"y": 1.7460365295410156,
"z": 0.022502630949020386
},
"originalTextures": "{\n \"file5\": \"http://mpassets.highfidelity.com/11c4208d-15d7-4449-9758-a08da6dbd3dc-v1/Fedora.fbx/Texture/Fedora_Hat1_Base_Color.png\",\n \"file7\": \"http://mpassets.highfidelity.com/11c4208d-15d7-4449-9758-a08da6dbd3dc-v1/Fedora.fbx/Texture/Fedora_Hat1_Roughness.png\"\n}\n",
"owningAvatarID": "{042ac463-7879-40f0-8126-e2e56c4345ca}",
"parentID": "{042ac463-7879-40f0-8126-e2e56c4345ca}",
"parentJointIndex": 66,
"position": {
"blue": -0.039825439453125,
"green": 0.02001953125,
"red": 0.0001678466796875,
"x": 0.0001678466796875,
"y": 0.02001953125,
"z": -0.039825439453125
},
"queryAACube": {
"scale": 1.6202316284179688,
"x": 495.21051025390625,
"y": 498.5577697753906,
"z": 497.6370849609375
},
"registrationPoint": {
"blue": 0.5,
"green": 0.5,
"red": 0.5,
"x": 0.5,
"y": 0.5,
"z": 0.5
},
"relayParentJoints": false,
"renderInfo": {
"drawCalls": 1,
"hasTransparent": false,
"texturesCount": 2,
"texturesSize": 327680,
"verticesCount": 719
},
"restitution": 0.5,
"rotation": {
"w": 0.9998477101325989,
"x": -9.898545982878204e-09,
"y": 5.670873406415922e-07,
"z": 0.017452405765652657
},
"script": "",
"scriptTimestamp": 0,
"serverScripts": "",
"shapeType": "box",
"staticCertificateVersion": 0,
"textures": "",
"type": "Model",
"userData": "{\"Attachment\":{\"action\":\"attach\",\"joint\":\"HeadTop_End\",\"attached\":false,\"options\":{\"translation\":{\"x\":0,\"y\":0,\"z\":0},\"scale\":1}},\"grabbableKey\":{\"cloneable\":false,\"grabbable\":true}}",
"velocity": {
"blue": 0,
"green": 0,
"red": 0,
"x": 0,
"y": 0,
"z": 0

View file

@ -1,21 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 22.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 100 100.8" style="enable-background:new 0 0 100 100.8;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FFFFFF;}
</style>
<path class="st0" d="M26.7,83.9c7.3,1.2,14.8,1.8,22.1,1.8c0.4,0,0.8,0,1.2,0c7.8-0.1,15.6-0.8,23.4-2.2l0,0
c5.7-1.1,11.3-6.6,12.5-12.3C87.3,64.2,88,57,88,50s-0.7-14.2-2.1-21.2c-1.2-5.6-6.8-11.1-12.5-12.2c-7.7-1.4-15.6-2.2-23.4-2.2
c-7.7-0.1-15.6,0.5-23.4,1.8c-5.7,1-11.4,6.5-12.6,12.3c-1.4,7.2-2.1,14.4-2.1,21.6s0.7,14.4,2.1,21.7
C15.3,77.4,20.9,82.9,26.7,83.9z M20.9,29.8c0.6-2.9,4-6.3,6.9-6.8c7-1.1,14-1.7,21-1.7c0.4,0,0.8,0,1.2,0
c7.4,0.1,14.8,0.8,22.1,2.1c2.9,0.6,6.4,3.9,6.9,6.7c1.3,6.6,1.9,13.3,1.9,19.9c0,6.6-0.6,13.3-1.9,19.8c-0.6,2.8-4,6.2-6.9,6.8
c-7.3,1.3-14.8,2.1-22.1,2.1c-7.4,0.1-14.8-0.5-22.1-1.7c-2.9-0.5-6.3-3.9-6.9-6.7c-1.3-6.7-2-13.5-2-20.3
C19,43.3,19.6,36.4,20.9,29.8z"/>
<path class="st0" d="M32.3,61.4c-0.5,1.3-0.1,2.8,0.9,3.8c0.3,0.3,7.2,6.6,15.9,6.6c0.8,0,1.7-0.1,2.6-0.2
c9.8-1.5,15.5-11.1,15.8-11.5c0.7-1.2,0.6-2.8-0.2-3.9c-0.9-1.1-2.3-1.6-3.7-1.3c-9.2,2.5-18.6,3.9-28.1,4.2
C34,59.1,32.8,60,32.3,61.4z"/>
<circle class="st0" cx="36.5" cy="42.8" r="9"/>
<path class="st0" d="M61.4,44.1h6.1c1.9,0,3.3-1.5,3.3-3.3c0-1.9-1.5-3.3-3.3-3.3h-6.1c-1.9,0-3.3,1.5-3.3,3.3
C58.1,42.7,59.6,44.1,61.4,44.1z"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

View file

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 22.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 50 50" style="enable-background:new 0 0 50 50;" xml:space="preserve">
<g>
<path d="M39.8,10.7c0.8,0.1,1.5,0.7,1.8,1.4s0.2,1.6-0.2,2.2c-0.3,0.4-0.2,1,0.3,1.4c0.2,0.1,0.4,0.1,0.5,0.1
c0.3,0,0.6-0.1,0.6-0.2l0.1-0.1l0.1-0.1c0.8-1.2,1-2.8,0.4-4.1C42.8,10,41.6,9,40.1,8.8c-0.3-0.1-0.5,0-0.7,0.1
C39.2,9,39,9.3,39,9.5C38.8,10.1,39.2,10.6,39.8,10.7z"/>
<path d="M42.5,8.1c1.2,0.2,2.2,1,2.7,2.1c0.4,1.1,0.3,2.4-0.3,3.3c-0.2,0.2-0.2,0.5-0.2,0.7s0.2,0.5,0.5,0.7
c0.2,0.1,0.4,0.1,0.5,0.1c0.4,0,0.7-0.2,0.8-0.5c1-1.5,1.2-3.4,0.5-5.1s-2.3-3-4.2-3.3c-0.5-0.1-1,0.2-1.1,0.8
C41.6,7.5,41.9,8,42.5,8.1z M42.1,7L42.1,7L42.1,7L42.1,7z"/>
<path d="M6.7,14.3c-0.4-0.6-0.5-1.5-0.2-2.2c0.3-0.8,1-1.3,1.9-1.4c0.6-0.1,0.9-0.6,0.8-1.1C9.1,9.3,9,9,8.7,8.9
C8.5,8.8,8.3,8.7,8,8.8C6.5,9,5.3,10,4.7,11.4s-0.4,2.9,0.4,4.1l0.1,0.1l0.1,0.1c0.1,0,0.4,0.2,0.6,0.2c0.1,0,0.3,0,0.6-0.2
C6.9,15.4,7,14.8,6.7,14.3z"/>
<path d="M5.6,8.1C5.9,8,6.1,7.9,6.3,7.7C6.5,7.5,6.5,7.2,6.5,7C6.4,6.7,6.2,6.4,6,6.3C5.8,6.1,5.6,6.1,5.3,6.1
C3.5,6.5,1.9,7.7,1.1,9.5c-0.7,1.7-0.5,3.6,0.5,5.1c0.1,0.3,0.4,0.5,0.8,0.5c0.1,0,0.3,0,0.6-0.2c0.2-0.2,0.4-0.4,0.4-0.6
c0-0.3,0-0.5-0.2-0.7c-0.6-1-0.8-2.2-0.3-3.3C3.4,9.1,4.4,8.3,5.6,8.1z"/>
<path d="M48.8,25.1c-0.7-0.7-1.8-0.7-2.6-0.2c-0.4,0.3-0.7,0.5-1,0.8L44.9,26c-0.3,0.3-0.6,0.5-0.9,0.8c-0.6,0.6-1.2,1.1-1.9,1.6
c-1.2,0.8-2.7,1-4.1,0.5c-1.3-0.5-2.3-1.6-2.6-3c-0.1-0.6-0.2-1.3-0.2-2c-0.2-2.8-0.3-5.6-0.5-8.5l-0.3-5.2c0-0.7-0.1-1.4-0.2-2.2
c-0.1-0.8-0.5-1.5-1.1-1.8s-1.2-0.3-1.8,0c-1,0.5-1.1,1.6-1.1,2.1c-0.1,1.4-0.2,2.8-0.2,4.2c0,0.9-0.1,1.8-0.1,2.7
c-0.1,2.4-0.3,4.8-0.4,7.2v0.2c0,0.8-0.1,0.9-0.5,0.9s-0.4-0.1-0.6-0.9L27,16.4c-0.8-3.3-1.5-6.7-2.3-10c-0.3-1.3-1.2-1.9-2.4-1.7
c-1.1,0.2-1.7,1.2-1.6,2.4C20.7,8,20.9,9,21,9.9c0.1,0.6,0.2,1.3,0.2,1.9c0.5,3.8,0.9,7.6,1.4,11.3l0.1,1.1
c0.1,0.8-0.1,0.9-0.4,0.9c-0.3,0.1-0.4,0.1-0.7-0.6L20,20.7c-1.2-2.9-2.5-5.7-3.7-8.6c-0.8-1.9-2-1.9-2.8-1.5
c-0.6,0.2-1.5,0.9-0.9,2.9c0.3,1,0.6,1.9,0.9,2.9c0.3,0.9,0.5,1.8,0.8,2.7c0.5,1.8,1,3.5,1.6,5.3l1.1,3.7c0.2,0.6,0,0.7-0.2,0.8
c-0.1,0.1-0.3,0.1-0.7-0.4c-0.1-0.1-0.2-0.2-0.2-0.3l-3.8-5.7c-0.5-0.7-0.9-1.4-1.4-2.1c-0.6-0.8-1.4-1-2.3-0.6s-1.3,1.1-1.2,2
c0.1,0.5,0.3,0.9,0.4,1.2c0.8,1.6,1.6,3.2,2.4,4.8c2.1,4.2,4.2,8.5,6.4,12.8c2.3,4.5,6.2,7,11.5,7.3l0,0l0,0
c4.7-0.2,8.2-1.9,10.7-5.2c2.1-2.8,4.2-5.7,6.2-8.5c0.9-1.3,1.8-2.5,2.7-3.7c0.2-0.3,0.4-0.5,0.6-0.8c0.4-0.6,0.8-1.1,1.2-1.7
C49.5,26.7,49.5,25.8,48.8,25.1z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

View file

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 22.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 50 50" style="enable-background:new 0 0 50 50;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FFFFFF;}
</style>
<g>
<path class="st0" d="M39.8,10.7c0.8,0.1,1.5,0.7,1.8,1.4s0.2,1.6-0.2,2.2c-0.3,0.4-0.2,1,0.3,1.4c0.2,0.1,0.4,0.1,0.5,0.1
c0.3,0,0.6-0.1,0.6-0.2l0.1-0.1l0.1-0.1c0.8-1.2,1-2.8,0.4-4.1C42.8,10,41.6,9,40.1,8.8c-0.3-0.1-0.5,0-0.7,0.1
C39.2,9,39,9.3,39,9.5C38.8,10.1,39.2,10.6,39.8,10.7z"/>
<path class="st0" d="M42.5,8.1c1.2,0.2,2.2,1,2.7,2.1c0.4,1.1,0.3,2.4-0.3,3.3c-0.2,0.2-0.2,0.5-0.2,0.7s0.2,0.5,0.5,0.7
c0.2,0.1,0.4,0.1,0.5,0.1c0.4,0,0.7-0.2,0.8-0.5c1-1.5,1.2-3.4,0.5-5.1s-2.3-3-4.2-3.3c-0.5-0.1-1,0.2-1.1,0.8
C41.6,7.5,41.9,8,42.5,8.1z M42.1,7L42.1,7L42.1,7L42.1,7z"/>
<path class="st0" d="M6.7,14.3c-0.4-0.6-0.5-1.5-0.2-2.2c0.3-0.8,1-1.3,1.9-1.4c0.6-0.1,0.9-0.6,0.8-1.1C9.1,9.3,9,9,8.7,8.9
C8.5,8.8,8.3,8.7,8,8.8C6.5,9,5.3,10,4.7,11.4s-0.4,2.9,0.4,4.1l0.1,0.1l0.1,0.1c0.1,0,0.4,0.2,0.6,0.2c0.1,0,0.3,0,0.6-0.2
C6.9,15.4,7,14.8,6.7,14.3z"/>
<path class="st0" d="M5.6,8.1C5.9,8,6.1,7.9,6.3,7.7C6.5,7.5,6.5,7.2,6.5,7C6.4,6.7,6.2,6.4,6,6.3C5.8,6.1,5.6,6.1,5.3,6.1
C3.5,6.5,1.9,7.7,1.1,9.5c-0.7,1.7-0.5,3.6,0.5,5.1c0.1,0.3,0.4,0.5,0.8,0.5c0.1,0,0.3,0,0.6-0.2c0.2-0.2,0.4-0.4,0.4-0.6
c0-0.3,0-0.5-0.2-0.7c-0.6-1-0.8-2.2-0.3-3.3C3.4,9.1,4.4,8.3,5.6,8.1z"/>
<path class="st0" d="M48.8,25.1c-0.7-0.7-1.8-0.7-2.6-0.2c-0.4,0.3-0.7,0.5-1,0.8L44.9,26c-0.3,0.3-0.6,0.5-0.9,0.8
c-0.6,0.6-1.2,1.1-1.9,1.6c-1.2,0.8-2.7,1-4.1,0.5c-1.3-0.5-2.3-1.6-2.6-3c-0.1-0.6-0.2-1.3-0.2-2c-0.2-2.8-0.3-5.6-0.5-8.5
l-0.3-5.2c0-0.7-0.1-1.4-0.2-2.2c-0.1-0.8-0.5-1.5-1.1-1.8s-1.2-0.3-1.8,0c-1,0.5-1.1,1.6-1.1,2.1c-0.1,1.4-0.2,2.8-0.2,4.2
c0,0.9-0.1,1.8-0.1,2.7c-0.1,2.4-0.3,4.8-0.4,7.2v0.2c0,0.8-0.1,0.9-0.5,0.9s-0.4-0.1-0.6-0.9L27,16.4c-0.8-3.3-1.5-6.7-2.3-10
c-0.3-1.3-1.2-1.9-2.4-1.7c-1.1,0.2-1.7,1.2-1.6,2.4C20.7,8,20.9,9,21,9.9c0.1,0.6,0.2,1.3,0.2,1.9c0.5,3.8,0.9,7.6,1.4,11.3
l0.1,1.1c0.1,0.8-0.1,0.9-0.4,0.9c-0.3,0.1-0.4,0.1-0.7-0.6L20,20.7c-1.2-2.9-2.5-5.7-3.7-8.6c-0.8-1.9-2-1.9-2.8-1.5
c-0.6,0.2-1.5,0.9-0.9,2.9c0.3,1,0.6,1.9,0.9,2.9c0.3,0.9,0.5,1.8,0.8,2.7c0.5,1.8,1,3.5,1.6,5.3l1.1,3.7c0.2,0.6,0,0.7-0.2,0.8
c-0.1,0.1-0.3,0.1-0.7-0.4c-0.1-0.1-0.2-0.2-0.2-0.3l-3.8-5.7c-0.5-0.7-0.9-1.4-1.4-2.1c-0.6-0.8-1.4-1-2.3-0.6s-1.3,1.1-1.2,2
c0.1,0.5,0.3,0.9,0.4,1.2c0.8,1.6,1.6,3.2,2.4,4.8c2.1,4.2,4.2,8.5,6.4,12.8c2.3,4.5,6.2,7,11.5,7.3l0,0l0,0
c4.7-0.2,8.2-1.9,10.7-5.2c2.1-2.8,4.2-5.7,6.2-8.5c0.9-1.3,1.8-2.5,2.7-3.7c0.2-0.3,0.4-0.5,0.6-0.8c0.4-0.6,0.8-1.1,1.2-1.7
C49.5,26.7,49.5,25.8,48.8,25.1z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

View file

@ -62,6 +62,7 @@ Windows.ScrollingWindow {
url: "about:blank"
anchors.fill: parent
focus: true
profile: HFWebEngineProfile;
property string userScriptUrl: ""

View file

@ -173,6 +173,21 @@ Item {
StatText {
text: "Yaw: " + root.yaw.toFixed(1)
}
StatText {
visible: root.animStackNames.length > 0;
text: "Anim Stack Names:"
}
ListView {
width: geoCol.width
height: root.animStackNames.length * 15
visible: root.animStackNames.length > 0;
model: root.animStackNames
delegate: StatText {
text: modelData.length > 30
? modelData.substring(0, 5) + "..." + modelData.substring(modelData.length - 22)
: modelData
}
}
StatText {
visible: root.expanded;
text: "Avatar Mixer In: " + root.avatarMixerInKbps + " kbps, " +

View file

@ -471,7 +471,6 @@ TabletModalWindow {
bottomMargin: hifi.dimensions.contentSpacing.y + currentSelection.controlHeight - currentSelection.height
}
headerVisible: !selectDirectory
onClicked: navigateToRow(row);
onDoubleClicked: navigateToRow(row);
focus: true
Keys.onReturnPressed: navigateToCurrentRow();

View file

@ -186,6 +186,8 @@ Windows.ScrollingWindow {
return;
}
var grabbable = MenuInterface.isOptionChecked("Create Entities As Grabbable (except Zones, Particles, and Lights)");
if (defaultURL.endsWith(".jpg") || defaultURL.endsWith(".png")) {
var name = assetProxyModel.data(treeView.selection.currentIndex);
var modelURL = "https://hifi-content.s3.amazonaws.com/DomainContent/production/default-image-model.fbx";
@ -195,7 +197,7 @@ Windows.ScrollingWindow {
var collisionless = true;
var position = Vec3.sum(MyAvatar.position, Vec3.multiply(2, Quat.getForward(MyAvatar.orientation)));
var gravity = Vec3.multiply(Vec3.fromPolar(Math.PI / 2, 0), 0);
Entities.addModelEntity(name, modelURL, textures, shapeType, dynamic, collisionless, position, gravity);
Entities.addModelEntity(name, modelURL, textures, shapeType, dynamic, collisionless, grabbable, position, gravity);
} else {
var SHAPE_TYPE_NONE = 0;
var SHAPE_TYPE_SIMPLE_HULL = 1;
@ -281,7 +283,7 @@ Windows.ScrollingWindow {
print("Asset browser - adding asset " + url + " (" + name + ") to world.");
// Entities.addEntity doesn't work from QML, so we use this.
Entities.addModelEntity(name, url, "", shapeType, dynamic, collisionless, addPosition, gravity);
Entities.addModelEntity(name, url, "", shapeType, dynamic, collisionless, grabbable, addPosition, gravity);
}
}
});

View file

@ -41,6 +41,8 @@ Column {
property var goFunction: null;
property var http: null;
property bool autoScrollTimerEnabled: false;
HifiConstants { id: hifi }
Component.onCompleted: suggestions.getFirstPage();
HifiModels.PSFListModel {
@ -88,7 +90,9 @@ Column {
online_users: data.details.connections || data.details.concurrency || 0,
// Server currently doesn't give isStacked (undefined). Could give bool.
drillDownToPlace: data.is_stacked || (data.action === 'concurrency'),
isStacked: !!data.is_stacked
isStacked: !!data.is_stacked,
time_before_autoscroll_ms: data.hold_time || 3000
};
}
@ -102,9 +106,12 @@ Column {
id: scroll;
model: suggestions;
orientation: ListView.Horizontal;
highlightFollowsCurrentItem: false
highlightMoveDuration: -1;
highlightMoveVelocity: -1;
highlightFollowsCurrentItem: true;
preferredHighlightBegin: 0;
preferredHighlightEnd: cardWidth;
highlightRangeMode: ListView.StrictlyEnforceRange;
highlightMoveDuration: 800;
highlightMoveVelocity: 1;
currentIndex: -1;
spacing: 12;
@ -134,8 +141,49 @@ Column {
textSizeSmall: root.textSizeSmall;
stackShadowNarrowing: root.stackShadowNarrowing;
shadowHeight: root.stackedCardShadowHeight;
hoverThunk: function () { hovered = true }
unhoverThunk: function () { hovered = false }
hoverThunk: function () {
hovered = true;
if(root.autoScrollTimerEnabled) {
autoScrollTimer.stop();
}
}
unhoverThunk: function () {
hovered = false;
if(root.autoScrollTimerEnabled) {
autoScrollTimer.start();
}
}
}
onCountChanged: {
if (scroll.currentIndex === -1 && scroll.count > 0 && root.autoScrollTimerEnabled) {
scroll.currentIndex = 0;
autoScrollTimer.interval = suggestions.get(scroll.currentIndex).time_before_autoscroll_ms;
autoScrollTimer.start();
}
}
onCurrentIndexChanged: {
if (root.autoScrollTimerEnabled) {
autoScrollTimer.interval = suggestions.get(scroll.currentIndex).time_before_autoscroll_ms;
autoScrollTimer.start();
}
}
}
Timer {
id: autoScrollTimer;
interval: 3000;
running: false;
repeat: false;
onTriggered: {
if (scroll.currentIndex !== -1) {
if (scroll.currentIndex === scroll.count - 1) {
scroll.currentIndex = 0;
} else {
scroll.currentIndex++;
}
}
}
}
}

View file

@ -526,11 +526,13 @@ Item {
anchors.left: nameCardVUMeter.left;
// Properties
visible: (isMyCard || (selected && pal.activeTab == "nearbyTab")) && isPresent;
value: Users.getAvatarGain(uuid)
minimumValue: -60.0
maximumValue: 20.0
stepSize: 5
updateValueWhileDragging: true
Component.onCompleted: {
value = Users.getAvatarGain(uuid);
}
onValueChanged: {
updateGainFromQML(uuid, value, false);
}
@ -587,9 +589,11 @@ Item {
}
function updateGainFromQML(avatarUuid, sliderValue, isReleased) {
Users.setAvatarGain(avatarUuid, sliderValue);
if (isReleased) {
UserActivityLogger.palAction("avatar_gain_changed", avatarUuid);
if (Users.getAvatarGain(avatarUuid) != sliderValue) {
Users.setAvatarGain(avatarUuid, sliderValue);
if (isReleased) {
UserActivityLogger.palAction("avatar_gain_changed", avatarUuid);
}
}
}

View file

@ -1046,12 +1046,13 @@ Rectangle {
enabled: myData.userName !== "Unknown user" && !userInfoViewer.visible;
hoverEnabled: true;
onClicked: {
// TODO: Change language from "Happening Now" to something else (or remove entirely)
popupComboDialog("Set your availability:",
availabilityComboBox.availabilityStrings,
["Your username will be visible in everyone's 'Nearby' list. Anyone will be able to jump to your location from within the 'Nearby' list.",
"Your location will be visible in the 'Connections' list only for those with whom you are connected or friends. They'll be able to jump to your location if the domain allows.",
"Your location will be visible in the 'Connections' list only for those with whom you are friends. They'll be able to jump to your location if the domain allows. You will only receive 'Happening Now' notifications in 'Go To' from friends.",
"You will appear offline in the 'Connections' list, and you will not receive 'Happening Now' notifications in 'Go To'."],
"Your location will be visible in the 'Connections' list only for those with whom you are connected or friends. They'll be able to jump to your location if the domain allows, and you will see 'Snaps' Blasts from them in 'Go To'.",
"Your location will be visible in the 'Connections' list only for those with whom you are friends. They'll be able to jump to your location if the domain allows, and you will see 'Snaps' Blasts from them in 'Go To'",
"You will appear offline in the 'Connections' list, and you will not receive Snaps Blasts from connections or friends in 'Go To'."],
["all", "connections", "friends", "none"]);
}
onEntered: availabilityComboBox.color = hifi.colors.lightGrayText;

View file

@ -41,8 +41,8 @@ MessageBox {
popup.button1text = 'CANCEL'
popup.titleText = 'Get Wearables'
popup.bodyText = 'Buy wearables from <b><a href="app://marketplace">Marketplace.</a></b>' + '<br/>' +
'Wear wearables from <b><a href="app://purchases">My Purchases.</a></b>' + '<br/>' + '<br/>' +
'Visit “AvatarIsland” to get wearables'
'Use wearables in <b><a href="app://purchases">My Purchases.</a></b>' + '<br/>' + '<br/>' +
'Visit “AvatarIsland” to get wearables.'
popup.imageSource = getWearablesUrl;
popup.onButton2Clicked = function() {
@ -102,7 +102,7 @@ MessageBox {
popup.titleText = 'Get Avatars'
popup.bodyText = 'Buy avatars from <b><a href="app://marketplace">Marketplace.</a></b>' + '<br/>' +
'Wear avatars from <b><a href="app://purchases">My Purchases.</a></b>' + '<br/>' + '<br/>' +
'Wear avatars in <b><a href="app://purchases">My Purchases.</a></b>' + '<br/>' + '<br/>' +
'Visit “BodyMart” to get free avatars.'
popup.imageSource = getAvatarsUrl;

View file

@ -186,6 +186,8 @@ Rectangle {
return;
}
var grabbable = MenuInterface.isOptionChecked("Create Entities As Grabbable (except Zones, Particles, and Lights)");
if (defaultURL.endsWith(".jpg") || defaultURL.endsWith(".png")) {
var name = assetProxyModel.data(treeView.selection.currentIndex);
var modelURL = "https://hifi-content.s3.amazonaws.com/DomainContent/production/default-image-model.fbx";
@ -195,7 +197,7 @@ Rectangle {
var collisionless = true;
var position = Vec3.sum(MyAvatar.position, Vec3.multiply(2, Quat.getForward(MyAvatar.orientation)));
var gravity = Vec3.multiply(Vec3.fromPolar(Math.PI / 2, 0), 0);
Entities.addModelEntity(name, modelURL, textures, shapeType, dynamic, collisionless, position, gravity);
Entities.addModelEntity(name, modelURL, textures, shapeType, dynamic, collisionless, grabbable, position, gravity);
} else {
var SHAPE_TYPE_NONE = 0;
var SHAPE_TYPE_SIMPLE_HULL = 1;
@ -281,7 +283,7 @@ Rectangle {
print("Asset browser - adding asset " + url + " (" + name + ") to world.");
// Entities.addEntity doesn't work from QML, so we use this.
Entities.addModelEntity(name, url, "", shapeType, dynamic, collisionless, addPosition, gravity);
Entities.addModelEntity(name, url, "", shapeType, dynamic, collisionless, grabbable, addPosition, gravity);
}
}
});

View file

@ -57,9 +57,9 @@ ListModel {
// Not normally set directly, but rather by giving a truthy argument to getFirstPage(true);
property bool delayedClear: false;
function resetModel() {
if (!delayedClear) { root.clear(); }
currentPageToRetrieve = 1;
retrievedAtLeastOnePage = false;
if (!delayedClear) { root.clear(); }
totalPages = 0;
totalEntries = 0;
}
@ -94,11 +94,13 @@ ListModel {
function needsMoreHorizontalResults() {
return flickable
&& currentPageToRetrieve > 0
&& retrievedAtLeastOnePage
&& flickable.contentWidth < flickable.width;
}
function needsMoreVerticalResults() {
return flickable
&& currentPageToRetrieve > 0
&& retrievedAtLeastOnePage
&& flickable.contentHeight < flickable.height;
}
function getNextPageIfNotEnoughHorizontalResults() {

View file

@ -299,7 +299,7 @@ Item {
anchors.fill: stackView
id: controllerPrefereneces
objectName: "TabletControllerPreferences"
showCategories: [( (HMD.active) ? "VR Movement" : "Movement"), "Game Controller", "Sixense Controllers", "Perception Neuron", "Leap Motion"]
showCategories: ["VR Movement", "Game Controller", "Sixense Controllers", "Perception Neuron", "Leap Motion"]
categoryProperties: {
"VR Movement" : {
"User real-world height (meters)" : { "anchors.right" : "undefined" },

View file

@ -320,11 +320,12 @@ StackView {
width: parent.width;
cardWidth: 312 + (2 * 4);
cardHeight: 163 + (2 * 4);
labelText: 'HAPPENING NOW';
labelText: 'FEATURED';
actions: 'announcement';
filter: addressLine.text;
goFunction: goCard;
http: http;
autoScrollTimerEnabled: true;
}
Feed {
id: places;

View file

@ -45,9 +45,7 @@ QString AboutUtil::getQtVersion() const {
}
void AboutUtil::openUrl(const QString& url) const {
auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
auto tablet = tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system");
auto tablet = DependencyManager::get<TabletScriptingInterface>()->getTablet("com.highfidelity.interface.tablet.system");
auto hmd = DependencyManager::get<HMDScriptingInterface>();
auto offscreenUi = DependencyManager::get<OffscreenUi>();

View file

@ -1139,8 +1139,10 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
// setup a timer for domain-server check ins
QTimer* domainCheckInTimer = new QTimer(this);
connect(domainCheckInTimer, &QTimer::timeout, [this, nodeList] {
if (!isServerlessMode()) {
QWeakPointer<NodeList> nodeListWeak = nodeList;
connect(domainCheckInTimer, &QTimer::timeout, [this, nodeListWeak] {
auto nodeList = nodeListWeak.lock();
if (!isServerlessMode() && nodeList) {
nodeList->sendDomainServerCheckIn();
}
});
@ -1150,33 +1152,34 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
domainCheckInTimer->deleteLater();
});
{
auto audioIO = DependencyManager::get<AudioClient>().data();
audioIO->setPositionGetter([] {
auto avatarManager = DependencyManager::get<AvatarManager>();
auto myAvatar = avatarManager ? avatarManager->getMyAvatar() : nullptr;
auto audioIO = DependencyManager::get<AudioClient>();
audioIO->setPositionGetter([]{
auto avatarManager = DependencyManager::get<AvatarManager>();
auto myAvatar = avatarManager ? avatarManager->getMyAvatar() : nullptr;
return myAvatar ? myAvatar->getPositionForAudio() : Vectors::ZERO;
});
audioIO->setOrientationGetter([] {
auto avatarManager = DependencyManager::get<AvatarManager>();
auto myAvatar = avatarManager ? avatarManager->getMyAvatar() : nullptr;
return myAvatar ? myAvatar->getPositionForAudio() : Vectors::ZERO;
});
audioIO->setOrientationGetter([]{
auto avatarManager = DependencyManager::get<AvatarManager>();
auto myAvatar = avatarManager ? avatarManager->getMyAvatar() : nullptr;
return myAvatar ? myAvatar->getOrientationForAudio() : Quaternions::IDENTITY;
});
return myAvatar ? myAvatar->getOrientationForAudio() : Quaternions::IDENTITY;
});
recording::Frame::registerFrameHandler(AudioConstants::getAudioFrameName(), [&audioIO](recording::Frame::ConstPointer frame) {
audioIO->handleRecordedAudioInput(frame->data);
});
recording::Frame::registerFrameHandler(AudioConstants::getAudioFrameName(), [=](recording::Frame::ConstPointer frame) {
audioIO->handleRecordedAudioInput(frame->data);
});
connect(audioIO.data(), &AudioClient::inputReceived, [](const QByteArray& audio){
static auto recorder = DependencyManager::get<recording::Recorder>();
if (recorder->isRecording()) {
static const recording::FrameType AUDIO_FRAME_TYPE = recording::Frame::registerFrameType(AudioConstants::getAudioFrameName());
recorder->recordFrame(AUDIO_FRAME_TYPE, audio);
}
});
audioIO->startThread();
connect(audioIO, &AudioClient::inputReceived, [](const QByteArray& audio) {
static auto recorder = DependencyManager::get<recording::Recorder>();
if (recorder->isRecording()) {
static const recording::FrameType AUDIO_FRAME_TYPE = recording::Frame::registerFrameType(AudioConstants::getAudioFrameName());
recorder->recordFrame(AUDIO_FRAME_TYPE, audio);
}
});
audioIO->startThread();
}
// Make sure we don't time out during slow operations at startup
updateHeartbeat();
@ -1247,7 +1250,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
accountManager->setAuthURL(NetworkingConstants::METAVERSE_SERVER_URL());
// use our MyAvatar position and quat for address manager path
addressManager->setPositionGetter([this]{ return getMyAvatar()->getWorldPosition(); });
addressManager->setPositionGetter([this]{ return getMyAvatar()->getWorldFeetPosition(); });
addressManager->setOrientationGetter([this]{ return getMyAvatar()->getWorldOrientation(); });
connect(addressManager.data(), &AddressManager::hostChanged, this, &Application::updateWindowTitle);
@ -1275,27 +1278,29 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
// Inititalize sample before registering
_sampleSound = DependencyManager::get<SoundCache>()->getSound(PathUtils::resourcesUrl("sounds/sample.wav"));
auto scriptEngines = DependencyManager::get<ScriptEngines>().data();
scriptEngines->registerScriptInitializer([this](ScriptEnginePointer engine){
registerScriptEngineWithApplicationServices(engine);
});
{
auto scriptEngines = DependencyManager::get<ScriptEngines>().data();
scriptEngines->registerScriptInitializer([this](ScriptEnginePointer engine) {
registerScriptEngineWithApplicationServices(engine);
});
connect(scriptEngines, &ScriptEngines::scriptCountChanged, scriptEngines, [this] {
auto scriptEngines = DependencyManager::get<ScriptEngines>();
if (scriptEngines->getRunningScripts().isEmpty()) {
getMyAvatar()->clearScriptableSettings();
}
}, Qt::QueuedConnection);
connect(scriptEngines, &ScriptEngines::scriptCountChanged, this, [this] {
auto scriptEngines = DependencyManager::get<ScriptEngines>();
if (scriptEngines->getRunningScripts().isEmpty()) {
getMyAvatar()->clearScriptableSettings();
}
}, Qt::QueuedConnection);
connect(scriptEngines, &ScriptEngines::scriptsReloading, scriptEngines, [this] {
getEntities()->reloadEntityScripts();
loadAvatarScripts(getMyAvatar()->getScriptUrls());
}, Qt::QueuedConnection);
connect(scriptEngines, &ScriptEngines::scriptsReloading, this, [this] {
getEntities()->reloadEntityScripts();
loadAvatarScripts(getMyAvatar()->getScriptUrls());
}, Qt::QueuedConnection);
connect(scriptEngines, &ScriptEngines::scriptLoadError,
scriptEngines, [](const QString& filename, const QString& error){
OffscreenUi::asyncWarning(nullptr, "Error Loading Script", filename + " failed to load.");
}, Qt::QueuedConnection);
connect(scriptEngines, &ScriptEngines::scriptLoadError,
this, [](const QString& filename, const QString& error) {
OffscreenUi::asyncWarning(nullptr, "Error Loading Script", filename + " failed to load.");
}, Qt::QueuedConnection);
}
#ifdef _WIN32
WSADATA WsaData;
@ -1365,10 +1370,11 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
// so we defer the setup of the `scripting::Audio` class until this point
{
auto audioScriptingInterface = DependencyManager::set<AudioScriptingInterface, scripting::Audio>();
connect(audioIO.data(), &AudioClient::mutedByMixer, audioScriptingInterface.data(), &AudioScriptingInterface::mutedByMixer);
connect(audioIO.data(), &AudioClient::receivedFirstPacket, audioScriptingInterface.data(), &AudioScriptingInterface::receivedFirstPacket);
connect(audioIO.data(), &AudioClient::disconnected, audioScriptingInterface.data(), &AudioScriptingInterface::disconnected);
connect(audioIO.data(), &AudioClient::muteEnvironmentRequested, [](glm::vec3 position, float radius) {
auto audioIO = DependencyManager::get<AudioClient>().data();
connect(audioIO, &AudioClient::mutedByMixer, audioScriptingInterface.data(), &AudioScriptingInterface::mutedByMixer);
connect(audioIO, &AudioClient::receivedFirstPacket, audioScriptingInterface.data(), &AudioScriptingInterface::receivedFirstPacket);
connect(audioIO, &AudioClient::disconnected, audioScriptingInterface.data(), &AudioScriptingInterface::disconnected);
connect(audioIO, &AudioClient::muteEnvironmentRequested, [](glm::vec3 position, float radius) {
auto audioClient = DependencyManager::get<AudioClient>();
auto audioScriptingInterface = DependencyManager::get<AudioScriptingInterface>();
auto myAvatarPosition = DependencyManager::get<AvatarManager>()->getMyAvatar()->getWorldPosition();
@ -1697,23 +1703,26 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
userInputMapper->registerDevice(_touchscreenVirtualPadDevice->getInputDevice());
}
// this will force the model the look at the correct directory (weird order of operations issue)
scriptEngines->reloadLocalFiles();
{
auto scriptEngines = DependencyManager::get<ScriptEngines>().data();
// this will force the model the look at the correct directory (weird order of operations issue)
scriptEngines->reloadLocalFiles();
// do this as late as possible so that all required subsystems are initialized
// If we've overridden the default scripts location, just load default scripts
// otherwise, load 'em all
// do this as late as possible so that all required subsystems are initialized
// If we've overridden the default scripts location, just load default scripts
// otherwise, load 'em all
// we just want to see if --scripts was set, we've already parsed it and done
// the change in PathUtils. Rather than pass that in the constructor, lets just
// look (this could be debated)
QString scriptsSwitch = QString("--").append(SCRIPTS_SWITCH);
QDir defaultScriptsLocation(getCmdOption(argc, constArgv, scriptsSwitch.toStdString().c_str()));
if (!defaultScriptsLocation.exists()) {
scriptEngines->loadDefaultScripts();
scriptEngines->defaultScriptsLocationOverridden(true);
} else {
scriptEngines->loadScripts();
// we just want to see if --scripts was set, we've already parsed it and done
// the change in PathUtils. Rather than pass that in the constructor, lets just
// look (this could be debated)
QString scriptsSwitch = QString("--").append(SCRIPTS_SWITCH);
QDir defaultScriptsLocation(getCmdOption(argc, constArgv, scriptsSwitch.toStdString().c_str()));
if (!defaultScriptsLocation.exists()) {
scriptEngines->loadDefaultScripts();
scriptEngines->defaultScriptsLocationOverridden(true);
} else {
scriptEngines->loadScripts();
}
}
// Make sure we don't time out during slow operations at startup
@ -1763,13 +1772,16 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
cameraMenuChanged();
}
// set the local loopback interface for local sounds
AudioInjector::setLocalAudioInterface(audioIO.data());
auto audioScriptingInterface = DependencyManager::get<AudioScriptingInterface>();
audioScriptingInterface->setLocalAudioInterface(audioIO.data());
connect(audioIO.data(), &AudioClient::noiseGateOpened, audioScriptingInterface.data(), &AudioScriptingInterface::noiseGateOpened);
connect(audioIO.data(), &AudioClient::noiseGateClosed, audioScriptingInterface.data(), &AudioScriptingInterface::noiseGateClosed);
connect(audioIO.data(), &AudioClient::inputReceived, audioScriptingInterface.data(), &AudioScriptingInterface::inputReceived);
{
auto audioIO = DependencyManager::get<AudioClient>().data();
// set the local loopback interface for local sounds
AudioInjector::setLocalAudioInterface(audioIO);
auto audioScriptingInterface = DependencyManager::get<AudioScriptingInterface>();
audioScriptingInterface->setLocalAudioInterface(audioIO);
connect(audioIO, &AudioClient::noiseGateOpened, audioScriptingInterface.data(), &AudioScriptingInterface::noiseGateOpened);
connect(audioIO, &AudioClient::noiseGateClosed, audioScriptingInterface.data(), &AudioScriptingInterface::noiseGateClosed);
connect(audioIO, &AudioClient::inputReceived, audioScriptingInterface.data(), &AudioScriptingInterface::inputReceived);
}
this->installEventFilter(this);
@ -1826,13 +1838,13 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
}
});
connect(entityScriptingInterface.data(), &EntityScriptingInterface::deletingEntity, [=](const EntityItemID& entityItemID) {
connect(entityScriptingInterface.data(), &EntityScriptingInterface::deletingEntity, [this](const EntityItemID& entityItemID) {
if (entityItemID == _keyboardFocusedEntity.get()) {
setKeyboardFocusEntity(UNKNOWN_ENTITY_ID);
}
});
connect(getEntities()->getTree().get(), &EntityTree::deletingEntity, [=](const EntityItemID& entityItemID) {
connect(getEntities()->getTree().get(), &EntityTree::deletingEntity, [](const EntityItemID& entityItemID) {
auto avatarManager = DependencyManager::get<AvatarManager>();
auto myAvatar = avatarManager ? avatarManager->getMyAvatar() : nullptr;
if (myAvatar) {
@ -1840,7 +1852,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
}
});
EntityTree::setAddMaterialToEntityOperator([&](const QUuid& entityID, graphics::MaterialLayer material, const std::string& parentMaterialName) {
EntityTree::setAddMaterialToEntityOperator([this](const QUuid& entityID, graphics::MaterialLayer material, const std::string& parentMaterialName) {
// try to find the renderable
auto renderable = getEntities()->renderableForEntityId(entityID);
if (renderable) {
@ -1855,7 +1867,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
}
return false;
});
EntityTree::setRemoveMaterialFromEntityOperator([&](const QUuid& entityID, graphics::MaterialPointer material, const std::string& parentMaterialName) {
EntityTree::setRemoveMaterialFromEntityOperator([this](const QUuid& entityID, graphics::MaterialPointer material, const std::string& parentMaterialName) {
// try to find the renderable
auto renderable = getEntities()->renderableForEntityId(entityID);
if (renderable) {
@ -1890,7 +1902,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
return false;
});
EntityTree::setAddMaterialToOverlayOperator([&](const QUuid& overlayID, graphics::MaterialLayer material, const std::string& parentMaterialName) {
EntityTree::setAddMaterialToOverlayOperator([this](const QUuid& overlayID, graphics::MaterialLayer material, const std::string& parentMaterialName) {
auto overlay = _overlays.getOverlay(overlayID);
if (overlay) {
overlay->addMaterial(material, parentMaterialName);
@ -1898,7 +1910,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
}
return false;
});
EntityTree::setRemoveMaterialFromOverlayOperator([&](const QUuid& overlayID, graphics::MaterialPointer material, const std::string& parentMaterialName) {
EntityTree::setRemoveMaterialFromOverlayOperator([this](const QUuid& overlayID, graphics::MaterialPointer material, const std::string& parentMaterialName) {
auto overlay = _overlays.getOverlay(overlayID);
if (overlay) {
overlay->removeMaterial(material, parentMaterialName);
@ -1909,13 +1921,13 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
// Keyboard focus handling for Web overlays.
auto overlays = &(qApp->getOverlays());
connect(overlays, &Overlays::overlayDeleted, [=](const OverlayID& overlayID) {
connect(overlays, &Overlays::overlayDeleted, [this](const OverlayID& overlayID) {
if (overlayID == _keyboardFocusedOverlay.get()) {
setKeyboardFocusOverlay(UNKNOWN_OVERLAY_ID);
}
});
connect(this, &Application::aboutToQuit, [=]() {
connect(this, &Application::aboutToQuit, [this]() {
setKeyboardFocusOverlay(UNKNOWN_OVERLAY_ID);
setKeyboardFocusEntity(UNKNOWN_ENTITY_ID);
});
@ -2184,23 +2196,22 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
QVariant testProperty = property(hifi::properties::TEST);
qDebug() << testProperty;
if (testProperty.isValid()) {
auto scriptEngines = DependencyManager::get<ScriptEngines>();
const auto testScript = property(hifi::properties::TEST).toUrl();
// Set last parameter to exit interface when the test script finishes, if so requested
scriptEngines->loadScript(testScript, false, false, false, false, quitWhenFinished);
DependencyManager::get<ScriptEngines>()->loadScript(testScript, false, false, false, false, quitWhenFinished);
// This is done so we don't get a "connection time-out" message when we haven't passed in a URL.
if (arguments().contains("--url")) {
auto reply = SandboxUtils::getStatus();
connect(reply, &QNetworkReply::finished, this, [=] {
connect(reply, &QNetworkReply::finished, this, [this, reply] {
handleSandboxStatus(reply);
});
}
} else {
PROFILE_RANGE(render, "GetSandboxStatus");
auto reply = SandboxUtils::getStatus();
connect(reply, &QNetworkReply::finished, this, [=] {
connect(reply, &QNetworkReply::finished, this, [this, reply] {
handleSandboxStatus(reply);
});
}
@ -2227,8 +2238,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
connect(&_myCamera, &Camera::modeUpdated, this, &Application::cameraModeChanged);
DependencyManager::get<PickManager>()->setShouldPickHUDOperator([&]() { return DependencyManager::get<HMDScriptingInterface>()->isHMDMode(); });
DependencyManager::get<PickManager>()->setCalculatePos2DFromHUDOperator([&](const glm::vec3& intersection) {
DependencyManager::get<PickManager>()->setShouldPickHUDOperator([]() { return DependencyManager::get<HMDScriptingInterface>()->isHMDMode(); });
DependencyManager::get<PickManager>()->setCalculatePos2DFromHUDOperator([this](const glm::vec3& intersection) {
const glm::vec2 MARGIN(25.0f);
glm::vec2 maxPos = _controllerScriptingInterface->getViewportDimensions() - MARGIN;
glm::vec2 pos2D = DependencyManager::get<HMDScriptingInterface>()->overlayFromWorldPoint(intersection);
@ -2238,7 +2249,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
// Setup the mouse ray pick and related operators
DependencyManager::get<EntityTreeRenderer>()->setMouseRayPickID(DependencyManager::get<PickManager>()->addPick(PickQuery::Ray, std::make_shared<MouseRayPick>(
PickFilter(PickScriptingInterface::PICK_ENTITIES() | PickScriptingInterface::PICK_INCLUDE_NONCOLLIDABLE()), 0.0f, true)));
DependencyManager::get<EntityTreeRenderer>()->setMouseRayPickResultOperator([&](unsigned int rayPickID) {
DependencyManager::get<EntityTreeRenderer>()->setMouseRayPickResultOperator([](unsigned int rayPickID) {
RayToEntityIntersectionResult entityResult;
entityResult.intersects = false;
auto pickResult = DependencyManager::get<PickManager>()->getPrevPickResultTyped<RayPickResult>(rayPickID);
@ -2254,7 +2265,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
}
return entityResult;
});
DependencyManager::get<EntityTreeRenderer>()->setSetPrecisionPickingOperator([&](unsigned int rayPickID, bool value) {
DependencyManager::get<EntityTreeRenderer>()->setSetPrecisionPickingOperator([](unsigned int rayPickID, bool value) {
DependencyManager::get<PickManager>()->setPrecisionPicking(rayPickID, value);
});
@ -2453,21 +2464,35 @@ void Application::cleanupBeforeQuit() {
_keyboardFocusHighlight = nullptr;
}
auto nodeList = DependencyManager::get<NodeList>();
{
auto nodeList = DependencyManager::get<NodeList>();
// send the domain a disconnect packet, force stoppage of domain-server check-ins
nodeList->getDomainHandler().disconnect();
nodeList->setIsShuttingDown(true);
// send the domain a disconnect packet, force stoppage of domain-server check-ins
nodeList->getDomainHandler().disconnect();
nodeList->setIsShuttingDown(true);
// tell the packet receiver we're shutting down, so it can drop packets
nodeList->getPacketReceiver().setShouldDropPackets(true);
// tell the packet receiver we're shutting down, so it can drop packets
nodeList->getPacketReceiver().setShouldDropPackets(true);
}
getEntities()->shutdown(); // tell the entities system we're shutting down, so it will stop running scripts
// Clear any queued processing (I/O, FBX/OBJ/Texture parsing)
QThreadPool::globalInstance()->clear();
DependencyManager::destroy<RecordingScriptingInterface>();
// FIXME: Something is still holding on to the ScriptEnginePointers contained in ScriptEngines, and they hold backpointers to ScriptEngines,
// so this doesn't shut down properly
DependencyManager::get<ScriptEngines>()->shutdownScripting(); // stop all currently running global scripts
// These classes hold ScriptEnginePointers, so they must be destroyed before ScriptEngines
// Must be done after shutdownScripting in case any scripts try to access these things
{
DependencyManager::destroy<StandAloneJSConsole>();
EntityTreePointer tree = getEntities()->getTree();
tree->setSimulation(nullptr);
DependencyManager::destroy<EntityTreeRenderer>();
}
DependencyManager::destroy<ScriptEngines>();
_displayPlugin.reset();
@ -2504,6 +2529,8 @@ void Application::cleanupBeforeQuit() {
DependencyManager::destroy<EyeTracker>();
#endif
DependencyManager::destroy<ContextOverlayInterface>(); // Must be destroyed before TabletScriptingInterface
// stop QML
DependencyManager::destroy<TabletScriptingInterface>();
DependencyManager::destroy<ToolbarScriptingInterface>();
@ -2515,10 +2542,6 @@ void Application::cleanupBeforeQuit() {
_snapshotSoundInjector->stop();
}
// FIXME: something else is holding a reference to AudioClient,
// so it must be explicitly synchronously stopped here
DependencyManager::get<AudioClient>()->cleanupBeforeQuit();
// destroy Audio so it and its threads have a chance to go down safely
// this must happen after QML, as there are unexplained audio crashes originating in qtwebengine
DependencyManager::destroy<AudioClient>();
@ -2558,9 +2581,6 @@ Application::~Application() {
_entityClipboard->eraseAllOctreeElements();
_entityClipboard.reset();
EntityTreePointer tree = getEntities()->getTree();
tree->setSimulation(nullptr);
_octreeProcessor.terminate();
_entityEditSender.terminate();
@ -2703,8 +2723,6 @@ void Application::initializeGL() {
// contexts
_glWidget->makeCurrent();
gpu::Context::init<gpu::gl::GLBackend>();
qApp->setProperty(hifi::properties::gl::MAKE_PROGRAM_CALLBACK,
QVariant::fromValue((void*)(&gpu::gl::GLBackend::makeProgram)));
_glWidget->makeCurrent();
_gpuContext = std::make_shared<gpu::Context>();
@ -2926,6 +2944,15 @@ void Application::initializeUi() {
// Pre-create a couple of Web3D overlays to speed up tablet UI
auto offscreenSurfaceCache = DependencyManager::get<OffscreenQmlSurfaceCache>();
offscreenSurfaceCache->setOnRootContextCreated([&](const QString& rootObject, QQmlContext* surfaceContext) {
if (rootObject == TabletScriptingInterface::QML) {
// in Qt 5.10.0 there is already an "Audio" object in the QML context
// though I failed to find it (from QtMultimedia??). So.. let it be "AudioScriptingInterface"
surfaceContext->setContextProperty("AudioScriptingInterface", DependencyManager::get<AudioScriptingInterface>().data());
surfaceContext->setContextProperty("Account", AccountServicesScriptingInterface::getInstance()); // DEPRECATED - TO BE REMOVED
}
});
offscreenSurfaceCache->reserve(TabletScriptingInterface::QML, 1);
offscreenSurfaceCache->reserve(Web3DOverlay::QML, 2);
@ -3267,8 +3294,7 @@ void Application::showHelp() {
QUrlQuery queryString;
queryString.addQueryItem("handControllerName", handControllerName);
queryString.addQueryItem("defaultTab", defaultTab);
auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
TabletProxy* tablet = dynamic_cast<TabletProxy*>(tabletScriptingInterface->getTablet(SYSTEM_TABLET));
TabletProxy* tablet = dynamic_cast<TabletProxy*>(DependencyManager::get<TabletScriptingInterface>()->getTablet(SYSTEM_TABLET));
tablet->gotoWebScreen(PathUtils::resourcesUrl() + INFO_HELP_PATH + "?" + queryString.toString());
DependencyManager::get<HMDScriptingInterface>()->openTablet();
//InfoView::show(INFO_HELP_PATH, false, queryString.toString());
@ -3581,6 +3607,10 @@ static void dumpEventQueue(QThread* thread) {
bool Application::event(QEvent* event) {
if (_aboutToQuit) {
return false;
}
if (!Menu::getInstance()) {
return false;
}
@ -3698,7 +3728,10 @@ static bool _altPressed{ false };
void Application::keyPressEvent(QKeyEvent* event) {
_altPressed = event->key() == Qt::Key_Alt;
_keysPressed.insert(event->key());
if (!event->isAutoRepeat()) {
_keysPressed.insert(event->key(), *event);
}
_controllerScriptingInterface->emitKeyPressEvent(event); // send events to any registered scripts
// if one of our scripts have asked to capture this event, then stop processing it
@ -3909,7 +3942,9 @@ void Application::keyPressEvent(QKeyEvent* event) {
}
void Application::keyReleaseEvent(QKeyEvent* event) {
_keysPressed.remove(event->key());
if (!event->isAutoRepeat()) {
_keysPressed.remove(event->key());
}
#if defined(Q_OS_ANDROID)
if (event->key() == Qt::Key_Back) {
@ -3945,11 +3980,14 @@ void Application::focusOutEvent(QFocusEvent* event) {
#endif
// synthesize events for keys currently pressed, since we may not get their release events
foreach (int key, _keysPressed) {
QKeyEvent keyEvent(QEvent::KeyRelease, key, Qt::NoModifier);
keyReleaseEvent(&keyEvent);
// Because our key event handlers may manipulate _keysPressed, lets swap the keys pressed into a local copy,
// clearing the existing list.
QHash<int, QKeyEvent> keysPressed;
std::swap(keysPressed, _keysPressed);
for (auto& ev : keysPressed) {
QKeyEvent synthesizedEvent { QKeyEvent::KeyRelease, ev.key(), Qt::NoModifier, ev.text() };
keyReleaseEvent(&synthesizedEvent);
}
_keysPressed.clear();
}
void Application::maybeToggleMenuVisible(QMouseEvent* event) const {
@ -3977,10 +4015,6 @@ void Application::maybeToggleMenuVisible(QMouseEvent* event) const {
void Application::mouseMoveEvent(QMouseEvent* event) {
PROFILE_RANGE(app_input_mouse, __FUNCTION__);
if (_aboutToQuit) {
return;
}
maybeToggleMenuVisible(event);
auto& compositor = getApplicationCompositor();
@ -4045,11 +4079,9 @@ void Application::mousePressEvent(QMouseEvent* event) {
event->screenPos(), event->button(),
event->buttons(), event->modifiers());
if (!_aboutToQuit) {
getOverlays().mousePressEvent(&mappedEvent);
if (!_controllerScriptingInterface->areEntityClicksCaptured()) {
getEntities()->mousePressEvent(&mappedEvent);
}
getOverlays().mousePressEvent(&mappedEvent);
if (!_controllerScriptingInterface->areEntityClicksCaptured()) {
getEntities()->mousePressEvent(&mappedEvent);
}
_controllerScriptingInterface->emitMousePressEvent(&mappedEvent); // send events to any registered scripts
@ -4086,14 +4118,11 @@ void Application::mouseDoublePressEvent(QMouseEvent* event) {
event->screenPos(), event->button(),
event->buttons(), event->modifiers());
if (!_aboutToQuit) {
getOverlays().mouseDoublePressEvent(&mappedEvent);
if (!_controllerScriptingInterface->areEntityClicksCaptured()) {
getEntities()->mouseDoublePressEvent(&mappedEvent);
}
getOverlays().mouseDoublePressEvent(&mappedEvent);
if (!_controllerScriptingInterface->areEntityClicksCaptured()) {
getEntities()->mouseDoublePressEvent(&mappedEvent);
}
// if one of our scripts have asked to capture this event, then stop processing it
if (_controllerScriptingInterface->isMouseCaptured()) {
return;
@ -4112,10 +4141,8 @@ void Application::mouseReleaseEvent(QMouseEvent* event) {
event->screenPos(), event->button(),
event->buttons(), event->modifiers());
if (!_aboutToQuit) {
getOverlays().mouseReleaseEvent(&mappedEvent);
getEntities()->mouseReleaseEvent(&mappedEvent);
}
getOverlays().mouseReleaseEvent(&mappedEvent);
getEntities()->mouseReleaseEvent(&mappedEvent);
_controllerScriptingInterface->emitMouseReleaseEvent(&mappedEvent); // send events to any registered scripts
@ -4266,7 +4293,6 @@ bool Application::shouldPaint() const {
return false;
}
auto displayPlugin = getActiveDisplayPlugin();
#ifdef DEBUG_PAINT_DELAY
@ -4574,11 +4600,14 @@ void Application::idle() {
_lastTimeUpdated.start();
// If the offscreen Ui has something active that is NOT the root, then assume it has keyboard focus.
if (_keyboardDeviceHasFocus && offscreenUi && offscreenUi->getWindow()->activeFocusItem() != offscreenUi->getRootItem()) {
_keyboardMouseDevice->pluginFocusOutEvent();
_keyboardDeviceHasFocus = false;
} else if (offscreenUi && offscreenUi->getWindow()->activeFocusItem() == offscreenUi->getRootItem()) {
_keyboardDeviceHasFocus = true;
if (offscreenUi && offscreenUi->getWindow()) {
auto activeFocusItem = offscreenUi->getWindow()->activeFocusItem();
if (_keyboardDeviceHasFocus && activeFocusItem != offscreenUi->getRootItem()) {
_keyboardMouseDevice->pluginFocusOutEvent();
_keyboardDeviceHasFocus = false;
} else if (activeFocusItem == offscreenUi->getRootItem()) {
_keyboardDeviceHasFocus = true;
}
}
checkChangeCursor();
@ -4743,7 +4772,7 @@ bool Application::exportEntities(const QString& filename,
exportTree->createRootElement();
glm::vec3 root(TREE_SCALE, TREE_SCALE, TREE_SCALE);
bool success = true;
entityTree->withReadLock([&] {
entityTree->withReadLock([entityIDs, entityTree, givenOffset, myAvatarID, &root, &entities, &success, &exportTree] {
for (auto entityID : entityIDs) { // Gather entities and properties.
auto entityItem = entityTree->findEntityByEntityItemID(entityID);
if (!entityItem) {
@ -5284,6 +5313,7 @@ void Application::reloadResourceCaches() {
queryOctree(NodeType::EntityServer, PacketType::EntityQuery);
DependencyManager::get<AssetClient>()->clearCache();
DependencyManager::get<ScriptCache>()->clearCache();
DependencyManager::get<AnimationCache>()->refreshAll();
DependencyManager::get<ModelCache>()->refreshAll();
@ -5491,6 +5521,10 @@ static bool domainLoadingInProgress = false;
void Application::update(float deltaTime) {
PROFILE_RANGE_EX(app, __FUNCTION__, 0xffff0000, (uint64_t)_renderFrameCount + 1);
if (_aboutToQuit) {
return;
}
if (!_physicsEnabled) {
if (!domainLoadingInProgress) {
PROFILE_ASYNC_BEGIN(app, "Scene Loading", "");
@ -5759,15 +5793,13 @@ void Application::update(float deltaTime) {
_entitySimulation->handleDeactivatedMotionStates(deactivations);
});
if (!_aboutToQuit) {
// handleCollisionEvents() AFTER handleChangedMotionStates()
{
PROFILE_RANGE(simulation_physics, "CollisionEvents");
avatarManager->handleCollisionEvents(collisionEvents);
// Collision events (and their scripts) must not be handled when we're locked, above. (That would risk
// deadlock.)
_entitySimulation->handleCollisionEvents(collisionEvents);
}
// handleCollisionEvents() AFTER handleChangedMotionStates()
{
PROFILE_RANGE(simulation_physics, "CollisionEvents");
avatarManager->handleCollisionEvents(collisionEvents);
// Collision events (and their scripts) must not be handled when we're locked, above. (That would risk
// deadlock.)
_entitySimulation->handleCollisionEvents(collisionEvents);
}
{
@ -5785,11 +5817,9 @@ void Application::update(float deltaTime) {
}
auto t4 = std::chrono::high_resolution_clock::now();
if (!_aboutToQuit) {
// NOTE: the getEntities()->update() call below will wait for lock
// and will provide non-physical entity motion
getEntities()->update(true); // update the models...
}
// NOTE: the getEntities()->update() call below will wait for lock
// and will provide non-physical entity motion
getEntities()->update(true); // update the models...
auto t5 = std::chrono::high_resolution_clock::now();
@ -6355,7 +6385,6 @@ void Application::domainURLChanged(QUrl domainURL) {
void Application::resettingDomain() {
_notifiedPacketVersionMismatchThisDomain = false;
auto nodeList = DependencyManager::get<NodeList>();
clearDomainOctreeDetails();
}
@ -6619,11 +6648,12 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEnginePointe
scriptEngine->registerGetterSetter("location", LocationScriptingInterface::locationGetter,
LocationScriptingInterface::locationSetter);
bool clientScript = scriptEngine->isClientScript();
scriptEngine->registerFunction("OverlayWindow", clientScript ? QmlWindowClass::constructor : QmlWindowClass::restricted_constructor);
#if !defined(Q_OS_ANDROID)
scriptEngine->registerFunction("OverlayWebWindow", QmlWebWindowClass::constructor);
scriptEngine->registerFunction("OverlayWebWindow", clientScript ? QmlWebWindowClass::constructor : QmlWebWindowClass::restricted_constructor);
#endif
scriptEngine->registerFunction("OverlayWindow", QmlWindowClass::constructor);
scriptEngine->registerFunction("QmlFragment", QmlFragmentClass::constructor);
scriptEngine->registerFunction("QmlFragment", clientScript ? QmlFragmentClass::constructor : QmlFragmentClass::restricted_constructor);
scriptEngine->registerGlobalObject("Menu", MenuScriptingInterface::getInstance());
scriptEngine->registerGlobalObject("DesktopPreviewProvider", DependencyManager::get<DesktopPreviewProvider>().data());
@ -6705,19 +6735,16 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEnginePointe
registerInteractiveWindowMetaType(scriptEngine.data());
DependencyManager::get<PickScriptingInterface>()->registerMetaTypes(scriptEngine.data());
auto pickScriptingInterface = DependencyManager::get<PickScriptingInterface>();
pickScriptingInterface->registerMetaTypes(scriptEngine.data());
// connect this script engines printedMessage signal to the global ScriptEngines these various messages
connect(scriptEngine.data(), &ScriptEngine::printedMessage,
DependencyManager::get<ScriptEngines>().data(), &ScriptEngines::onPrintedMessage);
connect(scriptEngine.data(), &ScriptEngine::errorMessage,
DependencyManager::get<ScriptEngines>().data(), &ScriptEngines::onErrorMessage);
connect(scriptEngine.data(), &ScriptEngine::warningMessage,
DependencyManager::get<ScriptEngines>().data(), &ScriptEngines::onWarningMessage);
connect(scriptEngine.data(), &ScriptEngine::infoMessage,
DependencyManager::get<ScriptEngines>().data(), &ScriptEngines::onInfoMessage);
connect(scriptEngine.data(), &ScriptEngine::clearDebugWindow,
DependencyManager::get<ScriptEngines>().data(), &ScriptEngines::onClearDebugWindow);
auto scriptEngines = DependencyManager::get<ScriptEngines>().data();
connect(scriptEngine.data(), &ScriptEngine::printedMessage, scriptEngines, &ScriptEngines::onPrintedMessage);
connect(scriptEngine.data(), &ScriptEngine::errorMessage, scriptEngines, &ScriptEngines::onErrorMessage);
connect(scriptEngine.data(), &ScriptEngine::warningMessage, scriptEngines, &ScriptEngines::onWarningMessage);
connect(scriptEngine.data(), &ScriptEngine::infoMessage, scriptEngines, &ScriptEngines::onInfoMessage);
connect(scriptEngine.data(), &ScriptEngine::clearDebugWindow, scriptEngines, &ScriptEngines::onClearDebugWindow);
}
@ -7025,10 +7052,9 @@ void Application::showDialog(const QUrl& widgetUrl, const QUrl& tabletUrl, const
}
void Application::showScriptLogs() {
auto scriptEngines = DependencyManager::get<ScriptEngines>();
QUrl defaultScriptsLoc = PathUtils::defaultScriptsLocation();
defaultScriptsLoc.setPath(defaultScriptsLoc.path() + "developer/debugging/debugWindow.js");
scriptEngines->loadScript(defaultScriptsLoc.toString());
DependencyManager::get<ScriptEngines>()->loadScript(defaultScriptsLoc.toString());
}
void Application::showAssetServerWidget(QString filePath) {
@ -7281,6 +7307,8 @@ void Application::addAssetToWorldAddEntity(QString filePath, QString mapping) {
}
properties.setCollisionless(true); // Temporarily set so that doesn't collide with avatar.
properties.setVisible(false); // Temporarily set so that don't see at large unresized dimensions.
bool grabbable = (Menu::getInstance()->isOptionChecked(MenuOption::CreateEntitiesGrabbable));
properties.setUserData(grabbable ? GRABBABLE_USER_DATA : NOT_GRABBABLE_USER_DATA);
glm::vec3 positionOffset = getMyAvatar()->getWorldOrientation() * (getMyAvatar()->getSensorToWorldScale() * glm::vec3(0.0f, 0.0f, -2.0f));
properties.setPosition(getMyAvatar()->getWorldPosition() + positionOffset);
properties.setRotation(getMyAvatar()->getWorldOrientation());
@ -7323,7 +7351,6 @@ void Application::addAssetToWorldCheckModelSize() {
auto name = properties.getName();
auto dimensions = properties.getDimensions().toGlm();
const QString GRABBABLE_USER_DATA = "{\"grabbableKey\":{\"grabbable\":true}}";
bool doResize = false;
const glm::vec3 DEFAULT_DIMENSIONS = glm::vec3(0.1f, 0.1f, 0.1f);
@ -7367,7 +7394,8 @@ void Application::addAssetToWorldCheckModelSize() {
if (!name.toLower().endsWith(PNG_EXTENSION) && !name.toLower().endsWith(JPG_EXTENSION)) {
properties.setCollisionless(false);
}
properties.setUserData(GRABBABLE_USER_DATA);
bool grabbable = (Menu::getInstance()->isOptionChecked(MenuOption::CreateEntitiesGrabbable));
properties.setUserData(grabbable ? GRABBABLE_USER_DATA : NOT_GRABBABLE_USER_DATA);
properties.setLastEdited(usecTimestampNow());
entityScriptingInterface->editEntity(entityID, properties);
}
@ -7592,7 +7620,6 @@ void Application::openUrl(const QUrl& url) const {
}
void Application::loadDialog() {
auto scriptEngines = DependencyManager::get<ScriptEngines>();
ModalDialogListener* dlg = OffscreenUi::getOpenFileNameAsync(_glWidget, tr("Open Script"),
getPreviousScriptLocation(),
tr("JavaScript Files (*.js)"));

View file

@ -621,7 +621,7 @@ private:
float _mirrorYawOffset;
float _raiseMirror;
QSet<int> _keysPressed;
QHash<int, QKeyEvent> _keysPressed;
bool _enableProcessOctreeThread;

View file

@ -64,7 +64,7 @@ void addAvatarEntities(const QVariantList& avatarEntities) {
EntityItemID id = EntityItemID(QUuid::createUuid());
bool success = true;
entityTree->withWriteLock([&] {
entityTree->withWriteLock([&entityTree, id, &entityProperties, &success] {
EntityItemPointer entity = entityTree->addEntity(id, entityProperties);
if (entity) {
if (entityProperties.queryAACubeRelatedPropertyChanged()) {
@ -171,6 +171,13 @@ void AvatarBookmarks::loadBookmark(const QString& bookmarkName) {
if (bookmarkEntry != _bookmarks.end()) {
QVariantMap bookmark = bookmarkEntry.value().toMap();
if (bookmark.empty()) { // compatibility with bookmarks like this: "Wooden Doll": "http://mpassets.highfidelity.com/7fe80a1e-f445-4800-9e89-40e677b03bee-v1/mannequin.fst?noDownload=false",
auto avatarUrl = bookmarkEntry.value().toString();
if (!avatarUrl.isEmpty()) {
bookmark.insert(ENTRY_AVATAR_URL, avatarUrl);
}
}
if (!bookmark.empty()) {
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
auto treeRenderer = DependencyManager::get<EntityTreeRenderer>();

View file

@ -147,9 +147,11 @@ Menu::Menu() {
auto assetServerAction = addActionToQMenuAndActionHash(editMenu, MenuOption::AssetServer,
Qt::CTRL | Qt::SHIFT | Qt::Key_A,
qApp, SLOT(showAssetServerWidget()));
auto nodeList = DependencyManager::get<NodeList>();
QObject::connect(nodeList.data(), &NodeList::canWriteAssetsChanged, assetServerAction, &QAction::setEnabled);
assetServerAction->setEnabled(nodeList->getThisNodeCanWriteAssets());
{
auto nodeList = DependencyManager::get<NodeList>();
QObject::connect(nodeList.data(), &NodeList::canWriteAssetsChanged, assetServerAction, &QAction::setEnabled);
assetServerAction->setEnabled(nodeList->getThisNodeCanWriteAssets());
}
// Edit > Package Model as .fst...
addActionToQMenuAndActionHash(editMenu, MenuOption::PackageModel, 0,
@ -620,8 +622,11 @@ Menu::Menu() {
addCheckableActionToQMenuAndActionHash(networkMenu, MenuOption::SendWrongProtocolVersion, 0, false,
qApp, SLOT(sendWrongProtocolVersionsSignature(bool)));
addCheckableActionToQMenuAndActionHash(networkMenu, MenuOption::SendWrongDSConnectVersion, 0, false,
nodeList.data(), SLOT(toggleSendNewerDSConnectVersion(bool)));
{
auto nodeList = DependencyManager::get<NodeList>();
addCheckableActionToQMenuAndActionHash(networkMenu, MenuOption::SendWrongDSConnectVersion, 0, false,
nodeList.data(), SLOT(toggleSendNewerDSConnectVersion(bool)));
}
#endif
@ -655,10 +660,9 @@ Menu::Menu() {
action = addActionToQMenuAndActionHash(audioDebugMenu, "Stats...");
connect(action, &QAction::triggered, [] {
auto scriptEngines = DependencyManager::get<ScriptEngines>();
QUrl defaultScriptsLoc = PathUtils::defaultScriptsLocation();
defaultScriptsLoc.setPath(defaultScriptsLoc.path() + "developer/utilities/audio/stats.js");
scriptEngines->loadScript(defaultScriptsLoc.toString());
DependencyManager::get<ScriptEngines>()->loadScript(defaultScriptsLoc.toString());
});
action = addActionToQMenuAndActionHash(audioDebugMenu, "Buffers...");
@ -667,16 +671,14 @@ Menu::Menu() {
QString("hifi/tablet/TabletAudioBuffers.qml"), "AudioBuffersDialog");
});
auto audioIO = DependencyManager::get<AudioClient>();
addActionToQMenuAndActionHash(audioDebugMenu, MenuOption::MuteEnvironment, 0,
audioIO.data(), SLOT(sendMuteEnvironmentPacket()));
DependencyManager::get<AudioClient>().data(), SLOT(sendMuteEnvironmentPacket()));
action = addActionToQMenuAndActionHash(audioDebugMenu, MenuOption::AudioScope);
connect(action, &QAction::triggered, [] {
auto scriptEngines = DependencyManager::get<ScriptEngines>();
QUrl defaultScriptsLoc = PathUtils::defaultScriptsLocation();
defaultScriptsLoc.setPath(defaultScriptsLoc.path() + "developer/utilities/audio/audioScope.js");
scriptEngines->loadScript(defaultScriptsLoc.toString());
DependencyManager::get<ScriptEngines>()->loadScript(defaultScriptsLoc.toString());
});
// Developer > Physics >>>
@ -756,10 +758,9 @@ Menu::Menu() {
// Developer > API Debugger
action = addActionToQMenuAndActionHash(developerMenu, "API Debugger");
connect(action, &QAction::triggered, [] {
auto scriptEngines = DependencyManager::get<ScriptEngines>();
QUrl defaultScriptsLoc = PathUtils::defaultScriptsLocation();
defaultScriptsLoc.setPath(defaultScriptsLoc.path() + "developer/utilities/tools/currentAPI.js");
scriptEngines->loadScript(defaultScriptsLoc.toString());
DependencyManager::get<ScriptEngines>()->loadScript(defaultScriptsLoc.toString());
});
// Developer > Log...
@ -767,11 +768,14 @@ Menu::Menu() {
qApp, SLOT(toggleLogDialog()));
auto essLogAction = addActionToQMenuAndActionHash(developerMenu, MenuOption::EntityScriptServerLog, 0,
qApp, SLOT(toggleEntityScriptServerLogDialog()));
QObject::connect(nodeList.data(), &NodeList::canRezChanged, essLogAction, [essLogAction] {
{
auto nodeList = DependencyManager::get<NodeList>();
QObject::connect(nodeList.data(), &NodeList::canRezChanged, essLogAction, [essLogAction] {
auto nodeList = DependencyManager::get<NodeList>();
essLogAction->setEnabled(nodeList->getThisNodeCanRez());
});
essLogAction->setEnabled(nodeList->getThisNodeCanRez());
});
essLogAction->setEnabled(nodeList->getThisNodeCanRez());
}
addActionToQMenuAndActionHash(developerMenu, "Script Log (HMD friendly)...", Qt::NoButton,
qApp, SLOT(showScriptLogs()));

View file

@ -76,6 +76,7 @@ namespace MenuOption {
const QString CrashOutOfBoundsVectorAccessThreaded = "Out of Bounds Vector Access (threaded)";
const QString CrashNewFault = "New Fault";
const QString CrashNewFaultThreaded = "New Fault (threaded)";
const QString CreateEntitiesGrabbable = "Create Entities As Grabbable (except Zones, Particles, and Lights)";
const QString DeadlockInterface = "Deadlock Interface";
const QString UnresponsiveInterface = "Unresponsive Interface";
const QString DecreaseAvatarSize = "Decrease Avatar Size";

View file

@ -205,7 +205,9 @@ void SecondaryCameraRenderTask::build(JobModel& task, const render::Varying& inp
const auto items = task.addJob<RenderFetchCullSortTask>("FetchCullSort", cullFunctor, render::ItemKey::TAG_BITS_1, render::ItemKey::TAG_BITS_1);
assert(items.canCast<RenderFetchCullSortTask::Output>());
if (isDeferred) {
task.addJob<RenderDeferredTask>("RenderDeferredTask", items, false);
const render::Varying cascadeSceneBBoxes;
const auto renderInput = RenderDeferredTask::Input(items, cascadeSceneBBoxes).asVarying();
task.addJob<RenderDeferredTask>("RenderDeferredTask", renderInput, false);
} else {
task.addJob<RenderForwardTask>("Forward", items);
}

View file

@ -79,7 +79,7 @@ AvatarManager::AvatarManager(QObject* parent) :
// when we hear that the user has ignored an avatar by session UUID
// immediately remove that avatar instead of waiting for the absence of packets from avatar mixer
connect(nodeList.data(), &NodeList::ignoredNode, this, [=](const QUuid& nodeID, bool enabled) {
connect(nodeList.data(), &NodeList::ignoredNode, this, [this](const QUuid& nodeID, bool enabled) {
if (enabled) {
removeAvatar(nodeID, KillAvatarReason::AvatarIgnored);
}
@ -278,20 +278,8 @@ void AvatarManager::updateOtherAvatars(float deltaTime) {
}
if (_shouldRender) {
if (!_avatarsToFade.empty()) {
QReadLocker lock(&_hashLock);
QVector<AvatarSharedPointer>::iterator itr = _avatarsToFade.begin();
while (itr != _avatarsToFade.end() && usecTimestampNow() > updateExpiry) {
auto avatar = std::static_pointer_cast<Avatar>(*itr);
avatar->animateScaleChanges(deltaTime);
avatar->simulate(deltaTime, true);
avatar->updateRenderItem(transaction);
++itr;
}
}
qApp->getMain3DScene()->enqueueTransaction(transaction);
}
_numAvatarsUpdated = numAvatarsUpdated;
_numAvatarsNotUpdated = numAVatarsNotUpdated;
@ -333,16 +321,21 @@ void AvatarManager::postUpdate(float deltaTime, const render::ScenePointer& scen
void AvatarManager::sendIdentityRequest(const QUuid& avatarID) const {
auto nodeList = DependencyManager::get<NodeList>();
QWeakPointer<NodeList> nodeListWeak = nodeList;
nodeList->eachMatchingNode(
[&](const SharedNodePointer& node)->bool {
return node->getType() == NodeType::AvatarMixer && node->getActiveSocket();
},
[&](const SharedNodePointer& node) {
auto packet = NLPacket::create(PacketType::AvatarIdentityRequest, NUM_BYTES_RFC4122_UUID, true);
packet->write(avatarID.toRfc4122());
nodeList->sendPacket(std::move(packet), *node);
++_identityRequestsSent;
});
[](const SharedNodePointer& node)->bool {
return node->getType() == NodeType::AvatarMixer && node->getActiveSocket();
},
[this, avatarID, nodeListWeak](const SharedNodePointer& node) {
auto nodeList = nodeListWeak.lock();
if (nodeList) {
auto packet = NLPacket::create(PacketType::AvatarIdentityRequest, NUM_BYTES_RFC4122_UUID, true);
packet->write(avatarID.toRfc4122());
nodeList->sendPacket(std::move(packet), *node);
++_identityRequestsSent;
}
}
);
}
void AvatarManager::simulateAvatarFades(float deltaTime) {
@ -365,8 +358,6 @@ void AvatarManager::simulateAvatarFades(float deltaTime) {
}
avatarItr = _avatarsToFade.erase(avatarItr);
} else {
const bool inView = true; // HACK
avatar->simulate(deltaTime, inView);
++avatarItr;
}
}
@ -412,9 +403,6 @@ void AvatarManager::clearOtherAvatars() {
while (avatarIterator != _avatarHash.end()) {
auto avatar = std::static_pointer_cast<Avatar>(avatarIterator.value());
if (avatar != _myAvatar) {
if (avatar->isInScene()) {
avatar->removeFromScene(avatar, scene, transaction);
}
handleRemovedAvatar(avatar);
avatarIterator = _avatarHash.erase(avatarIterator);
} else {

View file

@ -139,6 +139,12 @@ MyAvatar::MyAvatar(QThread* thread) :
auto geometry = getSkeletonModel()->getFBXGeometry();
qApp->loadAvatarScripts(geometry.scripts);
_shouldLoadScripts = false;
}
// Load and convert old attachments to avatar entities
if (_oldAttachmentData.size() > 0) {
setAttachmentData(_oldAttachmentData);
_oldAttachmentData.clear();
_attachmentData.clear();
}
});
connect(_skeletonModel.get(), &Model::rigReady, this, &Avatar::rigReady);
@ -154,7 +160,7 @@ MyAvatar::MyAvatar(QThread* thread) :
// connect to AddressManager signal for location jumps
connect(DependencyManager::get<AddressManager>().data(), &AddressManager::locationChangeRequired,
this, static_cast<SlotType>(&MyAvatar::goToLocation));
this, static_cast<SlotType>(&MyAvatar::goToFeetLocation));
// handle scale constraints imposed on us by the domain-server
auto& domainHandler = DependencyManager::get<NodeList>()->getDomainHandler();
@ -492,14 +498,14 @@ void MyAvatar::update(float deltaTime) {
// Get audio loudness data from audio input device
// Also get the AudioClient so we can update the avatar bounding box data
// on the AudioClient side.
auto audio = DependencyManager::get<AudioClient>();
auto audio = DependencyManager::get<AudioClient>().data();
setAudioLoudness(audio->getLastInputLoudness());
setAudioAverageLoudness(audio->getAudioAverageInputLoudness());
glm::vec3 halfBoundingBoxDimensions(_characterController.getCapsuleRadius(), _characterController.getCapsuleHalfHeight(), _characterController.getCapsuleRadius());
// This might not be right! Isn't the capsule local offset in avatar space? -HRS 5/26/17
halfBoundingBoxDimensions += _characterController.getCapsuleLocalOffset();
QMetaObject::invokeMethod(audio.data(), "setAvatarBoundingBoxParameters",
QMetaObject::invokeMethod(audio, "setAvatarBoundingBoxParameters",
Q_ARG(glm::vec3, (getWorldPosition() - halfBoundingBoxDimensions)),
Q_ARG(glm::vec3, (halfBoundingBoxDimensions*2.0f)));
@ -1135,7 +1141,6 @@ void MyAvatar::saveData() {
settings.setValue("collisionSoundURL", _collisionSoundURL);
settings.setValue("useSnapTurn", _useSnapTurn);
settings.setValue("userHeight", getUserHeight());
settings.setValue("flyingDesktop", getFlyingDesktopPref());
settings.setValue("flyingHMD", getFlyingHMDPref());
settings.endGroup();
@ -1250,7 +1255,6 @@ void MyAvatar::loadData() {
useFullAvatarURL(_fullAvatarURLFromPreferences, _fullAvatarModelName);
QVector<AttachmentData> attachmentData;
int attachmentCount = settings.beginReadArray("attachmentData");
for (int i = 0; i < attachmentCount; i++) {
settings.setArrayIndex(i);
@ -1267,10 +1271,10 @@ void MyAvatar::loadData() {
attachment.rotation = glm::quat(eulers);
attachment.scale = loadSetting(settings, "scale", 1.0f);
attachment.isSoft = settings.value("isSoft").toBool();
attachmentData.append(attachment);
// old attachments are stored and loaded/converted later when rig is ready
_oldAttachmentData.append(attachment);
}
settings.endArray();
setAttachmentData(attachmentData);
int avatarEntityCount = settings.beginReadArray("avatarEntityData");
for (int i = 0; i < avatarEntityCount; i++) {
@ -1289,7 +1293,6 @@ void MyAvatar::loadData() {
// Flying preferences must be loaded before calling setFlyingEnabled()
Setting::Handle<bool> firstRunVal { Settings::firstRun, true };
setFlyingDesktopPref(firstRunVal.get() ? true : settings.value("flyingDesktop").toBool());
setFlyingHMDPref(firstRunVal.get() ? false : settings.value("flyingHMD").toBool());
setFlyingEnabled(getFlyingEnabled());
@ -1496,50 +1499,126 @@ void MyAvatar::setJointRotations(const QVector<glm::quat>& jointRotations) {
}
void MyAvatar::setJointData(int index, const glm::quat& rotation, const glm::vec3& translation) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "setJointData", Q_ARG(int, index), Q_ARG(const glm::quat&, rotation),
Q_ARG(const glm::vec3&, translation));
return;
switch (index) {
case FARGRAB_RIGHTHAND_INDEX: {
_farGrabRightMatrixCache.set(createMatFromQuatAndPos(rotation, translation));
break;
}
case FARGRAB_LEFTHAND_INDEX: {
_farGrabLeftMatrixCache.set(createMatFromQuatAndPos(rotation, translation));
break;
}
case FARGRAB_MOUSE_INDEX: {
_farGrabMouseMatrixCache.set(createMatFromQuatAndPos(rotation, translation));
break;
}
default: {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "setJointData", Q_ARG(int, index), Q_ARG(const glm::quat&, rotation),
Q_ARG(const glm::vec3&, translation));
return;
}
// HACK: ATM only JS scripts call setJointData() on MyAvatar so we hardcode the priority
_skeletonModel->getRig().setJointState(index, true, rotation, translation, SCRIPT_PRIORITY);
}
}
// HACK: ATM only JS scripts call setJointData() on MyAvatar so we hardcode the priority
_skeletonModel->getRig().setJointState(index, true, rotation, translation, SCRIPT_PRIORITY);
}
void MyAvatar::setJointRotation(int index, const glm::quat& rotation) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "setJointRotation", Q_ARG(int, index), Q_ARG(const glm::quat&, rotation));
return;
switch (index) {
case FARGRAB_RIGHTHAND_INDEX: {
glm::mat4 prevMat = _farGrabRightMatrixCache.get();
glm::vec3 previousTranslation = extractTranslation(prevMat);
_farGrabRightMatrixCache.set(createMatFromQuatAndPos(rotation, previousTranslation));
break;
}
case FARGRAB_LEFTHAND_INDEX: {
glm::mat4 prevMat = _farGrabLeftMatrixCache.get();
glm::vec3 previousTranslation = extractTranslation(prevMat);
_farGrabLeftMatrixCache.set(createMatFromQuatAndPos(rotation, previousTranslation));
break;
}
case FARGRAB_MOUSE_INDEX: {
glm::mat4 prevMat = _farGrabMouseMatrixCache.get();
glm::vec3 previousTranslation = extractTranslation(prevMat);
_farGrabMouseMatrixCache.set(createMatFromQuatAndPos(rotation, previousTranslation));
break;
}
default: {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "setJointRotation", Q_ARG(int, index), Q_ARG(const glm::quat&, rotation));
return;
}
// HACK: ATM only JS scripts call setJointData() on MyAvatar so we hardcode the priority
_skeletonModel->getRig().setJointRotation(index, true, rotation, SCRIPT_PRIORITY);
}
}
// HACK: ATM only JS scripts call setJointData() on MyAvatar so we hardcode the priority
_skeletonModel->getRig().setJointRotation(index, true, rotation, SCRIPT_PRIORITY);
}
void MyAvatar::setJointTranslation(int index, const glm::vec3& translation) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "setJointTranslation", Q_ARG(int, index), Q_ARG(const glm::vec3&, translation));
return;
switch (index) {
case FARGRAB_RIGHTHAND_INDEX: {
glm::mat4 prevMat = _farGrabRightMatrixCache.get();
glm::quat previousRotation = extractRotation(prevMat);
_farGrabRightMatrixCache.set(createMatFromQuatAndPos(previousRotation, translation));
break;
}
case FARGRAB_LEFTHAND_INDEX: {
glm::mat4 prevMat = _farGrabLeftMatrixCache.get();
glm::quat previousRotation = extractRotation(prevMat);
_farGrabLeftMatrixCache.set(createMatFromQuatAndPos(previousRotation, translation));
break;
}
case FARGRAB_MOUSE_INDEX: {
glm::mat4 prevMat = _farGrabMouseMatrixCache.get();
glm::quat previousRotation = extractRotation(prevMat);
_farGrabMouseMatrixCache.set(createMatFromQuatAndPos(previousRotation, translation));
break;
}
default: {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "setJointTranslation",
Q_ARG(int, index), Q_ARG(const glm::vec3&, translation));
return;
}
// HACK: ATM only JS scripts call setJointData() on MyAvatar so we hardcode the priority
_skeletonModel->getRig().setJointTranslation(index, true, translation, SCRIPT_PRIORITY);
}
}
// HACK: ATM only JS scripts call setJointData() on MyAvatar so we hardcode the priority
_skeletonModel->getRig().setJointTranslation(index, true, translation, SCRIPT_PRIORITY);
}
void MyAvatar::clearJointData(int index) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "clearJointData", Q_ARG(int, index));
return;
switch (index) {
case FARGRAB_RIGHTHAND_INDEX: {
_farGrabRightMatrixCache.invalidate();
break;
}
case FARGRAB_LEFTHAND_INDEX: {
_farGrabLeftMatrixCache.invalidate();
break;
}
case FARGRAB_MOUSE_INDEX: {
_farGrabMouseMatrixCache.invalidate();
break;
}
default: {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "clearJointData", Q_ARG(int, index));
return;
}
_skeletonModel->getRig().clearJointAnimationPriority(index);
}
}
_skeletonModel->getRig().clearJointAnimationPriority(index);
}
void MyAvatar::setJointData(const QString& name, const glm::quat& rotation, const glm::vec3& translation) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "setJointData", Q_ARG(QString, name), Q_ARG(const glm::quat&, rotation),
Q_ARG(const glm::vec3&, translation));
Q_ARG(const glm::vec3&, translation));
return;
}
writeLockWithNamedJointIndex(name, [&](int index) {
// HACK: ATM only JS scripts call setJointData() on MyAvatar so we hardcode the priority
_skeletonModel->getRig().setJointState(index, true, rotation, translation, SCRIPT_PRIORITY);
setJointData(index, rotation, translation);
});
}
@ -1549,8 +1628,7 @@ void MyAvatar::setJointRotation(const QString& name, const glm::quat& rotation)
return;
}
writeLockWithNamedJointIndex(name, [&](int index) {
// HACK: ATM only JS scripts call setJointData() on MyAvatar so we hardcode the priority
_skeletonModel->getRig().setJointRotation(index, true, rotation, SCRIPT_PRIORITY);
setJointRotation(index, rotation);
});
}
@ -1560,8 +1638,7 @@ void MyAvatar::setJointTranslation(const QString& name, const glm::vec3& transla
return;
}
writeLockWithNamedJointIndex(name, [&](int index) {
// HACK: ATM only JS scripts call setJointData() on MyAvatar so we hardcode the priority
_skeletonModel->getRig().setJointTranslation(index, true, translation, SCRIPT_PRIORITY);
setJointTranslation(index, translation);
});
}
@ -1571,7 +1648,7 @@ void MyAvatar::clearJointData(const QString& name) {
return;
}
writeLockWithNamedJointIndex(name, [&](int index) {
_skeletonModel->getRig().clearJointAnimationPriority(index);
clearJointData(index);
});
}
@ -1580,6 +1657,9 @@ void MyAvatar::clearJointsData() {
QMetaObject::invokeMethod(this, "clearJointsData");
return;
}
_farGrabRightMatrixCache.invalidate();
_farGrabLeftMatrixCache.invalidate();
_farGrabMouseMatrixCache.invalidate();
_skeletonModel->getRig().clearJointStates();
}
@ -1690,16 +1770,6 @@ void MyAvatar::useFullAvatarURL(const QUrl& fullAvatarURL, const QString& modelN
markIdentityDataChanged();
}
void MyAvatar::setAttachmentData(const QVector<AttachmentData>& attachmentData) {
if (QThread::currentThread() != thread()) {
BLOCKING_INVOKE_METHOD(this, "setAttachmentData",
Q_ARG(const QVector<AttachmentData>, attachmentData));
return;
}
Avatar::setAttachmentData(attachmentData);
emit attachmentsChanged();
}
glm::vec3 MyAvatar::getSkeletonPosition() const {
CameraMode mode = qApp->getCamera().getMode();
if (mode == CAMERA_MODE_THIRD_PERSON || mode == CAMERA_MODE_INDEPENDENT) {
@ -1970,20 +2040,164 @@ void MyAvatar::attach(const QString& modelURL, const QString& jointName,
float scale, bool isSoft,
bool allowDuplicates, bool useSaved) {
if (QThread::currentThread() != thread()) {
Avatar::attach(modelURL, jointName, translation, rotation, scale, isSoft, allowDuplicates, useSaved);
BLOCKING_INVOKE_METHOD(this, "attach",
Q_ARG(const QString&, modelURL),
Q_ARG(const QString&, jointName),
Q_ARG(const glm::vec3&, translation),
Q_ARG(const glm::quat&, rotation),
Q_ARG(float, scale),
Q_ARG(bool, isSoft),
Q_ARG(bool, allowDuplicates),
Q_ARG(bool, useSaved)
);
return;
}
if (useSaved) {
AttachmentData attachment = loadAttachmentData(modelURL, jointName);
if (attachment.isValid()) {
Avatar::attach(modelURL, attachment.jointName,
attachment.translation, attachment.rotation,
attachment.scale, attachment.isSoft,
allowDuplicates, useSaved);
return;
AttachmentData data;
data.modelURL = modelURL;
data.jointName = jointName;
data.translation = translation;
data.rotation = rotation;
data.scale = scale;
data.isSoft = isSoft;
EntityItemProperties properties;
attachmentDataToEntityProperties(data, properties);
DependencyManager::get<EntityScriptingInterface>()->addEntity(properties, true);
emit attachmentsChanged();
}
void MyAvatar::detachOne(const QString& modelURL, const QString& jointName) {
if (QThread::currentThread() != thread()) {
BLOCKING_INVOKE_METHOD(this, "detachOne",
Q_ARG(const QString&, modelURL),
Q_ARG(const QString&, jointName)
);
return;
}
QUuid entityID;
if (findAvatarEntity(modelURL, jointName, entityID)) {
DependencyManager::get<EntityScriptingInterface>()->deleteEntity(entityID);
}
emit attachmentsChanged();
}
void MyAvatar::detachAll(const QString& modelURL, const QString& jointName) {
if (QThread::currentThread() != thread()) {
BLOCKING_INVOKE_METHOD(this, "detachAll",
Q_ARG(const QString&, modelURL),
Q_ARG(const QString&, jointName)
);
return;
}
QUuid entityID;
while (findAvatarEntity(modelURL, jointName, entityID)) {
DependencyManager::get<EntityScriptingInterface>()->deleteEntity(entityID);
}
emit attachmentsChanged();
}
void MyAvatar::setAttachmentData(const QVector<AttachmentData>& attachmentData) {
if (QThread::currentThread() != thread()) {
BLOCKING_INVOKE_METHOD(this, "setAttachmentData",
Q_ARG(const QVector<AttachmentData>&, attachmentData));
return;
}
std::vector<EntityItemProperties> newEntitiesProperties;
for (auto& data : attachmentData) {
QUuid entityID;
EntityItemProperties properties;
if (findAvatarEntity(data.modelURL.toString(), data.jointName, entityID)) {
properties = DependencyManager::get<EntityScriptingInterface>()->getEntityProperties(entityID);
}
attachmentDataToEntityProperties(data, properties);
newEntitiesProperties.push_back(properties);
}
removeAvatarEntities();
for (auto& properties : newEntitiesProperties) {
DependencyManager::get<EntityScriptingInterface>()->addEntity(properties, true);
}
emit attachmentsChanged();
}
QVector<AttachmentData> MyAvatar::getAttachmentData() const {
QVector<AttachmentData> avatarData;
auto avatarEntities = getAvatarEntityData();
AvatarEntityMap::const_iterator dataItr = avatarEntities.begin();
while (dataItr != avatarEntities.end()) {
QUuid entityID = dataItr.key();
auto properties = DependencyManager::get<EntityScriptingInterface>()->getEntityProperties(entityID);
AttachmentData data = entityPropertiesToAttachmentData(properties);
avatarData.append(data);
dataItr++;
}
return avatarData;
}
QVariantList MyAvatar::getAttachmentsVariant() const {
QVariantList result;
for (const auto& attachment : getAttachmentData()) {
result.append(attachment.toVariant());
}
return result;
}
void MyAvatar::setAttachmentsVariant(const QVariantList& variant) {
if (QThread::currentThread() != thread()) {
BLOCKING_INVOKE_METHOD(this, "setAttachmentsVariant",
Q_ARG(const QVariantList&, variant));
return;
}
QVector<AttachmentData> newAttachments;
newAttachments.reserve(variant.size());
for (const auto& attachmentVar : variant) {
AttachmentData attachment;
if (attachment.fromVariant(attachmentVar)) {
newAttachments.append(attachment);
}
}
Avatar::attach(modelURL, jointName, translation, rotation, scale, isSoft, allowDuplicates, useSaved);
setAttachmentData(newAttachments);
}
bool MyAvatar::findAvatarEntity(const QString& modelURL, const QString& jointName, QUuid& entityID) {
auto avatarEntities = getAvatarEntityData();
AvatarEntityMap::const_iterator dataItr = avatarEntities.begin();
while (dataItr != avatarEntities.end()) {
entityID = dataItr.key();
auto props = DependencyManager::get<EntityScriptingInterface>()->getEntityProperties(entityID);
if (props.getModelURL() == modelURL &&
(jointName.isEmpty() || props.getParentJointIndex() == getJointIndex(jointName))) {
return true;
}
dataItr++;
}
return false;
}
AttachmentData MyAvatar::entityPropertiesToAttachmentData(const EntityItemProperties& properties) const {
AttachmentData data;
data.modelURL = properties.getModelURL();
data.translation = properties.getLocalPosition();
data.rotation = properties.getLocalRotation();
data.isSoft = properties.getRelayParentJoints();
int jointIndex = (int)properties.getParentJointIndex();
if (jointIndex > -1 && jointIndex < getJointNames().size()) {
data.jointName = getJointNames()[jointIndex];
}
return data;
}
void MyAvatar::attachmentDataToEntityProperties(const AttachmentData& data, EntityItemProperties& properties) {
QString url = data.modelURL.toString();
properties.setName(QFileInfo(url).baseName());
properties.setType(EntityTypes::Model);
properties.setParentID(AVATAR_SELF_ID);
properties.setLocalPosition(data.translation);
properties.setLocalRotation(data.rotation);
if (!data.isSoft) {
properties.setParentJointIndex(getJointIndex(data.jointName));
} else {
properties.setRelayParentJoints(true);
}
properties.setModelURL(url);
}
void MyAvatar::initHeadBones() {
@ -2338,6 +2552,23 @@ void MyAvatar::updateOrientation(float deltaTime) {
}
}
static float scaleSpeedByDirection(const glm::vec2 velocityDirection, const float forwardSpeed, const float backwardSpeed) {
// for the elipse function --> (x^2)/(backwardSpeed*backwardSpeed) + y^2/(forwardSpeed*forwardSpeed) = 1, scale == y^2 when x is 0
float fwdScale = forwardSpeed * forwardSpeed;
float backScale = backwardSpeed * backwardSpeed;
float scaledX = velocityDirection.x * backwardSpeed;
float scaledSpeed = forwardSpeed;
if (velocityDirection.y < 0.0f) {
if (backScale > 0.0f) {
float yValue = sqrtf(fwdScale * (1.0f - ((scaledX * scaledX) / backScale)));
scaledSpeed = sqrtf((scaledX * scaledX) + (yValue * yValue));
}
} else {
scaledSpeed = backwardSpeed;
}
return scaledSpeed;
}
void MyAvatar::updateActionMotor(float deltaTime) {
bool thrustIsPushing = (glm::length2(_thrust) > EPSILON);
bool scriptedMotorIsPushing = (_motionBehaviors & AVATAR_MOTION_SCRIPTED_MOTOR_ENABLED)
@ -2399,7 +2630,10 @@ void MyAvatar::updateActionMotor(float deltaTime) {
_actionMotorVelocity = motorSpeed * direction;
} else {
// we're interacting with a floor --> simple horizontal speed and exponential decay
_actionMotorVelocity = getSensorToWorldScale() * (_walkSpeed.get() * _walkSpeedScalar) * direction;
const glm::vec2 currentVel = { direction.x, direction.z };
float scaledSpeed = scaleSpeedByDirection(currentVel, _walkSpeed.get(), _walkBackwardSpeed.get());
// _walkSpeedScalar is a multiplier if we are in sprint mode, otherwise 1.0
_actionMotorVelocity = getSensorToWorldScale() * (scaledSpeed * _walkSpeedScalar) * direction;
}
float previousBoomLength = _boomLength;
@ -2625,6 +2859,49 @@ void MyAvatar::goToLocation(const QVariant& propertiesVar) {
}
}
void MyAvatar::goToFeetLocation(const glm::vec3& newPosition,
bool hasOrientation, const glm::quat& newOrientation,
bool shouldFaceLocation) {
qCDebug(interfaceapp).nospace() << "MyAvatar goToFeetLocation - moving to " << newPosition.x << ", "
<< newPosition.y << ", " << newPosition.z;
ShapeInfo shapeInfo;
computeShapeInfo(shapeInfo);
glm::vec3 halfExtents = shapeInfo.getHalfExtents();
glm::vec3 localFeetPos = shapeInfo.getOffset() - glm::vec3(0.0f, halfExtents.y + halfExtents.x, 0.0f);
glm::mat4 localFeet = createMatFromQuatAndPos(Quaternions::IDENTITY, localFeetPos);
glm::mat4 worldFeet = createMatFromQuatAndPos(Quaternions::IDENTITY, newPosition);
glm::mat4 avatarMat = worldFeet * glm::inverse(localFeet);
glm::vec3 adjustedPosition = extractTranslation(avatarMat);
_goToPending = true;
_goToPosition = adjustedPosition;
_goToOrientation = getWorldOrientation();
if (hasOrientation) {
qCDebug(interfaceapp).nospace() << "MyAvatar goToFeetLocation - new orientation is "
<< newOrientation.x << ", " << newOrientation.y << ", " << newOrientation.z << ", " << newOrientation.w;
// orient the user to face the target
glm::quat quatOrientation = cancelOutRollAndPitch(newOrientation);
if (shouldFaceLocation) {
quatOrientation = newOrientation * glm::angleAxis(PI, Vectors::UP);
// move the user a couple units away
const float DISTANCE_TO_USER = 2.0f;
_goToPosition = adjustedPosition - quatOrientation * IDENTITY_FORWARD * DISTANCE_TO_USER;
}
_goToOrientation = quatOrientation;
}
emit transformChanged();
}
void MyAvatar::goToLocation(const glm::vec3& newPosition,
bool hasOrientation, const glm::quat& newOrientation,
bool shouldFaceLocation) {
@ -3408,18 +3685,34 @@ float MyAvatar::getWalkSpeed() const {
return _walkSpeed.get() * _walkSpeedScalar;
}
float MyAvatar::getWalkBackwardSpeed() const {
return _walkSpeed.get() * _walkSpeedScalar;
}
bool MyAvatar::isReadyForPhysics() const {
return qApp->isServerlessMode() || _haveReceivedHeightLimitsFromDomain;
}
void MyAvatar::setSprintMode(bool sprint) {
_walkSpeedScalar = sprint ? AVATAR_SPRINT_SPEED_SCALAR : AVATAR_WALK_SPEED_SCALAR;
_walkSpeedScalar = sprint ? _sprintSpeed.get() : AVATAR_WALK_SPEED_SCALAR;
}
void MyAvatar::setWalkSpeed(float value) {
_walkSpeed.set(value);
}
void MyAvatar::setWalkBackwardSpeed(float value) {
_walkBackwardSpeed.set(value);
}
void MyAvatar::setSprintSpeed(float value) {
_sprintSpeed.set(value);
}
float MyAvatar::getSprintSpeed() const {
return _sprintSpeed.get();
}
QVector<QString> MyAvatar::getScriptUrls() {
QVector<QString> scripts = _skeletonModel->isLoaded() ? _skeletonModel->getFBXGeometry().scripts : QVector<QString>();
return scripts;

View file

@ -138,12 +138,14 @@ class MyAvatar : public Avatar {
* where MyAvatar.sessionUUID is not available (e.g., if not connected to a domain). Note: Likely to be deprecated.
* <em>Read-only.</em>
* @property {number} walkSpeed
* @property {number} walkBackwardSpeed
* @property {number} sprintSpeed
*
* @property {Vec3} skeletonOffset - Can be used to apply a translation offset between the avatar's position and the
* registration point of the 3D model.
*
* @property {Vec3} position
* @property {number} scale
* @property {number} scale - Returns the clamped scale of the avatar.
* @property {number} density <em>Read-only.</em>
* @property {Vec3} handPosition
* @property {number} bodyYaw - The rotation left or right about an axis running from the head to the feet of the avatar.
@ -233,6 +235,8 @@ class MyAvatar : public Avatar {
Q_PROPERTY(QUuid SELF_ID READ getSelfID CONSTANT)
Q_PROPERTY(float walkSpeed READ getWalkSpeed WRITE setWalkSpeed);
Q_PROPERTY(float walkBackwardSpeed READ getWalkBackwardSpeed WRITE setWalkBackwardSpeed);
Q_PROPERTY(float sprintSpeed READ getSprintSpeed WRITE setSprintSpeed);
const QString DOMINANT_LEFT_HAND = "left";
const QString DOMINANT_RIGHT_HAND = "right";
@ -865,8 +869,6 @@ public:
void resetFullAvatarURL();
virtual void setAttachmentData(const QVector<AttachmentData>& attachmentData) override;
MyCharacterController* getCharacterController() { return &_characterController; }
const MyCharacterController* getCharacterController() const { return &_characterController; }
@ -982,7 +984,7 @@ public:
/**jsdoc
* @function MyAvatar.getAvatarScale
* @returns {number}
* @returns {number}
*/
Q_INVOKABLE float getAvatarScale();
@ -1074,6 +1076,10 @@ public:
void setWalkSpeed(float value);
float getWalkSpeed() const;
void setWalkBackwardSpeed(float value);
float getWalkBackwardSpeed() const;
void setSprintSpeed(float value);
float getSprintSpeed() const;
QVector<QString> getScriptUrls();
@ -1082,6 +1088,12 @@ public:
float computeStandingHeightMode(const controller::Pose& head);
glm::quat computeAverageHeadRotation(const controller::Pose& head);
virtual void setAttachmentData(const QVector<AttachmentData>& attachmentData) override;
virtual QVector<AttachmentData> getAttachmentData() const override;
virtual QVariantList getAttachmentsVariant() const override;
virtual void setAttachmentsVariant(const QVariantList& variant) override;
public slots:
/**jsdoc
@ -1133,6 +1145,20 @@ public slots:
*/
float getGravity();
/**jsdoc
* Move the avatar to a new position and/or orientation in the domain, while taking into account Avatar leg-length.
* @function MyAvatar.goToFeetLocation
* @param {Vec3} position - The new position for the avatar, in world coordinates.
* @param {boolean} [hasOrientation=false] - Set to <code>true</code> to set the orientation of the avatar.
* @param {Quat} [orientation=Quat.IDENTITY] - The new orientation for the avatar.
* @param {boolean} [shouldFaceLocation=false] - Set to <code>true</code> to position the avatar a short distance away from
* the new position and orientate the avatar to face the position.
*/
void goToFeetLocation(const glm::vec3& newPosition,
bool hasOrientation, const glm::quat& newOrientation,
bool shouldFaceLocation);
/**jsdoc
* Move the avatar to a new position and/or orientation in the domain.
* @function MyAvatar.goToLocation
@ -1506,11 +1532,21 @@ private:
void setScriptedMotorTimescale(float timescale);
void setScriptedMotorFrame(QString frame);
void setScriptedMotorMode(QString mode);
// Attachments
virtual void attach(const QString& modelURL, const QString& jointName = QString(),
const glm::vec3& translation = glm::vec3(), const glm::quat& rotation = glm::quat(),
float scale = 1.0f, bool isSoft = false,
bool allowDuplicates = false, bool useSaved = true) override;
virtual void detachOne(const QString& modelURL, const QString& jointName = QString()) override;
virtual void detachAll(const QString& modelURL, const QString& jointName = QString()) override;
// Attachments/Avatar Entity
void attachmentDataToEntityProperties(const AttachmentData& data, EntityItemProperties& properties);
AttachmentData entityPropertiesToAttachmentData(const EntityItemProperties& properties) const;
bool findAvatarEntity(const QString& modelURL, const QString& jointName, QUuid& entityID);
bool cameraInsideHead(const glm::vec3& cameraPosition) const;
void updateEyeContactTarget(float deltaTime);
@ -1731,6 +1767,8 @@ private:
// max unscaled forward movement speed
ThreadSafeValueCache<float> _walkSpeed { DEFAULT_AVATAR_MAX_WALKING_SPEED };
ThreadSafeValueCache<float> _walkBackwardSpeed { DEFAULT_AVATAR_MAX_WALKING_BACKWARD_SPEED };
ThreadSafeValueCache<float> _sprintSpeed { AVATAR_SPRINT_SPEED_SCALAR };
float _walkSpeedScalar { AVATAR_WALK_SPEED_SCALAR };
// load avatar scripts once when rig is ready

View file

@ -352,8 +352,7 @@ bool QmlCommerce::openApp(const QString& itemHref) {
QJsonObject appFileJsonObject = appFileJsonDocument.object();
QString homeUrl = appFileJsonObject["homeURL"].toString();
auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
auto tablet = dynamic_cast<TabletProxy*>(tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system"));
auto tablet = dynamic_cast<TabletProxy*>(DependencyManager::get<TabletScriptingInterface>()->getTablet("com.highfidelity.interface.tablet.system"));
if (homeUrl.contains(".qml", Qt::CaseInsensitive)) {
tablet->loadQMLSource(homeUrl);
} else if (homeUrl.contains(".html", Qt::CaseInsensitive)) {

View file

@ -328,7 +328,7 @@ Wallet::Wallet() {
packetReceiver.registerListener(PacketType::ChallengeOwnership, this, "handleChallengeOwnershipPacket");
packetReceiver.registerListener(PacketType::ChallengeOwnershipRequest, this, "handleChallengeOwnershipPacket");
connect(ledger.data(), &Ledger::accountResult, this, [&](QJsonObject result) {
connect(ledger.data(), &Ledger::accountResult, this, [](QJsonObject result) {
auto wallet = DependencyManager::get<Wallet>();
auto walletScriptingInterface = DependencyManager::get<WalletScriptingInterface>();
uint status;

View file

@ -42,6 +42,48 @@ extern "C" {
int main(int argc, const char* argv[]) {
setupHifiApplication(BuildInfo::INTERFACE_NAME);
QStringList arguments;
for (int i = 0; i < argc; ++i) {
arguments << argv[i];
}
QCommandLineParser parser;
parser.setApplicationDescription("High Fidelity Interface");
QCommandLineOption versionOption = parser.addVersionOption();
QCommandLineOption helpOption = parser.addHelpOption();
QCommandLineOption urlOption("url", "", "value");
QCommandLineOption noUpdaterOption("no-updater", "Do not show auto-updater");
QCommandLineOption checkMinSpecOption("checkMinSpec", "Check if machine meets minimum specifications");
QCommandLineOption runServerOption("runServer", "Whether to run the server");
QCommandLineOption serverContentPathOption("serverContentPath", "Where to find server content", "serverContentPath");
QCommandLineOption allowMultipleInstancesOption("allowMultipleInstances", "Allow multiple instances to run");
QCommandLineOption overrideAppLocalDataPathOption("cache", "set test cache <dir>", "dir");
QCommandLineOption overrideScriptsPathOption(SCRIPTS_SWITCH, "set scripts <path>", "path");
parser.addOption(urlOption);
parser.addOption(noUpdaterOption);
parser.addOption(checkMinSpecOption);
parser.addOption(runServerOption);
parser.addOption(serverContentPathOption);
parser.addOption(overrideAppLocalDataPathOption);
parser.addOption(overrideScriptsPathOption);
parser.addOption(allowMultipleInstancesOption);
if (!parser.parse(arguments)) {
std::cout << parser.errorText().toStdString() << std::endl; // Avoid Qt log spam
}
if (parser.isSet(versionOption)) {
parser.showVersion();
Q_UNREACHABLE();
}
if (parser.isSet(helpOption)) {
QCoreApplication mockApp(argc, const_cast<char**>(argv)); // required for call to showHelp()
parser.showHelp();
Q_UNREACHABLE();
}
// Early check for --traceFile argument
auto tracer = DependencyManager::set<tracing::Tracer>();
const char * traceFile = nullptr;
@ -95,30 +137,6 @@ int main(int argc, const char* argv[]) {
qDebug() << "Crash handler started:" << crashHandlerStarted;
}
QStringList arguments;
for (int i = 0; i < argc; ++i) {
arguments << argv[i];
}
QCommandLineParser parser;
QCommandLineOption urlOption("url", "", "value");
QCommandLineOption noUpdaterOption("no-updater", "Do not show auto-updater");
QCommandLineOption checkMinSpecOption("checkMinSpec", "Check if machine meets minimum specifications");
QCommandLineOption runServerOption("runServer", "Whether to run the server");
QCommandLineOption serverContentPathOption("serverContentPath", "Where to find server content", "serverContentPath");
QCommandLineOption allowMultipleInstancesOption("allowMultipleInstances", "Allow multiple instances to run");
QCommandLineOption overrideAppLocalDataPathOption("cache", "set test cache <dir>", "dir");
QCommandLineOption overrideScriptsPathOption(SCRIPTS_SWITCH, "set scripts <path>", "path");
parser.addOption(urlOption);
parser.addOption(noUpdaterOption);
parser.addOption(checkMinSpecOption);
parser.addOption(runServerOption);
parser.addOption(serverContentPathOption);
parser.addOption(overrideAppLocalDataPathOption);
parser.addOption(overrideScriptsPathOption);
parser.addOption(allowMultipleInstancesOption);
parser.parse(arguments);
const QString& applicationName = getInterfaceSharedMemoryName();
bool instanceMightBeRunning = true;

View file

@ -0,0 +1,353 @@
//
// Created by Sabrina Shanman 7/16/2018
// Copyright 2018 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "CollisionPick.h"
#include <QtCore/QDebug>
#include <glm/gtx/transform.hpp>
#include "ScriptEngineLogging.h"
#include "UUIDHasher.h"
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);
if (at == intersections.end()) {
QVariantMap intersectingObject;
intersectingObject["id"] = objectIntersection.foundID;
intersectingObject["type"] = intersectionType;
intersections[objectIntersection.foundID] = intersectingObject;
collisionPointPairs[objectIntersection.foundID] = QVariantList();
}
QVariantMap collisionPointPair;
collisionPointPair["pointOnPick"] = vec3ToVariant(objectIntersection.testCollisionPoint);
collisionPointPair["pointOnObject"] = vec3ToVariant(objectIntersection.foundCollisionPoint);
collisionPointPairs[objectIntersection.foundID].append(collisionPointPair);
}
}
QVariantMap CollisionPickResult::toVariantMap() const {
QVariantMap variantMap;
variantMap["intersects"] = intersects;
std::unordered_map<QUuid, QVariantMap> intersections;
std::unordered_map<QUuid, QVariantList> collisionPointPairs;
buildObjectIntersectionsMap(ENTITY, entityIntersections, intersections, collisionPointPairs);
buildObjectIntersectionsMap(AVATAR, avatarIntersections, intersections, collisionPointPairs);
QVariantList qIntersectingObjects;
for (auto& intersectionKeyVal : intersections) {
const QUuid& id = intersectionKeyVal.first;
QVariantMap& intersection = intersectionKeyVal.second;
intersection["collisionContacts"] = collisionPointPairs[id];
qIntersectingObjects.append(intersection);
}
variantMap["intersectingObjects"] = qIntersectingObjects;
variantMap["loaded"] = (loadState == LOAD_STATE_LOADED);
variantMap["collisionRegion"] = pickVariant;
return variantMap;
}
bool CollisionPick::isShapeInfoReady() {
if (_mathPick.shouldComputeShapeInfo()) {
if (_cachedResource && _cachedResource->isLoaded()) {
computeShapeInfo(_mathPick, *_mathPick.shapeInfo, _cachedResource);
return true;
}
return false;
}
return true;
}
void CollisionPick::computeShapeInfo(CollisionRegion& pick, ShapeInfo& shapeInfo, QSharedPointer<GeometryResource> resource) {
// This code was copied and modified from RenderableModelEntityItem::computeShapeInfo
// TODO: Move to some shared code area (in entities-renderer? model-networking?)
// after we verify this is working and do a diff comparison with RenderableModelEntityItem::computeShapeInfo
// to consolidate the code.
// We may also want to make computeShapeInfo always abstract away from the gpu model mesh, like it does here.
const uint32_t TRIANGLE_STRIDE = 3;
const uint32_t QUAD_STRIDE = 4;
ShapeType type = shapeInfo.getType();
glm::vec3 dimensions = pick.transform.getScale();
if (type == SHAPE_TYPE_COMPOUND) {
// should never fall in here when collision model not fully loaded
// TODO: assert that all geometries exist and are loaded
//assert(_model && _model->isLoaded() && _compoundShapeResource && _compoundShapeResource->isLoaded());
const FBXGeometry& collisionGeometry = resource->getFBXGeometry();
ShapeInfo::PointCollection& pointCollection = shapeInfo.getPointCollection();
pointCollection.clear();
uint32_t i = 0;
// the way OBJ files get read, each section under a "g" line is its own meshPart. We only expect
// to find one actual "mesh" (with one or more meshParts in it), but we loop over the meshes, just in case.
foreach (const FBXMesh& mesh, collisionGeometry.meshes) {
// each meshPart is a convex hull
foreach (const FBXMeshPart &meshPart, mesh.parts) {
pointCollection.push_back(QVector<glm::vec3>());
ShapeInfo::PointList& pointsInPart = pointCollection[i];
// run through all the triangles and (uniquely) add each point to the hull
uint32_t numIndices = (uint32_t)meshPart.triangleIndices.size();
// TODO: assert rather than workaround after we start sanitizing FBXMesh higher up
//assert(numIndices % TRIANGLE_STRIDE == 0);
numIndices -= numIndices % TRIANGLE_STRIDE; // WORKAROUND lack of sanity checking in FBXReader
for (uint32_t j = 0; j < numIndices; j += TRIANGLE_STRIDE) {
glm::vec3 p0 = mesh.vertices[meshPart.triangleIndices[j]];
glm::vec3 p1 = mesh.vertices[meshPart.triangleIndices[j + 1]];
glm::vec3 p2 = mesh.vertices[meshPart.triangleIndices[j + 2]];
if (!pointsInPart.contains(p0)) {
pointsInPart << p0;
}
if (!pointsInPart.contains(p1)) {
pointsInPart << p1;
}
if (!pointsInPart.contains(p2)) {
pointsInPart << p2;
}
}
// run through all the quads and (uniquely) add each point to the hull
numIndices = (uint32_t)meshPart.quadIndices.size();
// TODO: assert rather than workaround after we start sanitizing FBXMesh higher up
//assert(numIndices % QUAD_STRIDE == 0);
numIndices -= numIndices % QUAD_STRIDE; // WORKAROUND lack of sanity checking in FBXReader
for (uint32_t j = 0; j < numIndices; j += QUAD_STRIDE) {
glm::vec3 p0 = mesh.vertices[meshPart.quadIndices[j]];
glm::vec3 p1 = mesh.vertices[meshPart.quadIndices[j + 1]];
glm::vec3 p2 = mesh.vertices[meshPart.quadIndices[j + 2]];
glm::vec3 p3 = mesh.vertices[meshPart.quadIndices[j + 3]];
if (!pointsInPart.contains(p0)) {
pointsInPart << p0;
}
if (!pointsInPart.contains(p1)) {
pointsInPart << p1;
}
if (!pointsInPart.contains(p2)) {
pointsInPart << p2;
}
if (!pointsInPart.contains(p3)) {
pointsInPart << p3;
}
}
if (pointsInPart.size() == 0) {
qCDebug(scriptengine) << "Warning -- meshPart has no faces";
pointCollection.pop_back();
continue;
}
++i;
}
}
// We expect that the collision model will have the same units and will be displaced
// from its origin in the same way the visual model is. The visual model has
// been centered and probably scaled. We take the scaling and offset which were applied
// to the visual model and apply them to the collision model (without regard for the
// collision model's extents).
glm::vec3 scaleToFit = dimensions / resource->getFBXGeometry().getUnscaledMeshExtents().size();
// multiply each point by scale
for (int32_t i = 0; i < pointCollection.size(); i++) {
for (int32_t j = 0; j < pointCollection[i].size(); j++) {
// back compensate for registration so we can apply that offset to the shapeInfo later
pointCollection[i][j] = scaleToFit * pointCollection[i][j];
}
}
shapeInfo.setParams(type, dimensions, resource->getURL().toString());
} else if (type >= SHAPE_TYPE_SIMPLE_HULL && type <= SHAPE_TYPE_STATIC_MESH) {
const FBXGeometry& fbxGeometry = resource->getFBXGeometry();
int numFbxMeshes = fbxGeometry.meshes.size();
int totalNumVertices = 0;
for (int i = 0; i < numFbxMeshes; i++) {
const FBXMesh& mesh = fbxGeometry.meshes.at(i);
totalNumVertices += mesh.vertices.size();
}
const int32_t MAX_VERTICES_PER_STATIC_MESH = 1e6;
if (totalNumVertices > MAX_VERTICES_PER_STATIC_MESH) {
qWarning() << "model" << resource->getURL() << "has too many vertices" << totalNumVertices << "and will collide as a box.";
shapeInfo.setParams(SHAPE_TYPE_BOX, 0.5f * dimensions);
return;
}
auto& meshes = resource->getFBXGeometry().meshes;
int32_t numMeshes = (int32_t)(meshes.size());
const int MAX_ALLOWED_MESH_COUNT = 1000;
if (numMeshes > MAX_ALLOWED_MESH_COUNT) {
// too many will cause the deadlock timer to throw...
shapeInfo.setParams(SHAPE_TYPE_BOX, 0.5f * dimensions);
return;
}
ShapeInfo::PointCollection& pointCollection = shapeInfo.getPointCollection();
pointCollection.clear();
if (type == SHAPE_TYPE_SIMPLE_COMPOUND) {
pointCollection.resize(numMeshes);
} else {
pointCollection.resize(1);
}
ShapeInfo::TriangleIndices& triangleIndices = shapeInfo.getTriangleIndices();
triangleIndices.clear();
Extents extents;
int32_t meshCount = 0;
int32_t pointListIndex = 0;
for (auto& mesh : meshes) {
if (!mesh.vertices.size()) {
continue;
}
QVector<glm::vec3> vertices = mesh.vertices;
ShapeInfo::PointList& points = pointCollection[pointListIndex];
// reserve room
int32_t sizeToReserve = (int32_t)(vertices.count());
if (type == SHAPE_TYPE_SIMPLE_COMPOUND) {
// a list of points for each mesh
pointListIndex++;
} else {
// only one list of points
sizeToReserve += (int32_t)points.size();
}
points.reserve(sizeToReserve);
// copy points
const glm::vec3* vertexItr = vertices.cbegin();
while (vertexItr != vertices.cend()) {
glm::vec3 point = *vertexItr;
points.push_back(point);
extents.addPoint(point);
++vertexItr;
}
if (type == SHAPE_TYPE_STATIC_MESH) {
// copy into triangleIndices
size_t triangleIndicesCount = 0;
for (const FBXMeshPart& meshPart : mesh.parts) {
triangleIndicesCount += meshPart.triangleIndices.count();
}
triangleIndices.reserve((int)triangleIndicesCount);
for (const FBXMeshPart& meshPart : mesh.parts) {
const int* indexItr = meshPart.triangleIndices.cbegin();
while (indexItr != meshPart.triangleIndices.cend()) {
triangleIndices.push_back(*indexItr);
++indexItr;
}
}
} else if (type == SHAPE_TYPE_SIMPLE_COMPOUND) {
// for each mesh copy unique part indices, separated by special bogus (flag) index values
for (const FBXMeshPart& meshPart : mesh.parts) {
// collect unique list of indices for this part
std::set<int32_t> uniqueIndices;
auto numIndices = meshPart.triangleIndices.count();
// TODO: assert rather than workaround after we start sanitizing FBXMesh higher up
//assert(numIndices% TRIANGLE_STRIDE == 0);
numIndices -= numIndices % TRIANGLE_STRIDE; // WORKAROUND lack of sanity checking in FBXReader
auto indexItr = meshPart.triangleIndices.cbegin();
while (indexItr != meshPart.triangleIndices.cend()) {
uniqueIndices.insert(*indexItr);
++indexItr;
}
// store uniqueIndices in triangleIndices
triangleIndices.reserve(triangleIndices.size() + (int32_t)uniqueIndices.size());
for (auto index : uniqueIndices) {
triangleIndices.push_back(index);
}
// flag end of part
triangleIndices.push_back(END_OF_MESH_PART);
}
// flag end of mesh
triangleIndices.push_back(END_OF_MESH);
}
++meshCount;
}
// scale and shift
glm::vec3 extentsSize = extents.size();
glm::vec3 scaleToFit = dimensions / extentsSize;
for (int32_t i = 0; i < 3; ++i) {
if (extentsSize[i] < 1.0e-6f) {
scaleToFit[i] = 1.0f;
}
}
for (auto points : pointCollection) {
for (int32_t i = 0; i < points.size(); ++i) {
points[i] = (points[i] * scaleToFit);
}
}
shapeInfo.setParams(type, 0.5f * dimensions, resource->getURL().toString());
}
}
CollisionRegion CollisionPick::getMathematicalPick() const {
return _mathPick;
}
const std::vector<ContactTestResult> CollisionPick::filterIntersections(const std::vector<ContactTestResult>& intersections) const {
std::vector<ContactTestResult> filteredIntersections;
const QVector<QUuid>& ignoreItems = getIgnoreItems();
const QVector<QUuid>& includeItems = getIncludeItems();
bool isWhitelist = includeItems.size();
for (const auto& intersection : intersections) {
const QUuid& id = intersection.foundID;
if (!ignoreItems.contains(id) && (!isWhitelist || includeItems.contains(id))) {
filteredIntersections.push_back(intersection);
}
}
return filteredIntersections;
}
PickResultPointer CollisionPick::getEntityIntersection(const CollisionRegion& pick) {
if (!isShapeInfoReady()) {
// Cannot compute result
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));
return std::make_shared<CollisionPickResult>(pick, CollisionPickResult::LOAD_STATE_LOADED, entityIntersections, std::vector<ContactTestResult>());
}
PickResultPointer CollisionPick::getOverlayIntersection(const CollisionRegion& pick) {
return std::make_shared<CollisionPickResult>(pick.toVariantMap(), isShapeInfoReady() ? CollisionPickResult::LOAD_STATE_LOADED : CollisionPickResult::LOAD_STATE_NOT_LOADED, std::vector<ContactTestResult>(), std::vector<ContactTestResult>());
}
PickResultPointer CollisionPick::getAvatarIntersection(const CollisionRegion& pick) {
if (!isShapeInfoReady()) {
// 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));
return std::make_shared<CollisionPickResult>(pick, CollisionPickResult::LOAD_STATE_LOADED, std::vector<ContactTestResult>(), avatarIntersections);
}
PickResultPointer CollisionPick::getHUDIntersection(const CollisionRegion& pick) {
return std::make_shared<CollisionPickResult>(pick.toVariantMap(), isShapeInfoReady() ? CollisionPickResult::LOAD_STATE_LOADED : CollisionPickResult::LOAD_STATE_NOT_LOADED, std::vector<ContactTestResult>(), std::vector<ContactTestResult>());
}

View file

@ -0,0 +1,102 @@
//
// Created by Sabrina Shanman 7/11/2018
// Copyright 2018 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_CollisionPick_h
#define hifi_CollisionPick_h
#include <PhysicsEngine.h>
#include <model-networking/ModelCache.h>
#include <RegisteredMetaTypes.h>
#include <Pick.h>
class CollisionPickResult : public PickResult {
public:
enum LoadState {
LOAD_STATE_UNKNOWN,
LOAD_STATE_NOT_LOADED,
LOAD_STATE_LOADED
};
CollisionPickResult() {}
CollisionPickResult(const QVariantMap& pickVariant) : PickResult(pickVariant) {}
CollisionPickResult(const CollisionRegion& searchRegion, LoadState loadState, const std::vector<ContactTestResult>& entityIntersections, const std::vector<ContactTestResult>& avatarIntersections) :
PickResult(searchRegion.toVariantMap()),
loadState(loadState),
intersects(entityIntersections.size() || avatarIntersections.size()),
entityIntersections(entityIntersections),
avatarIntersections(avatarIntersections) {
}
CollisionPickResult(const CollisionPickResult& collisionPickResult) : PickResult(collisionPickResult.pickVariant) {
avatarIntersections = collisionPickResult.avatarIntersections;
entityIntersections = collisionPickResult.entityIntersections;
intersects = collisionPickResult.intersects;
loadState = collisionPickResult.loadState;
}
LoadState loadState { LOAD_STATE_UNKNOWN };
bool intersects { false };
std::vector<ContactTestResult> entityIntersections;
std::vector<ContactTestResult> avatarIntersections;
QVariantMap toVariantMap() const override;
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);
}
};
class CollisionPick : public Pick<CollisionRegion> {
public:
CollisionPick(const PickFilter& filter, float maxDistance, bool enabled, CollisionRegion collisionRegion, PhysicsEnginePointer physicsEngine) :
Pick(filter, maxDistance, enabled),
_mathPick(collisionRegion),
_physicsEngine(physicsEngine) {
if (collisionRegion.shouldComputeShapeInfo()) {
_cachedResource = DependencyManager::get<ModelCache>()->getCollisionGeometryResource(collisionRegion.modelURL);
}
}
CollisionRegion getMathematicalPick() const override;
PickResultPointer getDefaultResult(const QVariantMap& pickVariant) const override {
return std::make_shared<CollisionPickResult>(pickVariant, CollisionPickResult::LOAD_STATE_UNKNOWN, std::vector<ContactTestResult>(), std::vector<ContactTestResult>());
}
PickResultPointer getEntityIntersection(const CollisionRegion& pick) override;
PickResultPointer getOverlayIntersection(const CollisionRegion& pick) override;
PickResultPointer getAvatarIntersection(const CollisionRegion& pick) override;
PickResultPointer getHUDIntersection(const CollisionRegion& pick) override;
protected:
// Returns true if pick.shapeInfo is valid. Otherwise, attempts to get the shapeInfo ready for use.
bool isShapeInfoReady();
void computeShapeInfo(CollisionRegion& pick, ShapeInfo& shapeInfo, QSharedPointer<GeometryResource> resource);
const std::vector<ContactTestResult> filterIntersections(const std::vector<ContactTestResult>& intersections) const;
CollisionRegion _mathPick;
PhysicsEnginePointer _physicsEngine;
QSharedPointer<GeometryResource> _cachedResource;
};
#endif // hifi_CollisionPick_h

View file

@ -12,11 +12,9 @@
#include <StencilMaskPass.h>
#include <DependencyManager.h>
#include <shaders/Shaders.h>
#include "ParabolaPick.h"
#include "render-utils/parabola_vert.h"
#include "render-utils/parabola_frag.h"
const glm::vec4 ParabolaPointer::RenderState::ParabolaRenderItem::DEFAULT_PARABOLA_COLOR { 1.0f };
const float ParabolaPointer::RenderState::ParabolaRenderItem::DEFAULT_PARABOLA_WIDTH { 0.01f };
const bool ParabolaPointer::RenderState::ParabolaRenderItem::DEFAULT_PARABOLA_ISVISIBLEINSECONDARYCAMERA { false };
@ -117,8 +115,6 @@ ParabolaPointer::RenderState::RenderState(const OverlayID& startID, const Overla
_pathWidth = pathWidth;
if (render::Item::isValidID(_pathID)) {
auto renderItem = std::make_shared<ParabolaRenderItem>(pathColor, pathAlpha, pathWidth, isVisibleInSecondaryCamera, pathEnabled);
// TODO: update bounds properly
renderItem->editBound().setBox(glm::vec3(-16000.0f), 32000.0f);
transaction.resetItem(_pathID, std::make_shared<ParabolaRenderItem::Payload>(renderItem));
scene->enqueueTransaction(transaction);
}
@ -182,6 +178,7 @@ void ParabolaPointer::RenderState::update(const glm::vec3& origin, const glm::ve
item.setAcceleration(acceleration);
item.setParabolicDistance(parabolicDistance);
item.setWidth(width);
item.updateBounds();
});
scene->enqueueTransaction(transaction);
}
@ -304,10 +301,10 @@ void ParabolaPointer::RenderState::ParabolaRenderItem::setVisible(bool visible)
}
void ParabolaPointer::RenderState::ParabolaRenderItem::updateKey() {
// FIXME: There's no way to designate a render item as non-shadow-reciever, and since a parabola's bounding box covers the entire domain,
// it seems to block all shadows. I think this is a bug with shadows.
//auto builder = _parabolaData.color.a < 1.0f ? render::ItemKey::Builder::transparentShape() : render::ItemKey::Builder::opaqueShape();
auto builder = render::ItemKey::Builder::transparentShape();
auto builder = _parabolaData.color.a < 1.0f ? render::ItemKey::Builder::transparentShape() : render::ItemKey::Builder::opaqueShape();
// TODO: parabolas should cast shadows, but they're so thin that the cascaded shadow maps make them look pretty bad
//builder.withShadowCaster();
if (_enabled && _visible) {
builder.withVisible();
@ -324,15 +321,36 @@ void ParabolaPointer::RenderState::ParabolaRenderItem::updateKey() {
_key = builder.build();
}
void ParabolaPointer::RenderState::ParabolaRenderItem::updateBounds() {
glm::vec3 max = _origin;
glm::vec3 min = _origin;
glm::vec3 end = _origin + _parabolaData.velocity * _parabolaData.parabolicDistance +
0.5f * _parabolaData.acceleration * _parabolaData.parabolicDistance * _parabolaData.parabolicDistance;
max = glm::max(max, end);
min = glm::min(min, end);
for (int i = 0; i < 3; i++) {
if (fabsf(_parabolaData.velocity[i]) > EPSILON && fabsf(_parabolaData.acceleration[i]) > EPSILON) {
float maxT = -_parabolaData.velocity[i] / _parabolaData.acceleration[i];
if (maxT > 0.0f && maxT < _parabolaData.parabolicDistance) {
glm::vec3 maxPoint = _origin + _parabolaData.velocity * maxT + 0.5f * _parabolaData.acceleration * maxT * maxT;
max = glm::max(max, maxPoint);
min = glm::min(min, maxPoint);
}
}
}
glm::vec3 halfWidth = glm::vec3(0.5f * _parabolaData.width);
max += halfWidth;
min -= halfWidth;
_bound = AABox(min, max - min);
}
const gpu::PipelinePointer ParabolaPointer::RenderState::ParabolaRenderItem::getParabolaPipeline() {
if (!_parabolaPipeline || !_transparentParabolaPipeline) {
auto vs = parabola_vert::getShader();
auto ps = parabola_frag::getShader();
gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps);
gpu::Shader::BindingSet slotBindings;
slotBindings.insert(gpu::Shader::Binding(std::string("parabolaData"), 0));
gpu::Shader::makeProgram(*program, slotBindings);
gpu::ShaderPointer program = gpu::Shader::createProgram(shader::render_utils::program::parabola);
{
auto state = std::make_shared<gpu::State>();

View file

@ -36,6 +36,7 @@ public:
void setVisible(bool visible);
void updateKey();
void updateUniformBuffer() { _uniformBuffer->setSubData(0, _parabolaData); }
void updateBounds();
void setColor(const glm::vec3& color) { _parabolaData.color = glm::vec4(color, _parabolaData.color.a); }
void setAlpha(const float& alpha) { _parabolaData.color.a = alpha; }

View file

@ -11,6 +11,7 @@
#include <QVariant>
#include "GLMHelpers.h"
#include "Application.h"
#include <PickManager.h>
#include "StaticRayPick.h"
@ -20,6 +21,7 @@
#include "StaticParabolaPick.h"
#include "JointParabolaPick.h"
#include "MouseParabolaPick.h"
#include "CollisionPick.h"
#include <ScriptEngine.h>
@ -31,6 +33,8 @@ unsigned int PickScriptingInterface::createPick(const PickQuery::PickType type,
return createStylusPick(properties);
case PickQuery::PickType::Parabola:
return createParabolaPick(properties);
case PickQuery::PickType::Collision:
return createCollisionPick(properties);
default:
return PickManager::INVALID_PICK_ID;
}
@ -234,6 +238,48 @@ unsigned int PickScriptingInterface::createParabolaPick(const QVariant& properti
return PickManager::INVALID_PICK_ID;
}
/**jsdoc
* A Shape defines a physical volume.
*
* @typedef {object} Shape
* @property {string} shapeType The type of shape to use. Can be one of the following: "box", "sphere", "capsule-x", "capsule-y", "capsule-z", "cylinder-x", "cylinder-y", "cylinder-z"
* @property {Vec3} dimensions - The size to scale the shape to.
*/
// TODO: Add this property to the Shape jsdoc above once model picks work properly
// * @property {string} modelURL - If shapeType is one of: "compound", "simple-hull", "simple-compound", or "static-mesh", this defines the model to load to generate the collision volume.
/**jsdoc
* A set of properties that can be passed to {@link Picks.createPick} to create a new Collision Pick.
* @typedef {object} Picks.CollisionPickProperties
* @property {Shape} shape - The information about the collision region's size and shape.
* @property {Vec3} position - The position of the collision region.
* @property {Quat} orientation - The orientation of the collision region.
*/
unsigned int PickScriptingInterface::createCollisionPick(const QVariant& properties) {
QVariantMap propMap = properties.toMap();
bool enabled = false;
if (propMap["enabled"].isValid()) {
enabled = propMap["enabled"].toBool();
}
PickFilter filter = PickFilter();
if (propMap["filter"].isValid()) {
filter = PickFilter(propMap["filter"].toUInt());
}
float maxDistance = 0.0f;
if (propMap["maxDistance"].isValid()) {
maxDistance = propMap["maxDistance"].toFloat();
}
CollisionRegion collisionRegion(propMap);
return DependencyManager::get<PickManager>()->addPick(PickQuery::Collision, std::make_shared<CollisionPick>(filter, maxDistance, enabled, collisionRegion, qApp->getPhysicsEngine()));
}
void PickScriptingInterface::enablePick(unsigned int uid) {
DependencyManager::get<PickManager>()->enablePick(uid);
}

View file

@ -12,6 +12,7 @@
#include <RegisteredMetaTypes.h>
#include <DependencyManager.h>
#include <PhysicsEngine.h>
#include <Pick.h>
/**jsdoc
@ -62,6 +63,7 @@ class PickScriptingInterface : public QObject, public Dependency {
public:
unsigned int createRayPick(const QVariant& properties);
unsigned int createStylusPick(const QVariant& properties);
unsigned int createCollisionPick(const QVariant& properties);
unsigned int createParabolaPick(const QVariant& properties);
void registerMetaTypes(QScriptEngine* engine);
@ -72,7 +74,7 @@ public:
* with PickType.Ray, depending on which optional parameters you pass, you could create a Static Ray Pick, a Mouse Ray Pick, or a Joint Ray Pick.
* @function Picks.createPick
* @param {PickType} type A PickType that specifies the method of picking to use
* @param {Picks.RayPickProperties|Picks.StylusPickProperties|Picks.ParabolaPickProperties} properties A PickProperties object, containing all the properties for initializing this Pick
* @param {Picks.RayPickProperties|Picks.StylusPickProperties|Picks.ParabolaPickProperties|Picks.CollisionPickProperties} properties A PickProperties object, containing all the properties for initializing this Pick
* @returns {number} The ID of the created Pick. Used for managing the Pick. 0 if invalid.
*/
Q_INVOKABLE unsigned int createPick(const PickQuery::PickType type, const QVariant& properties);
@ -138,14 +140,43 @@ public:
* @property {Vec3} intersection The intersection point in world-space.
* @property {Vec3} surfaceNormal The surface normal at the intersected point. All NANs if type == INTERSECTED_HUD.
* @property {Variant} extraInfo Additional intersection details when available for Model objects.
* @property {StylusTip} parabola The PickParabola that was used. Valid even if there was no intersection.
* @property {PickParabola} parabola The PickParabola that was used. Valid even if there was no intersection.
*/
/**jsdoc
* An intersection result for a Collision Pick.
*
* @typedef {object} CollisionPickResult
* @property {boolean} intersects If there was at least one valid intersection (intersectingObjects.length > 0)
* @property {IntersectingObject[]} intersectingObjects The collision information of each object which intersect with the CollisionRegion.
* @property {CollisionRegion} collisionRegion The CollisionRegion that was used. Valid even if there was no intersection.
*/
// TODO: Add this to the CollisionPickResult jsdoc once model collision picks are working
//* @property {boolean} loaded If the CollisionRegion was successfully loaded (may be false if a model was used)
/**jsdoc
* Information about the Collision Pick's intersection with an object
*
* @typedef {object} IntersectingObject
* @property {QUuid} id The ID of the object.
* @property {number} type The type of the object, either Picks.INTERSECTED_ENTITY() or Picks.INTERSECTED_AVATAR()
* @property {CollisionContact[]} collisionContacts Pairs of points representing penetration information between the pick and the object
*/
/**jsdoc
* A pair of points that represents part of an overlap between a Collision Pick and an object in the physics engine. Points which are further apart represent deeper overlap
*
* @typedef {object} CollisionContact
* @property {Vec3} pointOnPick A point representing a penetration of the object's surface into the volume of the pick, in world space.
* @property {Vec3} pointOnObject A point representing a penetration of the pick's surface into the volume of the found object, in world space.
*/
/**jsdoc
* Get the most recent pick result from this Pick. This will be updated as long as the Pick is enabled.
* @function Picks.getPrevPickResult
* @param {number} uid The ID of the Pick, as returned by {@link Picks.createPick}.
* @returns {RayPickResult|StylusPickResult} The most recent intersection result. This will be different for different PickTypes.
* @returns {RayPickResult|StylusPickResult|ParabolaPickResult|CollisionPickResult} The most recent intersection result. This will be different for different PickTypes.
*/
Q_INVOKABLE QVariantMap getPrevPickResult(unsigned int uid);

View file

@ -92,7 +92,7 @@ unsigned int PointerScriptingInterface::createStylus(const QVariant& properties)
* @typedef {object} Pointers.LaserPointerProperties
* @property {boolean} [faceAvatar=false] If true, the end of the Pointer will always rotate to face the avatar.
* @property {boolean} [centerEndY=true] If false, the end of the Pointer will be moved up by half of its height.
* @property {boolean} [lockEnd=false] If true, the end of the Pointer will lock on to the center of the object at which the laser is pointing.
* @property {boolean} [lockEnd=false] If true, the end of the Pointer will lock on to the center of the object at which the pointer is pointing.
* @property {boolean} [distanceScaleEnd=false] If true, the dimensions of the end of the Pointer will scale linearly with distance.
* @property {boolean} [scaleWithAvatar=false] If true, the width of the Pointer's path will scale linearly with your avatar's scale.
* @property {boolean} [followNormal=false] If true, the end of the Pointer will rotate to follow the normal of the intersected surface.
@ -207,9 +207,10 @@ unsigned int PointerScriptingInterface::createLaserPointer(const QVariant& prope
* The rendering properties of the parabolic path
*
* @typedef {object} Pointers.ParabolaProperties
* @property {Color} color The color of the parabola.
* @property {number} alpha The alpha of the parabola.
* @property {number} width The width of the parabola, in meters.
* @property {Color} color=255,255,255 The color of the parabola.
* @property {number} alpha=1.0 The alpha of the parabola.
* @property {number} width=0.01 The width of the parabola, in meters.
* @property {boolean} isVisibleInSecondaryCamera=false The width of the parabola, in meters.
*/
/**jsdoc
* A set of properties used to define the visual aspect of a Parabola Pointer in the case that the Pointer is not intersecting something. Same as a {@link Pointers.ParabolaPointerRenderState},
@ -232,10 +233,10 @@ unsigned int PointerScriptingInterface::createLaserPointer(const QVariant& prope
*/
/**jsdoc
* A set of properties that can be passed to {@link Pointers.createPointer} to create a new Pointer. Contains the relevant {@link Picks.PickProperties} to define the underlying Pick.
* @typedef {object} Pointers.LaserPointerProperties
* @typedef {object} Pointers.ParabolaPointerProperties
* @property {boolean} [faceAvatar=false] If true, the end of the Pointer will always rotate to face the avatar.
* @property {boolean} [centerEndY=true] If false, the end of the Pointer will be moved up by half of its height.
* @property {boolean} [lockEnd=false] If true, the end of the Pointer will lock on to the center of the object at which the laser is pointing.
* @property {boolean} [lockEnd=false] If true, the end of the Pointer will lock on to the center of the object at which the pointer is pointing.
* @property {boolean} [distanceScaleEnd=false] If true, the dimensions of the end of the Pointer will scale linearly with distance.
* @property {boolean} [scaleWithAvatar=false] If true, the width of the Pointer's path will scale linearly with your avatar's scale.
* @property {boolean} [followNormal=false] If true, the end of the Pointer will rotate to follow the normal of the intersected surface.

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