From 440ba7a6de63779422944ed7933e1da5e8448c27 Mon Sep 17 00:00:00 2001 From: Cristian Duarte Date: Tue, 12 Jun 2018 19:46:59 -0300 Subject: [PATCH 001/144] Add a friends fragment and expose the access token to java --- android/app/src/main/cpp/native.cpp | 11 +++++ .../hifiinterface/MainActivity.java | 13 ++++++ .../fragment/FriendsFragment.java | 40 +++++++++++++++++++ .../src/main/res/layout/fragment_friends.xml | 9 +++++ .../app/src/main/res/menu/menu_navigation.xml | 4 ++ android/app/src/main/res/values/strings.xml | 1 + 6 files changed, 78 insertions(+) create mode 100644 android/app/src/main/java/io/highfidelity/hifiinterface/fragment/FriendsFragment.java create mode 100644 android/app/src/main/res/layout/fragment_friends.xml diff --git a/android/app/src/main/cpp/native.cpp b/android/app/src/main/cpp/native.cpp index 437505be3f..3e3fb8f0b5 100644 --- a/android/app/src/main/cpp/native.cpp +++ b/android/app/src/main/cpp/native.cpp @@ -252,6 +252,17 @@ 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) { + return AndroidHelper::instance().getAccountManager()->isLoggedIn(); +} + +JNIEXPORT jstring JNICALL +Java_io_highfidelity_hifiinterface_fragment_FriendsFragment_nativeGetAccessToken(JNIEnv *env, jobject instance) { + auto accountManager = AndroidHelper::instance().getAccountManager(); + return env->NewStringUTF(accountManager->getAccountInfo().getAccessToken().token.toLatin1().data()); +} + JNIEXPORT void JNICALL Java_io_highfidelity_hifiinterface_SplashActivity_registerLoadCompleteListener(JNIEnv *env, jobject instance) { diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/MainActivity.java b/android/app/src/main/java/io/highfidelity/hifiinterface/MainActivity.java index 54161f60c6..8bb84168a5 100644 --- a/android/app/src/main/java/io/highfidelity/hifiinterface/MainActivity.java +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/MainActivity.java @@ -29,6 +29,7 @@ import android.widget.TextView; import com.squareup.picasso.Callback; import com.squareup.picasso.Picasso; +import io.highfidelity.hifiinterface.fragment.FriendsFragment; import io.highfidelity.hifiinterface.fragment.HomeFragment; import io.highfidelity.hifiinterface.fragment.LoginFragment; import io.highfidelity.hifiinterface.fragment.PolicyFragment; @@ -114,6 +115,9 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On case "Privacy Policy": loadPrivacyPolicyFragment(); break; + case "Friends": + loadFriendsFragment(); + break; default: Log.e(TAG, "Unknown fragment " + fragment); } @@ -137,6 +141,12 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On loadFragment(fragment, getString(R.string.privacyPolicy), true); } + private void loadFriendsFragment() { + Fragment fragment = FriendsFragment.newInstance(); + + loadFragment(fragment, getString(R.string.friends), true); + } + private void loadFragment(Fragment fragment, String title, boolean addToBackStack) { FragmentManager fragmentManager = getFragmentManager(); FragmentTransaction ft = fragmentManager.beginTransaction(); @@ -202,6 +212,9 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On case R.id.action_home: loadHomeFragment(); return true; + case R.id.action_friends: + loadFriendsFragment(); + return true; } return false; } diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/FriendsFragment.java b/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/FriendsFragment.java new file mode 100644 index 0000000000..64c02505cb --- /dev/null +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/FriendsFragment.java @@ -0,0 +1,40 @@ +package io.highfidelity.hifiinterface.fragment; + + +import android.app.Fragment; +import android.os.Bundle; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import io.highfidelity.hifiinterface.R; + +public class FriendsFragment extends Fragment { + + public native boolean nativeIsLoggedIn(); + + public native String nativeGetAccessToken(); + + 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(); + + Log.d("[TOKENX]", "token : [" + accessToken + "]"); + + return rootView; + } + +} diff --git a/android/app/src/main/res/layout/fragment_friends.xml b/android/app/src/main/res/layout/fragment_friends.xml new file mode 100644 index 0000000000..347d7ffe24 --- /dev/null +++ b/android/app/src/main/res/layout/fragment_friends.xml @@ -0,0 +1,9 @@ + + + + diff --git a/android/app/src/main/res/menu/menu_navigation.xml b/android/app/src/main/res/menu/menu_navigation.xml index cf80c84177..de496bc4cb 100644 --- a/android/app/src/main/res/menu/menu_navigation.xml +++ b/android/app/src/main/res/menu/menu_navigation.xml @@ -5,4 +5,8 @@ android:id="@+id/action_home" android:title="@string/home" /> + diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml index 4f5f29e671..e29252c2c0 100644 --- a/android/app/src/main/res/values/strings.xml +++ b/android/app/src/main/res/values/strings.xml @@ -1,6 +1,7 @@ Interface Home + Friends Open in browser Share link Shared a link From 248be87fa3bb69c4e704f8df3b47dbe426d4a985 Mon Sep 17 00:00:00 2001 From: Cristian Luis Duarte Date: Thu, 14 Jun 2018 15:19:21 -0300 Subject: [PATCH 002/144] Android - First list of friends implementation in List of Friends --- .../fragment/FriendsFragment.java | 15 +- .../provider/EndpointUsersProvider.java | 130 ++++++++++++++++++ .../hifiinterface/provider/UsersProvider.java | 20 +++ .../hifiinterface/view/UserListAdapter.java | 97 +++++++++++++ .../src/main/res/layout/fragment_friends.xml | 9 ++ android/app/src/main/res/layout/user_item.xml | 26 ++++ 6 files changed, 296 insertions(+), 1 deletion(-) create mode 100644 android/app/src/main/java/io/highfidelity/hifiinterface/provider/EndpointUsersProvider.java create mode 100644 android/app/src/main/java/io/highfidelity/hifiinterface/provider/UsersProvider.java create mode 100644 android/app/src/main/java/io/highfidelity/hifiinterface/view/UserListAdapter.java create mode 100644 android/app/src/main/res/layout/user_item.xml diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/FriendsFragment.java b/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/FriendsFragment.java index 64c02505cb..e70ecfbc57 100644 --- a/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/FriendsFragment.java +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/FriendsFragment.java @@ -3,12 +3,15 @@ package io.highfidelity.hifiinterface.fragment; import android.app.Fragment; import android.os.Bundle; +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 io.highfidelity.hifiinterface.R; +import io.highfidelity.hifiinterface.view.UserListAdapter; public class FriendsFragment extends Fragment { @@ -16,6 +19,9 @@ public class FriendsFragment extends Fragment { public native String nativeGetAccessToken(); + private RecyclerView mUsersView; + private UserListAdapter mUsersAdapter; + public FriendsFragment() { // Required empty public constructor } @@ -32,7 +38,14 @@ public class FriendsFragment extends Fragment { String accessToken = nativeGetAccessToken(); - Log.d("[TOKENX]", "token : [" + accessToken + "]"); + Log.d("[USERS]", "token : [" + accessToken + "]"); + + mUsersView = rootView.findViewById(R.id.rvUsers); + int numberOfColumns = 1; + GridLayoutManager gridLayoutMgr = new GridLayoutManager(getContext(), numberOfColumns); + mUsersView.setLayoutManager(gridLayoutMgr); + mUsersAdapter = new UserListAdapter(getContext(), accessToken); + mUsersView.setAdapter(mUsersAdapter); return rootView; } diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/provider/EndpointUsersProvider.java b/android/app/src/main/java/io/highfidelity/hifiinterface/provider/EndpointUsersProvider.java new file mode 100644 index 0000000000..3e034b654b --- /dev/null +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/provider/EndpointUsersProvider.java @@ -0,0 +1,130 @@ +package io.highfidelity.hifiinterface.provider; + +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.GET; +import retrofit2.http.Query; + +/** + * Created by cduarte on 6/13/18. + */ + +public class EndpointUsersProvider implements UsersProvider { + + public static final String BASE_URL = "https://metaverse.highfidelity.com/"; + private final Retrofit mRetrofit; + private final EndpointUsersProviderService mEndpointUsersProviderService; + + public EndpointUsersProvider(String accessToken) { + mRetrofit = createAuthorizedRetrofit(accessToken); + mEndpointUsersProviderService = mRetrofit.create(EndpointUsersProviderService.class); + } + + private Retrofit createAuthorizedRetrofit(String accessToken) { + Retrofit mRetrofit; + OkHttpClient.Builder httpClient = new OkHttpClient.Builder(); + httpClient.addInterceptor(new Interceptor() { + @Override + public Response intercept(Chain chain) throws IOException { + Request original = chain.request(); + + Request request = original.newBuilder() + .header("Authorization", "Bearer " + accessToken) + .method(original.method(), original.body()) + .build(); + + return chain.proceed(request); + } + }); + OkHttpClient client = httpClient.build(); + + mRetrofit = new Retrofit.Builder() + .baseUrl(BASE_URL) + .addConverterFactory(GsonConverterFactory.create()) + .client(client) + .build(); + return mRetrofit; + } + + @Override + public void retrieve(UsersCallback usersCallback) { + Call friendsCall = mEndpointUsersProviderService.getUsers( + "friends", + 400, + null); + friendsCall.enqueue(new Callback() { + @Override + public void onResponse(Call call, retrofit2.Response response) { + if (!response.isSuccessful()) { + usersCallback.retrieveError(new Exception("Error calling Users API"), "Error calling Users API"); + return; + } + UsersResponse usersResponse = response.body(); + List adapterUsers = new ArrayList<>(usersResponse.total_entries); + for (User user : usersResponse.data.users) { + UserListAdapter.User adapterUser = new UserListAdapter.User(); + adapterUser.connection = user.connection; + adapterUser.imageUrl = user.images.thumbnail; + adapterUser.name = user.username; + adapterUser.online = user.online; + adapterUsers.add(adapterUser); + } + usersCallback.retrieveOk(adapterUsers); + } + + @Override + public void onFailure(Call call, Throwable t) { + usersCallback.retrieveError(new Exception(t), "Error calling Users API"); + } + }); + } + + public interface EndpointUsersProviderService { + @GET("api/v1/users") + Call getUsers(@Query("filter") String filter, + @Query("per_page") int perPage, + @Query("online") Boolean online); + } + + class UsersResponse { + public UsersResponse() {} + String status; + int current_page; + int total_pages; + int per_page; + int total_entries; + Data data; + } + + class Data { + public Data() {} + List users; + } + + class User { + public User() {} + String username; + boolean online; + String connection; + Images images; + } + + class Images { + public Images() {} + String hero; + String thumbnail; + String tiny; + } + +} diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/provider/UsersProvider.java b/android/app/src/main/java/io/highfidelity/hifiinterface/provider/UsersProvider.java new file mode 100644 index 0000000000..13ed812ce6 --- /dev/null +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/provider/UsersProvider.java @@ -0,0 +1,20 @@ +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 { + + void retrieve(UsersProvider.UsersCallback usersCallback); + + interface UsersCallback { + void retrieveOk(List users); + void retrieveError(Exception e, String message); + } + +} diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/view/UserListAdapter.java b/android/app/src/main/java/io/highfidelity/hifiinterface/view/UserListAdapter.java new file mode 100644 index 0000000000..32993500fe --- /dev/null +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/view/UserListAdapter.java @@ -0,0 +1,97 @@ +package io.highfidelity.hifiinterface.view; + +import android.content.Context; +import android.net.Uri; +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.Picasso; + +import java.util.ArrayList; +import java.util.List; + +import io.highfidelity.hifiinterface.R; +import io.highfidelity.hifiinterface.provider.EndpointUsersProvider; +import io.highfidelity.hifiinterface.provider.UsersProvider; + +/** + * Created by cduarte on 6/13/18. + */ + +public class UserListAdapter extends RecyclerView.Adapter { + + private UsersProvider mProvider; + private LayoutInflater mInflater; + private Context mContext; + private List mUsers = new ArrayList<>(); + + public UserListAdapter(Context c, String accessToken) { + mContext = c; + mInflater = LayoutInflater.from(mContext); + mProvider = new EndpointUsersProvider(accessToken); + loadUsers(); + } + + private void loadUsers() { + mProvider.retrieve(new UsersProvider.UsersCallback() { + @Override + public void retrieveOk(List users) { + mUsers = new ArrayList<>(users); + notifyDataSetChanged(); + } + + @Override + public void retrieveError(Exception e, String message) { + Log.e("[USERS]", message, e); + } + }); + } + + @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.mOnline.setText(aUser.online?"ONLINE":"OFFLINE"); + Uri uri = Uri.parse(aUser.imageUrl); + Picasso.get().load(uri).into(holder.mImage); + } + + @Override + public int getItemCount() { + return mUsers.size(); + } + + public class ViewHolder extends RecyclerView.ViewHolder { + + TextView mUsername; + TextView mOnline; + ImageView mImage; + + public ViewHolder(View itemView) { + super(itemView); + mUsername = itemView.findViewById(R.id.userName); + mOnline = itemView.findViewById(R.id.userOnline); + mImage = itemView.findViewById(R.id.userImage); + } + } + + public static class User { + public String name; + public String imageUrl; + public String connection; + public boolean online; + + public User() {} + } +} diff --git a/android/app/src/main/res/layout/fragment_friends.xml b/android/app/src/main/res/layout/fragment_friends.xml index 347d7ffe24..8129f5d53c 100644 --- a/android/app/src/main/res/layout/fragment_friends.xml +++ b/android/app/src/main/res/layout/fragment_friends.xml @@ -6,4 +6,13 @@ android:layout_height="match_parent" android:background="@color/backgroundLight"> + + diff --git a/android/app/src/main/res/layout/user_item.xml b/android/app/src/main/res/layout/user_item.xml new file mode 100644 index 0000000000..043c9161de --- /dev/null +++ b/android/app/src/main/res/layout/user_item.xml @@ -0,0 +1,26 @@ + + + + + + + + + \ No newline at end of file From 61d1bf7bf7e7ae2e203604f1dd2e94f12efd478e Mon Sep 17 00:00:00 2001 From: Cristian Luis Duarte Date: Thu, 14 Jun 2018 21:06:40 -0300 Subject: [PATCH 003/144] Android - Show names of screens when going back in the Main screen (Home-Login-Friends) --- .../io/highfidelity/hifiinterface/MainActivity.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/MainActivity.java b/android/app/src/main/java/io/highfidelity/hifiinterface/MainActivity.java index 8bb84168a5..d1ac6d5ec8 100644 --- a/android/app/src/main/java/io/highfidelity/hifiinterface/MainActivity.java +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/MainActivity.java @@ -126,7 +126,7 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On private void loadHomeFragment() { Fragment fragment = HomeFragment.newInstance(); - loadFragment(fragment, getString(R.string.home), false); + loadFragment(fragment, getString(R.string.home), true); } private void loadLoginFragment() { @@ -152,7 +152,7 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On FragmentTransaction ft = fragmentManager.beginTransaction(); ft.replace(R.id.content_frame, fragment); if (addToBackStack) { - ft.addToBackStack(null); + ft.addToBackStack(title); } ft.commit(); setTitle(title); @@ -298,8 +298,12 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On @Override public void onBackPressed() { int index = getFragmentManager().getBackStackEntryCount() - 1; - if (index > -1) { + if (index > 0) { super.onBackPressed(); + index--; + if (index > -1) { + setTitle(getFragmentManager().getBackStackEntryAt(index).getName()); + } if (backToScene) { backToScene = false; goToLastLocation(); From 7954865141c39bd7cdcfbe7381d99d08c3bdab3f Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Thu, 5 Jul 2018 14:34:37 -0700 Subject: [PATCH 004/144] trying to fix dependency shutdown --- interface/src/AboutUtil.cpp | 4 +- interface/src/Application.cpp | 248 +++++++++--------- interface/src/Menu.cpp | 12 +- interface/src/avatar/MyAvatar.cpp | 4 +- interface/src/commerce/QmlCommerce.cpp | 3 +- interface/src/scripting/Audio.cpp | 9 +- interface/src/scripting/AudioDevices.cpp | 18 +- interface/src/ui/LoginDialog.cpp | 4 +- interface/src/ui/Stats.cpp | 2 +- interface/src/ui/TestingDialog.cpp | 3 +- libraries/audio-client/src/AudioClient.cpp | 21 +- libraries/audio-client/src/AudioClient.h | 1 - libraries/audio-client/src/AudioIOStats.cpp | 2 - libraries/script-engine/src/ScriptEngines.cpp | 3 +- libraries/shared/src/DependencyManager.h | 8 +- libraries/ui/src/ui/OffscreenQmlSurface.cpp | 3 +- 16 files changed, 157 insertions(+), 188 deletions(-) diff --git a/interface/src/AboutUtil.cpp b/interface/src/AboutUtil.cpp index 634e52b481..56cabce03d 100644 --- a/interface/src/AboutUtil.cpp +++ b/interface/src/AboutUtil.cpp @@ -45,9 +45,7 @@ QString AboutUtil::getQtVersion() const { } void AboutUtil::openUrl(const QString& url) const { - - auto tabletScriptingInterface = DependencyManager::get(); - auto tablet = tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system"); + auto tablet = DependencyManager::get()->getTablet("com.highfidelity.interface.tablet.system"); auto hmd = DependencyManager::get(); auto offscreenUi = DependencyManager::get(); diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 4b530dc1d0..bc881e592b 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1138,33 +1138,34 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo domainCheckInTimer->deleteLater(); }); + { + auto audioIO = DependencyManager::get().data(); + audioIO->setPositionGetter([] { + auto avatarManager = DependencyManager::get(); + auto myAvatar = avatarManager ? avatarManager->getMyAvatar() : nullptr; - auto audioIO = DependencyManager::get(); - audioIO->setPositionGetter([]{ - auto avatarManager = DependencyManager::get(); - auto myAvatar = avatarManager ? avatarManager->getMyAvatar() : nullptr; + return myAvatar ? myAvatar->getPositionForAudio() : Vectors::ZERO; + }); + audioIO->setOrientationGetter([] { + auto avatarManager = DependencyManager::get(); + auto myAvatar = avatarManager ? avatarManager->getMyAvatar() : nullptr; - return myAvatar ? myAvatar->getPositionForAudio() : Vectors::ZERO; - }); - audioIO->setOrientationGetter([]{ - auto avatarManager = DependencyManager::get(); - auto myAvatar = avatarManager ? avatarManager->getMyAvatar() : nullptr; + return myAvatar ? myAvatar->getOrientationForAudio() : Quaternions::IDENTITY; + }); - return myAvatar ? myAvatar->getOrientationForAudio() : Quaternions::IDENTITY; - }); + recording::Frame::registerFrameHandler(AudioConstants::getAudioFrameName(), [=](recording::Frame::ConstPointer frame) { + audioIO->handleRecordedAudioInput(frame->data); + }); - recording::Frame::registerFrameHandler(AudioConstants::getAudioFrameName(), [=](recording::Frame::ConstPointer frame) { - audioIO->handleRecordedAudioInput(frame->data); - }); - - connect(audioIO.data(), &AudioClient::inputReceived, [](const QByteArray& audio){ - static auto recorder = DependencyManager::get(); - if (recorder->isRecording()) { - static const recording::FrameType AUDIO_FRAME_TYPE = recording::Frame::registerFrameType(AudioConstants::getAudioFrameName()); - recorder->recordFrame(AUDIO_FRAME_TYPE, audio); - } - }); - audioIO->startThread(); + connect(audioIO, &AudioClient::inputReceived, [](const QByteArray& audio) { + static auto recorder = DependencyManager::get(); + if (recorder->isRecording()) { + static const recording::FrameType AUDIO_FRAME_TYPE = recording::Frame::registerFrameType(AudioConstants::getAudioFrameName()); + recorder->recordFrame(AUDIO_FRAME_TYPE, audio); + } + }); + audioIO->startThread(); + } // Make sure we don't time out during slow operations at startup updateHeartbeat(); @@ -1263,27 +1264,29 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo // Inititalize sample before registering _sampleSound = DependencyManager::get()->getSound(PathUtils::resourcesUrl("sounds/sample.wav")); - auto scriptEngines = DependencyManager::get().data(); - scriptEngines->registerScriptInitializer([this](ScriptEnginePointer engine){ - registerScriptEngineWithApplicationServices(engine); - }); + { + auto scriptEngines = DependencyManager::get().data(); + scriptEngines->registerScriptInitializer([this](ScriptEnginePointer engine) { + registerScriptEngineWithApplicationServices(engine); + }); - connect(scriptEngines, &ScriptEngines::scriptCountChanged, scriptEngines, [this] { - auto scriptEngines = DependencyManager::get(); - if (scriptEngines->getRunningScripts().isEmpty()) { - getMyAvatar()->clearScriptableSettings(); - } - }, Qt::QueuedConnection); + connect(scriptEngines, &ScriptEngines::scriptCountChanged, this, [this] { + auto scriptEngines = DependencyManager::get(); + if (scriptEngines->getRunningScripts().isEmpty()) { + getMyAvatar()->clearScriptableSettings(); + } + }, Qt::QueuedConnection); - connect(scriptEngines, &ScriptEngines::scriptsReloading, scriptEngines, [this] { - getEntities()->reloadEntityScripts(); - loadAvatarScripts(getMyAvatar()->getScriptUrls()); - }, Qt::QueuedConnection); + connect(scriptEngines, &ScriptEngines::scriptsReloading, this, [this] { + getEntities()->reloadEntityScripts(); + loadAvatarScripts(getMyAvatar()->getScriptUrls()); + }, Qt::QueuedConnection); - connect(scriptEngines, &ScriptEngines::scriptLoadError, - scriptEngines, [](const QString& filename, const QString& error){ - OffscreenUi::asyncWarning(nullptr, "Error Loading Script", filename + " failed to load."); - }, Qt::QueuedConnection); + connect(scriptEngines, &ScriptEngines::scriptLoadError, + this, [](const QString& filename, const QString& error) { + OffscreenUi::asyncWarning(nullptr, "Error Loading Script", filename + " failed to load."); + }, Qt::QueuedConnection); + } #ifdef _WIN32 WSADATA WsaData; @@ -1353,10 +1356,11 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo // so we defer the setup of the `scripting::Audio` class until this point { auto audioScriptingInterface = DependencyManager::set(); - connect(audioIO.data(), &AudioClient::mutedByMixer, audioScriptingInterface.data(), &AudioScriptingInterface::mutedByMixer); - connect(audioIO.data(), &AudioClient::receivedFirstPacket, audioScriptingInterface.data(), &AudioScriptingInterface::receivedFirstPacket); - connect(audioIO.data(), &AudioClient::disconnected, audioScriptingInterface.data(), &AudioScriptingInterface::disconnected); - connect(audioIO.data(), &AudioClient::muteEnvironmentRequested, [](glm::vec3 position, float radius) { + auto audioIO = DependencyManager::get().data(); + connect(audioIO, &AudioClient::mutedByMixer, audioScriptingInterface.data(), &AudioScriptingInterface::mutedByMixer); + connect(audioIO, &AudioClient::receivedFirstPacket, audioScriptingInterface.data(), &AudioScriptingInterface::receivedFirstPacket); + connect(audioIO, &AudioClient::disconnected, audioScriptingInterface.data(), &AudioScriptingInterface::disconnected); + connect(audioIO, &AudioClient::muteEnvironmentRequested, [](glm::vec3 position, float radius) { auto audioClient = DependencyManager::get(); auto audioScriptingInterface = DependencyManager::get(); auto myAvatarPosition = DependencyManager::get()->getMyAvatar()->getWorldPosition(); @@ -1688,23 +1692,26 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo userInputMapper->registerDevice(_touchscreenVirtualPadDevice->getInputDevice()); } - // this will force the model the look at the correct directory (weird order of operations issue) - scriptEngines->reloadLocalFiles(); + { + auto scriptEngines = DependencyManager::get().data(); + // this will force the model the look at the correct directory (weird order of operations issue) + scriptEngines->reloadLocalFiles(); - // do this as late as possible so that all required subsystems are initialized - // If we've overridden the default scripts location, just load default scripts - // otherwise, load 'em all + // do this as late as possible so that all required subsystems are initialized + // If we've overridden the default scripts location, just load default scripts + // otherwise, load 'em all - // we just want to see if --scripts was set, we've already parsed it and done - // the change in PathUtils. Rather than pass that in the constructor, lets just - // look (this could be debated) - QString scriptsSwitch = QString("--").append(SCRIPTS_SWITCH); - QDir defaultScriptsLocation(getCmdOption(argc, constArgv, scriptsSwitch.toStdString().c_str())); - if (!defaultScriptsLocation.exists()) { - scriptEngines->loadDefaultScripts(); - scriptEngines->defaultScriptsLocationOverridden(true); - } else { - scriptEngines->loadScripts(); + // we just want to see if --scripts was set, we've already parsed it and done + // the change in PathUtils. Rather than pass that in the constructor, lets just + // look (this could be debated) + QString scriptsSwitch = QString("--").append(SCRIPTS_SWITCH); + QDir defaultScriptsLocation(getCmdOption(argc, constArgv, scriptsSwitch.toStdString().c_str())); + if (!defaultScriptsLocation.exists()) { + scriptEngines->loadDefaultScripts(); + scriptEngines->defaultScriptsLocationOverridden(true); + } else { + scriptEngines->loadScripts(); + } } // Make sure we don't time out during slow operations at startup @@ -1754,13 +1761,16 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo cameraMenuChanged(); } - // set the local loopback interface for local sounds - AudioInjector::setLocalAudioInterface(audioIO.data()); - auto audioScriptingInterface = DependencyManager::get(); - audioScriptingInterface->setLocalAudioInterface(audioIO.data()); - connect(audioIO.data(), &AudioClient::noiseGateOpened, audioScriptingInterface.data(), &AudioScriptingInterface::noiseGateOpened); - connect(audioIO.data(), &AudioClient::noiseGateClosed, audioScriptingInterface.data(), &AudioScriptingInterface::noiseGateClosed); - connect(audioIO.data(), &AudioClient::inputReceived, audioScriptingInterface.data(), &AudioScriptingInterface::inputReceived); + { + auto audioIO = DependencyManager::get().data(); + // set the local loopback interface for local sounds + AudioInjector::setLocalAudioInterface(audioIO); + auto audioScriptingInterface = DependencyManager::get(); + audioScriptingInterface->setLocalAudioInterface(audioIO); + connect(audioIO, &AudioClient::noiseGateOpened, audioScriptingInterface.data(), &AudioScriptingInterface::noiseGateOpened); + connect(audioIO, &AudioClient::noiseGateClosed, audioScriptingInterface.data(), &AudioScriptingInterface::noiseGateClosed); + connect(audioIO, &AudioClient::inputReceived, audioScriptingInterface.data(), &AudioScriptingInterface::inputReceived); + } this->installEventFilter(this); @@ -2174,11 +2184,10 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo QVariant testProperty = property(hifi::properties::TEST); qDebug() << testProperty; if (testProperty.isValid()) { - auto scriptEngines = DependencyManager::get(); const auto testScript = property(hifi::properties::TEST).toUrl(); // Set last parameter to exit interface when the test script finishes, if so requested - scriptEngines->loadScript(testScript, false, false, false, false, quitWhenFinished); + DependencyManager::get()->loadScript(testScript, false, false, false, false, quitWhenFinished); // This is done so we don't get a "connection time-out" message when we haven't passed in a URL. if (arguments().contains("--url")) { @@ -2456,6 +2465,15 @@ void Application::cleanupBeforeQuit() { // Clear any queued processing (I/O, FBX/OBJ/Texture parsing) QThreadPool::globalInstance()->clear(); + // These classes hold ScriptEnginePointers, so they must be destroyed before ScriptEngines + { + DependencyManager::destroy(); + DependencyManager::destroy(); + EntityTreePointer tree = getEntities()->getTree(); + tree->setSimulation(nullptr); + DependencyManager::destroy(); + } + DependencyManager::get()->shutdownScripting(); // stop all currently running global scripts DependencyManager::destroy(); @@ -2493,6 +2511,8 @@ void Application::cleanupBeforeQuit() { DependencyManager::destroy(); #endif + DependencyManager::destroy(); // Must be destroyed before TabletScriptingInterface + // stop QML DependencyManager::destroy(); DependencyManager::destroy(); @@ -2504,10 +2524,6 @@ void Application::cleanupBeforeQuit() { _snapshotSoundInjector->stop(); } - // FIXME: something else is holding a reference to AudioClient, - // so it must be explicitly synchronously stopped here - DependencyManager::get()->cleanupBeforeQuit(); - // destroy Audio so it and its threads have a chance to go down safely // this must happen after QML, as there are unexplained audio crashes originating in qtwebengine DependencyManager::destroy(); @@ -2547,9 +2563,6 @@ Application::~Application() { _entityClipboard->eraseAllOctreeElements(); _entityClipboard.reset(); - EntityTreePointer tree = getEntities()->getTree(); - tree->setSimulation(nullptr); - _octreeProcessor.terminate(); _entityEditSender.terminate(); @@ -3239,8 +3252,7 @@ void Application::showHelp() { QUrlQuery queryString; queryString.addQueryItem("handControllerName", handControllerName); queryString.addQueryItem("defaultTab", defaultTab); - auto tabletScriptingInterface = DependencyManager::get(); - TabletProxy* tablet = dynamic_cast(tabletScriptingInterface->getTablet(SYSTEM_TABLET)); + TabletProxy* tablet = dynamic_cast(DependencyManager::get()->getTablet(SYSTEM_TABLET)); tablet->gotoWebScreen(PathUtils::resourcesUrl() + INFO_HELP_PATH + "?" + queryString.toString()); DependencyManager::get()->openTablet(); //InfoView::show(INFO_HELP_PATH, false, queryString.toString()); @@ -3545,6 +3557,10 @@ static void dumpEventQueue(QThread* thread) { bool Application::event(QEvent* event) { + if (_aboutToQuit) { + return false; + } + if (!Menu::getInstance()) { return false; } @@ -3937,10 +3953,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(); @@ -4005,11 +4017,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 @@ -4035,14 +4045,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; @@ -4061,10 +4068,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 @@ -4215,7 +4220,6 @@ bool Application::shouldPaint() const { return false; } - auto displayPlugin = getActiveDisplayPlugin(); #ifdef DEBUG_PAINT_DELAY @@ -5439,6 +5443,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", ""); @@ -5705,15 +5713,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); } { @@ -5731,11 +5737,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(); @@ -6639,16 +6643,12 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEnginePointe DependencyManager::get()->registerMetaTypes(scriptEngine.data()); // connect this script engines printedMessage signal to the global ScriptEngines these various messages - connect(scriptEngine.data(), &ScriptEngine::printedMessage, - DependencyManager::get().data(), &ScriptEngines::onPrintedMessage); - connect(scriptEngine.data(), &ScriptEngine::errorMessage, - DependencyManager::get().data(), &ScriptEngines::onErrorMessage); - connect(scriptEngine.data(), &ScriptEngine::warningMessage, - DependencyManager::get().data(), &ScriptEngines::onWarningMessage); - connect(scriptEngine.data(), &ScriptEngine::infoMessage, - DependencyManager::get().data(), &ScriptEngines::onInfoMessage); - connect(scriptEngine.data(), &ScriptEngine::clearDebugWindow, - DependencyManager::get().data(), &ScriptEngines::onClearDebugWindow); + auto scriptEngines = DependencyManager::get().data(); + connect(scriptEngine.data(), &ScriptEngine::printedMessage, scriptEngines, &ScriptEngines::onPrintedMessage); + connect(scriptEngine.data(), &ScriptEngine::errorMessage, scriptEngines, &ScriptEngines::onErrorMessage); + connect(scriptEngine.data(), &ScriptEngine::warningMessage, scriptEngines, &ScriptEngines::onWarningMessage); + connect(scriptEngine.data(), &ScriptEngine::infoMessage, scriptEngines, &ScriptEngines::onInfoMessage); + connect(scriptEngine.data(), &ScriptEngine::clearDebugWindow, scriptEngines, &ScriptEngines::onClearDebugWindow); } @@ -6956,10 +6956,9 @@ void Application::showDialog(const QUrl& widgetUrl, const QUrl& tabletUrl, const } void Application::showScriptLogs() { - auto scriptEngines = DependencyManager::get(); QUrl defaultScriptsLoc = PathUtils::defaultScriptsLocation(); defaultScriptsLoc.setPath(defaultScriptsLoc.path() + "developer/debugging/debugWindow.js"); - scriptEngines->loadScript(defaultScriptsLoc.toString()); + DependencyManager::get()->loadScript(defaultScriptsLoc.toString()); } void Application::showAssetServerWidget(QString filePath) { @@ -7523,7 +7522,6 @@ void Application::openUrl(const QUrl& url) const { } void Application::loadDialog() { - auto scriptEngines = DependencyManager::get(); ModalDialogListener* dlg = OffscreenUi::getOpenFileNameAsync(_glWidget, tr("Open Script"), getPreviousScriptLocation(), tr("JavaScript Files (*.js)")); diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 646067169f..ecc402bff9 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -692,10 +692,9 @@ Menu::Menu() { action = addActionToQMenuAndActionHash(audioDebugMenu, "Stats..."); connect(action, &QAction::triggered, [] { - auto scriptEngines = DependencyManager::get(); QUrl defaultScriptsLoc = PathUtils::defaultScriptsLocation(); defaultScriptsLoc.setPath(defaultScriptsLoc.path() + "developer/utilities/audio/stats.js"); - scriptEngines->loadScript(defaultScriptsLoc.toString()); + DependencyManager::get()->loadScript(defaultScriptsLoc.toString()); }); action = addActionToQMenuAndActionHash(audioDebugMenu, "Buffers..."); @@ -704,16 +703,14 @@ Menu::Menu() { QString("hifi/tablet/TabletAudioBuffers.qml"), "AudioBuffersDialog"); }); - auto audioIO = DependencyManager::get(); addActionToQMenuAndActionHash(audioDebugMenu, MenuOption::MuteEnvironment, 0, - audioIO.data(), SLOT(sendMuteEnvironmentPacket())); + DependencyManager::get().data(), SLOT(sendMuteEnvironmentPacket())); action = addActionToQMenuAndActionHash(audioDebugMenu, MenuOption::AudioScope); connect(action, &QAction::triggered, [] { - auto scriptEngines = DependencyManager::get(); QUrl defaultScriptsLoc = PathUtils::defaultScriptsLocation(); defaultScriptsLoc.setPath(defaultScriptsLoc.path() + "developer/utilities/audio/audioScope.js"); - scriptEngines->loadScript(defaultScriptsLoc.toString()); + DependencyManager::get()->loadScript(defaultScriptsLoc.toString()); }); // Developer > Physics >>> @@ -793,10 +790,9 @@ Menu::Menu() { // Developer > API Debugger action = addActionToQMenuAndActionHash(developerMenu, "API Debugger"); connect(action, &QAction::triggered, [] { - auto scriptEngines = DependencyManager::get(); QUrl defaultScriptsLoc = PathUtils::defaultScriptsLocation(); defaultScriptsLoc.setPath(defaultScriptsLoc.path() + "developer/utilities/tools/currentAPI.js"); - scriptEngines->loadScript(defaultScriptsLoc.toString()); + DependencyManager::get()->loadScript(defaultScriptsLoc.toString()); }); // Developer > Log... diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index b08496c2b8..382a210fa0 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -460,14 +460,14 @@ void MyAvatar::update(float deltaTime) { // Get audio loudness data from audio input device // Also get the AudioClient so we can update the avatar bounding box data // on the AudioClient side. - auto audio = DependencyManager::get(); + auto audio = DependencyManager::get().data(); setAudioLoudness(audio->getLastInputLoudness()); setAudioAverageLoudness(audio->getAudioAverageInputLoudness()); glm::vec3 halfBoundingBoxDimensions(_characterController.getCapsuleRadius(), _characterController.getCapsuleHalfHeight(), _characterController.getCapsuleRadius()); // This might not be right! Isn't the capsule local offset in avatar space? -HRS 5/26/17 halfBoundingBoxDimensions += _characterController.getCapsuleLocalOffset(); - QMetaObject::invokeMethod(audio.data(), "setAvatarBoundingBoxParameters", + QMetaObject::invokeMethod(audio, "setAvatarBoundingBoxParameters", Q_ARG(glm::vec3, (getWorldPosition() - halfBoundingBoxDimensions)), Q_ARG(glm::vec3, (halfBoundingBoxDimensions*2.0f))); diff --git a/interface/src/commerce/QmlCommerce.cpp b/interface/src/commerce/QmlCommerce.cpp index b960c0b703..09059b32b2 100644 --- a/interface/src/commerce/QmlCommerce.cpp +++ b/interface/src/commerce/QmlCommerce.cpp @@ -338,8 +338,7 @@ bool QmlCommerce::openApp(const QString& itemHref) { QJsonObject appFileJsonObject = appFileJsonDocument.object(); QString homeUrl = appFileJsonObject["homeURL"].toString(); - auto tabletScriptingInterface = DependencyManager::get(); - auto tablet = dynamic_cast(tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system")); + auto tablet = dynamic_cast(DependencyManager::get()->getTablet("com.highfidelity.interface.tablet.system")); if (homeUrl.contains(".qml", Qt::CaseInsensitive)) { tablet->loadQMLSource(homeUrl); } else if (homeUrl.contains(".html", Qt::CaseInsensitive)) { diff --git a/interface/src/scripting/Audio.cpp b/interface/src/scripting/Audio.cpp index b27cc344c3..eb4b5948d9 100644 --- a/interface/src/scripting/Audio.cpp +++ b/interface/src/scripting/Audio.cpp @@ -59,23 +59,20 @@ Audio::Audio() : _devices(_contextIsHMD) { } bool Audio::startRecording(const QString& filepath) { - auto client = DependencyManager::get().data(); return resultWithWriteLock([&] { - return client->startRecording(filepath); + return DependencyManager::get()->startRecording(filepath); }); } bool Audio::getRecording() { - auto client = DependencyManager::get().data(); return resultWithReadLock([&] { - return client->getRecording(); + return DependencyManager::get()->getRecording(); }); } void Audio::stopRecording() { - auto client = DependencyManager::get().data(); withWriteLock([&] { - client->stopRecording(); + DependencyManager::get()->stopRecording(); }); } diff --git a/interface/src/scripting/AudioDevices.cpp b/interface/src/scripting/AudioDevices.cpp index f08a0bf382..ce82786c8d 100644 --- a/interface/src/scripting/AudioDevices.cpp +++ b/interface/src/scripting/AudioDevices.cpp @@ -360,11 +360,11 @@ void AudioInputDeviceList::onPeakValueListChanged(const QList& peakValueL } AudioDevices::AudioDevices(bool& contextIsHMD) : _contextIsHMD(contextIsHMD) { - auto client = DependencyManager::get(); + auto client = DependencyManager::get().data(); - connect(client.data(), &AudioClient::deviceChanged, this, &AudioDevices::onDeviceChanged, Qt::QueuedConnection); - connect(client.data(), &AudioClient::devicesChanged, this, &AudioDevices::onDevicesChanged, Qt::QueuedConnection); - connect(client.data(), &AudioClient::peakValueListChanged, &_inputs, &AudioInputDeviceList::onPeakValueListChanged, Qt::QueuedConnection); + connect(client, &AudioClient::deviceChanged, this, &AudioDevices::onDeviceChanged, Qt::QueuedConnection); + connect(client, &AudioClient::devicesChanged, this, &AudioDevices::onDevicesChanged, Qt::QueuedConnection); + connect(client, &AudioClient::peakValueListChanged, &_inputs, &AudioInputDeviceList::onPeakValueListChanged, Qt::QueuedConnection); _inputs.onDeviceChanged(client->getActiveAudioDevice(QAudio::AudioInput), contextIsHMD); _outputs.onDeviceChanged(client->getActiveAudioDevice(QAudio::AudioOutput), contextIsHMD); @@ -446,7 +446,7 @@ void AudioDevices::onDevicesChanged(QAudio::Mode mode, const QList(); + auto client = DependencyManager::get().data(); _inputs._hmdSavedDeviceName = getTargetDevice(true, QAudio::AudioInput); _inputs._desktopSavedDeviceName = getTargetDevice(false, QAudio::AudioInput); @@ -494,9 +494,9 @@ void AudioDevices::onDevicesChanged(QAudio::Mode mode, const QList(); + auto client = DependencyManager::get().data(); _requestedInputDevice = device; - QMetaObject::invokeMethod(client.data(), "switchAudioDevice", + QMetaObject::invokeMethod(client, "switchAudioDevice", Q_ARG(QAudio::Mode, QAudio::AudioInput), Q_ARG(const QAudioDeviceInfo&, device)); } else { @@ -511,9 +511,9 @@ void AudioDevices::chooseInputDevice(const QAudioDeviceInfo& device, bool isHMD) void AudioDevices::chooseOutputDevice(const QAudioDeviceInfo& device, bool isHMD) { //check if current context equals device to change if (_contextIsHMD == isHMD) { - auto client = DependencyManager::get(); + auto client = DependencyManager::get().data(); _requestedOutputDevice = device; - QMetaObject::invokeMethod(client.data(), "switchAudioDevice", + QMetaObject::invokeMethod(client, "switchAudioDevice", Q_ARG(QAudio::Mode, QAudio::AudioOutput), Q_ARG(const QAudioDeviceInfo&, device)); } else { diff --git a/interface/src/ui/LoginDialog.cpp b/interface/src/ui/LoginDialog.cpp index 4d8592a9d3..b3912289ac 100644 --- a/interface/src/ui/LoginDialog.cpp +++ b/interface/src/ui/LoginDialog.cpp @@ -162,9 +162,7 @@ void LoginDialog::createAccountFromStream(QString username) { } void LoginDialog::openUrl(const QString& url) const { - - auto tabletScriptingInterface = DependencyManager::get(); - auto tablet = dynamic_cast(tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system")); + auto tablet = dynamic_cast(DependencyManager::get()->getTablet("com.highfidelity.interface.tablet.system")); auto hmd = DependencyManager::get(); auto offscreenUi = DependencyManager::get(); diff --git a/interface/src/ui/Stats.cpp b/interface/src/ui/Stats.cpp index c7ee868855..6675ab9c39 100644 --- a/interface/src/ui/Stats.cpp +++ b/interface/src/ui/Stats.cpp @@ -211,7 +211,7 @@ void Stats::updateStats(bool force) { STAT_UPDATE_FLOAT(myAvatarSendRate, avatarManager->getMyAvatarSendRate(), 0.1f); SharedNodePointer audioMixerNode = nodeList->soloNodeOfType(NodeType::AudioMixer); - auto audioClient = DependencyManager::get(); + auto audioClient = DependencyManager::get().data(); if (audioMixerNode || force) { STAT_UPDATE(audioMixerKbps, (int)roundf( bandwidthRecorder->getAverageInputKilobitsPerSecond(NodeType::AudioMixer) + diff --git a/interface/src/ui/TestingDialog.cpp b/interface/src/ui/TestingDialog.cpp index 6143f20ee6..5f0b20ca7e 100644 --- a/interface/src/ui/TestingDialog.cpp +++ b/interface/src/ui/TestingDialog.cpp @@ -23,8 +23,7 @@ TestingDialog::TestingDialog(QWidget* parent) : _console->setFixedHeight(TESTING_CONSOLE_HEIGHT); - auto _engines = DependencyManager::get(); - _engine = _engines->loadScript(qApp->applicationDirPath() + testRunnerRelativePath); + _engine = DependencyManager::get()->loadScript(qApp->applicationDirPath() + testRunnerRelativePath); _console->setScriptEngine(_engine); connect(_engine.data(), &ScriptEngine::finished, this, &TestingDialog::onTestingFinished); } diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index a6f0416a30..2d1f24cfd7 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -269,31 +269,14 @@ AudioClient::~AudioClient() { } void AudioClient::customDeleter() { - deleteLater(); -} - -void AudioClient::cleanupBeforeQuit() { - // FIXME: this should be put in customDeleter, but there is still a reference to this when it is called, - // so this must be explicitly, synchronously stopped - static ConditionalGuard guard; - if (QThread::currentThread() != thread()) { - // This will likely be called from the main thread, but we don't want to do blocking queued calls - // from the main thread, so we use a normal auto-connection invoke, and then use a conditional to wait - // for completion - // The effect is the same, yes, but we actually want to avoid the use of Qt::BlockingQueuedConnection - // in the code - QMetaObject::invokeMethod(this, "cleanupBeforeQuit"); - guard.wait(); - return; - } - #if defined(Q_OS_ANDROID) _shouldRestartInputSetup = false; #endif stop(); _checkDevicesTimer->stop(); _checkPeakValuesTimer->stop(); - guard.trigger(); + + deleteLater(); } void AudioClient::handleMismatchAudioFormat(SharedNodePointer node, const QString& currentCodec, const QString& recievedCodec) { diff --git a/libraries/audio-client/src/AudioClient.h b/libraries/audio-client/src/AudioClient.h index 9ee7bcfeba..8599c990a3 100644 --- a/libraries/audio-client/src/AudioClient.h +++ b/libraries/audio-client/src/AudioClient.h @@ -171,7 +171,6 @@ public: public slots: void start(); void stop(); - void cleanupBeforeQuit(); void handleAudioEnvironmentDataPacket(QSharedPointer message); void handleAudioDataPacket(QSharedPointer message); diff --git a/libraries/audio-client/src/AudioIOStats.cpp b/libraries/audio-client/src/AudioIOStats.cpp index 1717ad1f9c..1e45622fdc 100644 --- a/libraries/audio-client/src/AudioIOStats.cpp +++ b/libraries/audio-client/src/AudioIOStats.cpp @@ -95,8 +95,6 @@ void AudioIOStats::processStreamStatsPacket(QSharedPointer mess } void AudioIOStats::publish() { - auto audioIO = DependencyManager::get(); - // call _receivedAudioStream's per-second callback _receivedAudioStream->perSecondCallbackForUpdatingStats(); diff --git a/libraries/script-engine/src/ScriptEngines.cpp b/libraries/script-engine/src/ScriptEngines.cpp index d385dcca84..b2815ff2b4 100644 --- a/libraries/script-engine/src/ScriptEngines.cpp +++ b/libraries/script-engine/src/ScriptEngines.cpp @@ -492,8 +492,7 @@ ScriptEnginePointer ScriptEngines::loadScript(const QUrl& scriptFilename, bool i return scriptEngine; } - scriptEngine = ScriptEnginePointer(new ScriptEngine(_context, NO_SCRIPT, "about:" + scriptFilename.fileName())); - addScriptEngine(scriptEngine); + scriptEngine = scriptEngineFactory(_context, NO_SCRIPT, "about:" + scriptFilename.fileName()); scriptEngine->setUserLoaded(isUserLoaded); scriptEngine->setQuitWhenFinished(quitWhenFinished); diff --git a/libraries/shared/src/DependencyManager.h b/libraries/shared/src/DependencyManager.h index e6fc7ce96b..8490526533 100644 --- a/libraries/shared/src/DependencyManager.h +++ b/libraries/shared/src/DependencyManager.h @@ -139,7 +139,13 @@ QSharedPointer DependencyManager::set(Args&&... args) { template void DependencyManager::destroy() { static size_t hashCode = manager().getHashCode(); - manager().safeGet(hashCode).clear(); + QSharedPointer& shared = manager().safeGet(hashCode); + QWeakPointer weak = shared; + shared.clear(); + // Check that the dependency was actually destroyed. If it wasn't, it was improperly captured somewhere + if (weak.lock()) { + qWarning() << "DependencyManager::destroy(): Dependency was not properly destroyed!"; + } } template diff --git a/libraries/ui/src/ui/OffscreenQmlSurface.cpp b/libraries/ui/src/ui/OffscreenQmlSurface.cpp index 48e778c063..dfb825411d 100644 --- a/libraries/ui/src/ui/OffscreenQmlSurface.cpp +++ b/libraries/ui/src/ui/OffscreenQmlSurface.cpp @@ -331,8 +331,7 @@ void OffscreenQmlSurface::onRootCreated() { getSurfaceContext()->setContextProperty("offscreenWindow", QVariant::fromValue(getWindow())); // Connect with the audio client and listen for audio device changes - auto audioIO = DependencyManager::get(); - connect(audioIO.data(), &AudioClient::deviceChanged, this, [&](QAudio::Mode mode, const QAudioDeviceInfo& device) { + connect(DependencyManager::get().data(), &AudioClient::deviceChanged, this, [&](QAudio::Mode mode, const QAudioDeviceInfo& device) { if (mode == QAudio::Mode::AudioOutput) { QMetaObject::invokeMethod(this, "changeAudioOutputDevice", Qt::QueuedConnection, Q_ARG(QString, device.deviceName())); } From d2699e8e5b7cf9d2ef25650266f0c6ca46a05f0e Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Thu, 5 Jul 2018 15:44:05 -0700 Subject: [PATCH 005/144] some more attempted cleanup --- interface/src/Application.cpp | 2 ++ interface/src/ui/JSConsole.cpp | 2 +- libraries/script-engine/src/ScriptEngines.cpp | 6 +++--- libraries/shared/src/DependencyManager.h | 2 +- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index bc881e592b..bbfcee53b9 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2474,6 +2474,8 @@ void Application::cleanupBeforeQuit() { DependencyManager::destroy(); } + // FIXME: Something is still holding on to the ScriptEnginePointers contained in ScriptEngines, and they hold backpointers to ScriptEngines, + // so this doesn't shut down properly DependencyManager::get()->shutdownScripting(); // stop all currently running global scripts DependencyManager::destroy(); diff --git a/interface/src/ui/JSConsole.cpp b/interface/src/ui/JSConsole.cpp index 1ca1ac2842..ed4ee97780 100644 --- a/interface/src/ui/JSConsole.cpp +++ b/interface/src/ui/JSConsole.cpp @@ -312,7 +312,7 @@ JSConsole::~JSConsole() { delete _ui; } -void JSConsole::setScriptEngine(const ScriptEnginePointer& scriptEngine) { +void JSConsole::setScriptEngine(const ScriptEnginePointer& scriptEngine) { if (_scriptEngine == scriptEngine && scriptEngine != nullptr) { return; } diff --git a/libraries/script-engine/src/ScriptEngines.cpp b/libraries/script-engine/src/ScriptEngines.cpp index b2815ff2b4..6393ff33ea 100644 --- a/libraries/script-engine/src/ScriptEngines.cpp +++ b/libraries/script-engine/src/ScriptEngines.cpp @@ -133,7 +133,7 @@ QUrl expandScriptUrl(const QUrl& rawScriptURL) { QObject* scriptsModel(); bool NativeScriptInitializers::registerNativeScriptInitializer(NativeScriptInitializer initializer) { - return registerScriptInitializer([=](ScriptEnginePointer engine) { + return registerScriptInitializer([initializer](ScriptEnginePointer engine) { initializer(qobject_cast(engine.data())); }); } @@ -563,10 +563,10 @@ int ScriptEngines::runScriptInitializers(ScriptEnginePointer scriptEngine) { void ScriptEngines::launchScriptEngine(ScriptEnginePointer scriptEngine) { connect(scriptEngine.data(), &ScriptEngine::finished, this, &ScriptEngines::onScriptFinished, Qt::DirectConnection); - connect(scriptEngine.data(), &ScriptEngine::loadScript, [&](const QString& scriptName, bool userLoaded) { + connect(scriptEngine.data(), &ScriptEngine::loadScript, [this](const QString& scriptName, bool userLoaded) { loadScript(scriptName, userLoaded); }); - connect(scriptEngine.data(), &ScriptEngine::reloadScript, [&](const QString& scriptName, bool userLoaded) { + connect(scriptEngine.data(), &ScriptEngine::reloadScript, [this](const QString& scriptName, bool userLoaded) { loadScript(scriptName, userLoaded, false, false, true); }); diff --git a/libraries/shared/src/DependencyManager.h b/libraries/shared/src/DependencyManager.h index 8490526533..da877f7b3b 100644 --- a/libraries/shared/src/DependencyManager.h +++ b/libraries/shared/src/DependencyManager.h @@ -144,7 +144,7 @@ void DependencyManager::destroy() { shared.clear(); // Check that the dependency was actually destroyed. If it wasn't, it was improperly captured somewhere if (weak.lock()) { - qWarning() << "DependencyManager::destroy(): Dependency was not properly destroyed!"; + qWarning() << "DependencyManager::destroy():" << typeid(T).name() << "was not properly destroyed!"; } } From f33ee55f9eea0b0df5a067f5c5b46d4c734665be Mon Sep 17 00:00:00 2001 From: sabrina-shanman Date: Thu, 12 Jul 2018 10:59:09 -0700 Subject: [PATCH 006/144] Create CollisionPick API --- interface/src/Application.cpp | 4 +- interface/src/raypick/CollisionPick.cpp | 301 ++++++++++++++++++ interface/src/raypick/CollisionPick.h | 238 ++++++++++++++ .../src/raypick/PickScriptingInterface.cpp | 26 ++ .../src/raypick/PickScriptingInterface.h | 10 + libraries/physics/src/PhysicsEngine.h | 2 + libraries/pointers/src/Pick.h | 1 + libraries/pointers/src/PickManager.cpp | 1 + libraries/pointers/src/PickManager.h | 3 +- libraries/shared/src/RegisteredMetaTypes.h | 94 ++++++ libraries/shared/src/ShapeInfo.cpp | 11 + libraries/shared/src/ShapeInfo.h | 1 + 12 files changed, 690 insertions(+), 2 deletions(-) create mode 100644 interface/src/raypick/CollisionPick.cpp create mode 100644 interface/src/raypick/CollisionPick.h diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 7e683c7cc8..ad61e6a4a1 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -6654,7 +6654,9 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEnginePointe registerInteractiveWindowMetaType(scriptEngine.data()); - DependencyManager::get()->registerMetaTypes(scriptEngine.data()); + auto pickScriptingInterface = DependencyManager::get(); + pickScriptingInterface->registerMetaTypes(scriptEngine.data()); + pickScriptingInterface->setCollisionWorld(_physicsEngine->getDynamicsWorld()); // connect this script engines printedMessage signal to the global ScriptEngines these various messages connect(scriptEngine.data(), &ScriptEngine::printedMessage, diff --git a/interface/src/raypick/CollisionPick.cpp b/interface/src/raypick/CollisionPick.cpp new file mode 100644 index 0000000000..7ed146300d --- /dev/null +++ b/interface/src/raypick/CollisionPick.cpp @@ -0,0 +1,301 @@ +// +// Created by Sabrina Shanman 7/16/2018 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "CollisionPick.h" + +#include + +#include + +#include "ScriptEngineLogging.h" +#include "model-networking/ModelCache.h" + +bool CollisionPick::isShapeInfoReady(CollisionRegion& pick) { + if (pick.shouldComputeShapeInfo()) { + if (!_cachedResource || _cachedResource->getURL() != pick.modelURL) { + _cachedResource = DependencyManager::get()->getCollisionGeometryResource(pick.modelURL); + } + + if (_cachedResource->isLoaded()) { + computeShapeInfo(pick, pick.shapeInfo, _cachedResource); + } + + return false; + } + + return true; +} + +void CollisionPick::computeShapeInfo(CollisionRegion& pick, ShapeInfo& shapeInfo, QSharedPointer resource) { + // This code was copied and modified from RenderableModelEntityItem::computeShapeInfo + // TODO: Move to some shared code area (in entities-renderer? model-networking?) + // after we verify this is working and do a diff comparison with RenderableModelEntityItem::computeShapeInfo + // to consolidate the code. + // We may also want to make computeShapeInfo always abstract away from the gpu model mesh, like it does here. + const uint32_t TRIANGLE_STRIDE = 3; + const uint32_t QUAD_STRIDE = 4; + + ShapeType type = shapeInfo.getType(); + glm::vec3 dimensions = pick.transform.getScale(); + if (type == SHAPE_TYPE_COMPOUND) { + // should never fall in here when collision model not fully loaded + // TODO: assert that all geometries exist and are loaded + //assert(_model && _model->isLoaded() && _compoundShapeResource && _compoundShapeResource->isLoaded()); + const FBXGeometry& collisionGeometry = resource->getFBXGeometry(); + + ShapeInfo::PointCollection& pointCollection = shapeInfo.getPointCollection(); + pointCollection.clear(); + uint32_t i = 0; + + // the way OBJ files get read, each section under a "g" line is its own meshPart. We only expect + // to find one actual "mesh" (with one or more meshParts in it), but we loop over the meshes, just in case. + foreach (const FBXMesh& mesh, collisionGeometry.meshes) { + // each meshPart is a convex hull + foreach (const FBXMeshPart &meshPart, mesh.parts) { + pointCollection.push_back(QVector()); + ShapeInfo::PointList& pointsInPart = pointCollection[i]; + + // run through all the triangles and (uniquely) add each point to the hull + uint32_t numIndices = (uint32_t)meshPart.triangleIndices.size(); + // TODO: assert rather than workaround after we start sanitizing FBXMesh higher up + //assert(numIndices % TRIANGLE_STRIDE == 0); + numIndices -= numIndices % TRIANGLE_STRIDE; // WORKAROUND lack of sanity checking in FBXReader + + for (uint32_t j = 0; j < numIndices; j += TRIANGLE_STRIDE) { + glm::vec3 p0 = mesh.vertices[meshPart.triangleIndices[j]]; + glm::vec3 p1 = mesh.vertices[meshPart.triangleIndices[j + 1]]; + glm::vec3 p2 = mesh.vertices[meshPart.triangleIndices[j + 2]]; + if (!pointsInPart.contains(p0)) { + pointsInPart << p0; + } + if (!pointsInPart.contains(p1)) { + pointsInPart << p1; + } + if (!pointsInPart.contains(p2)) { + pointsInPart << p2; + } + } + + // run through all the quads and (uniquely) add each point to the hull + numIndices = (uint32_t)meshPart.quadIndices.size(); + // TODO: assert rather than workaround after we start sanitizing FBXMesh higher up + //assert(numIndices % QUAD_STRIDE == 0); + numIndices -= numIndices % QUAD_STRIDE; // WORKAROUND lack of sanity checking in FBXReader + + for (uint32_t j = 0; j < numIndices; j += QUAD_STRIDE) { + glm::vec3 p0 = mesh.vertices[meshPart.quadIndices[j]]; + glm::vec3 p1 = mesh.vertices[meshPart.quadIndices[j + 1]]; + glm::vec3 p2 = mesh.vertices[meshPart.quadIndices[j + 2]]; + glm::vec3 p3 = mesh.vertices[meshPart.quadIndices[j + 3]]; + if (!pointsInPart.contains(p0)) { + pointsInPart << p0; + } + if (!pointsInPart.contains(p1)) { + pointsInPart << p1; + } + if (!pointsInPart.contains(p2)) { + pointsInPart << p2; + } + if (!pointsInPart.contains(p3)) { + pointsInPart << p3; + } + } + + if (pointsInPart.size() == 0) { + qCDebug(scriptengine) << "Warning -- meshPart has no faces"; + pointCollection.pop_back(); + continue; + } + ++i; + } + } + + // We expect that the collision model will have the same units and will be displaced + // from its origin in the same way the visual model is. The visual model has + // been centered and probably scaled. We take the scaling and offset which were applied + // to the visual model and apply them to the collision model (without regard for the + // collision model's extents). + + glm::vec3 scaleToFit = dimensions / resource->getFBXGeometry().getUnscaledMeshExtents().size(); + // multiply each point by scale + for (int32_t i = 0; i < pointCollection.size(); i++) { + for (int32_t j = 0; j < pointCollection[i].size(); j++) { + // back compensate for registration so we can apply that offset to the shapeInfo later + pointCollection[i][j] = scaleToFit * pointCollection[i][j]; + } + } + shapeInfo.setParams(type, dimensions, resource->getURL().toString()); + } else if (type >= SHAPE_TYPE_SIMPLE_HULL && type <= SHAPE_TYPE_STATIC_MESH) { + const FBXGeometry& fbxGeometry = resource->getFBXGeometry(); + int numFbxMeshes = fbxGeometry.meshes.size(); + int totalNumVertices = 0; + glm::mat4 invRegistrationOffset = glm::translate(dimensions * (-ENTITY_ITEM_DEFAULT_REGISTRATION_POINT)); + for (int i = 0; i < numFbxMeshes; i++) { + const FBXMesh& mesh = fbxGeometry.meshes.at(i); + totalNumVertices += mesh.vertices.size(); + } + const int32_t MAX_VERTICES_PER_STATIC_MESH = 1e6; + if (totalNumVertices > MAX_VERTICES_PER_STATIC_MESH) { + qWarning() << "model" << resource->getURL() << "has too many vertices" << totalNumVertices << "and will collide as a box."; + shapeInfo.setParams(SHAPE_TYPE_BOX, 0.5f * dimensions); + return; + } + + auto& meshes = resource->getFBXGeometry().meshes; + int32_t numMeshes = (int32_t)(meshes.size()); + + const int MAX_ALLOWED_MESH_COUNT = 1000; + if (numMeshes > MAX_ALLOWED_MESH_COUNT) { + // too many will cause the deadlock timer to throw... + shapeInfo.setParams(SHAPE_TYPE_BOX, 0.5f * dimensions); + return; + } + + ShapeInfo::PointCollection& pointCollection = shapeInfo.getPointCollection(); + pointCollection.clear(); + if (type == SHAPE_TYPE_SIMPLE_COMPOUND) { + pointCollection.resize(numMeshes); + } else { + pointCollection.resize(1); + } + + ShapeInfo::TriangleIndices& triangleIndices = shapeInfo.getTriangleIndices(); + triangleIndices.clear(); + + Extents extents; + int32_t meshCount = 0; + int32_t pointListIndex = 0; + for (auto& mesh : meshes) { + if (!mesh.vertices.size()) { + continue; + } + QVector vertices = mesh.vertices; + + ShapeInfo::PointList& points = pointCollection[pointListIndex]; + + // reserve room + int32_t sizeToReserve = (int32_t)(vertices.count()); + if (type == SHAPE_TYPE_SIMPLE_COMPOUND) { + // a list of points for each mesh + pointListIndex++; + } else { + // only one list of points + sizeToReserve += (int32_t)points.size(); + } + points.reserve(sizeToReserve); + + // copy points + uint32_t meshIndexOffset = (uint32_t)points.size(); + 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(triangleIndicesCount); + + for (const FBXMeshPart& meshPart : mesh.parts) { + const int* indexItr = meshPart.triangleIndices.cbegin(); + while (indexItr != meshPart.triangleIndices.cend()) { + triangleIndices.push_back(*indexItr); + ++indexItr; + } + } + } else if (type == SHAPE_TYPE_SIMPLE_COMPOUND) { + // for each mesh copy unique part indices, separated by special bogus (flag) index values + for (const FBXMeshPart& meshPart : mesh.parts) { + // collect unique list of indices for this part + std::set uniqueIndices; + auto numIndices = meshPart.triangleIndices.count(); + // TODO: assert rather than workaround after we start sanitizing FBXMesh higher up + //assert(numIndices% TRIANGLE_STRIDE == 0); + numIndices -= numIndices % TRIANGLE_STRIDE; // WORKAROUND lack of sanity checking in FBXReader + + auto indexItr = meshPart.triangleIndices.cbegin(); + while (indexItr != meshPart.triangleIndices.cend()) { + uniqueIndices.insert(*indexItr); + ++indexItr; + } + + // store uniqueIndices in triangleIndices + triangleIndices.reserve(triangleIndices.size() + (int32_t)uniqueIndices.size()); + for (auto index : uniqueIndices) { + triangleIndices.push_back(index); + } + // flag end of part + triangleIndices.push_back(END_OF_MESH_PART); + } + // flag end of mesh + triangleIndices.push_back(END_OF_MESH); + } + ++meshCount; + } + + // scale and shift + glm::vec3 extentsSize = extents.size(); + glm::vec3 scaleToFit = dimensions / extentsSize; + for (int32_t i = 0; i < 3; ++i) { + if (extentsSize[i] < 1.0e-6f) { + scaleToFit[i] = 1.0f; + } + } + for (auto points : pointCollection) { + for (int32_t i = 0; i < points.size(); ++i) { + points[i] = (points[i] * scaleToFit); + } + } + + shapeInfo.setParams(type, 0.5f * dimensions, resource->getURL().toString()); + } +} + +CollisionRegion CollisionPick::getMathematicalPick() const { + return _mathPick; +} + +PickResultPointer CollisionPick::getEntityIntersection(const CollisionRegion& pick) { + if (!isShapeInfoReady(*const_cast(&pick))) { + // Cannot compute result + return std::make_shared(); + } + + auto entityCollisionCallback = AllObjectMotionStatesCallback(pick.shapeInfo, pick.transform); + btCollisionWorld* collisionWorld = const_cast(_collisionWorld); + collisionWorld->contactTest(&entityCollisionCallback.collisionObject, entityCollisionCallback); + + return std::make_shared(pick, entityCollisionCallback.intersectingObjects, std::vector()); +} + +PickResultPointer CollisionPick::getOverlayIntersection(const CollisionRegion& pick) { + return getDefaultResult(QVariantMap()); +} + +PickResultPointer CollisionPick::getAvatarIntersection(const CollisionRegion& pick) { + if (!isShapeInfoReady(*const_cast(&pick))) { + // Cannot compute result + return std::make_shared(); + } + + auto avatarCollisionCallback = AllObjectMotionStatesCallback(pick.shapeInfo, pick.transform); + btCollisionWorld* collisionWorld = const_cast(_collisionWorld); + collisionWorld->contactTest(&avatarCollisionCallback.collisionObject, avatarCollisionCallback); + + return std::make_shared(pick, std::vector(), avatarCollisionCallback.intersectingObjects); +} + +PickResultPointer CollisionPick::getHUDIntersection(const CollisionRegion& pick) { + return getDefaultResult(QVariantMap()); +} \ No newline at end of file diff --git a/interface/src/raypick/CollisionPick.h b/interface/src/raypick/CollisionPick.h new file mode 100644 index 0000000000..5cd8d4ccd6 --- /dev/null +++ b/interface/src/raypick/CollisionPick.h @@ -0,0 +1,238 @@ +// +// Created by Sabrina Shanman 7/11/2018 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#ifndef hifi_CollisionPick_h +#define hifi_CollisionPick_h + +#include + +#include +#include +#include +#include +#include + +class CollisionPickResult : public PickResult { +public: + struct EntityIntersection { + EntityIntersection() { } + + EntityIntersection(const EntityIntersection& entityIntersection) : + id(entityIntersection.id), + pickCollisionPoint(entityIntersection.pickCollisionPoint), + entityCollisionPoint(entityIntersection.entityCollisionPoint) { + } + + EntityIntersection(QUuid id, glm::vec3 pickCollisionPoint, glm::vec3 entityCollisionPoint) : + id(id), + pickCollisionPoint(pickCollisionPoint), + entityCollisionPoint(entityCollisionPoint) { + } + + QVariantMap toVariantMap() { + QVariantMap variantMap; + variantMap["objectID"] = id; + variantMap["pickCollisionPoint"] = vec3toVariant(pickCollisionPoint); + variantMap["entityCollisionPoint"] = vec3toVariant(entityCollisionPoint); + return variantMap; + } + + QUuid id; + glm::vec3 pickCollisionPoint; + glm::vec3 entityCollisionPoint; + }; + + CollisionPickResult() {} + CollisionPickResult(const QVariantMap& pickVariant) : PickResult(pickVariant) {} + + CollisionPickResult(const CollisionRegion& searchRegion, const std::vector& intersectingEntities, const std::vector& intersectingAvatars) : + PickResult(searchRegion.toVariantMap()), + intersects(intersectingEntities.size() || intersectingAvatars.size()), + intersectingEntities(intersectingEntities), + intersectingAvatars(intersectingAvatars) { + } + + CollisionPickResult(const CollisionPickResult& collisionPickResult) : PickResult(collisionPickResult.pickVariant) { + intersectingAvatars = collisionPickResult.intersectingAvatars; + intersectingEntities = collisionPickResult.intersectingEntities; + intersects = intersectingAvatars.size() || intersectingEntities.size(); + } + + bool intersects { false }; + std::vector intersectingEntities; + std::vector intersectingAvatars; + + virtual QVariantMap toVariantMap() const override { + QVariantMap variantMap; + + variantMap["intersects"] = intersects; + + QVariantList qIntersectingEntities; + for (auto intersectingEntity : intersectingEntities) { + qIntersectingEntities.append(intersectingEntity.toVariantMap()); + } + variantMap["intersectingEntities"] = qIntersectingEntities; + + QVariantList qIntersectingAvatars; + for (auto intersectingAvatar : intersectingAvatars) { + qIntersectingAvatars.append(intersectingAvatar.toVariantMap()); + } + variantMap["intersectingAvatars"] = qIntersectingAvatars; + + variantMap["collisionRegion"] = pickVariant; + + return variantMap; + } + + bool doesIntersect() const override { return intersects; } + bool checkOrFilterAgainstMaxDistance(float maxDistance) override { return true; } + + PickResultPointer compareAndProcessNewResult(const PickResultPointer& newRes) override { + const std::shared_ptr newCollisionResult = std::static_pointer_cast(*const_cast(&newRes)); + // Have to reference the raw pointer to work around strange type conversion errors + CollisionPickResult* newCollisionResultRaw = const_cast(newCollisionResult.get()); + + for (EntityIntersection& intersectingEntity : newCollisionResultRaw->intersectingEntities) { + intersectingEntities.push_back(intersectingEntity); + } + for (EntityIntersection& intersectingAvatar : newCollisionResultRaw->intersectingAvatars) { + intersectingAvatars.push_back(intersectingAvatar); + } + + return std::make_shared(*this); + } +}; + +class CollisionPick : public Pick { +public: + CollisionPick(const PickFilter& filter, float maxDistance, bool enabled, CollisionRegion collisionRegion, const btCollisionWorld* collisionWorld) : + Pick(filter, maxDistance, enabled), + _mathPick(collisionRegion), + _collisionWorld(collisionWorld) { + } + + CollisionRegion getMathematicalPick() const override; + PickResultPointer getDefaultResult(const QVariantMap& pickVariant) const { return std::make_shared(pickVariant); } + 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(CollisionRegion& pick); + void computeShapeInfo(CollisionRegion& pick, ShapeInfo& shapeInfo, QSharedPointer resource); + + CollisionRegion _mathPick; + const btCollisionWorld* _collisionWorld; + QSharedPointer _cachedResource; +}; + +// Callback for checking the motion states of all colliding rigid bodies for candidacy to be added to a list +struct RigidBodyFilterResultCallback : public btCollisionWorld::ContactResultCallback { + RigidBodyFilterResultCallback(const ShapeInfo& shapeInfo, const Transform& transform) : + btCollisionWorld::ContactResultCallback(), collisionObject() { + const btCollisionShape* collisionShape = ObjectMotionState::getShapeManager()->getShape(shapeInfo); + + collisionObject.setCollisionShape(const_cast(collisionShape)); + + btTransform bulletTransform; + bulletTransform.setOrigin(glmToBullet(transform.getTranslation())); + bulletTransform.setRotation(glmToBullet(transform.getRotation())); + + collisionObject.setWorldTransform(bulletTransform); + } + + ~RigidBodyFilterResultCallback() { + ObjectMotionState::getShapeManager()->releaseShape(collisionObject.getCollisionShape()); + } + + RigidBodyFilterResultCallback(btCollisionObject& testCollisionObject) : + btCollisionWorld::ContactResultCallback(), collisionObject(testCollisionObject) { + } + + btCollisionObject collisionObject; + + // Check candidacy for adding to a list + virtual void checkOrAddCollidingState(const btMotionState* otherMotionState, btVector3& point, btVector3& otherPoint) = 0; + + btScalar addSingleResult(btManifoldPoint& cp, const btCollisionObjectWrapper* colObj0, int partId0, int index0, const btCollisionObjectWrapper* colObj1, int partId1, int index1) override { + const btCollisionObject* otherBody; + btVector3 point; + btVector3 otherPoint; + if (colObj0->m_collisionObject == &collisionObject) { + otherBody = colObj1->m_collisionObject; + point = cp.m_localPointA; + otherPoint = cp.m_localPointB; + } + else { + otherBody = colObj0->m_collisionObject; + point = cp.m_localPointB; + otherPoint = cp.m_localPointA; + } + const btRigidBody* collisionCandidate = dynamic_cast(otherBody); + if (!collisionCandidate) { + return 0; + } + const btMotionState* motionStateCandidate = collisionCandidate->getMotionState(); + + checkOrAddCollidingState(motionStateCandidate, point, otherPoint); + + return 0; + } +}; + +// Callback for getting colliding avatars in the world. +struct AllAvatarsCallback : public RigidBodyFilterResultCallback { + std::vector intersectingAvatars; + + void checkOrAddCollidingState(const btMotionState* otherMotionState, btVector3& point, btVector3& otherPoint) override { + const AvatarMotionState* avatarCandidate = dynamic_cast(otherMotionState); + if (!avatarCandidate) { + return; + } + + // This is the correct object type. Add it to the list. + intersectingAvatars.emplace_back(avatarCandidate->getObjectID(), bulletToGLM(point), bulletToGLM(otherPoint)); + } +}; + +// Callback for getting colliding entities in the world. +struct AllEntitiesCallback : public RigidBodyFilterResultCallback { + std::vector intersectingEntities; + + void checkOrAddCollidingState(const btMotionState* otherMotionState, btVector3& point, btVector3& otherPoint) override { + const EntityMotionState* entityCandidate = dynamic_cast(otherMotionState); + if (!entityCandidate) { + return; + } + + // This is the correct object type. Add it to the list. + intersectingEntities.emplace_back(entityCandidate->getObjectID(), bulletToGLM(point), bulletToGLM(otherPoint)); + } +}; + +// TODO: Test if this works. Revert to above code if it doesn't +// Callback for getting colliding ObjectMotionStates in the world, or optionally a more specific type. +template +struct AllObjectMotionStatesCallback : public RigidBodyFilterResultCallback { + AllObjectMotionStatesCallback(const ShapeInfo& shapeInfo, const Transform& transform) : RigidBodyFilterResultCallback(shapeInfo, transform) { } + + std::vector intersectingObjects; + + void checkOrAddCollidingState(const btMotionState* otherMotionState, btVector3& point, btVector3& otherPoint) override { + const T* candidate = dynamic_cast(otherMotionState); + if (!candidate) { + return; + } + + // This is the correct object type. Add it to the list. + intersectingObjects.emplace_back(candidate->getObjectID(), bulletToGLM(point), bulletToGLM(otherPoint)); + } +}; + +#endif // hifi_CollisionPick_h \ No newline at end of file diff --git a/interface/src/raypick/PickScriptingInterface.cpp b/interface/src/raypick/PickScriptingInterface.cpp index 74459ca624..526d099cb4 100644 --- a/interface/src/raypick/PickScriptingInterface.cpp +++ b/interface/src/raypick/PickScriptingInterface.cpp @@ -17,6 +17,7 @@ #include "JointRayPick.h" #include "MouseRayPick.h" #include "StylusPick.h" +#include "CollisionPick.h" #include @@ -26,6 +27,8 @@ unsigned int PickScriptingInterface::createPick(const PickQuery::PickType type, return createRayPick(properties); case PickQuery::PickType::Stylus: return createStylusPick(properties); + case PickQuery::PickType::Collision: + return createCollisionPick(properties); default: return PickManager::INVALID_PICK_ID; } @@ -134,6 +137,29 @@ unsigned int PickScriptingInterface::createStylusPick(const QVariant& properties return DependencyManager::get()->addPick(PickQuery::Stylus, std::make_shared(side, filter, maxDistance, enabled)); } +unsigned int PickScriptingInterface::createCollisionPick(const QVariant& properties) { + QVariantMap propMap = properties.toMap(); + + bool enabled = false; + if (propMap["enabled"].isValid()) { + enabled = propMap["enabled"].toBool(); + } + + PickFilter filter = PickFilter(); + if (propMap["filter"].isValid()) { + filter = PickFilter(propMap["filter"].toUInt()); + } + + float maxDistance = 0.0f; + if (propMap["maxDistance"].isValid()) { + maxDistance = propMap["maxDistance"].toFloat(); + } + + CollisionRegion collisionRegion(propMap); + + return DependencyManager::get()->addPick(PickQuery::Collision, std::make_shared(filter, maxDistance, enabled, collisionRegion, _collisionWorld)); +} + void PickScriptingInterface::enablePick(unsigned int uid) { DependencyManager::get()->enablePick(uid); } diff --git a/interface/src/raypick/PickScriptingInterface.h b/interface/src/raypick/PickScriptingInterface.h index 0ee091716d..48bc6e598e 100644 --- a/interface/src/raypick/PickScriptingInterface.h +++ b/interface/src/raypick/PickScriptingInterface.h @@ -9,6 +9,7 @@ #define hifi_PickScriptingInterface_h #include +#include #include #include @@ -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); void registerMetaTypes(QScriptEngine* engine); @@ -273,6 +275,14 @@ public slots: * @returns {number} */ static constexpr unsigned int INTERSECTED_HUD() { return IntersectionType::HUD; } + + // Set to allow CollisionPicks to have access to the physics engine + void setCollisionWorld(const btCollisionWorld* collisionWorld) { + _collisionWorld = collisionWorld; + } + +protected: + const btCollisionWorld* _collisionWorld; }; #endif // hifi_PickScriptingInterface_h diff --git a/libraries/physics/src/PhysicsEngine.h b/libraries/physics/src/PhysicsEngine.h index 2ac195956a..6a491c3791 100644 --- a/libraries/physics/src/PhysicsEngine.h +++ b/libraries/physics/src/PhysicsEngine.h @@ -73,6 +73,8 @@ public: const VectorOfMotionStates& getChangedMotionStates(); const VectorOfMotionStates& getDeactivatedMotionStates() const { return _dynamicsWorld->getDeactivatedMotionStates(); } + const ThreadSafeDynamicsWorld* getDynamicsWorld() { return _dynamicsWorld; } + /// \return reference to list of Collision events. The list is only valid until beginning of next simulation loop. const CollisionEvents& getCollisionEvents(); diff --git a/libraries/pointers/src/Pick.h b/libraries/pointers/src/Pick.h index 53606b154f..db9302a979 100644 --- a/libraries/pointers/src/Pick.h +++ b/libraries/pointers/src/Pick.h @@ -161,6 +161,7 @@ public: enum PickType { Ray = 0, Stylus, + Collision, NUM_PICK_TYPES }; diff --git a/libraries/pointers/src/PickManager.cpp b/libraries/pointers/src/PickManager.cpp index ba8fa814f0..b1698a7d1c 100644 --- a/libraries/pointers/src/PickManager.cpp +++ b/libraries/pointers/src/PickManager.cpp @@ -100,6 +100,7 @@ void PickManager::update() { // and the rayPicks updae will ALWAYS update at least one ray even when there is no budget _stylusPickCacheOptimizer.update(cachedPicks[PickQuery::Stylus], _nextPickToUpdate[PickQuery::Stylus], expiry, false); _rayPickCacheOptimizer.update(cachedPicks[PickQuery::Ray], _nextPickToUpdate[PickQuery::Ray], expiry, shouldPickHUD); + _collisionPickCacheOptimizer.update(cachedPicks[PickQuery::Collision], _nextPickToUpdate[PickQuery::Collision], expiry, false); } bool PickManager::isLeftHand(unsigned int uid) { diff --git a/libraries/pointers/src/PickManager.h b/libraries/pointers/src/PickManager.h index 3b466be2bc..f6871ee9cd 100644 --- a/libraries/pointers/src/PickManager.h +++ b/libraries/pointers/src/PickManager.h @@ -59,12 +59,13 @@ protected: std::shared_ptr findPick(unsigned int uid) const; std::unordered_map>> _picks; - unsigned int _nextPickToUpdate[PickQuery::NUM_PICK_TYPES] { 0, 0 }; + unsigned int _nextPickToUpdate[PickQuery::NUM_PICK_TYPES] { 0, 0, 0 }; std::unordered_map _typeMap; unsigned int _nextPickID { INVALID_PICK_ID + 1 }; PickCacheOptimizer _rayPickCacheOptimizer; PickCacheOptimizer _stylusPickCacheOptimizer; + PickCacheOptimizer _collisionPickCacheOptimizer; static const unsigned int DEFAULT_PER_FRAME_TIME_BUDGET = 2 * USECS_PER_MSEC; unsigned int _perFrameTimeBudget { DEFAULT_PER_FRAME_TIME_BUDGET }; diff --git a/libraries/shared/src/RegisteredMetaTypes.h b/libraries/shared/src/RegisteredMetaTypes.h index 467d6374a5..d20a993117 100644 --- a/libraries/shared/src/RegisteredMetaTypes.h +++ b/libraries/shared/src/RegisteredMetaTypes.h @@ -20,8 +20,10 @@ #include #include "AACube.h" +#include "ShapeInfo.h" #include "SharedUtil.h" #include "shared/Bilateral.h" +#include "Transform.h" class QColor; class QUrl; @@ -219,6 +221,80 @@ public: } }; +class CollisionRegion : public MathPick { +public: + CollisionRegion() { } + CollisionRegion(const QVariantMap& pickVariant) { + if (pickVariant["shape"].isValid()) { + auto shape = pickVariant["shape"].toMap(); + if (!shape.empty()) { + ShapeType shapeType = SHAPE_TYPE_NONE; + if (shape["shapeType"].isValid()) { + shapeType = ShapeInfo::getShapeTypeForName(shape["shapeType"].toString()); + } + if (shapeType >= SHAPE_TYPE_COMPOUND && shapeType <= SHAPE_TYPE_STATIC_MESH && shape["modelURL"].isValid()) { + QString newURL = shape["modelURL"].toString(); + modelURL.setUrl(newURL); + } + else { + modelURL.setUrl(""); + } + + if (shape["dimensions"].isValid()) { + transform.setScale(vec3FromVariant(shape["dimensions"])); + } + } + } + + if (pickVariant["position"].isValid()) { + transform.setTranslation(vec3FromVariant(pickVariant["position"])); + } + if (pickVariant["orientation"].isValid()) { + transform.setRotation(quatFromVariant(pickVariant["orientation"])); + } + } + + QVariantMap toVariantMap() const override { + QVariantMap collisionRegion; + + QVariantMap shape; + shape["shapeType"] = ShapeInfo::getNameForShapeType(shapeInfo.getType()); + shape["modelURL"] = modelURL.toString(); + shape["dimensions"] = vec3toVariant(shapeInfo.getHalfExtents()); + + collisionRegion["shape"] = shape; + + collisionRegion["position"] = vec3toVariant(transform.getTranslation()); + collisionRegion["orientation"] = quatToVariant(transform.getRotation()); + + return collisionRegion; + } + + operator bool() const override { + return !(glm::any(glm::isnan(transform.getTranslation())) || + glm::any(glm::isnan(transform.getRotation())) || + shapeInfo.getType() == SHAPE_TYPE_NONE); + } + + bool shouldComputeShapeInfo() const { + if (!(shapeInfo.getType() == SHAPE_TYPE_HULL || + (shapeInfo.getType() >= SHAPE_TYPE_COMPOUND && + shapeInfo.getType() <= SHAPE_TYPE_STATIC_MESH) + )) { + return false; + } + + return !shapeInfo.getPointCollection().size(); + } + + // We can't load the model here because it would create a circular dependency, so we delegate that responsibility to the owning CollisionPick + QUrl modelURL; + + // We can't compute the shapeInfo here without loading the model first, so we delegate that responsibility to the owning CollisionPick + ShapeInfo shapeInfo; + Transform transform; +}; + namespace std { inline void hash_combine(std::size_t& seed) { } @@ -255,6 +331,15 @@ namespace std { } }; + template <> + struct hash { + size_t operator()(const Transform& a) const { + size_t result = 0; + hash_combine(result, a.getTranslation(), a.getRotation(), a.getScale()); + return result; + } + }; + template <> struct hash { size_t operator()(const PickRay& a) const { @@ -273,6 +358,15 @@ namespace std { } }; + template <> + struct hash { + size_t operator()(const CollisionRegion& a) const { + size_t result = 0; + hash_combine(result, a.transform, (int)a.shapeInfo.getType(), qHash(a.modelURL)); + return result; + } + }; + template <> struct hash { size_t operator()(const QString& a) const { diff --git a/libraries/shared/src/ShapeInfo.cpp b/libraries/shared/src/ShapeInfo.cpp index 968292da87..b0caf2d62d 100644 --- a/libraries/shared/src/ShapeInfo.cpp +++ b/libraries/shared/src/ShapeInfo.cpp @@ -74,6 +74,17 @@ QString ShapeInfo::getNameForShapeType(ShapeType type) { return shapeTypeNames[(int)type]; } +ShapeType ShapeInfo::getShapeTypeForName(QString string) { + for (int i = 0; i < SHAPETYPE_NAME_COUNT; i++) { + auto name = shapeTypeNames[i]; + if (name == string) { + return (ShapeType)i; + } + } + + return (ShapeType)0; +} + void ShapeInfo::clear() { _url.clear(); _pointCollection.clear(); diff --git a/libraries/shared/src/ShapeInfo.h b/libraries/shared/src/ShapeInfo.h index 069241e29d..5020e492cf 100644 --- a/libraries/shared/src/ShapeInfo.h +++ b/libraries/shared/src/ShapeInfo.h @@ -59,6 +59,7 @@ public: using TriangleIndices = QVector; static QString getNameForShapeType(ShapeType type); + static ShapeType getShapeTypeForName(QString string); void clear(); From 0bfe2671ddd1f9853aea3495c404fcece4c790bb Mon Sep 17 00:00:00 2001 From: Clement Date: Mon, 16 Jul 2018 16:13:11 -0700 Subject: [PATCH 007/144] Cleanup udt dead code --- assignment-client/src/assets/AssetServer.cpp | 12 +- .../networking/src/udt/CongestionControl.cpp | 188 --------- .../networking/src/udt/CongestionControl.h | 47 +-- libraries/networking/src/udt/Connection.cpp | 398 +----------------- libraries/networking/src/udt/Connection.h | 34 +- .../networking/src/udt/ConnectionStats.cpp | 13 - .../networking/src/udt/ConnectionStats.h | 9 - .../networking/src/udt/PacketTimeWindow.cpp | 28 +- .../networking/src/udt/PacketTimeWindow.h | 9 +- libraries/networking/src/udt/SendQueue.cpp | 75 +--- libraries/networking/src/udt/SendQueue.h | 8 - libraries/networking/src/udt/TCPVegasCC.cpp | 2 - libraries/networking/src/udt/TCPVegasCC.h | 6 - tools/udt-test/src/UDTTest.cpp | 15 +- 14 files changed, 34 insertions(+), 810 deletions(-) diff --git a/assignment-client/src/assets/AssetServer.cpp b/assignment-client/src/assets/AssetServer.cpp index e79473783a..41aeaba468 100644 --- a/assignment-client/src/assets/AssetServer.cpp +++ b/assignment-client/src/assets/AssetServer.cpp @@ -945,22 +945,14 @@ void AssetServer::sendStatsPacket() { upstreamStats["2. Sent Packets"] = stat.second.sentPackets; upstreamStats["3. Recvd ACK"] = events[Events::ReceivedACK]; upstreamStats["4. Procd ACK"] = events[Events::ProcessedACK]; - upstreamStats["5. Recvd LACK"] = events[Events::ReceivedLightACK]; - upstreamStats["6. Recvd NAK"] = events[Events::ReceivedNAK]; - upstreamStats["7. Recvd TNAK"] = events[Events::ReceivedTimeoutNAK]; - upstreamStats["8. Sent ACK2"] = events[Events::SentACK2]; - upstreamStats["9. Retransmitted"] = events[Events::Retransmission]; + upstreamStats["5. Retransmitted"] = events[Events::Retransmission]; nodeStats["Upstream Stats"] = upstreamStats; QJsonObject downstreamStats; downstreamStats["1. Recvd (P/s)"] = stat.second.receiveRate; downstreamStats["2. Recvd Packets"] = stat.second.receivedPackets; downstreamStats["3. Sent ACK"] = events[Events::SentACK]; - downstreamStats["4. Sent LACK"] = events[Events::SentLightACK]; - downstreamStats["5. Sent NAK"] = events[Events::SentNAK]; - downstreamStats["6. Sent TNAK"] = events[Events::SentTimeoutNAK]; - downstreamStats["7. Recvd ACK2"] = events[Events::ReceivedACK2]; - downstreamStats["8. Duplicates"] = events[Events::Duplicate]; + downstreamStats["4. Duplicates"] = events[Events::Duplicate]; nodeStats["Downstream Stats"] = downstreamStats; QString uuid; diff --git a/libraries/networking/src/udt/CongestionControl.cpp b/libraries/networking/src/udt/CongestionControl.cpp index 7ade4f004f..c0ad89e804 100644 --- a/libraries/networking/src/udt/CongestionControl.cpp +++ b/libraries/networking/src/udt/CongestionControl.cpp @@ -39,191 +39,3 @@ void CongestionControl::setPacketSendPeriod(double newSendPeriod) { _packetSendPeriod = newSendPeriod; } } - -DefaultCC::DefaultCC() : - _lastDecreaseMaxSeq(SequenceNumber {SequenceNumber::MAX }) -{ - _mss = udt::MAX_PACKET_SIZE_WITH_UDP_HEADER; - - _congestionWindowSize = 16; - setPacketSendPeriod(1.0); -} - -bool DefaultCC::onACK(SequenceNumber ackNum, p_high_resolution_clock::time_point receiveTime) { - double increase = 0; - - // Note from UDT original code: - // The minimum increase parameter is increased from "1.0 / _mss" to 0.01 - // because the original was too small and caused sending rate to stay at low level - // for long time. - const double minimumIncrease = 0.01; - - // we will only adjust once per sync interval so check that it has been at least that long now - auto now = p_high_resolution_clock::now(); - if (duration_cast(now - _lastRCTime).count() < synInterval()) { - return false; - } - - // our last rate increase time is now - _lastRCTime = now; - - if (_slowStart) { - // we are in slow start phase - increase the congestion window size by the number of packets just ACKed - _congestionWindowSize += seqlen(_lastACK, ackNum); - - // update the last ACK - _lastACK = ackNum; - - // check if we can get out of slow start (is our new congestion window size bigger than the max) - if (_congestionWindowSize > _maxCongestionWindowSize) { - _slowStart = false; - - if (_receiveRate > 0) { - // if we have a valid receive rate we set the send period to whatever the receive rate dictates - setPacketSendPeriod(USECS_PER_SECOND / _receiveRate); - } else { - // no valid receive rate, packet send period is dictated by estimated RTT and current congestion window size - setPacketSendPeriod((_rtt + synInterval()) / _congestionWindowSize); - } - } - } else { - // not in slow start - window size should be arrival rate * (RTT + SYN) + 16 - _congestionWindowSize = _receiveRate / USECS_PER_SECOND * (_rtt + synInterval()) + 16; - } - - // during slow start we perform no rate increases - if (_slowStart) { - return false; - } - - // if loss has happened since the last rate increase we do not perform another increase - if (_loss) { - _loss = false; - return false; - } - - double capacitySpeedDelta = (_bandwidth - USECS_PER_SECOND / _packetSendPeriod); - - // UDT uses what they call DAIMD - additive increase multiplicative decrease with decreasing increases - // This factor is a protocol parameter that is part of the DAIMD algorithim - static const int AIMD_DECREASING_INCREASE_FACTOR = 9; - - if ((_packetSendPeriod > _lastDecreasePeriod) && ((_bandwidth / AIMD_DECREASING_INCREASE_FACTOR) < capacitySpeedDelta)) { - capacitySpeedDelta = _bandwidth / AIMD_DECREASING_INCREASE_FACTOR; - } - - if (capacitySpeedDelta <= 0) { - increase = minimumIncrease; - } else { - // use UDTs DAIMD algorithm to figure out what the send period increase factor should be - - // inc = max(10 ^ ceil(log10(B * MSS * 8 ) * Beta / MSS, minimumIncrease) - // B = estimated link capacity - // Beta = 1.5 * 10^(-6) - - static const double BETA = 0.0000015; - static const double BITS_PER_BYTE = 8.0; - - increase = pow(10.0, ceil(log10(capacitySpeedDelta * _mss * BITS_PER_BYTE))) * BETA / _mss; - - if (increase < minimumIncrease) { - increase = minimumIncrease; - } - } - - setPacketSendPeriod((_packetSendPeriod * synInterval()) / (_packetSendPeriod * increase + synInterval())); - - return false; -} - -void DefaultCC::onLoss(SequenceNumber rangeStart, SequenceNumber rangeEnd) { - // stop the slow start if we haven't yet - if (_slowStart) { - stopSlowStart(); - - // if the change to send rate was driven by a known receive rate, then we don't continue with the decrease - if (_receiveRate > 0) { - return; - } - } - - _loss = true; - ++_nakCount; - - static const double INTER_PACKET_ARRIVAL_INCREASE = 1.125; - static const int MAX_DECREASES_PER_CONGESTION_EPOCH = 5; - - // check if this NAK starts a new congestion period - this will be the case if the - // NAK received occured for a packet sent after the last decrease - if (rangeStart > _lastDecreaseMaxSeq) { - _delayedDecrease = (rangeStart == rangeEnd); - - _lastDecreasePeriod = _packetSendPeriod; - - if (!_delayedDecrease) { - setPacketSendPeriod(ceil(_packetSendPeriod * INTER_PACKET_ARRIVAL_INCREASE)); - } else { - _loss = false; - } - - // use EWMA to update the average number of NAKs per congestion - static const double NAK_EWMA_ALPHA = 0.125; - _avgNAKNum = (int)ceil(_avgNAKNum * (1 - NAK_EWMA_ALPHA) + _nakCount * NAK_EWMA_ALPHA); - - // update the count of NAKs and count of decreases in this interval - _nakCount = 1; - _decreaseCount = 1; - - _lastDecreaseMaxSeq = _sendCurrSeqNum; - - if (_avgNAKNum < 1) { - _randomDecreaseThreshold = 1; - } else { - // avoid synchronous rate decrease across connections using randomization - std::random_device rd; - std::mt19937 generator(rd()); - std::uniform_int_distribution<> distribution(1, std::max(1, _avgNAKNum)); - - _randomDecreaseThreshold = distribution(generator); - } - } else if (_delayedDecrease && _nakCount == 2) { - setPacketSendPeriod(ceil(_packetSendPeriod * INTER_PACKET_ARRIVAL_INCREASE)); - } else if ((_decreaseCount++ < MAX_DECREASES_PER_CONGESTION_EPOCH) && ((_nakCount % _randomDecreaseThreshold) == 0)) { - // there have been fewer than MAX_DECREASES_PER_CONGESTION_EPOCH AND this NAK matches the random count at which we - // decided we would decrease the packet send period - - setPacketSendPeriod(ceil(_packetSendPeriod * INTER_PACKET_ARRIVAL_INCREASE)); - _lastDecreaseMaxSeq = _sendCurrSeqNum; - } -} - -void DefaultCC::onTimeout() { - if (_slowStart) { - stopSlowStart(); - } else { - // UDT used to do the following on timeout if not in slow start - we should check if it could be helpful - _lastDecreasePeriod = _packetSendPeriod; - _packetSendPeriod = ceil(_packetSendPeriod * 2); - - // this seems odd - the last ack they were setting _lastDecreaseMaxSeq to only applies to slow start - _lastDecreaseMaxSeq = _lastACK; - } -} - -void DefaultCC::stopSlowStart() { - _slowStart = false; - - if (_receiveRate > 0) { - // Set the sending rate to the receiving rate. - setPacketSendPeriod(USECS_PER_SECOND / _receiveRate); - } else { - // If no receiving rate is observed, we have to compute the sending - // rate according to the current window size, and decrease it - // using the method below. - setPacketSendPeriod(double(_congestionWindowSize) / (_rtt + synInterval())); - } -} - -void DefaultCC::setInitialSendSequenceNumber(udt::SequenceNumber seqNum) { - _lastACK = _lastDecreaseMaxSeq = seqNum - 1; -} diff --git a/libraries/networking/src/udt/CongestionControl.h b/libraries/networking/src/udt/CongestionControl.h index e6a462651e..6998f4fe65 100644 --- a/libraries/networking/src/udt/CongestionControl.h +++ b/libraries/networking/src/udt/CongestionControl.h @@ -44,21 +44,12 @@ public: // return value specifies if connection should perform a fast re-transmit of ACK + 1 (used in TCP style congestion control) virtual bool onACK(SequenceNumber ackNum, p_high_resolution_clock::time_point receiveTime) { return false; } - virtual void onLoss(SequenceNumber rangeStart, SequenceNumber rangeEnd) {} virtual void onTimeout() {} - virtual bool shouldNAK() { return true; } - virtual bool shouldACK2() { return true; } - virtual bool shouldProbe() { return true; } - virtual void onPacketSent(int wireSize, SequenceNumber seqNum, p_high_resolution_clock::time_point timePoint) {} protected: - void setAckInterval(int ackInterval) { _ackInterval = ackInterval; } - void setRTO(int rto) { _userDefinedRTO = true; _rto = rto; } - void setMSS(int mss) { _mss = mss; } void setMaxCongestionWindowSize(int window) { _maxCongestionWindowSize = window; } - void setBandwidth(int bandwidth) { _bandwidth = bandwidth; } virtual void setInitialSendSequenceNumber(SequenceNumber seqNum) = 0; void setSendCurrentSequenceNumber(SequenceNumber seqNum) { _sendCurrSeqNum = seqNum; } void setReceiveRate(int rate) { _receiveRate = rate; } @@ -67,8 +58,7 @@ protected: double _packetSendPeriod { 1.0 }; // Packet sending period, in microseconds int _congestionWindowSize { 16 }; // Congestion window size, in packets - - int _bandwidth { 0 }; // estimated bandwidth, packets per second + std::atomic _maxBandwidth { -1 }; // Maximum desired bandwidth, bits per second int _maxCongestionWindowSize { 0 }; // maximum cwnd size, in packets @@ -81,13 +71,7 @@ private: CongestionControl(const CongestionControl& other) = delete; CongestionControl& operator=(const CongestionControl& other) = delete; - int _ackInterval { 0 }; // How many packets to send one ACK, in packets - int _lightACKInterval { 64 }; // How many packets to send one light ACK, in packets - int _synInterval { DEFAULT_SYN_INTERVAL }; - - bool _userDefinedRTO { false }; // if the RTO value is defined by users - int _rto { -1 }; // RTO value, microseconds }; @@ -105,35 +89,6 @@ public: virtual ~CongestionControlFactory() {} virtual std::unique_ptr create() override { return std::unique_ptr(new T()); } }; - -class DefaultCC: public CongestionControl { -public: - DefaultCC(); - -public: - virtual bool onACK(SequenceNumber ackNum, p_high_resolution_clock::time_point receiveTime) override; - virtual void onLoss(SequenceNumber rangeStart, SequenceNumber rangeEnd) override; - virtual void onTimeout() override; - -protected: - virtual void setInitialSendSequenceNumber(SequenceNumber seqNum) override; - -private: - void stopSlowStart(); // stops the slow start on loss or timeout - - p_high_resolution_clock::time_point _lastRCTime = p_high_resolution_clock::now(); // last rate increase time - - bool _slowStart { true }; // if in slow start phase - SequenceNumber _lastACK; // last ACKed sequence number from previous - bool _loss { false }; // if loss happened since last rate increase - SequenceNumber _lastDecreaseMaxSeq; // max pkt seq num sent out when last decrease happened - double _lastDecreasePeriod { 1 }; // value of _packetSendPeriod when last decrease happened - int _nakCount { 0 }; // number of NAKs in congestion epoch - int _randomDecreaseThreshold { 1 }; // random threshold on decrease by number of loss events - int _avgNAKNum { 0 }; // average number of NAKs per congestion - int _decreaseCount { 0 }; // number of decreases in a congestion epoch - bool _delayedDecrease { false }; -}; } diff --git a/libraries/networking/src/udt/Connection.cpp b/libraries/networking/src/udt/Connection.cpp index 0bc86a28ad..b317e87e87 100644 --- a/libraries/networking/src/udt/Connection.cpp +++ b/libraries/networking/src/udt/Connection.cpp @@ -50,17 +50,11 @@ Connection::Connection(Socket* parentSocket, HifiSockAddr destination, std::uniq _congestionControl->setMaxCongestionWindowSize(_flowWindowSize); // Setup packets - static const int ACK_PACKET_PAYLOAD_BYTES = sizeof(_lastSentACK) + sizeof(_currentACKSubSequenceNumber) + static const int ACK_PACKET_PAYLOAD_BYTES = sizeof(_lastSentACK) + sizeof(_rtt) + sizeof(int32_t) + sizeof(int32_t) + sizeof(int32_t); - static const int LIGHT_ACK_PACKET_PAYLOAD_BYTES = sizeof(SequenceNumber); - static const int ACK2_PAYLOAD_BYTES = sizeof(SequenceNumber); - static const int NAK_PACKET_PAYLOAD_BYTES = 2 * sizeof(SequenceNumber); static const int HANDSHAKE_ACK_PAYLOAD_BYTES = sizeof(SequenceNumber); _ackPacket = ControlPacket::create(ControlPacket::ACK, ACK_PACKET_PAYLOAD_BYTES); - _lightACKPacket = ControlPacket::create(ControlPacket::LightACK, LIGHT_ACK_PACKET_PAYLOAD_BYTES); - _ack2Packet = ControlPacket::create(ControlPacket::ACK2, ACK2_PAYLOAD_BYTES); - _lossReport = ControlPacket::create(ControlPacket::NAK, NAK_PACKET_PAYLOAD_BYTES); _handshakeACK = ControlPacket::create(ControlPacket::HandshakeACK, HANDSHAKE_ACK_PAYLOAD_BYTES); @@ -135,7 +129,6 @@ SendQueue& Connection::getSendQueue() { QObject::connect(_sendQueue.get(), &SendQueue::packetRetransmitted, this, &Connection::recordRetransmission); QObject::connect(_sendQueue.get(), &SendQueue::queueInactive, this, &Connection::queueInactive); QObject::connect(_sendQueue.get(), &SendQueue::timeout, this, &Connection::queueTimeout); - QObject::connect(_sendQueue.get(), &SendQueue::shortCircuitLoss, this, &Connection::queueShortCircuitLoss); // set defaults on the send queue from our congestion control object and estimatedTimeout() @@ -143,7 +136,6 @@ SendQueue& Connection::getSendQueue() { _sendQueue->setSyncInterval(_synInterval); _sendQueue->setEstimatedTimeout(estimatedTimeout()); _sendQueue->setFlowWindowSize(std::min(_flowWindowSize, (int) _congestionControl->_congestionWindowSize)); - _sendQueue->setProbePacketEnabled(_congestionControl->shouldProbe()); // give the randomized sequence number to the congestion control object _congestionControl->setInitialSendSequenceNumber(_sendQueue->getCurrentSequenceNumber()); @@ -167,12 +159,6 @@ void Connection::queueTimeout() { }); } -void Connection::queueShortCircuitLoss(quint32 sequenceNumber) { - updateCongestionControlAndSendQueue([this, sequenceNumber] { - _congestionControl->onLoss(SequenceNumber { sequenceNumber }, SequenceNumber { sequenceNumber }); - }); -} - void Connection::sendReliablePacket(std::unique_ptr packet) { Q_ASSERT_X(packet->isReliable(), "Connection::send", "Trying to send an unreliable packet reliably."); getSendQueue().queuePacket(std::move(packet)); @@ -229,26 +215,6 @@ void Connection::sync() { // the receive side of this connection is expired _isReceivingData = false; } - - // reset the number of light ACKs or non SYN ACKs during this sync interval - _lightACKsDuringSYN = 1; - _acksDuringSYN = 1; - - if (_congestionControl->_ackInterval > 1) { - // we send out a periodic ACK every rate control interval - sendACK(); - } - - if (_congestionControl->shouldNAK() && _lossList.getLength() > 0) { - // check if we need to re-transmit a loss list - // we do this if it has been longer than the current nakInterval since we last sent - auto now = p_high_resolution_clock::now(); - - if (duration_cast(now - _lastNAKTime).count() >= _nakInterval) { - // Send a timeout NAK packet - sendTimeoutNAK(); - } - } } } @@ -267,28 +233,10 @@ void Connection::recordRetransmission(int wireSize, SequenceNumber seqNum, p_hig void Connection::sendACK(bool wasCausedBySyncTimeout) { static p_high_resolution_clock::time_point lastACKSendTime; - auto currentTime = p_high_resolution_clock::now(); SequenceNumber nextACKNumber = nextACK(); Q_ASSERT_X(nextACKNumber >= _lastSentACK, "Connection::sendACK", "Sending lower ACK, something is wrong"); - // if our congestion control doesn't want to send an ACK for every packet received - // check if we already sent this ACK - if (_congestionControl->_ackInterval > 1 && nextACKNumber == _lastSentACK) { - - // if we use ACK2s, check if the receiving side already confirmed receipt of this ACK - if (_congestionControl->shouldACK2() && nextACKNumber < _lastReceivedAcknowledgedACK) { - // we already got an ACK2 for this ACK we would be sending, don't bother - return; - } - - // We will re-send if it has been more than the estimated timeout since the last ACK - microseconds sinceLastACK = duration_cast(currentTime - lastACKSendTime); - - if (sinceLastACK.count() < estimatedTimeout()) { - return; - } - } // we have received new packets since the last sent ACK // or our congestion control dictates that we always send ACKs @@ -296,10 +244,7 @@ void Connection::sendACK(bool wasCausedBySyncTimeout) { _lastSentACK = nextACKNumber; _ackPacket->reset(); // We need to reset it every time. - - // pack in the ACK sub-sequence number - _ackPacket->writePrimitive(++_currentACKSubSequenceNumber); - + // pack in the ACK number _ackPacket->writePrimitive(nextACKNumber); @@ -311,17 +256,14 @@ void Connection::sendACK(bool wasCausedBySyncTimeout) { _ackPacket->writePrimitive((int32_t) udt::MAX_PACKETS_IN_FLIGHT); if (wasCausedBySyncTimeout) { - // grab the up to date packet receive speed and estimated bandwidth + // grab the up to date packet receive speed int32_t packetReceiveSpeed = _receiveWindow.getPacketReceiveSpeed(); - int32_t estimatedBandwidth = _receiveWindow.getEstimatedBandwidth(); // update those values in our connection stats _stats.recordReceiveRate(packetReceiveSpeed); - _stats.recordEstimatedBandwidth(estimatedBandwidth); - // pack in the receive speed and estimatedBandwidth + // pack in the receive speed _ackPacket->writePrimitive(packetReceiveSpeed); - _ackPacket->writePrimitive(estimatedBandwidth); } // record this as the last ACK send time @@ -330,94 +272,9 @@ void Connection::sendACK(bool wasCausedBySyncTimeout) { // have the socket send off our packet _parentSocket->writeBasePacket(*_ackPacket, _destination); - Q_ASSERT_X(_sentACKs.empty() || _sentACKs.back().first + 1 == _currentACKSubSequenceNumber, - "Connection::sendACK", "Adding an invalid ACK to _sentACKs"); - - // write this ACK to the map of sent ACKs - _sentACKs.push_back({ _currentACKSubSequenceNumber, { nextACKNumber, p_high_resolution_clock::now() }}); - - // reset the number of data packets received since last ACK - _packetsSinceACK = 0; - _stats.record(ConnectionStats::Stats::SentACK); } -void Connection::sendLightACK() { - SequenceNumber nextACKNumber = nextACK(); - - if (nextACKNumber == _lastReceivedAcknowledgedACK) { - // we already got an ACK2 for this ACK we would be sending, don't bother - return; - } - - // reset the lightACKPacket before we go to write the ACK to it - _lightACKPacket->reset(); - - // pack in the ACK - _lightACKPacket->writePrimitive(nextACKNumber); - - // have the socket send off our packet immediately - _parentSocket->writeBasePacket(*_lightACKPacket, _destination); - - _stats.record(ConnectionStats::Stats::SentLightACK); -} - -void Connection::sendACK2(SequenceNumber currentACKSubSequenceNumber) { - // reset the ACK2 Packet before writing the sub-sequence number to it - _ack2Packet->reset(); - - // write the sub sequence number for this ACK2 - _ack2Packet->writePrimitive(currentACKSubSequenceNumber); - - // send the ACK2 packet - _parentSocket->writeBasePacket(*_ack2Packet, _destination); - - // update the last sent ACK2 and the last ACK2 send time - _lastSentACK2 = currentACKSubSequenceNumber; - - _stats.record(ConnectionStats::Stats::SentACK2); -} - -void Connection::sendNAK(SequenceNumber sequenceNumberRecieved) { - _lossReport->reset(); // We need to reset it every time. - - // pack in the loss report - _lossReport->writePrimitive(_lastReceivedSequenceNumber + 1); - if (_lastReceivedSequenceNumber + 1 != sequenceNumberRecieved - 1) { - _lossReport->writePrimitive(sequenceNumberRecieved - 1); - } - - // have the parent socket send off our packet immediately - _parentSocket->writeBasePacket(*_lossReport, _destination); - - // record our last NAK time - _lastNAKTime = p_high_resolution_clock::now(); - - _stats.record(ConnectionStats::Stats::SentNAK); -} - -void Connection::sendTimeoutNAK() { - if (_lossList.getLength() > 0) { - - int timeoutPayloadSize = std::min((int) (_lossList.getLength() * 2 * sizeof(SequenceNumber)), - ControlPacket::maxPayloadSize()); - - // construct a NAK packet that will hold all of the lost sequence numbers - auto lossListPacket = ControlPacket::create(ControlPacket::TimeoutNAK, timeoutPayloadSize); - - // Pack in the lost sequence numbers - _lossList.write(*lossListPacket, timeoutPayloadSize / (2 * sizeof(SequenceNumber))); - - // have our parent socket send off this control packet - _parentSocket->writeBasePacket(*lossListPacket, _destination); - - // record this as the last NAK time - _lastNAKTime = p_high_resolution_clock::now(); - - _stats.record(ConnectionStats::Stats::SentTimeoutNAK); - } -} - SequenceNumber Connection::nextACK() const { if (_lossList.getLength() > 0) { return _lossList.getFirstSequenceNumber() - 1; @@ -452,21 +309,6 @@ bool Connection::processReceivedSequenceNumber(SequenceNumber sequenceNumber, in // mark our last receive time as now (to push the potential expiry farther) _lastReceiveTime = p_high_resolution_clock::now(); - if (_congestionControl->shouldProbe()) { - // check if this is a packet pair we should estimate bandwidth from, or just a regular packet - if (((uint32_t) sequenceNumber & 0xF) == 0) { - _receiveWindow.onProbePair1Arrival(); - } else if (((uint32_t) sequenceNumber & 0xF) == 1) { - // only use this packet for bandwidth estimation if we didn't just receive a control packet in its place - if (!_receivedControlProbeTail) { - _receiveWindow.onProbePair2Arrival(); - } else { - // reset our control probe tail marker so the next probe that comes with data can be used - _receivedControlProbeTail = false; - } - } - } - _receiveWindow.onPacketArrival(); // If this is not the next sequence number, report loss @@ -476,24 +318,6 @@ bool Connection::processReceivedSequenceNumber(SequenceNumber sequenceNumber, in } else { _lossList.append(_lastReceivedSequenceNumber + 1, sequenceNumber - 1); } - - if (_congestionControl->shouldNAK()) { - // Send a NAK packet - sendNAK(sequenceNumber); - - // figure out when we should send the next loss report, if we haven't heard anything back - _nakInterval = estimatedTimeout(); - - int receivedPacketsPerSecond = _receiveWindow.getPacketReceiveSpeed(); - if (receivedPacketsPerSecond > 0) { - // the NAK interval is at least the _minNAKInterval - // but might be the time required for all lost packets to be retransmitted - _nakInterval += (int) (_lossList.getLength() * (USECS_PER_SECOND / receivedPacketsPerSecond)); - } - - // the NAK interval is at least the _minNAKInterval but might be the value calculated above, if that is larger - _nakInterval = std::max(_nakInterval, _minNAKInterval); - } } bool wasDuplicate = false; @@ -505,22 +329,9 @@ bool Connection::processReceivedSequenceNumber(SequenceNumber sequenceNumber, in // Otherwise, it could be a resend, try and remove it from the loss list wasDuplicate = !_lossList.remove(sequenceNumber); } - - // increment the counters for data packets received - ++_packetsSinceACK; - - // check if we need to send an ACK, according to CC params - if (_congestionControl->_ackInterval == 1) { - // using a congestion control that ACKs every packet (like TCP Vegas) - sendACK(true); - } else if (_congestionControl->_ackInterval > 0 && _packetsSinceACK >= _congestionControl->_ackInterval * _acksDuringSYN) { - _acksDuringSYN++; - sendACK(false); - } else if (_congestionControl->_lightACKInterval > 0 - && _packetsSinceACK >= _congestionControl->_lightACKInterval * _lightACKsDuringSYN) { - sendLightACK(); - ++_lightACKsDuringSYN; - } + + // using a congestion control that ACKs every packet (like TCP Vegas) + sendACK(true); if (wasDuplicate) { _stats.record(ConnectionStats::Stats::Duplicate); @@ -544,37 +355,12 @@ void Connection::processControl(ControlPacketPointer controlPacket) { processACK(move(controlPacket)); } break; - case ControlPacket::LightACK: - if (_hasReceivedHandshakeACK) { - processLightACK(move(controlPacket)); - } - break; - case ControlPacket::ACK2: - if (_hasReceivedHandshake) { - processACK2(move(controlPacket)); - } - break; - case ControlPacket::NAK: - if (_hasReceivedHandshakeACK) { - processNAK(move(controlPacket)); - } - break; - case ControlPacket::TimeoutNAK: - if (_hasReceivedHandshakeACK) { - processTimeoutNAK(move(controlPacket)); - } - break; case ControlPacket::Handshake: processHandshake(move(controlPacket)); break; case ControlPacket::HandshakeACK: processHandshakeACK(move(controlPacket)); break; - case ControlPacket::ProbeTail: - if (_isReceivingData) { - processProbeTail(move(controlPacket)); - } - break; case ControlPacket::HandshakeRequest: if (_hasReceivedHandshakeACK) { // We're already in a state where we've received a handshake ack, so we are likely in a state @@ -587,31 +373,16 @@ void Connection::processControl(ControlPacketPointer controlPacket) { stopSendQueue(); } break; + case ControlPacket::LightACK: + case ControlPacket::ACK2: + case ControlPacket::NAK: + case ControlPacket::TimeoutNAK: + case ControlPacket::ProbeTail: + break; } } void Connection::processACK(ControlPacketPointer controlPacket) { - // read the ACK sub-sequence number - SequenceNumber currentACKSubSequenceNumber; - controlPacket->readPrimitive(¤tACKSubSequenceNumber); - - // Check if we need send an ACK2 for this ACK - // This will be the case if it has been longer than the sync interval OR - // it looks like they haven't received our ACK2 for this ACK - auto currentTime = p_high_resolution_clock::now(); - static p_high_resolution_clock::time_point lastACK2SendTime = - p_high_resolution_clock::now() - std::chrono::microseconds(_synInterval); - - microseconds sinceLastACK2 = duration_cast(currentTime - lastACK2SendTime); - - if (_congestionControl->shouldACK2() - && (sinceLastACK2.count() >= _synInterval || currentACKSubSequenceNumber == _lastSentACK2)) { - // Send ACK2 packet - sendACK2(currentACKSubSequenceNumber); - - lastACK2SendTime = p_high_resolution_clock::now(); - } - // read the ACKed sequence number SequenceNumber ack; controlPacket->readPrimitive(&ack); @@ -661,27 +432,23 @@ void Connection::processACK(ControlPacketPointer controlPacket) { _congestionControl->setRTT(_rtt); if (controlPacket->bytesLeftToRead() > 0) { - int32_t receiveRate, bandwidth; + int32_t receiveRate; - Q_ASSERT_X(controlPacket->bytesLeftToRead() == sizeof(receiveRate) + sizeof(bandwidth), + Q_ASSERT_X(controlPacket->bytesLeftToRead() == sizeof(receiveRate), "Connection::processACK", "sync interval ACK packet does not contain expected data"); controlPacket->readPrimitive(&receiveRate); - controlPacket->readPrimitive(&bandwidth); - // set the delivery rate and bandwidth for congestion control + // set the delivery rate for congestion control // these are calculated using an EWMA static const int EMWA_ALPHA_NUMERATOR = 8; // record these samples in connection stats _stats.recordSendRate(receiveRate); - _stats.recordEstimatedBandwidth(bandwidth); _deliveryRate = (_deliveryRate * (EMWA_ALPHA_NUMERATOR - 1) + receiveRate) / EMWA_ALPHA_NUMERATOR; - _bandwidth = (_bandwidth * (EMWA_ALPHA_NUMERATOR - 1) + bandwidth) / EMWA_ALPHA_NUMERATOR; _congestionControl->setReceiveRate(_deliveryRate); - _congestionControl->setBandwidth(_bandwidth); } // give this ACK to the congestion control and update the send queue parameters @@ -695,92 +462,6 @@ void Connection::processACK(ControlPacketPointer controlPacket) { _stats.record(ConnectionStats::Stats::ProcessedACK); } -void Connection::processLightACK(ControlPacketPointer controlPacket) { - // read the ACKed sequence number - SequenceNumber ack; - controlPacket->readPrimitive(&ack); - - // must be larger than the last received ACK to be processed - if (ack > _lastReceivedACK) { - // NOTE: the following makes sense in UDT where there is a dynamic receive buffer. - // Since we have a receive buffer that is always of a default size, we don't use this light ACK to - // drop the flow window size. - - // decrease the flow window size by the offset between the last received ACK and this ACK - // _flowWindowSize -= seqoff(_lastReceivedACK, ack); - - // update the last received ACK to the this one - _lastReceivedACK = ack; - - // send light ACK to the send queue - getSendQueue().ack(ack); - } - - _stats.record(ConnectionStats::Stats::ReceivedLightACK); -} - -void Connection::processACK2(ControlPacketPointer controlPacket) { - // pull the sub sequence number from the packet - SequenceNumber subSequenceNumber; - controlPacket->readPrimitive(&subSequenceNumber); - - // check if we had that subsequence number in our map - auto it = std::find_if_not(_sentACKs.begin(), _sentACKs.end(), [&subSequenceNumber](const ACKListPair& pair){ - return pair.first < subSequenceNumber; - }); - - if (it != _sentACKs.end()) { - if (it->first == subSequenceNumber){ - // update the RTT using the ACK window - - // calculate the RTT (time now - time ACK sent) - auto now = p_high_resolution_clock::now(); - int rtt = duration_cast(now - it->second.second).count(); - - updateRTT(rtt); - // write this RTT to stats - _stats.recordRTT(rtt); - - // set the RTT for congestion control - _congestionControl->setRTT(_rtt); - - // update the last ACKed ACK - if (it->second.first > _lastReceivedAcknowledgedACK) { - _lastReceivedAcknowledgedACK = it->second.first; - } - } else if (it->first < subSequenceNumber) { - Q_UNREACHABLE(); - } - } - - // erase this sub-sequence number and anything below it now that we've gotten our timing information - _sentACKs.erase(_sentACKs.begin(), it); - - _stats.record(ConnectionStats::Stats::ReceivedACK2); -} - -void Connection::processNAK(ControlPacketPointer controlPacket) { - // read the loss report - SequenceNumber start, end; - controlPacket->readPrimitive(&start); - - end = start; - - if (controlPacket->bytesLeftToRead() >= (qint64)sizeof(SequenceNumber)) { - controlPacket->readPrimitive(&end); - } - - // send that off to the send queue so it knows there was loss - getSendQueue().nak(start, end); - - // give the loss to the congestion control object and update the send queue parameters - updateCongestionControlAndSendQueue([this, start, end] { - _congestionControl->onLoss(start, end); - }); - - _stats.record(ConnectionStats::Stats::ReceivedNAK); -} - void Connection::processHandshake(ControlPacketPointer controlPacket) { SequenceNumber initialSequenceNumber; controlPacket->readPrimitive(&initialSequenceNumber); @@ -829,68 +510,27 @@ void Connection::processHandshakeACK(ControlPacketPointer controlPacket) { } } -void Connection::processTimeoutNAK(ControlPacketPointer controlPacket) { - // Override SendQueue's LossList with the timeout NAK list - getSendQueue().overrideNAKListFromPacket(*controlPacket); - - // we don't tell the congestion control object there was loss here - this matches UDTs implementation - // a possible improvement would be to tell it which new loss this timeout packet told us about - - _stats.record(ConnectionStats::Stats::ReceivedTimeoutNAK); -} - -void Connection::processProbeTail(ControlPacketPointer controlPacket) { - if (((uint32_t) _lastReceivedSequenceNumber & 0xF) == 0) { - // this is the second packet in a probe set so we can estimate bandwidth - // the sender sent this to us in lieu of sending new data (because they didn't have any) - -#ifdef UDT_CONNECTION_DEBUG - qCDebug(networking) << "Processing second packet of probe from control packet instead of data packet"; -#endif - - _receiveWindow.onProbePair2Arrival(); - - // mark that we processed a control packet for the second in the pair and we should not mark - // the next data packet received - _receivedControlProbeTail = true; - } -} - void Connection::resetReceiveState() { // reset all SequenceNumber member variables back to default SequenceNumber defaultSequenceNumber; _lastReceivedSequenceNumber = defaultSequenceNumber; - - _lastReceivedAcknowledgedACK = defaultSequenceNumber; - _currentACKSubSequenceNumber = defaultSequenceNumber; - + _lastSentACK = defaultSequenceNumber; - // clear the sent ACKs - _sentACKs.clear(); - - // clear the loss list and _lastNAKTime + // clear the loss list _lossList.clear(); - _lastNAKTime = p_high_resolution_clock::now(); - - // the _nakInterval need not be reset, that will happen on loss // clear sync variables _isReceivingData = false; _connectionStart = p_high_resolution_clock::now(); - _acksDuringSYN = 1; - _lightACKsDuringSYN = 1; - _packetsSinceACK = 0; - // reset RTT to initial value resetRTT(); // clear the intervals in the receive window _receiveWindow.reset(); - _receivedControlProbeTail = false; // clear any pending received messages for (auto& pendingMessage : _pendingReceivedMessages) { @@ -920,7 +560,7 @@ void Connection::updateRTT(int rtt) { } int Connection::estimatedTimeout() const { - return _congestionControl->_userDefinedRTO ? _congestionControl->_rto : _rtt + _rttVariance * 4; + return _rtt + _rttVariance * 4; } void Connection::updateCongestionControlAndSendQueue(std::function congestionCallback) { diff --git a/libraries/networking/src/udt/Connection.h b/libraries/networking/src/udt/Connection.h index 0017eb204a..ebe58e32e2 100644 --- a/libraries/networking/src/udt/Connection.h +++ b/libraries/networking/src/udt/Connection.h @@ -51,9 +51,6 @@ private: class Connection : public QObject { Q_OBJECT public: - using SequenceNumberTimePair = std::pair; - using ACKListPair = std::pair; - using SentACKList = std::list; using ControlPacketPointer = std::unique_ptr; Connection(Socket* parentSocket, HifiSockAddr destination, std::unique_ptr congestionControl); @@ -87,23 +84,13 @@ private slots: void recordRetransmission(int wireSize, SequenceNumber sequenceNumber, p_high_resolution_clock::time_point timePoint); void queueInactive(); void queueTimeout(); - void queueShortCircuitLoss(quint32 sequenceNumber); private: void sendACK(bool wasCausedBySyncTimeout = true); - void sendLightACK(); - void sendACK2(SequenceNumber currentACKSubSequenceNumber); - void sendNAK(SequenceNumber sequenceNumberRecieved); - void sendTimeoutNAK(); void processACK(ControlPacketPointer controlPacket); - void processLightACK(ControlPacketPointer controlPacket); - void processACK2(ControlPacketPointer controlPacket); - void processNAK(ControlPacketPointer controlPacket); - void processTimeoutNAK(ControlPacketPointer controlPacket); void processHandshake(ControlPacketPointer controlPacket); void processHandshakeACK(ControlPacketPointer controlPacket); - void processProbeTail(ControlPacketPointer controlPacket); void resetReceiveState(); void resetRTT(); @@ -120,10 +107,6 @@ private: int _synInterval; // Periodical Rate Control Interval, in microseconds - int _nakInterval { -1 }; // NAK timeout interval, in microseconds, set on loss - int _minNAKInterval { 100000 }; // NAK timeout interval lower bound, default of 100ms - p_high_resolution_clock::time_point _lastNAKTime = p_high_resolution_clock::now(); - bool _hasReceivedHandshake { false }; // flag for receipt of handshake from server bool _hasReceivedHandshakeACK { false }; // flag for receipt of handshake ACK from client bool _didRequestHandshake { false }; // flag for request of handshake from server @@ -141,43 +124,28 @@ private: LossList _lossList; // List of all missing packets SequenceNumber _lastReceivedSequenceNumber; // The largest sequence number received from the peer SequenceNumber _lastReceivedACK; // The last ACK received - SequenceNumber _lastReceivedAcknowledgedACK; // The last sent ACK that has been acknowledged via an ACK2 from the peer - SequenceNumber _currentACKSubSequenceNumber; // The current ACK sub-sequence number (used for Acknowledgment of ACKs) SequenceNumber _lastSentACK; // The last sent ACK - SequenceNumber _lastSentACK2; // The last sent ACK sub-sequence number in an ACK2 - - int _acksDuringSYN { 1 }; // The number of non-SYN ACKs sent during SYN - int _lightACKsDuringSYN { 1 }; // The number of lite ACKs sent during SYN interval int32_t _rtt; // RTT, in microseconds int32_t _rttVariance; // RTT variance int _flowWindowSize { udt::MAX_PACKETS_IN_FLIGHT }; // Flow control window size - int _bandwidth { 1 }; // Exponential moving average for estimated bandwidth, in packets per second int _deliveryRate { 16 }; // Exponential moving average for receiver's receive rate, in packets per second - SentACKList _sentACKs; // Map of ACK sub-sequence numbers to ACKed sequence number and sent time - Socket* _parentSocket { nullptr }; HifiSockAddr _destination; - PacketTimeWindow _receiveWindow { 16, 64 }; // Window of interval between packets (16) and probes (64) for timing - bool _receivedControlProbeTail { false }; // Marker for receipt of control packet probe tail (in lieu of probe with data) + PacketTimeWindow _receiveWindow { 16 }; // Window of interval between packets (16) std::unique_ptr _congestionControl; std::unique_ptr _sendQueue; std::map _pendingReceivedMessages; - - int _packetsSinceACK { 0 }; // The number of packets that have been received during the current ACK interval // Re-used control packets ControlPacketPointer _ackPacket; - ControlPacketPointer _lightACKPacket; - ControlPacketPointer _ack2Packet; - ControlPacketPointer _lossReport; ControlPacketPointer _handshakeACK; ConnectionStats _stats; diff --git a/libraries/networking/src/udt/ConnectionStats.cpp b/libraries/networking/src/udt/ConnectionStats.cpp index 986da062f2..e30c588dba 100644 --- a/libraries/networking/src/udt/ConnectionStats.cpp +++ b/libraries/networking/src/udt/ConnectionStats.cpp @@ -95,11 +95,6 @@ void ConnectionStats::recordReceiveRate(int sample) { _total.receiveRate = (int)((_total.receiveRate * EWMA_PREVIOUS_SAMPLES_WEIGHT) + (sample * EWMA_CURRENT_SAMPLE_WEIGHT)); } -void ConnectionStats::recordEstimatedBandwidth(int sample) { - _currentSample.estimatedBandwith = sample; - _total.estimatedBandwith = (int)((_total.estimatedBandwith * EWMA_PREVIOUS_SAMPLES_WEIGHT) + (sample * EWMA_CURRENT_SAMPLE_WEIGHT)); -} - void ConnectionStats::recordRTT(int sample) { _currentSample.rtt = sample; _total.rtt = (int)((_total.rtt * EWMA_PREVIOUS_SAMPLES_WEIGHT) + (sample * EWMA_CURRENT_SAMPLE_WEIGHT)); @@ -122,14 +117,6 @@ QDebug& operator<<(QDebug&& debug, const udt::ConnectionStats::Stats& stats) { HIFI_LOG_EVENT(SentACK) HIFI_LOG_EVENT(ReceivedACK) HIFI_LOG_EVENT(ProcessedACK) - HIFI_LOG_EVENT(SentLightACK) - HIFI_LOG_EVENT(ReceivedLightACK) - HIFI_LOG_EVENT(SentACK2) - HIFI_LOG_EVENT(ReceivedACK2) - HIFI_LOG_EVENT(SentNAK) - HIFI_LOG_EVENT(ReceivedNAK) - HIFI_LOG_EVENT(SentTimeoutNAK) - HIFI_LOG_EVENT(ReceivedTimeoutNAK) HIFI_LOG_EVENT(Retransmission) HIFI_LOG_EVENT(Duplicate) ; diff --git a/libraries/networking/src/udt/ConnectionStats.h b/libraries/networking/src/udt/ConnectionStats.h index 7ec7b163ee..0fdd1636b3 100644 --- a/libraries/networking/src/udt/ConnectionStats.h +++ b/libraries/networking/src/udt/ConnectionStats.h @@ -24,14 +24,6 @@ public: SentACK, ReceivedACK, ProcessedACK, - SentLightACK, - ReceivedLightACK, - SentACK2, - ReceivedACK2, - SentNAK, - ReceivedNAK, - SentTimeoutNAK, - ReceivedTimeoutNAK, Retransmission, Duplicate, @@ -89,7 +81,6 @@ public: void recordSendRate(int sample); void recordReceiveRate(int sample); - void recordEstimatedBandwidth(int sample); void recordRTT(int sample); void recordCongestionWindowSize(int sample); void recordPacketSendPeriod(int sample); diff --git a/libraries/networking/src/udt/PacketTimeWindow.cpp b/libraries/networking/src/udt/PacketTimeWindow.cpp index 0c95d21bc6..c95045c48f 100644 --- a/libraries/networking/src/udt/PacketTimeWindow.cpp +++ b/libraries/networking/src/udt/PacketTimeWindow.cpp @@ -20,20 +20,16 @@ using namespace udt; using namespace std::chrono; static const int DEFAULT_PACKET_INTERVAL_MICROSECONDS = 1000000; // 1s -static const int DEFAULT_PROBE_INTERVAL_MICROSECONDS = 1000; // 1ms -PacketTimeWindow::PacketTimeWindow(int numPacketIntervals, int numProbeIntervals) : +PacketTimeWindow::PacketTimeWindow(int numPacketIntervals) : _numPacketIntervals(numPacketIntervals), - _numProbeIntervals(numProbeIntervals), - _packetIntervals(_numPacketIntervals, DEFAULT_PACKET_INTERVAL_MICROSECONDS), - _probeIntervals(_numProbeIntervals, DEFAULT_PROBE_INTERVAL_MICROSECONDS) + _packetIntervals(_numPacketIntervals, DEFAULT_PACKET_INTERVAL_MICROSECONDS) { } void PacketTimeWindow::reset() { _packetIntervals.assign(_numPacketIntervals, DEFAULT_PACKET_INTERVAL_MICROSECONDS); - _probeIntervals.assign(_numProbeIntervals, DEFAULT_PROBE_INTERVAL_MICROSECONDS); } template @@ -87,11 +83,6 @@ int32_t PacketTimeWindow::getPacketReceiveSpeed() const { return meanOfMedianFilteredValues(_packetIntervals, _numPacketIntervals, _numPacketIntervals / 2); } -int32_t PacketTimeWindow::getEstimatedBandwidth() const { - // return mean value of median filtered values (per second) - return meanOfMedianFilteredValues(_probeIntervals, _numProbeIntervals); -} - void PacketTimeWindow::onPacketArrival() { // take the current time @@ -108,18 +99,3 @@ void PacketTimeWindow::onPacketArrival() { // remember this as the last packet arrival time _lastPacketTime = now; } - -void PacketTimeWindow::onProbePair1Arrival() { - // take the current time as the first probe time - _firstProbeTime = p_high_resolution_clock::now(); -} - -void PacketTimeWindow::onProbePair2Arrival() { - // store the interval between the two probes - auto now = p_high_resolution_clock::now(); - - _probeIntervals[_currentProbeInterval++] = duration_cast(now - _firstProbeTime).count(); - - // reset the currentProbeInterval index when it wraps - _currentProbeInterval %= _numProbeIntervals; -} diff --git a/libraries/networking/src/udt/PacketTimeWindow.h b/libraries/networking/src/udt/PacketTimeWindow.h index 6f7ed9f70f..359097ac8c 100644 --- a/libraries/networking/src/udt/PacketTimeWindow.h +++ b/libraries/networking/src/udt/PacketTimeWindow.h @@ -22,28 +22,21 @@ namespace udt { class PacketTimeWindow { public: - PacketTimeWindow(int numPacketIntervals = 16, int numProbeIntervals = 16); + PacketTimeWindow(int numPacketIntervals = 16); void onPacketArrival(); - void onProbePair1Arrival(); - void onProbePair2Arrival(); int32_t getPacketReceiveSpeed() const; - int32_t getEstimatedBandwidth() const; void reset(); private: int _numPacketIntervals { 0 }; // the number of packet intervals to store - int _numProbeIntervals { 0 }; // the number of probe intervals to store int _currentPacketInterval { 0 }; // index for the current packet interval - int _currentProbeInterval { 0 }; // index for the current probe interval std::vector _packetIntervals; // vector of microsecond intervals between packet arrivals - std::vector _probeIntervals; // vector of microsecond intervals between probe pair arrivals p_high_resolution_clock::time_point _lastPacketTime = p_high_resolution_clock::now(); // the time_point when last packet arrived - p_high_resolution_clock::time_point _firstProbeTime = p_high_resolution_clock::now(); // the time_point when first probe in pair arrived }; } diff --git a/libraries/networking/src/udt/SendQueue.cpp b/libraries/networking/src/udt/SendQueue.cpp index 0df54d539d..b1dfb9a8cf 100644 --- a/libraries/networking/src/udt/SendQueue.cpp +++ b/libraries/networking/src/udt/SendQueue.cpp @@ -164,16 +164,6 @@ void SendQueue::ack(SequenceNumber ack) { _emptyCondition.notify_one(); } -void SendQueue::nak(SequenceNumber start, SequenceNumber end) { - { - std::lock_guard nakLocker(_naksLock); - _naks.insert(start, end); - } - - // call notify_one on the condition_variable_any in case the send thread is sleeping waiting for losses to re-send - _emptyCondition.notify_one(); -} - void SendQueue::fastRetransmit(udt::SequenceNumber ack) { { std::lock_guard nakLocker(_naksLock); @@ -184,28 +174,6 @@ void SendQueue::fastRetransmit(udt::SequenceNumber ack) { _emptyCondition.notify_one(); } -void SendQueue::overrideNAKListFromPacket(ControlPacket& packet) { - { - std::lock_guard nakLocker(_naksLock); - _naks.clear(); - - SequenceNumber first, second; - while (packet.bytesLeftToRead() >= (qint64)(2 * sizeof(SequenceNumber))) { - packet.readPrimitive(&first); - packet.readPrimitive(&second); - - if (first == second) { - _naks.append(first); - } else { - _naks.append(first, second); - } - } - } - - // call notify_one on the condition_variable_any in case the send thread is sleeping waiting for losses to re-send - _emptyCondition.notify_one(); -} - void SendQueue::sendHandshake() { std::unique_lock handshakeLock { _handshakeMutex }; if (!_hasReceivedHandshakeACK) { @@ -268,8 +236,6 @@ bool SendQueue::sendNewPacketAndAddToSentList(std::unique_ptr newPacket, _naks.append(sequenceNumber); } - emit shortCircuitLoss(quint32(sequenceNumber)); - return false; } else { return true; @@ -385,10 +351,6 @@ void SendQueue::run() { } } -void SendQueue::setProbePacketEnabled(bool enabled) { - _shouldSendProbes = enabled; -} - int SendQueue::maybeSendNewPacket() { if (!isFlowWindowFull()) { // we didn't re-send a packet, so time to send a new one @@ -397,40 +359,15 @@ int SendQueue::maybeSendNewPacket() { SequenceNumber nextNumber = getNextSequenceNumber(); // grab the first packet we will send - std::unique_ptr firstPacket = _packets.takePacket(); - Q_ASSERT(firstPacket); + std::unique_ptr packet = _packets.takePacket(); + Q_ASSERT(packet); - // attempt to send the first packet - if (sendNewPacketAndAddToSentList(move(firstPacket), nextNumber)) { - std::unique_ptr secondPacket; - bool shouldSendPairTail = false; + // attempt to send the packet + sendNewPacketAndAddToSentList(move(packet), nextNumber); - if (_shouldSendProbes && ((uint32_t) nextNumber & 0xF) == 0) { - // the first packet is the first in a probe pair - every 16 (rightmost 16 bits = 0) packets - // pull off a second packet if we can before we unlock - shouldSendPairTail = true; - - secondPacket = _packets.takePacket(); - } - - // do we have a second in a pair to send as well? - if (secondPacket) { - sendNewPacketAndAddToSentList(move(secondPacket), getNextSequenceNumber()); - } else if (shouldSendPairTail) { - // we didn't get a second packet to send in the probe pair - // send a control packet of type ProbePairTail so the receiver can still do - // proper bandwidth estimation - static auto pairTailPacket = ControlPacket::create(ControlPacket::ProbeTail); - _socket->writeBasePacket(*pairTailPacket, _destination); - } - - // return the number of attempted packet sends - return shouldSendPairTail ? 2 : 1; - } else { - // we attempted to send a single packet, return 1 - return 1; - } + // we attempted to send a packet, return 1 + return 1; } } diff --git a/libraries/networking/src/udt/SendQueue.h b/libraries/networking/src/udt/SendQueue.h index a11aacdb91..65b1b89c7e 100644 --- a/libraries/networking/src/udt/SendQueue.h +++ b/libraries/networking/src/udt/SendQueue.h @@ -69,16 +69,12 @@ public: void setEstimatedTimeout(int estimatedTimeout) { _estimatedTimeout = estimatedTimeout; } void setSyncInterval(int syncInterval) { _syncInterval = syncInterval; } - - void setProbePacketEnabled(bool enabled); public slots: void stop(); void ack(SequenceNumber ack); - void nak(SequenceNumber start, SequenceNumber end); void fastRetransmit(SequenceNumber ack); - void overrideNAKListFromPacket(ControlPacket& packet); void handshakeACK(); signals: @@ -87,7 +83,6 @@ signals: void queueInactive(); - void shortCircuitLoss(quint32 sequenceNumber); void timeout(); private slots: @@ -145,9 +140,6 @@ private: std::condition_variable _handshakeACKCondition; std::condition_variable_any _emptyCondition; - - - std::atomic _shouldSendProbes { true }; }; } diff --git a/libraries/networking/src/udt/TCPVegasCC.cpp b/libraries/networking/src/udt/TCPVegasCC.cpp index 5738ea8421..e39e474f39 100644 --- a/libraries/networking/src/udt/TCPVegasCC.cpp +++ b/libraries/networking/src/udt/TCPVegasCC.cpp @@ -21,8 +21,6 @@ TCPVegasCC::TCPVegasCC() { _packetSendPeriod = 0.0; _congestionWindowSize = 2; - setAckInterval(1); // TCP sends an ACK for every packet received - // set our minimum RTT variables to the maximum possible value // we can't do this as a member initializer until our VS has support for constexpr _currentMinRTT = std::numeric_limits::max(); diff --git a/libraries/networking/src/udt/TCPVegasCC.h b/libraries/networking/src/udt/TCPVegasCC.h index 862ea36d8f..38b70ea325 100644 --- a/libraries/networking/src/udt/TCPVegasCC.h +++ b/libraries/networking/src/udt/TCPVegasCC.h @@ -27,13 +27,8 @@ public: TCPVegasCC(); virtual bool onACK(SequenceNumber ackNum, p_high_resolution_clock::time_point receiveTime) override; - virtual void onLoss(SequenceNumber rangeStart, SequenceNumber rangeEnd) override {}; virtual void onTimeout() override {}; - virtual bool shouldNAK() override { return false; } - virtual bool shouldACK2() override { return false; } - virtual bool shouldProbe() override { return false; } - virtual void onPacketSent(int wireSize, SequenceNumber seqNum, p_high_resolution_clock::time_point timePoint) override; protected: @@ -65,7 +60,6 @@ private: int _duplicateACKCount { 0 }; // Counter for duplicate ACKs received int _slowStartOddAdjust { 0 }; // Marker for every window adjustment every other RTT in slow-start - }; } diff --git a/tools/udt-test/src/UDTTest.cpp b/tools/udt-test/src/UDTTest.cpp index ce89f04ce5..b6efe4b42e 100644 --- a/tools/udt-test/src/UDTTest.cpp +++ b/tools/udt-test/src/UDTTest.cpp @@ -58,14 +58,12 @@ const QCommandLineOption STATS_INTERVAL { const QStringList CLIENT_STATS_TABLE_HEADERS { "Send (Mb/s)", "Est. Max (Mb/s)", "RTT (ms)", "CW (P)", "Period (us)", - "Recv ACK", "Procd ACK", "Recv LACK", "Recv NAK", "Recv TNAK", - "Sent ACK2", "Sent Packets", "Re-sent Packets" + "Recv ACK", "Procd ACK", "Sent Packets", "Re-sent Packets" }; const QStringList SERVER_STATS_TABLE_HEADERS { " Mb/s ", "Recv Mb/s", "Est. Max (Mb/s)", "RTT (ms)", "CW (P)", - "Sent ACK", "Sent LACK", "Sent NAK", "Sent TNAK", - "Recv ACK2", "Duplicates (P)" + "Sent ACK", "Duplicates (P)" }; UDTTest::UDTTest(int& argc, char** argv) : @@ -387,11 +385,6 @@ void UDTTest::sampleStats() { QString::number(stats.packetSendPeriod).rightJustified(CLIENT_STATS_TABLE_HEADERS[++headerIndex].size()), QString::number(stats.events[udt::ConnectionStats::Stats::ReceivedACK]).rightJustified(CLIENT_STATS_TABLE_HEADERS[++headerIndex].size()), QString::number(stats.events[udt::ConnectionStats::Stats::ProcessedACK]).rightJustified(CLIENT_STATS_TABLE_HEADERS[++headerIndex].size()), - QString::number(stats.events[udt::ConnectionStats::Stats::ReceivedLightACK]).rightJustified(CLIENT_STATS_TABLE_HEADERS[++headerIndex].size()), - QString::number(stats.events[udt::ConnectionStats::Stats::ReceivedNAK]).rightJustified(CLIENT_STATS_TABLE_HEADERS[++headerIndex].size()), - QString::number(stats.events[udt::ConnectionStats::Stats::ReceivedTimeoutNAK]).rightJustified(CLIENT_STATS_TABLE_HEADERS[++headerIndex].size()), - QString::number(stats.events[udt::ConnectionStats::Stats::SentACK2]).rightJustified(CLIENT_STATS_TABLE_HEADERS[++headerIndex].size()), - QString::number(stats.sentPackets).rightJustified(CLIENT_STATS_TABLE_HEADERS[++headerIndex].size()), QString::number(stats.events[udt::ConnectionStats::Stats::Retransmission]).rightJustified(CLIENT_STATS_TABLE_HEADERS[++headerIndex].size()) }; @@ -420,10 +413,6 @@ void UDTTest::sampleStats() { QString::number(stats.rtt / USECS_PER_MSEC, 'f', 2).rightJustified(SERVER_STATS_TABLE_HEADERS[++headerIndex].size()), QString::number(stats.congestionWindowSize).rightJustified(SERVER_STATS_TABLE_HEADERS[++headerIndex].size()), QString::number(stats.events[udt::ConnectionStats::Stats::SentACK]).rightJustified(SERVER_STATS_TABLE_HEADERS[++headerIndex].size()), - QString::number(stats.events[udt::ConnectionStats::Stats::SentLightACK]).rightJustified(SERVER_STATS_TABLE_HEADERS[++headerIndex].size()), - QString::number(stats.events[udt::ConnectionStats::Stats::SentNAK]).rightJustified(SERVER_STATS_TABLE_HEADERS[++headerIndex].size()), - QString::number(stats.events[udt::ConnectionStats::Stats::SentTimeoutNAK]).rightJustified(SERVER_STATS_TABLE_HEADERS[++headerIndex].size()), - QString::number(stats.events[udt::ConnectionStats::Stats::ReceivedACK2]).rightJustified(SERVER_STATS_TABLE_HEADERS[++headerIndex].size()), QString::number(stats.events[udt::ConnectionStats::Stats::Duplicate]).rightJustified(SERVER_STATS_TABLE_HEADERS[++headerIndex].size()) }; From 9a1c4afbd85b9ee5a62352c9a7445983f96afb0c Mon Sep 17 00:00:00 2001 From: Clement Date: Tue, 17 Jul 2018 14:01:14 -0700 Subject: [PATCH 008/144] More cleanup --- .../networking/src/udt/CongestionControl.h | 20 +-- libraries/networking/src/udt/Connection.cpp | 159 ++---------------- libraries/networking/src/udt/Connection.h | 21 +-- .../networking/src/udt/PacketHeaders.cpp | 2 +- .../networking/src/udt/PacketTimeWindow.cpp | 101 ----------- .../networking/src/udt/PacketTimeWindow.h | 44 ----- libraries/networking/src/udt/Socket.cpp | 47 ------ libraries/networking/src/udt/Socket.h | 4 - libraries/networking/src/udt/TCPVegasCC.cpp | 8 +- libraries/networking/src/udt/TCPVegasCC.h | 2 + 10 files changed, 25 insertions(+), 383 deletions(-) delete mode 100644 libraries/networking/src/udt/PacketTimeWindow.cpp delete mode 100644 libraries/networking/src/udt/PacketTimeWindow.h diff --git a/libraries/networking/src/udt/CongestionControl.h b/libraries/networking/src/udt/CongestionControl.h index 6998f4fe65..7093e8bd96 100644 --- a/libraries/networking/src/udt/CongestionControl.h +++ b/libraries/networking/src/udt/CongestionControl.h @@ -32,11 +32,9 @@ class CongestionControl { friend class Connection; public: - CongestionControl() {}; - CongestionControl(int synInterval) : _synInterval(synInterval) {} - virtual ~CongestionControl() {} - - int synInterval() const { return _synInterval; } + CongestionControl() = default; + virtual ~CongestionControl() = default; + void setMaxBandwidth(int maxBandwidth); virtual void init() {} @@ -47,31 +45,25 @@ public: virtual void onTimeout() {} virtual void onPacketSent(int wireSize, SequenceNumber seqNum, p_high_resolution_clock::time_point timePoint) {} + + virtual int estimatedTimeout() const = 0; protected: void setMSS(int mss) { _mss = mss; } - void setMaxCongestionWindowSize(int window) { _maxCongestionWindowSize = window; } virtual void setInitialSendSequenceNumber(SequenceNumber seqNum) = 0; void setSendCurrentSequenceNumber(SequenceNumber seqNum) { _sendCurrSeqNum = seqNum; } - void setReceiveRate(int rate) { _receiveRate = rate; } - void setRTT(int rtt) { _rtt = rtt; } void setPacketSendPeriod(double newSendPeriod); // call this internally to ensure send period doesn't go past max bandwidth double _packetSendPeriod { 1.0 }; // Packet sending period, in microseconds int _congestionWindowSize { 16 }; // Congestion window size, in packets std::atomic _maxBandwidth { -1 }; // Maximum desired bandwidth, bits per second - int _maxCongestionWindowSize { 0 }; // maximum cwnd size, in packets int _mss { 0 }; // Maximum Packet Size, including all packet headers SequenceNumber _sendCurrSeqNum; // current maximum seq num sent out - int _receiveRate { 0 }; // packet arrive rate at receiver side, packets per second - int _rtt { 0 }; // current estimated RTT, microsecond private: CongestionControl(const CongestionControl& other) = delete; CongestionControl& operator=(const CongestionControl& other) = delete; - - int _synInterval { DEFAULT_SYN_INTERVAL }; }; @@ -79,8 +71,6 @@ class CongestionControlVirtualFactory { public: virtual ~CongestionControlVirtualFactory() {} - static int synInterval() { return DEFAULT_SYN_INTERVAL; } - virtual std::unique_ptr create() = 0; }; diff --git a/libraries/networking/src/udt/Connection.cpp b/libraries/networking/src/udt/Connection.cpp index b317e87e87..8677e7f2f5 100644 --- a/libraries/networking/src/udt/Connection.cpp +++ b/libraries/networking/src/udt/Connection.cpp @@ -39,19 +39,9 @@ Connection::Connection(Socket* parentSocket, HifiSockAddr destination, std::uniq Q_ASSERT_X(_congestionControl, "Connection::Connection", "Must be called with a valid CongestionControl object"); _congestionControl->init(); - - // setup default SYN, RTT and RTT Variance based on the SYN interval in CongestionControl object - _synInterval = _congestionControl->synInterval(); - - resetRTT(); - - // set the initial RTT and flow window size on congestion control object - _congestionControl->setRTT(_rtt); - _congestionControl->setMaxCongestionWindowSize(_flowWindowSize); // Setup packets - static const int ACK_PACKET_PAYLOAD_BYTES = sizeof(_lastSentACK) - + sizeof(_rtt) + sizeof(int32_t) + sizeof(int32_t) + sizeof(int32_t); + static const int ACK_PACKET_PAYLOAD_BYTES = sizeof(SequenceNumber); static const int HANDSHAKE_ACK_PAYLOAD_BYTES = sizeof(SequenceNumber); _ackPacket = ControlPacket::create(ControlPacket::ACK, ACK_PACKET_PAYLOAD_BYTES); @@ -95,11 +85,6 @@ void Connection::stopSendQueue() { } } -void Connection::resetRTT() { - _rtt = _synInterval * 10; - _rttVariance = _rtt / 2; -} - void Connection::setMaxBandwidth(int maxBandwidth) { _congestionControl->setMaxBandwidth(maxBandwidth); } @@ -133,9 +118,8 @@ SendQueue& Connection::getSendQueue() { // set defaults on the send queue from our congestion control object and estimatedTimeout() _sendQueue->setPacketSendPeriod(_congestionControl->_packetSendPeriod); - _sendQueue->setSyncInterval(_synInterval); - _sendQueue->setEstimatedTimeout(estimatedTimeout()); - _sendQueue->setFlowWindowSize(std::min(_flowWindowSize, (int) _congestionControl->_congestionWindowSize)); + _sendQueue->setEstimatedTimeout(_congestionControl->estimatedTimeout()); + _sendQueue->setFlowWindowSize(_congestionControl->_congestionWindowSize); // give the randomized sequence number to the congestion control object _congestionControl->setInitialSendSequenceNumber(_sendQueue->getCurrentSequenceNumber()); @@ -199,23 +183,6 @@ void Connection::queueReceivedMessagePacket(std::unique_ptr packet) { } void Connection::sync() { - if (_isReceivingData) { - - // check if we should expire the receive portion of this connection - // this occurs if it has been 16 timeouts since the last data received and at least 5 seconds - static const int NUM_TIMEOUTS_BEFORE_EXPIRY = 16; - static const int MIN_SECONDS_BEFORE_EXPIRY = 5; - - auto now = p_high_resolution_clock::now(); - - auto sincePacketReceive = now - _lastReceiveTime; - - if (duration_cast(sincePacketReceive).count() >= NUM_TIMEOUTS_BEFORE_EXPIRY * estimatedTimeout() - && duration_cast(sincePacketReceive).count() >= MIN_SECONDS_BEFORE_EXPIRY ) { - // the receive side of this connection is expired - _isReceivingData = false; - } - } } void Connection::recordSentPackets(int wireSize, int payloadSize, @@ -231,44 +198,17 @@ void Connection::recordRetransmission(int wireSize, SequenceNumber seqNum, p_hig _congestionControl->onPacketSent(wireSize, seqNum, timePoint); } -void Connection::sendACK(bool wasCausedBySyncTimeout) { - static p_high_resolution_clock::time_point lastACKSendTime; - +void Connection::sendACK() { SequenceNumber nextACKNumber = nextACK(); - Q_ASSERT_X(nextACKNumber >= _lastSentACK, "Connection::sendACK", "Sending lower ACK, something is wrong"); // we have received new packets since the last sent ACK // or our congestion control dictates that we always send ACKs - - // update the last sent ACK - _lastSentACK = nextACKNumber; _ackPacket->reset(); // We need to reset it every time. // pack in the ACK number _ackPacket->writePrimitive(nextACKNumber); - - // pack in the RTT and variance - _ackPacket->writePrimitive(_rtt); - - // pack the available buffer size, in packets - // in our implementation we have no hard limit on receive buffer size, send the default value - _ackPacket->writePrimitive((int32_t) udt::MAX_PACKETS_IN_FLIGHT); - if (wasCausedBySyncTimeout) { - // grab the up to date packet receive speed - int32_t packetReceiveSpeed = _receiveWindow.getPacketReceiveSpeed(); - - // update those values in our connection stats - _stats.recordReceiveRate(packetReceiveSpeed); - - // pack in the receive speed - _ackPacket->writePrimitive(packetReceiveSpeed); - } - - // record this as the last ACK send time - lastACKSendTime = p_high_resolution_clock::now(); - // have the socket send off our packet _parentSocket->writeBasePacket(*_ackPacket, _destination); @@ -304,12 +244,8 @@ bool Connection::processReceivedSequenceNumber(SequenceNumber sequenceNumber, in return false; } - _isReceivingData = true; - // mark our last receive time as now (to push the potential expiry farther) _lastReceiveTime = p_high_resolution_clock::now(); - - _receiveWindow.onPacketArrival(); // If this is not the next sequence number, report loss if (sequenceNumber > _lastReceivedSequenceNumber + 1) { @@ -331,7 +267,7 @@ bool Connection::processReceivedSequenceNumber(SequenceNumber sequenceNumber, in } // using a congestion control that ACKs every packet (like TCP Vegas) - sendACK(true); + sendACK(); if (wasDuplicate) { _stats.record(ConnectionStats::Stats::Duplicate); @@ -397,22 +333,9 @@ void Connection::processACK(ControlPacketPointer controlPacket) { return; } - // read the RTT - int32_t rtt; - controlPacket->readPrimitive(&rtt); - - if (ack < _lastReceivedACK) { + if (ack <= _lastReceivedACK) { // this is an out of order ACK, bail - return; - } - - // this is a valid ACKed sequence number - update the flow window size and the last received ACK - int32_t packedFlowWindow; - controlPacket->readPrimitive(&packedFlowWindow); - - _flowWindowSize = packedFlowWindow; - - if (ack == _lastReceivedACK) { + // or // processing an already received ACK, bail return; } @@ -421,35 +344,7 @@ void Connection::processACK(ControlPacketPointer controlPacket) { // ACK the send queue so it knows what was received getSendQueue().ack(ack); - - // update the RTT - updateRTT(rtt); - - // write this RTT to stats - _stats.recordRTT(rtt); - - // set the RTT for congestion control - _congestionControl->setRTT(_rtt); - - if (controlPacket->bytesLeftToRead() > 0) { - int32_t receiveRate; - - Q_ASSERT_X(controlPacket->bytesLeftToRead() == sizeof(receiveRate), - "Connection::processACK", "sync interval ACK packet does not contain expected data"); - - controlPacket->readPrimitive(&receiveRate); - - // set the delivery rate for congestion control - // these are calculated using an EWMA - static const int EMWA_ALPHA_NUMERATOR = 8; - - // record these samples in connection stats - _stats.recordSendRate(receiveRate); - - _deliveryRate = (_deliveryRate * (EMWA_ALPHA_NUMERATOR - 1) + receiveRate) / EMWA_ALPHA_NUMERATOR; - - _congestionControl->setReceiveRate(_deliveryRate); - } + // give this ACK to the congestion control and update the send queue parameters updateCongestionControlAndSendQueue([this, ack, &controlPacket] { @@ -478,7 +373,6 @@ void Connection::processHandshake(ControlPacketPointer controlPacket) { resetReceiveState(); _initialReceiveSequenceNumber = initialSequenceNumber; _lastReceivedSequenceNumber = initialSequenceNumber - 1; - _lastSentACK = initialSequenceNumber - 1; } _handshakeACK->reset(); @@ -516,22 +410,13 @@ void Connection::resetReceiveState() { SequenceNumber defaultSequenceNumber; _lastReceivedSequenceNumber = defaultSequenceNumber; - - _lastSentACK = defaultSequenceNumber; // clear the loss list _lossList.clear(); // clear sync variables - _isReceivingData = false; _connectionStart = p_high_resolution_clock::now(); - // reset RTT to initial value - resetRTT(); - - // clear the intervals in the receive window - _receiveWindow.reset(); - // clear any pending received messages for (auto& pendingMessage : _pendingReceivedMessages) { _parentSocket->messageFailed(this, pendingMessage.first); @@ -539,30 +424,6 @@ void Connection::resetReceiveState() { _pendingReceivedMessages.clear(); } -void Connection::updateRTT(int rtt) { - // This updates the RTT using exponential weighted moving average - // This is the Jacobson's forumla for RTT estimation - // http://www.mathcs.emory.edu/~cheung/Courses/455/Syllabus/7-transport/Jacobson-88.pdf - - // Estimated RTT = (1 - x)(estimatedRTT) + (x)(sampleRTT) - // (where x = 0.125 via Jacobson) - - // Deviation = (1 - x)(deviation) + x |sampleRTT - estimatedRTT| - // (where x = 0.25 via Jacobson) - - static const int RTT_ESTIMATION_ALPHA_NUMERATOR = 8; - static const int RTT_ESTIMATION_VARIANCE_ALPHA_NUMERATOR = 4; - - _rtt = (_rtt * (RTT_ESTIMATION_ALPHA_NUMERATOR - 1) + rtt) / RTT_ESTIMATION_ALPHA_NUMERATOR; - - _rttVariance = (_rttVariance * (RTT_ESTIMATION_VARIANCE_ALPHA_NUMERATOR - 1) - + abs(rtt - _rtt)) / RTT_ESTIMATION_VARIANCE_ALPHA_NUMERATOR; -} - -int Connection::estimatedTimeout() const { - return _rtt + _rttVariance * 4; -} - void Connection::updateCongestionControlAndSendQueue(std::function congestionCallback) { // update the last sent sequence number in congestion control _congestionControl->setSendCurrentSequenceNumber(getSendQueue().getCurrentSequenceNumber()); @@ -574,8 +435,8 @@ void Connection::updateCongestionControlAndSendQueue(std::function cong // now that we've updated the congestion control, update the packet send period and flow window size sendQueue.setPacketSendPeriod(_congestionControl->_packetSendPeriod); - sendQueue.setEstimatedTimeout(estimatedTimeout()); - sendQueue.setFlowWindowSize(std::min(_flowWindowSize, (int) _congestionControl->_congestionWindowSize)); + sendQueue.setEstimatedTimeout(_congestionControl->estimatedTimeout()); + sendQueue.setFlowWindowSize(_congestionControl->_congestionWindowSize); // record connection stats _stats.recordPacketSendPeriod(_congestionControl->_packetSendPeriod); diff --git a/libraries/networking/src/udt/Connection.h b/libraries/networking/src/udt/Connection.h index ebe58e32e2..17e8a9b1f9 100644 --- a/libraries/networking/src/udt/Connection.h +++ b/libraries/networking/src/udt/Connection.h @@ -22,7 +22,6 @@ #include "ConnectionStats.h" #include "Constants.h" #include "LossList.h" -#include "PacketTimeWindow.h" #include "SendQueue.h" #include "../HifiSockAddr.h" @@ -86,35 +85,27 @@ private slots: void queueTimeout(); private: - void sendACK(bool wasCausedBySyncTimeout = true); + void sendACK(); void processACK(ControlPacketPointer controlPacket); void processHandshake(ControlPacketPointer controlPacket); void processHandshakeACK(ControlPacketPointer controlPacket); void resetReceiveState(); - void resetRTT(); SendQueue& getSendQueue(); SequenceNumber nextACK() const; - void updateRTT(int rtt); - - int estimatedTimeout() const; void updateCongestionControlAndSendQueue(std::function congestionCallback); void stopSendQueue(); - int _synInterval; // Periodical Rate Control Interval, in microseconds - bool _hasReceivedHandshake { false }; // flag for receipt of handshake from server bool _hasReceivedHandshakeACK { false }; // flag for receipt of handshake ACK from client bool _didRequestHandshake { false }; // flag for request of handshake from server p_high_resolution_clock::time_point _connectionStart = p_high_resolution_clock::now(); // holds the time_point for creation of this connection p_high_resolution_clock::time_point _lastReceiveTime; // holds the last time we received anything from sender - - bool _isReceivingData { false }; // flag used for expiry of receipt portion of connection SequenceNumber _initialSequenceNumber; // Randomized on Connection creation, identifies connection during re-connect requests SequenceNumber _initialReceiveSequenceNumber; // Randomized by peer Connection on creation, identifies connection during re-connect requests @@ -125,18 +116,8 @@ private: SequenceNumber _lastReceivedSequenceNumber; // The largest sequence number received from the peer SequenceNumber _lastReceivedACK; // The last ACK received - SequenceNumber _lastSentACK; // The last sent ACK - - int32_t _rtt; // RTT, in microseconds - int32_t _rttVariance; // RTT variance - int _flowWindowSize { udt::MAX_PACKETS_IN_FLIGHT }; // Flow control window size - - int _deliveryRate { 16 }; // Exponential moving average for receiver's receive rate, in packets per second - Socket* _parentSocket { nullptr }; HifiSockAddr _destination; - - PacketTimeWindow _receiveWindow { 16 }; // Window of interval between packets (16) std::unique_ptr _congestionControl; diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index 13ffcb5120..bb9666ee37 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -95,7 +95,7 @@ PacketVersion versionForPacketType(PacketType packetType) { case PacketType::AvatarIdentityRequest: return 22; default: - return 21; + return 22; } } diff --git a/libraries/networking/src/udt/PacketTimeWindow.cpp b/libraries/networking/src/udt/PacketTimeWindow.cpp deleted file mode 100644 index c95045c48f..0000000000 --- a/libraries/networking/src/udt/PacketTimeWindow.cpp +++ /dev/null @@ -1,101 +0,0 @@ -// -// PacketTimeWindow.cpp -// libraries/networking/src/udt -// -// Created by Stephen Birarda on 2015-07-28. -// Copyright 2015 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#include "PacketTimeWindow.h" - -#include -#include - -#include - -using namespace udt; -using namespace std::chrono; - -static const int DEFAULT_PACKET_INTERVAL_MICROSECONDS = 1000000; // 1s - -PacketTimeWindow::PacketTimeWindow(int numPacketIntervals) : - _numPacketIntervals(numPacketIntervals), - _packetIntervals(_numPacketIntervals, DEFAULT_PACKET_INTERVAL_MICROSECONDS) -{ - -} - -void PacketTimeWindow::reset() { - _packetIntervals.assign(_numPacketIntervals, DEFAULT_PACKET_INTERVAL_MICROSECONDS); -} - -template -int median(Iterator begin, Iterator end) { - // use std::nth_element to grab the middle - for an even number of elements this is the upper middle - Iterator middle = begin + (end - begin) / 2; - std::nth_element(begin, middle, end); - - if ((end - begin) % 2 != 0) { - // odd number of elements, just return the middle - return *middle; - } else { - // even number of elements, return the mean of the upper middle and the lower middle - Iterator lowerMiddle = std::max_element(begin, middle); - return (*middle + *lowerMiddle) / 2; - } -} - -int32_t meanOfMedianFilteredValues(std::vector intervals, int numValues, int valuesRequired = 0) { - // grab the median value of the intervals vector - int intervalsMedian = median(intervals.begin(), intervals.end()); - - // figure out our bounds for median filtering - static const int MEDIAN_FILTERING_BOUND_MULTIPLIER = 8; - int upperBound = intervalsMedian * MEDIAN_FILTERING_BOUND_MULTIPLIER; - int lowerBound = intervalsMedian / MEDIAN_FILTERING_BOUND_MULTIPLIER; - - int sum = 0; - int count = 0; - - // sum the values that are inside the median filtered bounds - for (auto& interval : intervals) { - if ((interval < upperBound) && (interval > lowerBound)) { - ++count; - sum += interval; - } - } - - // make sure we hit our threshold of values required - if (count >= valuesRequired) { - // return the frequency (per second) for the mean interval - static const double USECS_PER_SEC = 1000000.0; - return (int32_t) ceil(USECS_PER_SEC / (((double) sum) / ((double) count))); - } else { - return 0; - } -} - -int32_t PacketTimeWindow::getPacketReceiveSpeed() const { - // return the mean value of median filtered values (per second) - or zero if there are too few filtered values - return meanOfMedianFilteredValues(_packetIntervals, _numPacketIntervals, _numPacketIntervals / 2); -} - -void PacketTimeWindow::onPacketArrival() { - - // take the current time - auto now = p_high_resolution_clock::now(); - - if (_packetIntervals.size() > 0) { - // record the interval between this packet and the last one - _packetIntervals[_currentPacketInterval++] = duration_cast(now - _lastPacketTime).count(); - - // reset the currentPacketInterval index when it wraps - _currentPacketInterval %= _numPacketIntervals; - } - - // remember this as the last packet arrival time - _lastPacketTime = now; -} diff --git a/libraries/networking/src/udt/PacketTimeWindow.h b/libraries/networking/src/udt/PacketTimeWindow.h deleted file mode 100644 index 359097ac8c..0000000000 --- a/libraries/networking/src/udt/PacketTimeWindow.h +++ /dev/null @@ -1,44 +0,0 @@ -// -// PacketTimeWindow.h -// libraries/networking/src/udt -// -// Created by Stephen Birarda on 2015-07-28. -// Copyright 2015 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#pragma once - -#ifndef hifi_PacketTimeWindow_h -#define hifi_PacketTimeWindow_h - -#include - -#include - -namespace udt { - -class PacketTimeWindow { -public: - PacketTimeWindow(int numPacketIntervals = 16); - - void onPacketArrival(); - - int32_t getPacketReceiveSpeed() const; - - void reset(); -private: - int _numPacketIntervals { 0 }; // the number of packet intervals to store - - int _currentPacketInterval { 0 }; // index for the current packet interval - - std::vector _packetIntervals; // vector of microsecond intervals between packet arrivals - - p_high_resolution_clock::time_point _lastPacketTime = p_high_resolution_clock::now(); // the time_point when last packet arrived -}; - -} - -#endif // hifi_PacketTimeWindow_h diff --git a/libraries/networking/src/udt/Socket.cpp b/libraries/networking/src/udt/Socket.cpp index 4d4303698b..1ea5079bc5 100644 --- a/libraries/networking/src/udt/Socket.cpp +++ b/libraries/networking/src/udt/Socket.cpp @@ -33,18 +33,11 @@ using namespace udt; Socket::Socket(QObject* parent, bool shouldChangeSocketOptions) : QObject(parent), - _synTimer(new QTimer(this)), _readyReadBackupTimer(new QTimer(this)), _shouldChangeSocketOptions(shouldChangeSocketOptions) { connect(&_udpSocket, &QUdpSocket::readyRead, this, &Socket::readPendingDatagrams); - // make sure our synchronization method is called every SYN interval - connect(_synTimer, &QTimer::timeout, this, &Socket::rateControlSync); - - // start our timer for the synchronization time interval - _synTimer->start(_synInterval); - // make sure we hear about errors and state changes from the underlying socket connect(&_udpSocket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(handleSocketError(QAbstractSocket::SocketError))); @@ -427,49 +420,9 @@ void Socket::connectToSendSignal(const HifiSockAddr& destinationAddr, QObject* r } } -void Socket::rateControlSync() { - - // enumerate our list of connections and ask each of them to send off periodic ACK packet for rate control - - // the way we do this is a little funny looking - we need to avoid the case where we call sync and - // (because of our Qt direct connection to the Connection's signal that it has been deactivated) - // an iterator on _connectionsHash would be invalidated by our own call to cleanupConnection - - // collect the sockets for all connections in a vector - - std::vector sockAddrVector; - sockAddrVector.reserve(_connectionsHash.size()); - - for (auto& connection : _connectionsHash) { - sockAddrVector.emplace_back(connection.first); - } - - // enumerate that vector of HifiSockAddr objects - for (auto& sockAddr : sockAddrVector) { - // pull out the respective connection via a quick find on the unordered_map - auto it = _connectionsHash.find(sockAddr); - - if (it != _connectionsHash.end()) { - // if the connection is erased while calling sync since we are re-using the iterator that was invalidated - // we're good to go - auto& connection = _connectionsHash[sockAddr]; - connection->sync(); - } - } - - if (_synTimer->interval() != _synInterval) { - // if the _synTimer interval doesn't match the current _synInterval (changes when the CC factory is changed) - // then restart it now with the right interval - _synTimer->start(_synInterval); - } -} - void Socket::setCongestionControlFactory(std::unique_ptr ccFactory) { // swap the current unique_ptr for the new factory _ccFactory.swap(ccFactory); - - // update the _synInterval to the value from the factory - _synInterval = _ccFactory->synInterval(); } diff --git a/libraries/networking/src/udt/Socket.h b/libraries/networking/src/udt/Socket.h index 1919e00b41..1f28592c83 100644 --- a/libraries/networking/src/udt/Socket.h +++ b/libraries/networking/src/udt/Socket.h @@ -102,7 +102,6 @@ public slots: private slots: void readPendingDatagrams(); void checkForReadyReadBackup(); - void rateControlSync(); void handleSocketError(QAbstractSocket::SocketError socketError); void handleStateChanged(QAbstractSocket::SocketState socketState); @@ -133,9 +132,6 @@ private: std::unordered_map _unfilteredHandlers; std::unordered_map _unreliableSequenceNumbers; std::unordered_map> _connectionsHash; - - int _synInterval { 10 }; // 10ms - QTimer* _synTimer { nullptr }; QTimer* _readyReadBackupTimer { nullptr }; diff --git a/libraries/networking/src/udt/TCPVegasCC.cpp b/libraries/networking/src/udt/TCPVegasCC.cpp index e39e474f39..4842e5a204 100644 --- a/libraries/networking/src/udt/TCPVegasCC.cpp +++ b/libraries/networking/src/udt/TCPVegasCC.cpp @@ -101,12 +101,11 @@ bool TCPVegasCC::onACK(SequenceNumber ack, p_high_resolution_clock::time_point r auto it = _sentPacketTimes.find(ack + 1); if (it != _sentPacketTimes.end()) { - auto estimatedTimeout = _ewmaRTT + _rttVariance * 4; auto now = p_high_resolution_clock::now(); auto sinceSend = duration_cast(now - it->second).count(); - if (sinceSend >= estimatedTimeout) { + if (sinceSend >= estimatedTimeout()) { // break out of slow start, we've decided this is loss _slowStart = false; @@ -213,6 +212,11 @@ void TCPVegasCC::performCongestionAvoidance(udt::SequenceNumber ack) { _numACKs = 0; } + +int TCPVegasCC::estimatedTimeout() const { + return _ewmaRTT == -1 ? DEFAULT_SYN_INTERVAL : _ewmaRTT + _rttVariance * 4; +} + bool TCPVegasCC::isCongestionWindowLimited() { if (_slowStart) { return true; diff --git a/libraries/networking/src/udt/TCPVegasCC.h b/libraries/networking/src/udt/TCPVegasCC.h index 38b70ea325..bb14728d4b 100644 --- a/libraries/networking/src/udt/TCPVegasCC.h +++ b/libraries/networking/src/udt/TCPVegasCC.h @@ -30,6 +30,8 @@ public: virtual void onTimeout() override {}; virtual void onPacketSent(int wireSize, SequenceNumber seqNum, p_high_resolution_clock::time_point timePoint) override; + + virtual int estimatedTimeout() const override; protected: virtual void performCongestionAvoidance(SequenceNumber ack); From ae67064463d90fb9bd056bbe3da55941810897bf Mon Sep 17 00:00:00 2001 From: Clement Date: Tue, 17 Jul 2018 16:01:38 -0700 Subject: [PATCH 009/144] Fix UDTTest --- tools/udt-test/src/UDTTest.cpp | 1 + tools/udt-test/src/main.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/tools/udt-test/src/UDTTest.cpp b/tools/udt-test/src/UDTTest.cpp index b6efe4b42e..46e7ed0be0 100644 --- a/tools/udt-test/src/UDTTest.cpp +++ b/tools/udt-test/src/UDTTest.cpp @@ -385,6 +385,7 @@ void UDTTest::sampleStats() { QString::number(stats.packetSendPeriod).rightJustified(CLIENT_STATS_TABLE_HEADERS[++headerIndex].size()), QString::number(stats.events[udt::ConnectionStats::Stats::ReceivedACK]).rightJustified(CLIENT_STATS_TABLE_HEADERS[++headerIndex].size()), QString::number(stats.events[udt::ConnectionStats::Stats::ProcessedACK]).rightJustified(CLIENT_STATS_TABLE_HEADERS[++headerIndex].size()), + QString::number(stats.sentPackets).rightJustified(CLIENT_STATS_TABLE_HEADERS[++headerIndex].size()), QString::number(stats.events[udt::ConnectionStats::Stats::Retransmission]).rightJustified(CLIENT_STATS_TABLE_HEADERS[++headerIndex].size()) }; diff --git a/tools/udt-test/src/main.cpp b/tools/udt-test/src/main.cpp index d88218c0d0..13953c91cc 100644 --- a/tools/udt-test/src/main.cpp +++ b/tools/udt-test/src/main.cpp @@ -15,7 +15,7 @@ #include "UDTTest.h" int main(int argc, char* argv[]) { - setupHifiApplication("UDT Test); + setupHifiApplication("UDT Test"); UDTTest app(argc, argv); return app.exec(); From 87daa55f0bf9a04e3975846161c89c21a0ee961c Mon Sep 17 00:00:00 2001 From: Clement Date: Fri, 20 Jul 2018 18:11:52 -0700 Subject: [PATCH 010/144] Remove deprecated control packet types --- libraries/networking/src/udt/Connection.cpp | 6 ------ libraries/networking/src/udt/ControlPacket.h | 5 ----- 2 files changed, 11 deletions(-) diff --git a/libraries/networking/src/udt/Connection.cpp b/libraries/networking/src/udt/Connection.cpp index 8677e7f2f5..24e294881a 100644 --- a/libraries/networking/src/udt/Connection.cpp +++ b/libraries/networking/src/udt/Connection.cpp @@ -309,12 +309,6 @@ void Connection::processControl(ControlPacketPointer controlPacket) { stopSendQueue(); } break; - case ControlPacket::LightACK: - case ControlPacket::ACK2: - case ControlPacket::NAK: - case ControlPacket::TimeoutNAK: - case ControlPacket::ProbeTail: - break; } } diff --git a/libraries/networking/src/udt/ControlPacket.h b/libraries/networking/src/udt/ControlPacket.h index 3c770de9bb..46b9cdbd40 100644 --- a/libraries/networking/src/udt/ControlPacket.h +++ b/libraries/networking/src/udt/ControlPacket.h @@ -28,13 +28,8 @@ public: enum Type : uint16_t { ACK, - ACK2, - LightACK, - NAK, - TimeoutNAK, Handshake, HandshakeACK, - ProbeTail, HandshakeRequest }; From db2297dbcecb5273c232329f35205cb343d30028 Mon Sep 17 00:00:00 2001 From: Clement Date: Fri, 20 Jul 2018 18:29:48 -0700 Subject: [PATCH 011/144] Update Wireshark dissector --- tools/dissectors/1-hfudt.lua | 56 +++++------------------------------- 1 file changed, 7 insertions(+), 49 deletions(-) diff --git a/tools/dissectors/1-hfudt.lua b/tools/dissectors/1-hfudt.lua index 137bee659b..e473181dfe 100644 --- a/tools/dissectors/1-hfudt.lua +++ b/tools/dissectors/1-hfudt.lua @@ -26,9 +26,6 @@ local f_hmac_hash = ProtoField.bytes("hfudt.hmac_hash", "HMAC Hash") local f_control_type = ProtoField.uint16("hfudt.control_type", "Control Type", base.DEC) local f_control_type_text = ProtoField.string("hfudt.control_type_text", "Control Type Text", base.ASCII) local f_ack_sequence_number = ProtoField.uint32("hfudt.ack_sequence_number", "ACKed Sequence Number", base.DEC) -local f_control_sub_sequence = ProtoField.uint32("hfudt.control_sub_sequence", "Control Sub-Sequence Number", base.DEC) -local f_nak_sequence_number = ProtoField.uint32("hfudt.nak_sequence_number", "NAKed Sequence Number", base.DEC) -local f_nak_range_end = ProtoField.uint32("hfudt.nak_range_end", "NAK Range End", base.DEC) local SEQUENCE_NUMBER_MASK = 0x07FFFFFF @@ -37,19 +34,13 @@ p_hfudt.fields = { f_control_bit, f_reliable_bit, f_message_bit, f_sequence_number, f_type, f_type_text, f_version, f_sender_id, f_hmac_hash, f_message_position, f_message_number, f_message_part_number, f_obfuscation_level, - f_control_type, f_control_type_text, f_control_sub_sequence, f_ack_sequence_number, f_nak_sequence_number, f_nak_range_end, - f_data + f_control_type, f_control_type_text, f_ack_sequence_number, f_data } local control_types = { [0] = { "ACK", "Acknowledgement" }, - [1] = { "ACK2", "Acknowledgement of acknowledgement" }, - [2] = { "LightACK", "Light Acknowledgement" }, - [3] = { "NAK", "Loss report (NAK)" }, - [4] = { "TimeoutNAK", "Loss report re-transmission (TimeoutNAK)" }, [5] = { "Handshake", "Handshake" }, [6] = { "HandshakeACK", "Acknowledgement of Handshake" }, - [7] = { "ProbeTail", "Probe tail" }, [8] = { "HandshakeRequest", "Request a Handshake" } } @@ -160,51 +151,18 @@ function p_hfudt.dissector(buf, pinfo, tree) subtree:add(f_control_type_text, control_types[shifted_type][1]) end - if shifted_type == 0 or shifted_type == 1 then + if shifted_type == 0 then + local data_index = 4 - -- this has a sub-sequence number - local second_word = buf(4, 4):le_uint() - subtree:add(f_control_sub_sequence, bit32.band(second_word, SEQUENCE_NUMBER_MASK)) - - local data_index = 8 - - if shifted_type == 0 then - -- if this is an ACK let's read out the sequence number - local sequence_number = buf(8, 4):le_uint() - subtree:add(f_ack_sequence_number, bit32.band(sequence_number, SEQUENCE_NUMBER_MASK)) - - data_index = data_index + 4 - end + -- This is an ACK let's read out the sequence number + local sequence_number = buf(data_index, 4):le_uint() + subtree:add(f_ack_sequence_number, bit32.band(sequence_number, SEQUENCE_NUMBER_MASK)) + data_index = data_index + 4 data_length = buf:len() - data_index -- set the data from whatever is left in the packet subtree:add(f_data, buf(data_index, data_length)) - - elseif shifted_type == 2 then - -- this is a Light ACK let's read out the sequence number - local sequence_number = buf(4, 4):le_uint() - subtree:add(f_ack_sequence_number, bit32.band(sequence_number, SEQUENCE_NUMBER_MASK)) - - data_length = buf:len() - 4 - - -- set the data from whatever is left in the packet - subtree:add(f_data, buf(4, data_length)) - elseif shifted_type == 3 or shifted_type == 4 then - if buf:len() <= 12 then - -- this is a NAK pull the sequence number or range - local sequence_number = buf(4, 4):le_uint() - subtree:add(f_nak_sequence_number, bit32.band(sequence_number, SEQUENCE_NUMBER_MASK)) - - data_length = buf:len() - 4 - - if buf:len() > 8 then - local range_end = buf(8, 4):le_uint() - subtree:add(f_nak_range_end, bit32.band(range_end, SEQUENCE_NUMBER_MASK)) - - data_length = data_length - 4 - end - end else data_length = buf:len() - 4 From 7f743de1504dc07f3609a692915889acd75bb758 Mon Sep 17 00:00:00 2001 From: sabrina-shanman Date: Mon, 23 Jul 2018 12:59:23 -0700 Subject: [PATCH 012/144] Fix CollisionRegion serialization/deserialization issues and CollisionPick implicit cast warning --- interface/src/raypick/CollisionPick.cpp | 2 +- interface/src/raypick/CollisionPick.h | 2 ++ libraries/shared/src/RegisteredMetaTypes.h | 4 +++- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/interface/src/raypick/CollisionPick.cpp b/interface/src/raypick/CollisionPick.cpp index 7ed146300d..d9aa25228e 100644 --- a/interface/src/raypick/CollisionPick.cpp +++ b/interface/src/raypick/CollisionPick.cpp @@ -205,7 +205,7 @@ void CollisionPick::computeShapeInfo(CollisionRegion& pick, ShapeInfo& shapeInfo for (const FBXMeshPart& meshPart : mesh.parts) { triangleIndicesCount += meshPart.triangleIndices.count(); } - triangleIndices.reserve(triangleIndicesCount); + triangleIndices.reserve((int)triangleIndicesCount); for (const FBXMeshPart& meshPart : mesh.parts) { const int* indexItr = meshPart.triangleIndices.cbegin(); diff --git a/interface/src/raypick/CollisionPick.h b/interface/src/raypick/CollisionPick.h index 5cd8d4ccd6..06c8bd66e0 100644 --- a/interface/src/raypick/CollisionPick.h +++ b/interface/src/raypick/CollisionPick.h @@ -103,6 +103,8 @@ public: intersectingAvatars.push_back(intersectingAvatar); } + intersects = intersectingEntities.size() || intersectingAvatars.size(); + return std::make_shared(*this); } }; diff --git a/libraries/shared/src/RegisteredMetaTypes.h b/libraries/shared/src/RegisteredMetaTypes.h index d20a993117..efafd35a06 100644 --- a/libraries/shared/src/RegisteredMetaTypes.h +++ b/libraries/shared/src/RegisteredMetaTypes.h @@ -243,6 +243,8 @@ public: if (shape["dimensions"].isValid()) { transform.setScale(vec3FromVariant(shape["dimensions"])); } + + shapeInfo.setParams(shapeType, transform.getScale() / 2.0f, modelURL.toString()); } } @@ -260,7 +262,7 @@ public: QVariantMap shape; shape["shapeType"] = ShapeInfo::getNameForShapeType(shapeInfo.getType()); shape["modelURL"] = modelURL.toString(); - shape["dimensions"] = vec3toVariant(shapeInfo.getHalfExtents()); + shape["dimensions"] = vec3toVariant(transform.getScale()); collisionRegion["shape"] = shape; From ca30bb59afe44466df39b87658663056aea2b929 Mon Sep 17 00:00:00 2001 From: sabrina-shanman Date: Mon, 23 Jul 2018 13:25:09 -0700 Subject: [PATCH 013/144] Remove unused Bullet callbacks for collision picking --- interface/src/raypick/CollisionPick.h | 31 --------------------------- 1 file changed, 31 deletions(-) diff --git a/interface/src/raypick/CollisionPick.h b/interface/src/raypick/CollisionPick.h index 06c8bd66e0..ce596617da 100644 --- a/interface/src/raypick/CollisionPick.h +++ b/interface/src/raypick/CollisionPick.h @@ -188,37 +188,6 @@ struct RigidBodyFilterResultCallback : public btCollisionWorld::ContactResultCal } }; -// Callback for getting colliding avatars in the world. -struct AllAvatarsCallback : public RigidBodyFilterResultCallback { - std::vector intersectingAvatars; - - void checkOrAddCollidingState(const btMotionState* otherMotionState, btVector3& point, btVector3& otherPoint) override { - const AvatarMotionState* avatarCandidate = dynamic_cast(otherMotionState); - if (!avatarCandidate) { - return; - } - - // This is the correct object type. Add it to the list. - intersectingAvatars.emplace_back(avatarCandidate->getObjectID(), bulletToGLM(point), bulletToGLM(otherPoint)); - } -}; - -// Callback for getting colliding entities in the world. -struct AllEntitiesCallback : public RigidBodyFilterResultCallback { - std::vector intersectingEntities; - - void checkOrAddCollidingState(const btMotionState* otherMotionState, btVector3& point, btVector3& otherPoint) override { - const EntityMotionState* entityCandidate = dynamic_cast(otherMotionState); - if (!entityCandidate) { - return; - } - - // This is the correct object type. Add it to the list. - intersectingEntities.emplace_back(entityCandidate->getObjectID(), bulletToGLM(point), bulletToGLM(otherPoint)); - } -}; - -// TODO: Test if this works. Revert to above code if it doesn't // Callback for getting colliding ObjectMotionStates in the world, or optionally a more specific type. template struct AllObjectMotionStatesCallback : public RigidBodyFilterResultCallback { From 66f355e5ba6d5dc143f3caef2e82d9603c50b4d2 Mon Sep 17 00:00:00 2001 From: sabrina-shanman Date: Mon, 23 Jul 2018 13:33:16 -0700 Subject: [PATCH 014/144] Move bullet callbacks for collision picks to source file --- interface/src/raypick/CollisionPick.cpp | 56 +++++++++++++++++++++++++ interface/src/raypick/CollisionPick.h | 52 ++--------------------- 2 files changed, 60 insertions(+), 48 deletions(-) diff --git a/interface/src/raypick/CollisionPick.cpp b/interface/src/raypick/CollisionPick.cpp index d9aa25228e..c16a00ad54 100644 --- a/interface/src/raypick/CollisionPick.cpp +++ b/interface/src/raypick/CollisionPick.cpp @@ -298,4 +298,60 @@ PickResultPointer CollisionPick::getAvatarIntersection(const CollisionRegion& pi PickResultPointer CollisionPick::getHUDIntersection(const CollisionRegion& pick) { return getDefaultResult(QVariantMap()); +} + +template +RigidBodyFilterResultCallback::RigidBodyFilterResultCallback(const ShapeInfo& shapeInfo, const Transform& transform) : + btCollisionWorld::ContactResultCallback(), collisionObject() { + const btCollisionShape* collisionShape = ObjectMotionState::getShapeManager()->getShape(shapeInfo); + + collisionObject.setCollisionShape(const_cast(collisionShape)); + + btTransform bulletTransform; + bulletTransform.setOrigin(glmToBullet(transform.getTranslation())); + bulletTransform.setRotation(glmToBullet(transform.getRotation())); + + collisionObject.setWorldTransform(bulletTransform); +} + +template +RigidBodyFilterResultCallback::~RigidBodyFilterResultCallback() { + ObjectMotionState::getShapeManager()->releaseShape(collisionObject.getCollisionShape()); +} + +template +btScalar RigidBodyFilterResultCallback::addSingleResult(btManifoldPoint& cp, const btCollisionObjectWrapper* colObj0, int partId0, int index0, const btCollisionObjectWrapper* colObj1, int partId1, int index1) { + const btCollisionObject* otherBody; + btVector3 point; + btVector3 otherPoint; + if (colObj0->m_collisionObject == &collisionObject) { + otherBody = colObj1->m_collisionObject; + point = cp.m_localPointA; + otherPoint = cp.m_localPointB; + } + else { + otherBody = colObj0->m_collisionObject; + point = cp.m_localPointB; + otherPoint = cp.m_localPointA; + } + const btRigidBody* collisionCandidate = dynamic_cast(otherBody); + if (!collisionCandidate) { + return 0; + } + const btMotionState* motionStateCandidate = collisionCandidate->getMotionState(); + + checkOrAddCollidingState(motionStateCandidate, point, otherPoint); + + return 0; +} + +template +void AllObjectMotionStatesCallback::checkOrAddCollidingState(const btMotionState* otherMotionState, btVector3& point, btVector3& otherPoint) { + const T* candidate = dynamic_cast(otherMotionState); + if (!candidate) { + return; + } + + // This is the correct object type. Add it to the list. + intersectingObjects.emplace_back(candidate->getObjectID(), bulletToGLM(point), bulletToGLM(otherPoint)); } \ No newline at end of file diff --git a/interface/src/raypick/CollisionPick.h b/interface/src/raypick/CollisionPick.h index ce596617da..3208efd5ec 100644 --- a/interface/src/raypick/CollisionPick.h +++ b/interface/src/raypick/CollisionPick.h @@ -136,22 +136,9 @@ protected: // Callback for checking the motion states of all colliding rigid bodies for candidacy to be added to a list struct RigidBodyFilterResultCallback : public btCollisionWorld::ContactResultCallback { - RigidBodyFilterResultCallback(const ShapeInfo& shapeInfo, const Transform& transform) : - btCollisionWorld::ContactResultCallback(), collisionObject() { - const btCollisionShape* collisionShape = ObjectMotionState::getShapeManager()->getShape(shapeInfo); + RigidBodyFilterResultCallback(const ShapeInfo& shapeInfo, const Transform& transform); - collisionObject.setCollisionShape(const_cast(collisionShape)); - - btTransform bulletTransform; - bulletTransform.setOrigin(glmToBullet(transform.getTranslation())); - bulletTransform.setRotation(glmToBullet(transform.getRotation())); - - collisionObject.setWorldTransform(bulletTransform); - } - - ~RigidBodyFilterResultCallback() { - ObjectMotionState::getShapeManager()->releaseShape(collisionObject.getCollisionShape()); - } + ~RigidBodyFilterResultCallback(); RigidBodyFilterResultCallback(btCollisionObject& testCollisionObject) : btCollisionWorld::ContactResultCallback(), collisionObject(testCollisionObject) { @@ -162,30 +149,7 @@ struct RigidBodyFilterResultCallback : public btCollisionWorld::ContactResultCal // Check candidacy for adding to a list virtual void checkOrAddCollidingState(const btMotionState* otherMotionState, btVector3& point, btVector3& otherPoint) = 0; - btScalar addSingleResult(btManifoldPoint& cp, const btCollisionObjectWrapper* colObj0, int partId0, int index0, const btCollisionObjectWrapper* colObj1, int partId1, int index1) override { - const btCollisionObject* otherBody; - btVector3 point; - btVector3 otherPoint; - if (colObj0->m_collisionObject == &collisionObject) { - otherBody = colObj1->m_collisionObject; - point = cp.m_localPointA; - otherPoint = cp.m_localPointB; - } - else { - otherBody = colObj0->m_collisionObject; - point = cp.m_localPointB; - otherPoint = cp.m_localPointA; - } - const btRigidBody* collisionCandidate = dynamic_cast(otherBody); - if (!collisionCandidate) { - return 0; - } - const btMotionState* motionStateCandidate = collisionCandidate->getMotionState(); - - checkOrAddCollidingState(motionStateCandidate, point, otherPoint); - - return 0; - } + btScalar addSingleResult(btManifoldPoint& cp, const btCollisionObjectWrapper* colObj0, int partId0, int index0, const btCollisionObjectWrapper* colObj1, int partId1, int index1) override; }; // Callback for getting colliding ObjectMotionStates in the world, or optionally a more specific type. @@ -195,15 +159,7 @@ struct AllObjectMotionStatesCallback : public RigidBodyFilterResultCallback { std::vector intersectingObjects; - void checkOrAddCollidingState(const btMotionState* otherMotionState, btVector3& point, btVector3& otherPoint) override { - const T* candidate = dynamic_cast(otherMotionState); - if (!candidate) { - return; - } - - // This is the correct object type. Add it to the list. - intersectingObjects.emplace_back(candidate->getObjectID(), bulletToGLM(point), bulletToGLM(otherPoint)); - } + void checkOrAddCollidingState(const btMotionState* otherMotionState, btVector3& point, btVector3& otherPoint) override; }; #endif // hifi_CollisionPick_h \ No newline at end of file From a3560d1a1bd2a77897f6bdc178b098cfc5d68a6e Mon Sep 17 00:00:00 2001 From: sabrina-shanman Date: Mon, 23 Jul 2018 13:47:02 -0700 Subject: [PATCH 015/144] Remove wrong template from RigidBodyResultCallback function definitions --- interface/src/raypick/CollisionPick.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/interface/src/raypick/CollisionPick.cpp b/interface/src/raypick/CollisionPick.cpp index c16a00ad54..40d089bfd4 100644 --- a/interface/src/raypick/CollisionPick.cpp +++ b/interface/src/raypick/CollisionPick.cpp @@ -300,7 +300,6 @@ PickResultPointer CollisionPick::getHUDIntersection(const CollisionRegion& pick) return getDefaultResult(QVariantMap()); } -template RigidBodyFilterResultCallback::RigidBodyFilterResultCallback(const ShapeInfo& shapeInfo, const Transform& transform) : btCollisionWorld::ContactResultCallback(), collisionObject() { const btCollisionShape* collisionShape = ObjectMotionState::getShapeManager()->getShape(shapeInfo); @@ -314,12 +313,10 @@ RigidBodyFilterResultCallback::RigidBodyFilterResultCallback(const ShapeInfo& sh collisionObject.setWorldTransform(bulletTransform); } -template RigidBodyFilterResultCallback::~RigidBodyFilterResultCallback() { ObjectMotionState::getShapeManager()->releaseShape(collisionObject.getCollisionShape()); } -template btScalar RigidBodyFilterResultCallback::addSingleResult(btManifoldPoint& cp, const btCollisionObjectWrapper* colObj0, int partId0, int index0, const btCollisionObjectWrapper* colObj1, int partId1, int index1) { const btCollisionObject* otherBody; btVector3 point; From a1202d9bd533859819546d752648ef2ea05e3724 Mon Sep 17 00:00:00 2001 From: sabrina-shanman Date: Tue, 24 Jul 2018 09:07:18 -0700 Subject: [PATCH 016/144] Do not use dynamic cast with bullet objects, and add missing template syntax --- interface/src/raypick/CollisionPick.cpp | 10 +++++++--- interface/src/raypick/CollisionPick.h | 2 ++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/interface/src/raypick/CollisionPick.cpp b/interface/src/raypick/CollisionPick.cpp index 40d089bfd4..7330a64267 100644 --- a/interface/src/raypick/CollisionPick.cpp +++ b/interface/src/raypick/CollisionPick.cpp @@ -317,6 +317,10 @@ RigidBodyFilterResultCallback::~RigidBodyFilterResultCallback() { ObjectMotionState::getShapeManager()->releaseShape(collisionObject.getCollisionShape()); } +bool RigidBodyFilterResultCallback::needsCollision(btBroadphaseProxy* proxy) const { + return true; +} + btScalar RigidBodyFilterResultCallback::addSingleResult(btManifoldPoint& cp, const btCollisionObjectWrapper* colObj0, int partId0, int index0, const btCollisionObjectWrapper* colObj1, int partId1, int index1) { const btCollisionObject* otherBody; btVector3 point; @@ -331,10 +335,10 @@ btScalar RigidBodyFilterResultCallback::addSingleResult(btManifoldPoint& cp, con point = cp.m_localPointB; otherPoint = cp.m_localPointA; } - const btRigidBody* collisionCandidate = dynamic_cast(otherBody); - if (!collisionCandidate) { + if (!(otherBody->getInternalType() & btCollisionObject::CO_RIGID_BODY)) { return 0; } + const btRigidBody* collisionCandidate = static_cast(otherBody); const btMotionState* motionStateCandidate = collisionCandidate->getMotionState(); checkOrAddCollidingState(motionStateCandidate, point, otherPoint); @@ -343,7 +347,7 @@ btScalar RigidBodyFilterResultCallback::addSingleResult(btManifoldPoint& cp, con } template -void AllObjectMotionStatesCallback::checkOrAddCollidingState(const btMotionState* otherMotionState, btVector3& point, btVector3& otherPoint) { +void AllObjectMotionStatesCallback::checkOrAddCollidingState(const btMotionState* otherMotionState, btVector3& point, btVector3& otherPoint) { const T* candidate = dynamic_cast(otherMotionState); if (!candidate) { return; diff --git a/interface/src/raypick/CollisionPick.h b/interface/src/raypick/CollisionPick.h index 3208efd5ec..3d2bc2c4c9 100644 --- a/interface/src/raypick/CollisionPick.h +++ b/interface/src/raypick/CollisionPick.h @@ -146,6 +146,8 @@ struct RigidBodyFilterResultCallback : public btCollisionWorld::ContactResultCal btCollisionObject collisionObject; + virtual bool needsCollision(btBroadphaseProxy* proxy) const; + // Check candidacy for adding to a list virtual void checkOrAddCollidingState(const btMotionState* otherMotionState, btVector3& point, btVector3& otherPoint) = 0; From 4ca98990000984d06d8e7008910937a60eabeaa1 Mon Sep 17 00:00:00 2001 From: sabrina-shanman Date: Tue, 24 Jul 2018 11:37:52 -0700 Subject: [PATCH 017/144] Use SHAPE_TYPE_NONE instead of (ShapeType)0 in ShapeInfo.cpp --- libraries/shared/src/ShapeInfo.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/shared/src/ShapeInfo.cpp b/libraries/shared/src/ShapeInfo.cpp index b0caf2d62d..24942c0043 100644 --- a/libraries/shared/src/ShapeInfo.cpp +++ b/libraries/shared/src/ShapeInfo.cpp @@ -68,7 +68,7 @@ const float MIN_HALF_EXTENT = 0.005f; // 0.5 cm QString ShapeInfo::getNameForShapeType(ShapeType type) { if (((int)type <= 0) || ((int)type >= (int)SHAPETYPE_NAME_COUNT)) { - type = (ShapeType)0; + type = SHAPE_TYPE_NONE; } return shapeTypeNames[(int)type]; @@ -82,7 +82,7 @@ ShapeType ShapeInfo::getShapeTypeForName(QString string) { } } - return (ShapeType)0; + return SHAPE_TYPE_NONE; } void ShapeInfo::clear() { From 5ec277e45893514d585397d7869bcb1919ab7304 Mon Sep 17 00:00:00 2001 From: sabrina-shanman Date: Tue, 24 Jul 2018 15:47:49 -0700 Subject: [PATCH 018/144] Move bullet references in collision pick API to PhysicsEngine --- interface/src/Application.cpp | 2 +- interface/src/raypick/CollisionPick.cpp | 73 ++-------------- interface/src/raypick/CollisionPick.h | 83 ++++--------------- .../src/raypick/PickScriptingInterface.cpp | 2 +- .../src/raypick/PickScriptingInterface.h | 8 +- libraries/physics/src/PhysicsEngine.cpp | 72 ++++++++++++++++ libraries/physics/src/PhysicsEngine.h | 23 +++++ 7 files changed, 121 insertions(+), 142 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index ad61e6a4a1..bc2055bd06 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -6656,7 +6656,7 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEnginePointe auto pickScriptingInterface = DependencyManager::get(); pickScriptingInterface->registerMetaTypes(scriptEngine.data()); - pickScriptingInterface->setCollisionWorld(_physicsEngine->getDynamicsWorld()); + pickScriptingInterface->setPhysicsEngine(_physicsEngine); // connect this script engines printedMessage signal to the global ScriptEngines these various messages connect(scriptEngine.data(), &ScriptEngine::printedMessage, diff --git a/interface/src/raypick/CollisionPick.cpp b/interface/src/raypick/CollisionPick.cpp index 7330a64267..8ef2890299 100644 --- a/interface/src/raypick/CollisionPick.cpp +++ b/interface/src/raypick/CollisionPick.cpp @@ -271,12 +271,9 @@ PickResultPointer CollisionPick::getEntityIntersection(const CollisionRegion& pi // Cannot compute result return std::make_shared(); } - - auto entityCollisionCallback = AllObjectMotionStatesCallback(pick.shapeInfo, pick.transform); - btCollisionWorld* collisionWorld = const_cast(_collisionWorld); - collisionWorld->contactTest(&entityCollisionCallback.collisionObject, entityCollisionCallback); - - return std::make_shared(pick, entityCollisionCallback.intersectingObjects, std::vector()); + + const auto& intersectingEntities = _physicsEngine->getCollidingInRegion(MOTIONSTATE_TYPE_ENTITY, pick.shapeInfo, pick.transform); + return std::make_shared(pick, intersectingEntities, std::vector()); } PickResultPointer CollisionPick::getOverlayIntersection(const CollisionRegion& pick) { @@ -289,70 +286,10 @@ PickResultPointer CollisionPick::getAvatarIntersection(const CollisionRegion& pi return std::make_shared(); } - auto avatarCollisionCallback = AllObjectMotionStatesCallback(pick.shapeInfo, pick.transform); - btCollisionWorld* collisionWorld = const_cast(_collisionWorld); - collisionWorld->contactTest(&avatarCollisionCallback.collisionObject, avatarCollisionCallback); - - return std::make_shared(pick, std::vector(), avatarCollisionCallback.intersectingObjects); + const auto& intersectingAvatars = _physicsEngine->getCollidingInRegion(MOTIONSTATE_TYPE_AVATAR, pick.shapeInfo, pick.transform); + return std::make_shared(pick, std::vector(), intersectingAvatars); } PickResultPointer CollisionPick::getHUDIntersection(const CollisionRegion& pick) { return getDefaultResult(QVariantMap()); -} - -RigidBodyFilterResultCallback::RigidBodyFilterResultCallback(const ShapeInfo& shapeInfo, const Transform& transform) : - btCollisionWorld::ContactResultCallback(), collisionObject() { - const btCollisionShape* collisionShape = ObjectMotionState::getShapeManager()->getShape(shapeInfo); - - collisionObject.setCollisionShape(const_cast(collisionShape)); - - btTransform bulletTransform; - bulletTransform.setOrigin(glmToBullet(transform.getTranslation())); - bulletTransform.setRotation(glmToBullet(transform.getRotation())); - - collisionObject.setWorldTransform(bulletTransform); -} - -RigidBodyFilterResultCallback::~RigidBodyFilterResultCallback() { - ObjectMotionState::getShapeManager()->releaseShape(collisionObject.getCollisionShape()); -} - -bool RigidBodyFilterResultCallback::needsCollision(btBroadphaseProxy* proxy) const { - return true; -} - -btScalar RigidBodyFilterResultCallback::addSingleResult(btManifoldPoint& cp, const btCollisionObjectWrapper* colObj0, int partId0, int index0, const btCollisionObjectWrapper* colObj1, int partId1, int index1) { - const btCollisionObject* otherBody; - btVector3 point; - btVector3 otherPoint; - if (colObj0->m_collisionObject == &collisionObject) { - otherBody = colObj1->m_collisionObject; - point = cp.m_localPointA; - otherPoint = cp.m_localPointB; - } - else { - otherBody = colObj0->m_collisionObject; - point = cp.m_localPointB; - otherPoint = cp.m_localPointA; - } - if (!(otherBody->getInternalType() & btCollisionObject::CO_RIGID_BODY)) { - return 0; - } - const btRigidBody* collisionCandidate = static_cast(otherBody); - const btMotionState* motionStateCandidate = collisionCandidate->getMotionState(); - - checkOrAddCollidingState(motionStateCandidate, point, otherPoint); - - return 0; -} - -template -void AllObjectMotionStatesCallback::checkOrAddCollidingState(const btMotionState* otherMotionState, btVector3& point, btVector3& otherPoint) { - const T* candidate = dynamic_cast(otherMotionState); - if (!candidate) { - return; - } - - // This is the correct object type. Add it to the list. - intersectingObjects.emplace_back(candidate->getObjectID(), bulletToGLM(point), bulletToGLM(otherPoint)); } \ No newline at end of file diff --git a/interface/src/raypick/CollisionPick.h b/interface/src/raypick/CollisionPick.h index 3d2bc2c4c9..ef1fdc7b0c 100644 --- a/interface/src/raypick/CollisionPick.h +++ b/interface/src/raypick/CollisionPick.h @@ -8,44 +8,13 @@ #ifndef hifi_CollisionPick_h #define hifi_CollisionPick_h -#include - -#include -#include -#include +#include +#include #include #include class CollisionPickResult : public PickResult { public: - struct EntityIntersection { - EntityIntersection() { } - - EntityIntersection(const EntityIntersection& entityIntersection) : - id(entityIntersection.id), - pickCollisionPoint(entityIntersection.pickCollisionPoint), - entityCollisionPoint(entityIntersection.entityCollisionPoint) { - } - - EntityIntersection(QUuid id, glm::vec3 pickCollisionPoint, glm::vec3 entityCollisionPoint) : - id(id), - pickCollisionPoint(pickCollisionPoint), - entityCollisionPoint(entityCollisionPoint) { - } - - QVariantMap toVariantMap() { - QVariantMap variantMap; - variantMap["objectID"] = id; - variantMap["pickCollisionPoint"] = vec3toVariant(pickCollisionPoint); - variantMap["entityCollisionPoint"] = vec3toVariant(entityCollisionPoint); - return variantMap; - } - - QUuid id; - glm::vec3 pickCollisionPoint; - glm::vec3 entityCollisionPoint; - }; - CollisionPickResult() {} CollisionPickResult(const QVariantMap& pickVariant) : PickResult(pickVariant) {} @@ -73,13 +42,21 @@ public: QVariantList qIntersectingEntities; for (auto intersectingEntity : intersectingEntities) { - qIntersectingEntities.append(intersectingEntity.toVariantMap()); + QVariantMap qIntersectingEntity; + qIntersectingEntity["objectID"] = intersectingEntity.id; + qIntersectingEntity["pickCollisionPoint"] = vec3toVariant(intersectingEntity.pickCollisionPoint); + qIntersectingEntity["entityCollisionPoint"] = vec3toVariant(intersectingEntity.entityCollisionPoint); + qIntersectingEntities.append(qIntersectingEntity); } variantMap["intersectingEntities"] = qIntersectingEntities; QVariantList qIntersectingAvatars; for (auto intersectingAvatar : intersectingAvatars) { - qIntersectingAvatars.append(intersectingAvatar.toVariantMap()); + QVariantMap qIntersectingAvatar; + qIntersectingAvatar["objectID"] = intersectingAvatar.id; + qIntersectingAvatar["pickCollisionPoint"] = vec3toVariant(intersectingAvatar.pickCollisionPoint); + qIntersectingAvatar["entityCollisionPoint"] = vec3toVariant(intersectingAvatar.entityCollisionPoint); + qIntersectingAvatars.append(qIntersectingAvatar); } variantMap["intersectingAvatars"] = qIntersectingAvatars; @@ -111,10 +88,10 @@ public: class CollisionPick : public Pick { public: - CollisionPick(const PickFilter& filter, float maxDistance, bool enabled, CollisionRegion collisionRegion, const btCollisionWorld* collisionWorld) : + CollisionPick(const PickFilter& filter, float maxDistance, bool enabled, CollisionRegion collisionRegion, PhysicsEnginePointer physicsEngine) : Pick(filter, maxDistance, enabled), _mathPick(collisionRegion), - _collisionWorld(collisionWorld) { + _physicsEngine(physicsEngine) { } CollisionRegion getMathematicalPick() const override; @@ -130,38 +107,8 @@ protected: void computeShapeInfo(CollisionRegion& pick, ShapeInfo& shapeInfo, QSharedPointer resource); CollisionRegion _mathPick; - const btCollisionWorld* _collisionWorld; + PhysicsEnginePointer _physicsEngine; QSharedPointer _cachedResource; }; -// Callback for checking the motion states of all colliding rigid bodies for candidacy to be added to a list -struct RigidBodyFilterResultCallback : public btCollisionWorld::ContactResultCallback { - RigidBodyFilterResultCallback(const ShapeInfo& shapeInfo, const Transform& transform); - - ~RigidBodyFilterResultCallback(); - - RigidBodyFilterResultCallback(btCollisionObject& testCollisionObject) : - btCollisionWorld::ContactResultCallback(), collisionObject(testCollisionObject) { - } - - btCollisionObject collisionObject; - - virtual bool needsCollision(btBroadphaseProxy* proxy) const; - - // Check candidacy for adding to a list - virtual void checkOrAddCollidingState(const btMotionState* otherMotionState, btVector3& point, btVector3& otherPoint) = 0; - - btScalar addSingleResult(btManifoldPoint& cp, const btCollisionObjectWrapper* colObj0, int partId0, int index0, const btCollisionObjectWrapper* colObj1, int partId1, int index1) override; -}; - -// Callback for getting colliding ObjectMotionStates in the world, or optionally a more specific type. -template -struct AllObjectMotionStatesCallback : public RigidBodyFilterResultCallback { - AllObjectMotionStatesCallback(const ShapeInfo& shapeInfo, const Transform& transform) : RigidBodyFilterResultCallback(shapeInfo, transform) { } - - std::vector intersectingObjects; - - void checkOrAddCollidingState(const btMotionState* otherMotionState, btVector3& point, btVector3& otherPoint) override; -}; - #endif // hifi_CollisionPick_h \ No newline at end of file diff --git a/interface/src/raypick/PickScriptingInterface.cpp b/interface/src/raypick/PickScriptingInterface.cpp index 526d099cb4..3417eaf431 100644 --- a/interface/src/raypick/PickScriptingInterface.cpp +++ b/interface/src/raypick/PickScriptingInterface.cpp @@ -157,7 +157,7 @@ unsigned int PickScriptingInterface::createCollisionPick(const QVariant& propert CollisionRegion collisionRegion(propMap); - return DependencyManager::get()->addPick(PickQuery::Collision, std::make_shared(filter, maxDistance, enabled, collisionRegion, _collisionWorld)); + return DependencyManager::get()->addPick(PickQuery::Collision, std::make_shared(filter, maxDistance, enabled, collisionRegion, _physicsEngine)); } void PickScriptingInterface::enablePick(unsigned int uid) { diff --git a/interface/src/raypick/PickScriptingInterface.h b/interface/src/raypick/PickScriptingInterface.h index 48bc6e598e..5eb370adf7 100644 --- a/interface/src/raypick/PickScriptingInterface.h +++ b/interface/src/raypick/PickScriptingInterface.h @@ -9,10 +9,10 @@ #define hifi_PickScriptingInterface_h #include -#include #include #include +#include #include /**jsdoc @@ -277,12 +277,12 @@ public slots: static constexpr unsigned int INTERSECTED_HUD() { return IntersectionType::HUD; } // Set to allow CollisionPicks to have access to the physics engine - void setCollisionWorld(const btCollisionWorld* collisionWorld) { - _collisionWorld = collisionWorld; + void setPhysicsEngine(PhysicsEnginePointer physicsEngine) { + _physicsEngine = physicsEngine; } protected: - const btCollisionWorld* _collisionWorld; + PhysicsEnginePointer _physicsEngine; }; #endif // hifi_PickScriptingInterface_h diff --git a/libraries/physics/src/PhysicsEngine.cpp b/libraries/physics/src/PhysicsEngine.cpp index 66a4edb486..e82e201bb5 100644 --- a/libraries/physics/src/PhysicsEngine.cpp +++ b/libraries/physics/src/PhysicsEngine.cpp @@ -840,3 +840,75 @@ void PhysicsEngine::setShowBulletConstraintLimits(bool value) { } } +struct AllContactsCallback : public btCollisionWorld::ContactResultCallback { + AllContactsCallback(MotionStateType desiredObjectType, const ShapeInfo& shapeInfo, const Transform& transform) : + desiredObjectType(desiredObjectType), + btCollisionWorld::ContactResultCallback(), + collisionObject() { + const btCollisionShape* collisionShape = ObjectMotionState::getShapeManager()->getShape(shapeInfo); + + collisionObject.setCollisionShape(const_cast(collisionShape)); + + btTransform bulletTransform; + bulletTransform.setOrigin(glmToBullet(transform.getTranslation())); + bulletTransform.setRotation(glmToBullet(transform.getRotation())); + + collisionObject.setWorldTransform(bulletTransform); + } + + ~AllContactsCallback() { + ObjectMotionState::getShapeManager()->releaseShape(collisionObject.getCollisionShape()); + } + + AllContactsCallback(btCollisionObject& testCollisionObject) : + btCollisionWorld::ContactResultCallback(), collisionObject(testCollisionObject) { + } + + MotionStateType desiredObjectType; + btCollisionObject collisionObject; + std::vector intersectingObjects; + + virtual bool needsCollision(btBroadphaseProxy* proxy) const { + return true; + } + + btScalar addSingleResult(btManifoldPoint& cp, const btCollisionObjectWrapper* colObj0, int partId0, int index0, const btCollisionObjectWrapper* colObj1, int partId1, int index1) override { + const btCollisionObject* otherBody; + btVector3 point; + btVector3 otherPoint; + if (colObj0->m_collisionObject == &collisionObject) { + otherBody = colObj1->m_collisionObject; + point = cp.m_localPointA; + otherPoint = cp.m_localPointB; + } + else { + otherBody = colObj0->m_collisionObject; + point = cp.m_localPointB; + otherPoint = cp.m_localPointA; + } + + if (!(otherBody->getInternalType() & btCollisionObject::CO_RIGID_BODY)) { + return 0; + } + const btRigidBody* collisionCandidate = static_cast(otherBody); + const btMotionState* motionStateCandidate = collisionCandidate->getMotionState(); + + const ObjectMotionState* candidate = dynamic_cast(motionStateCandidate); + if (!candidate || candidate->getType() != desiredObjectType) { + return 0; + } + + // This is the correct object type. Add it to the list. + intersectingObjects.emplace_back(candidate->getObjectID(), bulletToGLM(point), bulletToGLM(otherPoint)); + + return 0; + } +}; + +std::vector PhysicsEngine::getCollidingInRegion(MotionStateType desiredObjectType, const ShapeInfo& regionShapeInfo, const Transform& regionTransform) const { + auto contactCallback = AllContactsCallback(desiredObjectType, regionShapeInfo, regionTransform); + _dynamicsWorld->contactTest(&contactCallback.collisionObject, contactCallback); + + return contactCallback.intersectingObjects; +} + diff --git a/libraries/physics/src/PhysicsEngine.h b/libraries/physics/src/PhysicsEngine.h index 6a491c3791..42005b5a5d 100644 --- a/libraries/physics/src/PhysicsEngine.h +++ b/libraries/physics/src/PhysicsEngine.h @@ -43,6 +43,26 @@ public: void* _b; // ObjectMotionState pointer }; +struct EntityIntersection { + EntityIntersection() { } + + EntityIntersection(const EntityIntersection& entityIntersection) : + id(entityIntersection.id), + pickCollisionPoint(entityIntersection.pickCollisionPoint), + entityCollisionPoint(entityIntersection.entityCollisionPoint) { + } + + EntityIntersection(QUuid id, glm::vec3 selfCollisionPoint, glm::vec3 otherCollisionPoint) : + id(id), + pickCollisionPoint(selfCollisionPoint), + entityCollisionPoint(otherCollisionPoint) { + } + + QUuid id; + glm::vec3 pickCollisionPoint; + glm::vec3 entityCollisionPoint; +}; + using ContactMap = std::map; using CollisionEvents = std::vector; @@ -105,6 +125,9 @@ public: void setShowBulletConstraints(bool value); void setShowBulletConstraintLimits(bool value); + // Function for getting colliding ObjectMotionStates in the world of specified type + std::vector getCollidingInRegion(MotionStateType desiredObjectType, const ShapeInfo& regionShapeInfo, const Transform& regionTransform) const; + private: QList removeDynamicsForBody(btRigidBody* body); void addObjectToDynamicsWorld(ObjectMotionState* motionState); From 083991dc1a4adc8bb9a805d57d7d5dc6ad12ffd0 Mon Sep 17 00:00:00 2001 From: sabrina-shanman Date: Tue, 24 Jul 2018 15:50:18 -0700 Subject: [PATCH 019/144] Fix CollisionPick not detecting when collision points for collision models are loaded --- interface/src/raypick/CollisionPick.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/interface/src/raypick/CollisionPick.cpp b/interface/src/raypick/CollisionPick.cpp index 8ef2890299..ac3342110b 100644 --- a/interface/src/raypick/CollisionPick.cpp +++ b/interface/src/raypick/CollisionPick.cpp @@ -23,6 +23,7 @@ bool CollisionPick::isShapeInfoReady(CollisionRegion& pick) { if (_cachedResource->isLoaded()) { computeShapeInfo(pick, pick.shapeInfo, _cachedResource); + return true; } return false; From 8f993e4740bf15952d464a326bfe8a83e67029f5 Mon Sep 17 00:00:00 2001 From: sabrina-shanman Date: Wed, 25 Jul 2018 11:31:27 -0700 Subject: [PATCH 020/144] Change meaning/naming of collision points in CollisionPick API to better match spec and be more clear --- interface/src/raypick/CollisionPick.h | 28 ++++++++++++------------- libraries/physics/src/PhysicsEngine.cpp | 19 ++++++++++------- libraries/physics/src/PhysicsEngine.h | 16 +++++++------- 3 files changed, 35 insertions(+), 28 deletions(-) diff --git a/interface/src/raypick/CollisionPick.h b/interface/src/raypick/CollisionPick.h index ef1fdc7b0c..d39e3be914 100644 --- a/interface/src/raypick/CollisionPick.h +++ b/interface/src/raypick/CollisionPick.h @@ -40,25 +40,25 @@ public: variantMap["intersects"] = intersects; - QVariantList qIntersectingEntities; + QVariantList qEntityIntersections; for (auto intersectingEntity : intersectingEntities) { - QVariantMap qIntersectingEntity; - qIntersectingEntity["objectID"] = intersectingEntity.id; - qIntersectingEntity["pickCollisionPoint"] = vec3toVariant(intersectingEntity.pickCollisionPoint); - qIntersectingEntity["entityCollisionPoint"] = vec3toVariant(intersectingEntity.entityCollisionPoint); - qIntersectingEntities.append(qIntersectingEntity); + QVariantMap qEntityIntersection; + qEntityIntersection["objectID"] = intersectingEntity.id; + qEntityIntersection["pickCollisionPoint"] = vec3toVariant(intersectingEntity.testCollisionPoint); + qEntityIntersection["entityCollisionPoint"] = vec3toVariant(intersectingEntity.foundCollisionPoint); + qEntityIntersections.append(qEntityIntersection); } - variantMap["intersectingEntities"] = qIntersectingEntities; + variantMap["entityIntersections"] = qEntityIntersections; - QVariantList qIntersectingAvatars; + QVariantList qAvatarIntersections; for (auto intersectingAvatar : intersectingAvatars) { - QVariantMap qIntersectingAvatar; - qIntersectingAvatar["objectID"] = intersectingAvatar.id; - qIntersectingAvatar["pickCollisionPoint"] = vec3toVariant(intersectingAvatar.pickCollisionPoint); - qIntersectingAvatar["entityCollisionPoint"] = vec3toVariant(intersectingAvatar.entityCollisionPoint); - qIntersectingAvatars.append(qIntersectingAvatar); + QVariantMap qAvatarIntersection; + qAvatarIntersection["objectID"] = intersectingAvatar.id; + qAvatarIntersection["pickCollisionPoint"] = vec3toVariant(intersectingAvatar.testCollisionPoint); + qAvatarIntersection["entityCollisionPoint"] = vec3toVariant(intersectingAvatar.foundCollisionPoint); + qAvatarIntersections.append(qAvatarIntersection); } - variantMap["intersectingAvatars"] = qIntersectingAvatars; + variantMap["avatarIntersections"] = qAvatarIntersections; variantMap["collisionRegion"] = pickVariant; diff --git a/libraries/physics/src/PhysicsEngine.cpp b/libraries/physics/src/PhysicsEngine.cpp index e82e201bb5..592272acd5 100644 --- a/libraries/physics/src/PhysicsEngine.cpp +++ b/libraries/physics/src/PhysicsEngine.cpp @@ -874,17 +874,17 @@ struct AllContactsCallback : public btCollisionWorld::ContactResultCallback { btScalar addSingleResult(btManifoldPoint& cp, const btCollisionObjectWrapper* colObj0, int partId0, int index0, const btCollisionObjectWrapper* colObj1, int partId1, int index1) override { const btCollisionObject* otherBody; - btVector3 point; - btVector3 otherPoint; + btVector3 penetrationPoint; + btVector3 otherPenetrationPoint; if (colObj0->m_collisionObject == &collisionObject) { otherBody = colObj1->m_collisionObject; - point = cp.m_localPointA; - otherPoint = cp.m_localPointB; + penetrationPoint = getWorldPoint(cp.m_localPointB, colObj1->getWorldTransform()); + otherPenetrationPoint = getWorldPoint(cp.m_localPointA, colObj0->getWorldTransform()); } else { otherBody = colObj0->m_collisionObject; - point = cp.m_localPointB; - otherPoint = cp.m_localPointA; + penetrationPoint = getWorldPoint(cp.m_localPointA, colObj0->getWorldTransform()); + otherPenetrationPoint = getWorldPoint(cp.m_localPointB, colObj1->getWorldTransform()); } if (!(otherBody->getInternalType() & btCollisionObject::CO_RIGID_BODY)) { @@ -899,10 +899,15 @@ struct AllContactsCallback : public btCollisionWorld::ContactResultCallback { } // This is the correct object type. Add it to the list. - intersectingObjects.emplace_back(candidate->getObjectID(), bulletToGLM(point), bulletToGLM(otherPoint)); + intersectingObjects.emplace_back(candidate->getObjectID(), bulletToGLM(penetrationPoint), bulletToGLM(otherPenetrationPoint)); return 0; } + +protected: + static btVector3 getWorldPoint(const btVector3& localPoint, const btTransform& transform) { + return quatRotate(transform.getRotation(), localPoint) + transform.getOrigin(); + } }; std::vector PhysicsEngine::getCollidingInRegion(MotionStateType desiredObjectType, const ShapeInfo& regionShapeInfo, const Transform& regionTransform) const { diff --git a/libraries/physics/src/PhysicsEngine.h b/libraries/physics/src/PhysicsEngine.h index 42005b5a5d..c6dfba518f 100644 --- a/libraries/physics/src/PhysicsEngine.h +++ b/libraries/physics/src/PhysicsEngine.h @@ -48,19 +48,21 @@ struct EntityIntersection { EntityIntersection(const EntityIntersection& entityIntersection) : id(entityIntersection.id), - pickCollisionPoint(entityIntersection.pickCollisionPoint), - entityCollisionPoint(entityIntersection.entityCollisionPoint) { + testCollisionPoint(entityIntersection.testCollisionPoint), + foundCollisionPoint(entityIntersection.foundCollisionPoint) { } - EntityIntersection(QUuid id, glm::vec3 selfCollisionPoint, glm::vec3 otherCollisionPoint) : + EntityIntersection(QUuid id, glm::vec3 testCollisionPoint, glm::vec3 otherCollisionPoint) : id(id), - pickCollisionPoint(selfCollisionPoint), - entityCollisionPoint(otherCollisionPoint) { + testCollisionPoint(testCollisionPoint), + foundCollisionPoint(otherCollisionPoint) { } QUuid id; - glm::vec3 pickCollisionPoint; - glm::vec3 entityCollisionPoint; + // The deepest point of an intersection within the volume of the test shape, in world space. + glm::vec3 testCollisionPoint; + // The deepest point of an intersection within the volume of the found object, in world space. + glm::vec3 foundCollisionPoint; }; using ContactMap = std::map; From de6d30e160cac96be54915e30e707552fc075b6d Mon Sep 17 00:00:00 2001 From: sabrina-shanman Date: Wed, 25 Jul 2018 16:49:41 -0700 Subject: [PATCH 021/144] Add JS docs for CollisionPick API --- .../src/raypick/PickScriptingInterface.cpp | 17 ++++++++++++++ .../src/raypick/PickScriptingInterface.h | 23 +++++++++++++++++-- 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/interface/src/raypick/PickScriptingInterface.cpp b/interface/src/raypick/PickScriptingInterface.cpp index 3417eaf431..384a193f25 100644 --- a/interface/src/raypick/PickScriptingInterface.cpp +++ b/interface/src/raypick/PickScriptingInterface.cpp @@ -137,6 +137,23 @@ unsigned int PickScriptingInterface::createStylusPick(const QVariant& properties return DependencyManager::get()->addPick(PickQuery::Stylus, std::make_shared(side, filter, maxDistance, enabled)); } +/**jsdoc +* A Shape defines a physical volume. +* +* @typedef {object} Shape +* @property {ShapeType} shapeType The type of shape to use. +* @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. +* @property {Vec3} dimensions - The size to scale the shape to. +*/ + +/**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(); diff --git a/interface/src/raypick/PickScriptingInterface.h b/interface/src/raypick/PickScriptingInterface.h index 5eb370adf7..4c857585be 100644 --- a/interface/src/raypick/PickScriptingInterface.h +++ b/interface/src/raypick/PickScriptingInterface.h @@ -73,7 +73,7 @@ public: * with PickType.Ray, depending on which optional parameters you pass, you could create a Static Ray Pick, a Mouse Ray Pick, or a Joint Ray Pick. * @function Picks.createPick * @param {PickType} type A PickType that specifies the method of picking to use - * @param {Picks.RayPickProperties|Picks.StylusPickProperties} properties A PickProperties object, containing all the properties for initializing this Pick + * @param {Picks.RayPickProperties|Picks.StylusPickProperties|Picks.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); @@ -127,11 +127,30 @@ public: * @property {StylusTip} stylusTip The StylusTip 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 (entityIntersections.length + avatarIntersections.length > 0) + * @property {EntityItersection[]} entityIntersections The collision information of entities which intersect with the CollisionRegion. There may be multiple intersections with the same entity which represent distinct collision points. + * @property {EntityItersection[]} avatarIntersections The collision information of avatars which intersect with the CollisionRegion. There may be multiple intersections with the same entity which represent distinct collision points. + * @property {CollisionRegion} collisionRegion The CollisionRegion that was used. Valid even if there was no intersection. + */ + + /**jsdoc + * A pair of intersection points between a CollisionPick and an entity/avatar. + * + * @typedef {object} EntityIntersection + * @property {QUuid} id The ID of the object. + * @property {Vec3} pickCollisionPoint A point within the volume of the CollisionPick which corresponds to a point on the surface of the collided entity, in world space. + * @property {Vec3} entityCollisionPoint A point within the volume of the collided entity which corresponds to a point on the surface of the CollisionPick, 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|CollisionPickResult} The most recent intersection result. This will be different for different PickTypes. */ Q_INVOKABLE QVariantMap getPrevPickResult(unsigned int uid); From d38f0b0fde6e2c45cb4707eb44af7cae870baded Mon Sep 17 00:00:00 2001 From: Alexander Ivash Date: Fri, 27 Jul 2018 12:26:04 +0300 Subject: [PATCH 022/144] use updated 'Last Legends: Male' from Mukul --- .../avatar/bookmarks/avatarbookmarks.json | 514 ++++++++++-------- 1 file changed, 299 insertions(+), 215 deletions(-) diff --git a/interface/resources/avatar/bookmarks/avatarbookmarks.json b/interface/resources/avatar/bookmarks/avatarbookmarks.json index 2ef59d53a3..9976036f8e 100644 --- a/interface/resources/avatar/bookmarks/avatarbookmarks.json +++ b/interface/resources/avatar/bookmarks/avatarbookmarks.json @@ -380,15 +380,21 @@ { "properties": { "acceleration": { + "blue": 0, + "green": 0, + "red": 0, "x": 0, "y": 0, "z": 0 }, "actionData": "", - "age": 14.011327743530273, - "ageAsText": "0 hours 0 minutes 14 seconds", + "age": 321.8835144042969, + "ageAsText": "0 hours 5 minutes 21 seconds", "angularDamping": 0.39346998929977417, "angularVelocity": { + "blue": 0, + "green": 0, + "red": 0, "x": 0, "y": 0, "z": 0 @@ -406,24 +412,36 @@ }, "boundingBox": { "brn": { - "x": -0.20154684782028198, - "y": 0.03644842654466629, - "z": -0.2641940414905548 + "blue": -0.03950843587517738, + "green": 0.20785385370254517, + "red": -0.04381325840950012, + "x": -0.04381325840950012, + "y": 0.20785385370254517, + "z": -0.03950843587517738 }, "center": { - "x": -0.030000001192092896, - "y": 0.12999820709228516, - "z": -0.07000023126602173 + "blue": 0, + "green": 0.23000000417232513, + "red": 0, + "x": 0, + "y": 0.23000000417232513, + "z": 0 }, "dimensions": { - "x": 0.3430936932563782, - "y": 0.18709957599639893, - "z": 0.38838762044906616 + "blue": 0.07901687175035477, + "green": 0.044292300939559937, + "red": 0.08762651681900024, + "x": 0.08762651681900024, + "y": 0.044292300939559937, + "z": 0.07901687175035477 }, "tfl": { - "x": 0.1415468454360962, - "y": 0.22354799509048462, - "z": 0.12419357895851135 + "blue": 0.03950843587517738, + "green": 0.2521461546421051, + "red": 0.04381325840950012, + "x": 0.04381325840950012, + "y": 0.2521461546421051, + "z": 0.03950843587517738 } }, "canCastShadow": true, @@ -441,189 +459,14 @@ "collisionless": false, "collisionsWillMove": false, "compoundShapeURL": "", - "created": "2018-06-06T17:25:42Z", - "damping": 0.39346998929977417, - "density": 1000, - "description": "", - "dimensions": { - "x": 0.33466479182243347, - "y": 0.16981728374958038, - "z": 0.38838762044906616 - }, - "dynamic": false, - "editionNumber": 19, - "entityInstanceNumber": 0, - "friction": 0.5, - "gravity": { - "x": 0, - "y": 0, - "z": 0 - }, - "href": "", - "id": "{6b0a2b08-e8e3-4d43-95cc-dfc4f7a4b0c9}", - "ignoreForCollisions": false, - "itemArtist": "jyoum", - "itemCategories": "Wearables", - "itemDescription": "A stylish and classic piece of headwear for your avatar.", - "itemLicense": "", - "itemName": "Fedora", - "jointRotations": [ - ], - "jointRotationsSet": [ - ], - "jointTranslations": [ - ], - "jointTranslationsSet": [ - ], - "lastEdited": 1528306032827319, - "lastEditedBy": "{4c770def-4abb-40c6-91a1-88da5247b2db}", - "lifetime": -1, - "limitedRun": 4294967295, - "localPosition": { - "x": -0.030000008642673492, - "y": 0.12999820709228516, - "z": -0.07000023126602173 - }, - "localRotation": { - "w": 0.9996573328971863, - "x": 0, - "y": 0, - "z": 0.026176949962973595 - }, - "locked": false, - "marketplaceID": "11c4208d-15d7-4449-9758-a08da6dbd3dc", - "modelURL": "http://mpassets.highfidelity.com/11c4208d-15d7-4449-9758-a08da6dbd3dc-v1/Fedora.fbx", - "name": "", - "naturalDimensions": { - "x": 0.2765824794769287, - "y": 0.14034485816955566, - "z": 0.320981502532959 - }, - "naturalPosition": { - "x": 0.000143393874168396, - "y": 1.7460365295410156, - "z": 0.022502630949020386 - }, - "originalTextures": "{\n \"file5\": \"http://mpassets.highfidelity.com/11c4208d-15d7-4449-9758-a08da6dbd3dc-v1/Fedora.fbx/Texture/Fedora_Hat1_Base_Color.png\",\n \"file7\": \"http://mpassets.highfidelity.com/11c4208d-15d7-4449-9758-a08da6dbd3dc-v1/Fedora.fbx/Texture/Fedora_Hat1_Roughness.png\"\n}\n", - "owningAvatarID": "{4c770def-4abb-40c6-91a1-88da5247b2db}", - "parentID": "{4c770def-4abb-40c6-91a1-88da5247b2db}", - "parentJointIndex": 64, - "position": { - "x": -0.030000008642673492, - "y": 0.12999820709228516, - "z": -0.07000023126602173 - }, - "queryAACube": { - "scale": 1.6202316284179688, - "x": -0.5601736903190613, - "y": -10.668098449707031, - "z": -0.8933582305908203 - }, - "registrationPoint": { - "x": 0.5, - "y": 0.5, - "z": 0.5 - }, - "relayParentJoints": false, - "renderInfo": { - "drawCalls": 1, - "hasTransparent": false, - "texturesCount": 2, - "texturesSize": 327680, - "verticesCount": 719 - }, - "restitution": 0.5, - "rotation": { - "w": 0.9996573328971863, - "x": 0, - "y": 0, - "z": 0.026176949962973595 - }, - "script": "", - "scriptTimestamp": 0, - "serverScripts": "", - "shapeType": "box", - "staticCertificateVersion": 0, - "textures": "", - "type": "Model", - "userData": "{\"Attachment\":{\"action\":\"attach\",\"joint\":\"HeadTop_End\",\"attached\":false,\"options\":{\"translation\":{\"x\":0,\"y\":0,\"z\":0},\"scale\":1}},\"grabbableKey\":{\"cloneable\":false,\"grabbable\":true}}", - "velocity": { - "x": 0, - "y": 0, - "z": 0 - }, - "visible": true - } - }, - { - "properties": { - "acceleration": { - "x": 0, - "y": 0, - "z": 0 - }, - "actionData": "", - "age": 14.011027336120605, - "ageAsText": "0 hours 0 minutes 14 seconds", - "angularDamping": 0.39346998929977417, - "angularVelocity": { - "x": 0, - "y": 0, - "z": 0 - }, - "animation": { - "allowTranslation": true, - "currentFrame": 0, - "firstFrame": 0, - "fps": 30, - "hold": false, - "lastFrame": 100000, - "loop": true, - "running": false, - "url": "" - }, - "boundingBox": { - "brn": { - "x": -0.04381517320871353, - "y": 0.20789726078510284, - "z": -0.0394962802529335 - }, - "center": { - "x": -1.9073486328125e-06, - "y": 0.2300434112548828, - "z": 1.2159347534179688e-05 - }, - "dimensions": { - "x": 0.08762653172016144, - "y": 0.04429228603839874, - "z": 0.07901687920093536 - }, - "tfl": { - "x": 0.043811358511447906, - "y": 0.2521895468235016, - "z": 0.03952059894800186 - } - }, - "canCastShadow": true, - "certificateID": "", - "clientOnly": true, - "cloneAvatarEntity": false, - "cloneDynamic": false, - "cloneLifetime": 300, - "cloneLimit": 0, - "cloneOriginID": "{00000000-0000-0000-0000-000000000000}", - "cloneable": false, - "collidesWith": "", - "collisionMask": 0, - "collisionSoundURL": "", - "collisionless": false, - "collisionsWillMove": false, - "compoundShapeURL": "", - "created": "2018-06-06T17:25:42Z", + "created": "2018-07-26T23:56:46Z", "damping": 0.39346998929977417, "density": 1000, "description": "", "dimensions": { + "blue": 0.07229919731616974, + "green": 0.06644226610660553, + "red": 0.03022606298327446, "x": 0.03022606298327446, "y": 0.06644226610660553, "z": 0.07229919731616974 @@ -633,12 +476,15 @@ "entityInstanceNumber": 0, "friction": 0.5, "gravity": { + "blue": 0, + "green": 0, + "red": 0, "x": 0, "y": 0, "z": 0 }, "href": "", - "id": "{d018c6ea-b2f4-441e-85e1-d17373ae6f34}", + "id": "{03053239-bb37-4c51-a013-a1772baaeed5}", "ignoreForCollisions": false, "itemArtist": "jyoum", "itemCategories": "Wearables", @@ -653,51 +499,66 @@ ], "jointTranslationsSet": [ ], - "lastEdited": 1528306032505220, - "lastEditedBy": "{b46f9c9e-4cd3-4964-96d6-cf3954abb908}", + "lastEdited": 1532649569894305, + "lastEditedBy": "{042ac463-7879-40f0-8126-e2e56c4345ca}", "lifetime": -1, "limitedRun": 4294967295, "localPosition": { - "x": -1.9073486328125e-06, - "y": 0.2300434112548828, - "z": 1.2159347534179688e-05 + "blue": 0, + "green": 0.23000000417232513, + "red": 0, + "x": 0, + "y": 0.23000000417232513, + "z": 0 }, "localRotation": { - "w": 0.5910987257957458, - "x": -0.48726412653923035, - "y": -0.4088631868362427, - "z": 0.49599069356918335 + "w": 0.5910986065864563, + "x": -0.48726415634155273, + "y": -0.4088630974292755, + "z": 0.49599072337150574 }, "locked": false, "marketplaceID": "0685794d-fddb-4bad-a608-6d7789ceda90", "modelURL": "http://mpassets.highfidelity.com/0685794d-fddb-4bad-a608-6d7789ceda90-v1/ScifiWatch.fbx", "name": "Scifi Watch by Jimi", "naturalDimensions": { + "blue": 0.055614765733480453, + "green": 0.0511094331741333, + "red": 0.023250818252563477, "x": 0.023250818252563477, "y": 0.0511094331741333, "z": 0.055614765733480453 }, "naturalPosition": { + "blue": -0.06031447649002075, + "green": 1.4500460624694824, + "red": 0.6493338942527771, "x": 0.6493338942527771, "y": 1.4500460624694824, "z": -0.06031447649002075 }, "originalTextures": "{\n \"file4\": \"http://mpassets.highfidelity.com/0685794d-fddb-4bad-a608-6d7789ceda90-v1/ScifiWatch.fbx/ScifiWatch/texture/lambert1_Base_Color.png\",\n \"file5\": \"http://mpassets.highfidelity.com/0685794d-fddb-4bad-a608-6d7789ceda90-v1/ScifiWatch.fbx/ScifiWatch/texture/lambert1_Normal_OpenGL.png\",\n \"file6\": \"http://mpassets.highfidelity.com/0685794d-fddb-4bad-a608-6d7789ceda90-v1/ScifiWatch.fbx/ScifiWatch/texture/lambert1_Metallic.png\",\n \"file7\": \"http://mpassets.highfidelity.com/0685794d-fddb-4bad-a608-6d7789ceda90-v1/ScifiWatch.fbx/ScifiWatch/texture/lambert1_Roughness.png\",\n \"file8\": \"http://mpassets.highfidelity.com/0685794d-fddb-4bad-a608-6d7789ceda90-v1/ScifiWatch.fbx/ScifiWatch/texture/lambert1_Emissive.png\"\n}\n", - "owningAvatarID": "{4c770def-4abb-40c6-91a1-88da5247b2db}", - "parentID": "{4c770def-4abb-40c6-91a1-88da5247b2db}", + "owningAvatarID": "{042ac463-7879-40f0-8126-e2e56c4345ca}", + "parentID": "{042ac463-7879-40f0-8126-e2e56c4345ca}", "parentJointIndex": 16, "position": { - "x": -1.9073486328125e-06, - "y": 0.2300434112548828, - "z": 1.2159347534179688e-05 + "blue": 0, + "green": 0.23000000417232513, + "red": 0, + "x": 0, + "y": 0.23000000417232513, + "z": 0 }, "queryAACube": { "scale": 0.3082179129123688, - "x": -0.19203892350196838, - "y": -10.429610252380371, - "z": -0.4076632857322693 + "x": 495.7716979980469, + "y": 498.345703125, + "z": 498.52044677734375 }, "registrationPoint": { + "blue": 0.5, + "green": 0.5, + "red": 0.5, "x": 0.5, "y": 0.5, "z": 0.5 @@ -712,10 +573,10 @@ }, "restitution": 0.5, "rotation": { - "w": 0.5910987257957458, - "x": -0.48726412653923035, - "y": -0.4088631868362427, - "z": 0.49599069356918335 + "w": 0.5910986065864563, + "x": -0.48726415634155273, + "y": -0.4088630974292755, + "z": 0.49599072337150574 }, "script": "", "scriptTimestamp": 0, @@ -726,6 +587,229 @@ "type": "Model", "userData": "{\"Attachment\":{\"action\":\"attach\",\"joint\":\"[LR]ForeArm\",\"attached\":false,\"options\":{\"translation\":{\"x\":0,\"y\":0,\"z\":0},\"scale\":1}},\"grabbableKey\":{\"cloneable\":false,\"grabbable\":true}}", "velocity": { + "blue": 0, + "green": 0, + "red": 0, + "x": 0, + "y": 0, + "z": 0 + }, + "visible": true + } + }, + { + "properties": { + "acceleration": { + "blue": 0, + "green": 0, + "red": 0, + "x": 0, + "y": 0, + "z": 0 + }, + "actionData": "", + "age": 308.8044128417969, + "ageAsText": "0 hours 5 minutes 8 seconds", + "angularDamping": 0.39346998929977417, + "angularVelocity": { + "blue": 0, + "green": 0, + "red": 0, + "x": 0, + "y": 0, + "z": 0 + }, + "animation": { + "allowTranslation": true, + "currentFrame": 0, + "firstFrame": 0, + "fps": 30, + "hold": false, + "lastFrame": 100000, + "loop": true, + "running": false, + "url": "" + }, + "boundingBox": { + "brn": { + "blue": -0.2340194433927536, + "green": -0.07067721337080002, + "red": -0.17002610862255096, + "x": -0.17002610862255096, + "y": -0.07067721337080002, + "z": -0.2340194433927536 + }, + "center": { + "blue": -0.039825439453125, + "green": 0.02001953125, + "red": 0.0001678466796875, + "x": 0.0001678466796875, + "y": 0.02001953125, + "z": -0.039825439453125 + }, + "dimensions": { + "blue": 0.3883880078792572, + "green": 0.18139348924160004, + "red": 0.34038791060447693, + "x": 0.34038791060447693, + "y": 0.18139348924160004, + "z": 0.3883880078792572 + }, + "tfl": { + "blue": 0.1543685644865036, + "green": 0.11071627587080002, + "red": 0.17036180198192596, + "x": 0.17036180198192596, + "y": 0.11071627587080002, + "z": 0.1543685644865036 + } + }, + "canCastShadow": true, + "certificateID": "", + "clientOnly": true, + "cloneAvatarEntity": false, + "cloneDynamic": false, + "cloneLifetime": 300, + "cloneLimit": 0, + "cloneOriginID": "{00000000-0000-0000-0000-000000000000}", + "cloneable": false, + "collidesWith": "", + "collisionMask": 0, + "collisionSoundURL": "", + "collisionless": false, + "collisionsWillMove": false, + "compoundShapeURL": "", + "created": "2018-07-26T23:56:46Z", + "damping": 0.39346998929977417, + "density": 1000, + "description": "", + "dimensions": { + "blue": 0.38838762044906616, + "green": 0.16981728374958038, + "red": 0.33466479182243347, + "x": 0.33466479182243347, + "y": 0.16981728374958038, + "z": 0.38838762044906616 + }, + "dynamic": false, + "editionNumber": 18, + "entityInstanceNumber": 0, + "friction": 0.5, + "gravity": { + "blue": 0, + "green": 0, + "red": 0, + "x": 0, + "y": 0, + "z": 0 + }, + "href": "", + "id": "{1bf231ce-3913-4c53-be3c-b1f4094dac51}", + "ignoreForCollisions": false, + "itemArtist": "jyoum", + "itemCategories": "Wearables", + "itemDescription": "A stylish and classic piece of headwear for your avatar.", + "itemLicense": "", + "itemName": "Fedora", + "jointRotations": [ + ], + "jointRotationsSet": [ + ], + "jointTranslations": [ + ], + "jointTranslationsSet": [ + ], + "lastEdited": 1532649698129709, + "lastEditedBy": "{042ac463-7879-40f0-8126-e2e56c4345ca}", + "lifetime": -1, + "limitedRun": 4294967295, + "localPosition": { + "blue": -0.039825439453125, + "green": 0.02001953125, + "red": 0.0001678466796875, + "x": 0.0001678466796875, + "y": 0.02001953125, + "z": -0.039825439453125 + }, + "localRotation": { + "w": 0.9998477101325989, + "x": -9.898545982878204e-09, + "y": 5.670873406415922e-07, + "z": 0.017452405765652657 + }, + "locked": false, + "marketplaceID": "11c4208d-15d7-4449-9758-a08da6dbd3dc", + "modelURL": "http://mpassets.highfidelity.com/11c4208d-15d7-4449-9758-a08da6dbd3dc-v1/Fedora.fbx", + "name": "", + "naturalDimensions": { + "blue": 0.320981502532959, + "green": 0.14034485816955566, + "red": 0.2765824794769287, + "x": 0.2765824794769287, + "y": 0.14034485816955566, + "z": 0.320981502532959 + }, + "naturalPosition": { + "blue": 0.022502630949020386, + "green": 1.7460365295410156, + "red": 0.000143393874168396, + "x": 0.000143393874168396, + "y": 1.7460365295410156, + "z": 0.022502630949020386 + }, + "originalTextures": "{\n \"file5\": \"http://mpassets.highfidelity.com/11c4208d-15d7-4449-9758-a08da6dbd3dc-v1/Fedora.fbx/Texture/Fedora_Hat1_Base_Color.png\",\n \"file7\": \"http://mpassets.highfidelity.com/11c4208d-15d7-4449-9758-a08da6dbd3dc-v1/Fedora.fbx/Texture/Fedora_Hat1_Roughness.png\"\n}\n", + "owningAvatarID": "{042ac463-7879-40f0-8126-e2e56c4345ca}", + "parentID": "{042ac463-7879-40f0-8126-e2e56c4345ca}", + "parentJointIndex": 66, + "position": { + "blue": -0.039825439453125, + "green": 0.02001953125, + "red": 0.0001678466796875, + "x": 0.0001678466796875, + "y": 0.02001953125, + "z": -0.039825439453125 + }, + "queryAACube": { + "scale": 1.6202316284179688, + "x": 495.21051025390625, + "y": 498.5577697753906, + "z": 497.6370849609375 + }, + "registrationPoint": { + "blue": 0.5, + "green": 0.5, + "red": 0.5, + "x": 0.5, + "y": 0.5, + "z": 0.5 + }, + "relayParentJoints": false, + "renderInfo": { + "drawCalls": 1, + "hasTransparent": false, + "texturesCount": 2, + "texturesSize": 327680, + "verticesCount": 719 + }, + "restitution": 0.5, + "rotation": { + "w": 0.9998477101325989, + "x": -9.898545982878204e-09, + "y": 5.670873406415922e-07, + "z": 0.017452405765652657 + }, + "script": "", + "scriptTimestamp": 0, + "serverScripts": "", + "shapeType": "box", + "staticCertificateVersion": 0, + "textures": "", + "type": "Model", + "userData": "{\"Attachment\":{\"action\":\"attach\",\"joint\":\"HeadTop_End\",\"attached\":false,\"options\":{\"translation\":{\"x\":0,\"y\":0,\"z\":0},\"scale\":1}},\"grabbableKey\":{\"cloneable\":false,\"grabbable\":true}}", + "velocity": { + "blue": 0, + "green": 0, + "red": 0, "x": 0, "y": 0, "z": 0 From 6be5ac8dee72fb40a48f504a0479902dbdcc3238 Mon Sep 17 00:00:00 2001 From: sabrina-shanman Date: Mon, 30 Jul 2018 13:58:14 -0700 Subject: [PATCH 023/144] Add missing equality operator to CollisionRegion to fix false cache collisions leading to false negatives --- libraries/shared/src/RegisteredMetaTypes.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/libraries/shared/src/RegisteredMetaTypes.h b/libraries/shared/src/RegisteredMetaTypes.h index efafd35a06..69fa5b2b5d 100644 --- a/libraries/shared/src/RegisteredMetaTypes.h +++ b/libraries/shared/src/RegisteredMetaTypes.h @@ -278,6 +278,14 @@ public: shapeInfo.getType() == SHAPE_TYPE_NONE); } + bool operator==(const CollisionRegion& other) const { + return glm::all(glm::equal(transform.getTranslation(), other.transform.getTranslation())) && + glm::all(glm::equal(transform.getRotation(), other.transform.getRotation())) && + glm::all(glm::equal(transform.getScale(), other.transform.getScale())) && + shapeInfo.getType() == other.shapeInfo.getType() && + modelURL == other.modelURL; + } + bool shouldComputeShapeInfo() const { if (!(shapeInfo.getType() == SHAPE_TYPE_HULL || (shapeInfo.getType() >= SHAPE_TYPE_COMPOUND && From 41a8deb57538c71fd477d0bf5dd642c4422632c3 Mon Sep 17 00:00:00 2001 From: sabrina-shanman Date: Mon, 30 Jul 2018 14:51:04 -0700 Subject: [PATCH 024/144] Fix Linux build warnings --- interface/src/raypick/CollisionPick.cpp | 2 -- interface/src/raypick/CollisionPick.h | 2 +- libraries/physics/src/PhysicsEngine.cpp | 11 ++++------- 3 files changed, 5 insertions(+), 10 deletions(-) diff --git a/interface/src/raypick/CollisionPick.cpp b/interface/src/raypick/CollisionPick.cpp index ac3342110b..49c08a8f5c 100644 --- a/interface/src/raypick/CollisionPick.cpp +++ b/interface/src/raypick/CollisionPick.cpp @@ -135,7 +135,6 @@ void CollisionPick::computeShapeInfo(CollisionRegion& pick, ShapeInfo& shapeInfo const FBXGeometry& fbxGeometry = resource->getFBXGeometry(); int numFbxMeshes = fbxGeometry.meshes.size(); int totalNumVertices = 0; - glm::mat4 invRegistrationOffset = glm::translate(dimensions * (-ENTITY_ITEM_DEFAULT_REGISTRATION_POINT)); for (int i = 0; i < numFbxMeshes; i++) { const FBXMesh& mesh = fbxGeometry.meshes.at(i); totalNumVertices += mesh.vertices.size(); @@ -191,7 +190,6 @@ void CollisionPick::computeShapeInfo(CollisionRegion& pick, ShapeInfo& shapeInfo points.reserve(sizeToReserve); // copy points - uint32_t meshIndexOffset = (uint32_t)points.size(); const glm::vec3* vertexItr = vertices.cbegin(); while (vertexItr != vertices.cend()) { glm::vec3 point = *vertexItr; diff --git a/interface/src/raypick/CollisionPick.h b/interface/src/raypick/CollisionPick.h index d39e3be914..9a76d047bf 100644 --- a/interface/src/raypick/CollisionPick.h +++ b/interface/src/raypick/CollisionPick.h @@ -95,7 +95,7 @@ public: } CollisionRegion getMathematicalPick() const override; - PickResultPointer getDefaultResult(const QVariantMap& pickVariant) const { return std::make_shared(pickVariant); } + PickResultPointer getDefaultResult(const QVariantMap& pickVariant) const override { return std::make_shared(pickVariant); } PickResultPointer getEntityIntersection(const CollisionRegion& pick) override; PickResultPointer getOverlayIntersection(const CollisionRegion& pick) override; PickResultPointer getAvatarIntersection(const CollisionRegion& pick) override; diff --git a/libraries/physics/src/PhysicsEngine.cpp b/libraries/physics/src/PhysicsEngine.cpp index 592272acd5..20f09f6adf 100644 --- a/libraries/physics/src/PhysicsEngine.cpp +++ b/libraries/physics/src/PhysicsEngine.cpp @@ -843,8 +843,9 @@ void PhysicsEngine::setShowBulletConstraintLimits(bool value) { struct AllContactsCallback : public btCollisionWorld::ContactResultCallback { AllContactsCallback(MotionStateType desiredObjectType, const ShapeInfo& shapeInfo, const Transform& transform) : desiredObjectType(desiredObjectType), - btCollisionWorld::ContactResultCallback(), - collisionObject() { + collisionObject(), + intersectingObjects(), + btCollisionWorld::ContactResultCallback() { const btCollisionShape* collisionShape = ObjectMotionState::getShapeManager()->getShape(shapeInfo); collisionObject.setCollisionShape(const_cast(collisionShape)); @@ -860,15 +861,11 @@ struct AllContactsCallback : public btCollisionWorld::ContactResultCallback { ObjectMotionState::getShapeManager()->releaseShape(collisionObject.getCollisionShape()); } - AllContactsCallback(btCollisionObject& testCollisionObject) : - btCollisionWorld::ContactResultCallback(), collisionObject(testCollisionObject) { - } - MotionStateType desiredObjectType; btCollisionObject collisionObject; std::vector intersectingObjects; - virtual bool needsCollision(btBroadphaseProxy* proxy) const { + bool needsCollision(btBroadphaseProxy* proxy) const override { return true; } From fadc094a01f072c9ad1f467104520389351f4317 Mon Sep 17 00:00:00 2001 From: sabrina-shanman Date: Mon, 30 Jul 2018 15:10:21 -0700 Subject: [PATCH 025/144] Do not work with raw pointer in CollisionPickResult::compareAndProcessNewResult --- interface/src/raypick/CollisionPick.h | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/interface/src/raypick/CollisionPick.h b/interface/src/raypick/CollisionPick.h index 9a76d047bf..b8da0db85e 100644 --- a/interface/src/raypick/CollisionPick.h +++ b/interface/src/raypick/CollisionPick.h @@ -69,14 +69,12 @@ public: bool checkOrFilterAgainstMaxDistance(float maxDistance) override { return true; } PickResultPointer compareAndProcessNewResult(const PickResultPointer& newRes) override { - const std::shared_ptr newCollisionResult = std::static_pointer_cast(*const_cast(&newRes)); - // Have to reference the raw pointer to work around strange type conversion errors - CollisionPickResult* newCollisionResultRaw = const_cast(newCollisionResult.get()); + const std::shared_ptr newCollisionResult = std::static_pointer_cast(newRes); - for (EntityIntersection& intersectingEntity : newCollisionResultRaw->intersectingEntities) { + for (EntityIntersection& intersectingEntity : newCollisionResult->intersectingEntities) { intersectingEntities.push_back(intersectingEntity); } - for (EntityIntersection& intersectingAvatar : newCollisionResultRaw->intersectingAvatars) { + for (EntityIntersection& intersectingAvatar : newCollisionResult->intersectingAvatars) { intersectingAvatars.push_back(intersectingAvatar); } From de7d9743369eb0ec5b99696767d5f979cd7d7e16 Mon Sep 17 00:00:00 2001 From: sabrina-shanman Date: Mon, 30 Jul 2018 16:45:31 -0700 Subject: [PATCH 026/144] Re-name EntityIntersection to ContactTestResult and remove its default constructor, plus update related variables --- interface/src/raypick/CollisionPick.cpp | 8 ++--- interface/src/raypick/CollisionPick.h | 44 ++++++++++++------------- libraries/physics/src/PhysicsEngine.cpp | 10 +++--- libraries/physics/src/PhysicsEngine.h | 20 +++++------ 4 files changed, 41 insertions(+), 41 deletions(-) diff --git a/interface/src/raypick/CollisionPick.cpp b/interface/src/raypick/CollisionPick.cpp index 49c08a8f5c..c1fc8e9c87 100644 --- a/interface/src/raypick/CollisionPick.cpp +++ b/interface/src/raypick/CollisionPick.cpp @@ -271,8 +271,8 @@ PickResultPointer CollisionPick::getEntityIntersection(const CollisionRegion& pi return std::make_shared(); } - const auto& intersectingEntities = _physicsEngine->getCollidingInRegion(MOTIONSTATE_TYPE_ENTITY, pick.shapeInfo, pick.transform); - return std::make_shared(pick, intersectingEntities, std::vector()); + const auto& entityIntersections = _physicsEngine->getCollidingInRegion(MOTIONSTATE_TYPE_ENTITY, pick.shapeInfo, pick.transform); + return std::make_shared(pick, entityIntersections, std::vector()); } PickResultPointer CollisionPick::getOverlayIntersection(const CollisionRegion& pick) { @@ -285,8 +285,8 @@ PickResultPointer CollisionPick::getAvatarIntersection(const CollisionRegion& pi return std::make_shared(); } - const auto& intersectingAvatars = _physicsEngine->getCollidingInRegion(MOTIONSTATE_TYPE_AVATAR, pick.shapeInfo, pick.transform); - return std::make_shared(pick, std::vector(), intersectingAvatars); + const auto& avatarIntersections = _physicsEngine->getCollidingInRegion(MOTIONSTATE_TYPE_AVATAR, pick.shapeInfo, pick.transform); + return std::make_shared(pick, std::vector(), avatarIntersections); } PickResultPointer CollisionPick::getHUDIntersection(const CollisionRegion& pick) { diff --git a/interface/src/raypick/CollisionPick.h b/interface/src/raypick/CollisionPick.h index b8da0db85e..29990f21a7 100644 --- a/interface/src/raypick/CollisionPick.h +++ b/interface/src/raypick/CollisionPick.h @@ -18,22 +18,22 @@ public: CollisionPickResult() {} CollisionPickResult(const QVariantMap& pickVariant) : PickResult(pickVariant) {} - CollisionPickResult(const CollisionRegion& searchRegion, const std::vector& intersectingEntities, const std::vector& intersectingAvatars) : + CollisionPickResult(const CollisionRegion& searchRegion, const std::vector& entityIntersections, const std::vector& avatarIntersections) : PickResult(searchRegion.toVariantMap()), - intersects(intersectingEntities.size() || intersectingAvatars.size()), - intersectingEntities(intersectingEntities), - intersectingAvatars(intersectingAvatars) { + intersects(entityIntersections.size() || avatarIntersections.size()), + entityIntersections(entityIntersections), + avatarIntersections(avatarIntersections) { } CollisionPickResult(const CollisionPickResult& collisionPickResult) : PickResult(collisionPickResult.pickVariant) { - intersectingAvatars = collisionPickResult.intersectingAvatars; - intersectingEntities = collisionPickResult.intersectingEntities; - intersects = intersectingAvatars.size() || intersectingEntities.size(); + avatarIntersections = collisionPickResult.avatarIntersections; + entityIntersections = collisionPickResult.entityIntersections; + intersects = avatarIntersections.size() || entityIntersections.size(); } bool intersects { false }; - std::vector intersectingEntities; - std::vector intersectingAvatars; + std::vector entityIntersections; + std::vector avatarIntersections; virtual QVariantMap toVariantMap() const override { QVariantMap variantMap; @@ -41,21 +41,21 @@ public: variantMap["intersects"] = intersects; QVariantList qEntityIntersections; - for (auto intersectingEntity : intersectingEntities) { + for (auto entityIntersection : entityIntersections) { QVariantMap qEntityIntersection; - qEntityIntersection["objectID"] = intersectingEntity.id; - qEntityIntersection["pickCollisionPoint"] = vec3toVariant(intersectingEntity.testCollisionPoint); - qEntityIntersection["entityCollisionPoint"] = vec3toVariant(intersectingEntity.foundCollisionPoint); + qEntityIntersection["objectID"] = entityIntersection.foundID; + qEntityIntersection["pickCollisionPoint"] = vec3toVariant(entityIntersection.testCollisionPoint); + qEntityIntersection["entityCollisionPoint"] = vec3toVariant(entityIntersection.foundCollisionPoint); qEntityIntersections.append(qEntityIntersection); } variantMap["entityIntersections"] = qEntityIntersections; QVariantList qAvatarIntersections; - for (auto intersectingAvatar : intersectingAvatars) { + for (auto avatarIntersection : avatarIntersections) { QVariantMap qAvatarIntersection; - qAvatarIntersection["objectID"] = intersectingAvatar.id; - qAvatarIntersection["pickCollisionPoint"] = vec3toVariant(intersectingAvatar.testCollisionPoint); - qAvatarIntersection["entityCollisionPoint"] = vec3toVariant(intersectingAvatar.foundCollisionPoint); + qAvatarIntersection["objectID"] = avatarIntersection.foundID; + qAvatarIntersection["pickCollisionPoint"] = vec3toVariant(avatarIntersection.testCollisionPoint); + qAvatarIntersection["entityCollisionPoint"] = vec3toVariant(avatarIntersection.foundCollisionPoint); qAvatarIntersections.append(qAvatarIntersection); } variantMap["avatarIntersections"] = qAvatarIntersections; @@ -71,14 +71,14 @@ public: PickResultPointer compareAndProcessNewResult(const PickResultPointer& newRes) override { const std::shared_ptr newCollisionResult = std::static_pointer_cast(newRes); - for (EntityIntersection& intersectingEntity : newCollisionResult->intersectingEntities) { - intersectingEntities.push_back(intersectingEntity); + for (ContactTestResult& entityIntersection : newCollisionResult->entityIntersections) { + entityIntersections.push_back(entityIntersection); } - for (EntityIntersection& intersectingAvatar : newCollisionResult->intersectingAvatars) { - intersectingAvatars.push_back(intersectingAvatar); + for (ContactTestResult& avatarIntersection : newCollisionResult->avatarIntersections) { + avatarIntersections.push_back(avatarIntersection); } - intersects = intersectingEntities.size() || intersectingAvatars.size(); + intersects = entityIntersections.size() || avatarIntersections.size(); return std::make_shared(*this); } diff --git a/libraries/physics/src/PhysicsEngine.cpp b/libraries/physics/src/PhysicsEngine.cpp index 20f09f6adf..32708f6ead 100644 --- a/libraries/physics/src/PhysicsEngine.cpp +++ b/libraries/physics/src/PhysicsEngine.cpp @@ -844,7 +844,7 @@ struct AllContactsCallback : public btCollisionWorld::ContactResultCallback { AllContactsCallback(MotionStateType desiredObjectType, const ShapeInfo& shapeInfo, const Transform& transform) : desiredObjectType(desiredObjectType), collisionObject(), - intersectingObjects(), + contacts(), btCollisionWorld::ContactResultCallback() { const btCollisionShape* collisionShape = ObjectMotionState::getShapeManager()->getShape(shapeInfo); @@ -863,7 +863,7 @@ struct AllContactsCallback : public btCollisionWorld::ContactResultCallback { MotionStateType desiredObjectType; btCollisionObject collisionObject; - std::vector intersectingObjects; + std::vector contacts; bool needsCollision(btBroadphaseProxy* proxy) const override { return true; @@ -896,7 +896,7 @@ struct AllContactsCallback : public btCollisionWorld::ContactResultCallback { } // This is the correct object type. Add it to the list. - intersectingObjects.emplace_back(candidate->getObjectID(), bulletToGLM(penetrationPoint), bulletToGLM(otherPenetrationPoint)); + contacts.emplace_back(candidate->getObjectID(), bulletToGLM(penetrationPoint), bulletToGLM(otherPenetrationPoint)); return 0; } @@ -907,10 +907,10 @@ protected: } }; -std::vector PhysicsEngine::getCollidingInRegion(MotionStateType desiredObjectType, const ShapeInfo& regionShapeInfo, const Transform& regionTransform) const { +const std::vector PhysicsEngine::getCollidingInRegion(MotionStateType desiredObjectType, const ShapeInfo& regionShapeInfo, const Transform& regionTransform) const { auto contactCallback = AllContactsCallback(desiredObjectType, regionShapeInfo, regionTransform); _dynamicsWorld->contactTest(&contactCallback.collisionObject, contactCallback); - return contactCallback.intersectingObjects; + return contactCallback.contacts; } diff --git a/libraries/physics/src/PhysicsEngine.h b/libraries/physics/src/PhysicsEngine.h index c6dfba518f..91e4cd4578 100644 --- a/libraries/physics/src/PhysicsEngine.h +++ b/libraries/physics/src/PhysicsEngine.h @@ -43,22 +43,22 @@ public: void* _b; // ObjectMotionState pointer }; -struct EntityIntersection { - EntityIntersection() { } +struct ContactTestResult { + ContactTestResult() = delete; - EntityIntersection(const EntityIntersection& entityIntersection) : - id(entityIntersection.id), - testCollisionPoint(entityIntersection.testCollisionPoint), - foundCollisionPoint(entityIntersection.foundCollisionPoint) { + ContactTestResult(const ContactTestResult& contactTestResult) : + foundID(contactTestResult.foundID), + testCollisionPoint(contactTestResult.testCollisionPoint), + foundCollisionPoint(contactTestResult.foundCollisionPoint) { } - EntityIntersection(QUuid id, glm::vec3 testCollisionPoint, glm::vec3 otherCollisionPoint) : - id(id), + ContactTestResult(QUuid foundID, glm::vec3 testCollisionPoint, glm::vec3 otherCollisionPoint) : + foundID(foundID), testCollisionPoint(testCollisionPoint), foundCollisionPoint(otherCollisionPoint) { } - QUuid id; + QUuid foundID; // The deepest point of an intersection within the volume of the test shape, in world space. glm::vec3 testCollisionPoint; // The deepest point of an intersection within the volume of the found object, in world space. @@ -128,7 +128,7 @@ public: void setShowBulletConstraintLimits(bool value); // Function for getting colliding ObjectMotionStates in the world of specified type - std::vector getCollidingInRegion(MotionStateType desiredObjectType, const ShapeInfo& regionShapeInfo, const Transform& regionTransform) const; + const std::vector getCollidingInRegion(MotionStateType desiredObjectType, const ShapeInfo& regionShapeInfo, const Transform& regionTransform) const; private: QList removeDynamicsForBody(btRigidBody* body); From cdca20101bcb68eff82812b12d9e618afe867951 Mon Sep 17 00:00:00 2001 From: Cristian Luis Duarte Date: Tue, 31 Jul 2018 14:37:51 -0300 Subject: [PATCH 027/144] Not an accountManager in AndroidHelper anymore --- android/app/src/main/cpp/native.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/android/app/src/main/cpp/native.cpp b/android/app/src/main/cpp/native.cpp index def706c128..9f0e088157 100644 --- a/android/app/src/main/cpp/native.cpp +++ b/android/app/src/main/cpp/native.cpp @@ -287,12 +287,13 @@ Java_io_highfidelity_hifiinterface_fragment_LoginFragment_nativeLogin(JNIEnv *en JNIEXPORT jboolean JNICALL Java_io_highfidelity_hifiinterface_fragment_FriendsFragment_nativeIsLoggedIn(JNIEnv *env, jobject instance) { - return AndroidHelper::instance().getAccountManager()->isLoggedIn(); + auto accountManager = DependencyManager::get(); + return accountManager->isLoggedIn(); } JNIEXPORT jstring JNICALL Java_io_highfidelity_hifiinterface_fragment_FriendsFragment_nativeGetAccessToken(JNIEnv *env, jobject instance) { - auto accountManager = AndroidHelper::instance().getAccountManager(); + auto accountManager = DependencyManager::get(); return env->NewStringUTF(accountManager->getAccountInfo().getAccessToken().token.toLatin1().data()); } From f98c19fd1dbe3d91a25c29efc72145dbe6cbe000 Mon Sep 17 00:00:00 2001 From: Gabriel Calero Date: Tue, 31 Jul 2018 15:15:21 -0300 Subject: [PATCH 028/144] Test 1: restore input sample rate to native (48khz) and set CALLBACK_ACCELERATOR_RATIO = 1 for android --- libraries/audio-client/src/AudioClient.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index c57360b09f..fb7523a24f 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -489,10 +489,10 @@ bool nativeFormatForAudioDevice(const QAudioDeviceInfo& audioDevice, #if defined(Q_OS_ANDROID) // Using the HW sample rate (AUDIO_INPUT_FLAG_FAST) in some samsung phones causes a low volume at input stream // Changing the sample rate forces a resampling that (in samsung) amplifies +18 dB - QAndroidJniObject brand = QAndroidJniObject::getStaticObjectField("android/os/Build", "BRAND"); - if (audioDevice == QAudioDeviceInfo::defaultInputDevice() && brand.toString().contains("samsung", Qt::CaseInsensitive)) { - audioFormat.setSampleRate(24000); - } +// QAndroidJniObject brand = QAndroidJniObject::getStaticObjectField("android/os/Build", "BRAND"); +// if (audioDevice == QAudioDeviceInfo::defaultInputDevice() && brand.toString().contains("samsung", Qt::CaseInsensitive)) { +// audioFormat.setSampleRate(24000); +// } #endif if (!audioDevice.isFormatSupported(audioFormat)) { @@ -1848,7 +1848,9 @@ const float AudioClient::CALLBACK_ACCELERATOR_RATIO = IsWindows8OrGreater() ? 1. const float AudioClient::CALLBACK_ACCELERATOR_RATIO = 2.0f; #endif -#ifdef Q_OS_LINUX +#ifdef Q_OS_ANDROID +const float AudioClient::CALLBACK_ACCELERATOR_RATIO = 1.0f; +#elif defined(Q_OS_LINUX) const float AudioClient::CALLBACK_ACCELERATOR_RATIO = 2.0f; #endif From b8bab858d9965cb9b7b04a1383f8153c32f4fc70 Mon Sep 17 00:00:00 2001 From: Gabriel Calero Date: Tue, 31 Jul 2018 15:32:39 -0300 Subject: [PATCH 029/144] Test 2: input sample rate: 24khz. Set CALLBACK_ACCELERATOR_RATIO = 0.5 for android --- libraries/audio-client/src/AudioClient.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index fb7523a24f..eb673b89e6 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -489,10 +489,10 @@ bool nativeFormatForAudioDevice(const QAudioDeviceInfo& audioDevice, #if defined(Q_OS_ANDROID) // Using the HW sample rate (AUDIO_INPUT_FLAG_FAST) in some samsung phones causes a low volume at input stream // Changing the sample rate forces a resampling that (in samsung) amplifies +18 dB -// QAndroidJniObject brand = QAndroidJniObject::getStaticObjectField("android/os/Build", "BRAND"); -// if (audioDevice == QAudioDeviceInfo::defaultInputDevice() && brand.toString().contains("samsung", Qt::CaseInsensitive)) { -// audioFormat.setSampleRate(24000); -// } + QAndroidJniObject brand = QAndroidJniObject::getStaticObjectField("android/os/Build", "BRAND"); + if (audioDevice == QAudioDeviceInfo::defaultInputDevice() && brand.toString().contains("samsung", Qt::CaseInsensitive)) { + audioFormat.setSampleRate(24000); + } #endif if (!audioDevice.isFormatSupported(audioFormat)) { @@ -1849,7 +1849,7 @@ const float AudioClient::CALLBACK_ACCELERATOR_RATIO = 2.0f; #endif #ifdef Q_OS_ANDROID -const float AudioClient::CALLBACK_ACCELERATOR_RATIO = 1.0f; +const float AudioClient::CALLBACK_ACCELERATOR_RATIO = 0.5f; #elif defined(Q_OS_LINUX) const float AudioClient::CALLBACK_ACCELERATOR_RATIO = 2.0f; #endif From b841a5f0168ee2c7d68d88cfd1f8f1ca389c6638 Mon Sep 17 00:00:00 2001 From: Gabriel Calero Date: Tue, 31 Jul 2018 16:54:20 -0300 Subject: [PATCH 030/144] Test 3: input sample rate: 24khz. Set CALLBACK_ACCELERATOR_RATIO = 2.0 for android (master version) --- libraries/audio-client/src/AudioClient.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index eb673b89e6..d5549444f8 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -1849,7 +1849,7 @@ const float AudioClient::CALLBACK_ACCELERATOR_RATIO = 2.0f; #endif #ifdef Q_OS_ANDROID -const float AudioClient::CALLBACK_ACCELERATOR_RATIO = 0.5f; +const float AudioClient::CALLBACK_ACCELERATOR_RATIO = 2.0f; #elif defined(Q_OS_LINUX) const float AudioClient::CALLBACK_ACCELERATOR_RATIO = 2.0f; #endif From 687f5e3df324fb181b800843b6656952f9a4969c Mon Sep 17 00:00:00 2001 From: Gabriel Date: Wed, 1 Aug 2018 19:49:04 -0300 Subject: [PATCH 031/144] Update AudioClient.cpp Trying 40ms --- libraries/audio-client/src/AudioClient.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index d5549444f8..2a07d2898a 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -1849,7 +1849,7 @@ const float AudioClient::CALLBACK_ACCELERATOR_RATIO = 2.0f; #endif #ifdef Q_OS_ANDROID -const float AudioClient::CALLBACK_ACCELERATOR_RATIO = 2.0f; +const float AudioClient::CALLBACK_ACCELERATOR_RATIO = 0.25f; #elif defined(Q_OS_LINUX) const float AudioClient::CALLBACK_ACCELERATOR_RATIO = 2.0f; #endif From 1ba28de5b62ae1673a6db332ac39d878aef08fb4 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Wed, 1 Aug 2018 17:52:27 -0700 Subject: [PATCH 032/144] try to fix nodelist not getting destroyed --- assignment-client/src/Agent.cpp | 2 +- domain-server/src/DomainGatekeeper.cpp | 6 +- domain-server/src/DomainServer.cpp | 12 ++-- interface/src/Application.cpp | 60 ++++++++--------- interface/src/AvatarBookmarks.cpp | 2 +- interface/src/Menu.cpp | 24 ++++--- interface/src/avatar/AvatarManager.cpp | 6 +- interface/src/commerce/Wallet.cpp | 2 +- interface/src/ui/PreferencesDialog.cpp | 65 +++++++++---------- interface/src/ui/Stats.cpp | 2 +- libraries/avatars/src/AvatarData.cpp | 2 +- .../entities/src/EntityScriptingInterface.cpp | 2 +- libraries/entities/src/EntityTree.cpp | 2 +- 13 files changed, 98 insertions(+), 89 deletions(-) diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index 15c6471b3d..f2747144b3 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -517,7 +517,7 @@ void Agent::setIsListeningToAudioStream(bool isListeningToAudioStream) { auto nodeList = DependencyManager::get(); nodeList->eachMatchingNode( - [&](const SharedNodePointer& node)->bool { + [](const SharedNodePointer& node)->bool { return (node->getType() == NodeType::AudioMixer) && node->getActiveSocket(); }, [&](const SharedNodePointer& node) { diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index 59cc8d10f6..39ec5463dc 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -264,7 +264,7 @@ void DomainGatekeeper::updateNodePermissions() { QList nodesToKill; auto limitedNodeList = DependencyManager::get(); - limitedNodeList->eachNode([this, limitedNodeList, &nodesToKill](const SharedNodePointer& node){ + limitedNodeList->eachNode([this, &limitedNodeList, &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(); @@ -458,7 +458,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 +1009,7 @@ void DomainGatekeeper::refreshGroupsCache() { getDomainOwnerFriendsList(); auto nodeList = DependencyManager::get(); - nodeList->eachNode([&](const SharedNodePointer& node) { + nodeList->eachNode([this](const SharedNodePointer& node) { if (!node->getPermissions().isAssignment) { // this node is an agent const QString& verifiedUserName = node->getPermissions().getVerifiedUserName(); diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 86a9a58058..05555314f3 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -1046,7 +1046,7 @@ bool DomainServer::isInInterestSet(const SharedNodePointer& nodeA, const SharedN unsigned int DomainServer::countConnectedUsers() { unsigned int result = 0; auto nodeList = DependencyManager::get(); - nodeList->eachNode([&](const SharedNodePointer& node){ + nodeList->eachNode([&result](const SharedNodePointer& node){ // only count unassigned agents (i.e., users) if (node->getType() == NodeType::Agent) { auto nodeData = static_cast(node->getLinkedData()); @@ -1149,7 +1149,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(); @@ -1209,7 +1209,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,7 +1217,7 @@ void DomainServer::broadcastNewNode(const SharedNodePointer& addedNode) { return false; } }, - [&](const SharedNodePointer& node) { + [this, &addNodePacket, connectionSecretIndex, addedNode, &limitedNodeList](const SharedNodePointer& node) { addNodePacket->seek(connectionSecretIndex); QByteArray rfcConnectionSecret = connectionSecretForNodes(node, addedNode).toRfc4122(); @@ -2832,7 +2832,7 @@ void DomainServer::updateReplicationNodes(ReplicationServerDirection direction) auto serversSettings = replicationSettings.value(serversKey).toList(); std::vector knownReplicationNodes; - nodeList->eachNode([&](const SharedNodePointer& otherNode) { + nodeList->eachNode([direction, &knownReplicationNodes](const SharedNodePointer& otherNode) { if ((direction == Upstream && NodeType::isUpstream(otherNode->getType())) || (direction == Downstream && NodeType::isDownstream(otherNode->getType()))) { knownReplicationNodes.push_back(otherNode->getPublicSocket()); @@ -2870,7 +2870,7 @@ void DomainServer::updateReplicationNodes(ReplicationServerDirection direction) // collect them in a vector to separately remove them with handleKillNode (since eachNode has a read lock and // we cannot recursively take the write lock required by handleKillNode) std::vector nodesToKill; - nodeList->eachNode([&](const SharedNodePointer& otherNode) { + nodeList->eachNode([this, direction, replicationNodesInSettings, replicationDirection, &nodesToKill](const SharedNodePointer& otherNode) { if ((direction == Upstream && NodeType::isUpstream(otherNode->getType())) || (direction == Downstream && NodeType::isDownstream(otherNode->getType()))) { bool nodeInSettings = find(replicationNodesInSettings.cbegin(), replicationNodesInSettings.cend(), diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 65653868da..ca2d9a919e 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1138,7 +1138,7 @@ 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] { + connect(domainCheckInTimer, &QTimer::timeout, [this, &nodeList] { if (!isServerlessMode()) { nodeList->sendDomainServerCheckIn(); } @@ -1164,7 +1164,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo return myAvatar ? myAvatar->getOrientationForAudio() : Quaternions::IDENTITY; }); - recording::Frame::registerFrameHandler(AudioConstants::getAudioFrameName(), [=](recording::Frame::ConstPointer frame) { + recording::Frame::registerFrameHandler(AudioConstants::getAudioFrameName(), [&audioIO](recording::Frame::ConstPointer frame) { audioIO->handleRecordedAudioInput(frame->data); }); @@ -1835,13 +1835,13 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo } }); - connect(entityScriptingInterface.data(), &EntityScriptingInterface::deletingEntity, [=](const EntityItemID& entityItemID) { + connect(entityScriptingInterface.data(), &EntityScriptingInterface::deletingEntity, [this](const EntityItemID& entityItemID) { if (entityItemID == _keyboardFocusedEntity.get()) { setKeyboardFocusEntity(UNKNOWN_ENTITY_ID); } }); - connect(getEntities()->getTree().get(), &EntityTree::deletingEntity, [=](const EntityItemID& entityItemID) { + connect(getEntities()->getTree().get(), &EntityTree::deletingEntity, [](const EntityItemID& entityItemID) { auto avatarManager = DependencyManager::get(); auto myAvatar = avatarManager ? avatarManager->getMyAvatar() : nullptr; if (myAvatar) { @@ -1849,7 +1849,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) { @@ -1864,7 +1864,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) { @@ -1899,7 +1899,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); @@ -1907,7 +1907,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); @@ -1918,13 +1918,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); }); @@ -2201,14 +2201,14 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo // 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); }); } @@ -2235,8 +2235,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo connect(&_myCamera, &Camera::modeUpdated, this, &Application::cameraModeChanged); - DependencyManager::get()->setShouldPickHUDOperator([&]() { return DependencyManager::get()->isHMDMode(); }); - DependencyManager::get()->setCalculatePos2DFromHUDOperator([&](const glm::vec3& intersection) { + DependencyManager::get()->setShouldPickHUDOperator([]() { return DependencyManager::get()->isHMDMode(); }); + DependencyManager::get()->setCalculatePos2DFromHUDOperator([this](const glm::vec3& intersection) { const glm::vec2 MARGIN(25.0f); glm::vec2 maxPos = _controllerScriptingInterface->getViewportDimensions() - MARGIN; glm::vec2 pos2D = DependencyManager::get()->overlayFromWorldPoint(intersection); @@ -2246,7 +2246,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo // Setup the mouse ray pick and related operators DependencyManager::get()->setMouseRayPickID(DependencyManager::get()->addPick(PickQuery::Ray, std::make_shared( PickFilter(PickScriptingInterface::PICK_ENTITIES() | PickScriptingInterface::PICK_INCLUDE_NONCOLLIDABLE()), 0.0f, true))); - DependencyManager::get()->setMouseRayPickResultOperator([&](unsigned int rayPickID) { + DependencyManager::get()->setMouseRayPickResultOperator([](unsigned int rayPickID) { RayToEntityIntersectionResult entityResult; entityResult.intersects = false; auto pickResult = DependencyManager::get()->getPrevPickResultTyped(rayPickID); @@ -2262,7 +2262,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo } return entityResult; }); - DependencyManager::get()->setSetPrecisionPickingOperator([&](unsigned int rayPickID, bool value) { + DependencyManager::get()->setSetPrecisionPickingOperator([](unsigned int rayPickID, bool value) { DependencyManager::get()->setPrecisionPicking(rayPickID, value); }); @@ -2461,32 +2461,35 @@ void Application::cleanupBeforeQuit() { _keyboardFocusHighlight = nullptr; } - auto nodeList = DependencyManager::get(); + { + auto nodeList = DependencyManager::get(); - // send the domain a disconnect packet, force stoppage of domain-server check-ins - nodeList->getDomainHandler().disconnect(); - nodeList->setIsShuttingDown(true); + // send the domain a disconnect packet, force stoppage of domain-server check-ins + nodeList->getDomainHandler().disconnect(); + nodeList->setIsShuttingDown(true); - // tell the packet receiver we're shutting down, so it can drop packets - nodeList->getPacketReceiver().setShouldDropPackets(true); + // tell the packet receiver we're shutting down, so it can drop packets + nodeList->getPacketReceiver().setShouldDropPackets(true); + } getEntities()->shutdown(); // tell the entities system we're shutting down, so it will stop running scripts // Clear any queued processing (I/O, FBX/OBJ/Texture parsing) QThreadPool::globalInstance()->clear(); + DependencyManager::destroy(); + + // FIXME: Something is still holding on to the ScriptEnginePointers contained in ScriptEngines, and they hold backpointers to ScriptEngines, + // so this doesn't shut down properly + DependencyManager::get()->shutdownScripting(); // stop all currently running global scripts // These classes hold ScriptEnginePointers, so they must be destroyed before ScriptEngines + // Must be done after shutdownScripting in case any scripts try to access these things { - DependencyManager::destroy(); DependencyManager::destroy(); EntityTreePointer tree = getEntities()->getTree(); tree->setSimulation(nullptr); DependencyManager::destroy(); } - - // FIXME: Something is still holding on to the ScriptEnginePointers contained in ScriptEngines, and they hold backpointers to ScriptEngines, - // so this doesn't shut down properly - DependencyManager::get()->shutdownScripting(); // stop all currently running global scripts DependencyManager::destroy(); _displayPlugin.reset(); @@ -4748,7 +4751,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) { @@ -6345,7 +6348,6 @@ void Application::domainURLChanged(QUrl domainURL) { void Application::resettingDomain() { _notifiedPacketVersionMismatchThisDomain = false; - auto nodeList = DependencyManager::get(); clearDomainOctreeDetails(); } diff --git a/interface/src/AvatarBookmarks.cpp b/interface/src/AvatarBookmarks.cpp index 3e0e643bd8..61babb2c18 100644 --- a/interface/src/AvatarBookmarks.cpp +++ b/interface/src/AvatarBookmarks.cpp @@ -64,7 +64,7 @@ void addAvatarEntities(const QVariantList& avatarEntities) { EntityItemID id = EntityItemID(QUuid::createUuid()); bool success = true; - entityTree->withWriteLock([&] { + entityTree->withWriteLock([&entityTree, id, &entityProperties, &success] { EntityItemPointer entity = entityTree->addEntity(id, entityProperties); if (entity) { if (entityProperties.queryAACubeRelatedPropertyChanged()) { diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 84b7855714..2c364154f7 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -147,9 +147,11 @@ Menu::Menu() { auto assetServerAction = addActionToQMenuAndActionHash(editMenu, MenuOption::AssetServer, Qt::CTRL | Qt::SHIFT | Qt::Key_A, qApp, SLOT(showAssetServerWidget())); - auto nodeList = DependencyManager::get(); - QObject::connect(nodeList.data(), &NodeList::canWriteAssetsChanged, assetServerAction, &QAction::setEnabled); - assetServerAction->setEnabled(nodeList->getThisNodeCanWriteAssets()); + { + auto nodeList = DependencyManager::get(); + QObject::connect(nodeList.data(), &NodeList::canWriteAssetsChanged, assetServerAction, &QAction::setEnabled); + assetServerAction->setEnabled(nodeList->getThisNodeCanWriteAssets()); + } // Edit > Package Model as .fst... addActionToQMenuAndActionHash(editMenu, MenuOption::PackageModel, 0, @@ -620,8 +622,11 @@ Menu::Menu() { addCheckableActionToQMenuAndActionHash(networkMenu, MenuOption::SendWrongProtocolVersion, 0, false, qApp, SLOT(sendWrongProtocolVersionsSignature(bool))); - addCheckableActionToQMenuAndActionHash(networkMenu, MenuOption::SendWrongDSConnectVersion, 0, false, - nodeList.data(), SLOT(toggleSendNewerDSConnectVersion(bool))); + { + auto nodeList = DependencyManager::get(); + addCheckableActionToQMenuAndActionHash(networkMenu, MenuOption::SendWrongDSConnectVersion, 0, false, + nodeList.data(), SLOT(toggleSendNewerDSConnectVersion(bool))); + } #endif @@ -763,11 +768,14 @@ Menu::Menu() { qApp, SLOT(toggleLogDialog())); auto essLogAction = addActionToQMenuAndActionHash(developerMenu, MenuOption::EntityScriptServerLog, 0, qApp, SLOT(toggleEntityScriptServerLogDialog())); - QObject::connect(nodeList.data(), &NodeList::canRezChanged, essLogAction, [essLogAction] { + { auto nodeList = DependencyManager::get(); + QObject::connect(nodeList.data(), &NodeList::canRezChanged, essLogAction, [essLogAction] { + auto nodeList = DependencyManager::get(); + essLogAction->setEnabled(nodeList->getThisNodeCanRez()); + }); essLogAction->setEnabled(nodeList->getThisNodeCanRez()); - }); - essLogAction->setEnabled(nodeList->getThisNodeCanRez()); + } addActionToQMenuAndActionHash(developerMenu, "Script Log (HMD friendly)...", Qt::NoButton, qApp, SLOT(showScriptLogs())); diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index fab512f787..eb96554cb2 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -79,7 +79,7 @@ AvatarManager::AvatarManager(QObject* parent) : // when we hear that the user has ignored an avatar by session UUID // immediately remove that avatar instead of waiting for the absence of packets from avatar mixer - connect(nodeList.data(), &NodeList::ignoredNode, this, [=](const QUuid& nodeID, bool enabled) { + connect(nodeList.data(), &NodeList::ignoredNode, this, [this](const QUuid& nodeID, bool enabled) { if (enabled) { removeAvatar(nodeID, KillAvatarReason::AvatarIgnored); } @@ -334,10 +334,10 @@ void AvatarManager::postUpdate(float deltaTime, const render::ScenePointer& scen void AvatarManager::sendIdentityRequest(const QUuid& avatarID) const { auto nodeList = DependencyManager::get(); nodeList->eachMatchingNode( - [&](const SharedNodePointer& node)->bool { + [](const SharedNodePointer& node)->bool { return node->getType() == NodeType::AvatarMixer && node->getActiveSocket(); }, - [&](const SharedNodePointer& node) { + [this, avatarID, &nodeList](const SharedNodePointer& node) { auto packet = NLPacket::create(PacketType::AvatarIdentityRequest, NUM_BYTES_RFC4122_UUID, true); packet->write(avatarID.toRfc4122()); nodeList->sendPacket(std::move(packet), *node); diff --git a/interface/src/commerce/Wallet.cpp b/interface/src/commerce/Wallet.cpp index 991f1ebf3f..ef6b4654f5 100644 --- a/interface/src/commerce/Wallet.cpp +++ b/interface/src/commerce/Wallet.cpp @@ -328,7 +328,7 @@ Wallet::Wallet() { packetReceiver.registerListener(PacketType::ChallengeOwnership, this, "handleChallengeOwnershipPacket"); packetReceiver.registerListener(PacketType::ChallengeOwnershipRequest, this, "handleChallengeOwnershipPacket"); - connect(ledger.data(), &Ledger::accountResult, this, [&](QJsonObject result) { + connect(ledger.data(), &Ledger::accountResult, this, [](QJsonObject result) { auto wallet = DependencyManager::get(); auto walletScriptingInterface = DependencyManager::get(); uint status; diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index 50a4d17cae..37f94f5b2a 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -27,28 +27,27 @@ void setupPreferences() { auto preferences = DependencyManager::get(); - auto nodeList = DependencyManager::get(); auto myAvatar = DependencyManager::get()->getMyAvatar(); static const QString AVATAR_BASICS { "Avatar Basics" }; { - auto getter = [=]()->QString { return myAvatar->getDisplayName(); }; - auto setter = [=](const QString& value) { myAvatar->setDisplayName(value); }; + auto getter = [myAvatar]()->QString { return myAvatar->getDisplayName(); }; + auto setter = [myAvatar](const QString& value) { myAvatar->setDisplayName(value); }; auto preference = new EditPreference(AVATAR_BASICS, "Avatar display name (optional)", getter, setter); preference->setPlaceholderText("Not showing a name"); preferences->addPreference(preference); } { - auto getter = [=]()->QString { return myAvatar->getCollisionSoundURL(); }; - auto setter = [=](const QString& value) { myAvatar->setCollisionSoundURL(value); }; + auto getter = [myAvatar]()->QString { return myAvatar->getCollisionSoundURL(); }; + auto setter = [myAvatar](const QString& value) { myAvatar->setCollisionSoundURL(value); }; auto preference = new EditPreference(AVATAR_BASICS, "Avatar collision sound URL (optional)", getter, setter); preference->setPlaceholderText("Enter the URL of a sound to play when you bump into something"); preferences->addPreference(preference); } { - auto getter = [=]()->QString { return myAvatar->getFullAvatarURLFromPreferences().toString(); }; - auto setter = [=](const QString& value) { myAvatar->useFullAvatarURL(value, ""); qApp->clearAvatarOverrideUrl(); }; + auto getter = [myAvatar]()->QString { return myAvatar->getFullAvatarURLFromPreferences().toString(); }; + auto setter = [myAvatar](const QString& value) { myAvatar->useFullAvatarURL(value, ""); qApp->clearAvatarOverrideUrl(); }; auto preference = new AvatarPreference(AVATAR_BASICS, "Appearance", getter, setter); preferences->addPreference(preference); } @@ -163,8 +162,8 @@ void setupPreferences() { static const QString VIEW_CATEGORY{ "View" }; { - auto getter = [=]()->float { return myAvatar->getRealWorldFieldOfView(); }; - auto setter = [=](float value) { myAvatar->setRealWorldFieldOfView(value); }; + auto getter = [myAvatar]()->float { return myAvatar->getRealWorldFieldOfView(); }; + auto setter = [myAvatar](float value) { myAvatar->setRealWorldFieldOfView(value); }; auto preference = new SpinnerPreference(VIEW_CATEGORY, "Real world vertical field of view (angular size of monitor)", getter, setter); preference->setMin(1); preference->setMax(180); @@ -219,13 +218,13 @@ void setupPreferences() { static const QString AVATAR_TUNING { "Avatar Tuning" }; { - auto getter = [=]()->QString { return myAvatar->getDominantHand(); }; - auto setter = [=](const QString& value) { myAvatar->setDominantHand(value); }; + auto getter = [myAvatar]()->QString { return myAvatar->getDominantHand(); }; + auto setter = [myAvatar](const QString& value) { myAvatar->setDominantHand(value); }; preferences->addPreference(new PrimaryHandPreference(AVATAR_TUNING, "Dominant Hand", getter, setter)); } { - auto getter = [=]()->float { return myAvatar->getTargetScale(); }; - auto setter = [=](float value) { myAvatar->setTargetScale(value); }; + auto getter = [myAvatar]()->float { return myAvatar->getTargetScale(); }; + auto setter = [myAvatar](float value) { myAvatar->setTargetScale(value); }; auto preference = new SpinnerSliderPreference(AVATAR_TUNING, "Avatar Scale", getter, setter); preference->setMin(0.25); preference->setMax(4); @@ -240,16 +239,16 @@ void setupPreferences() { } { - auto getter = [=]()->QString { return myAvatar->getAnimGraphOverrideUrl().toString(); }; - auto setter = [=](const QString& value) { myAvatar->setAnimGraphOverrideUrl(QUrl(value)); }; + auto getter = [myAvatar]()->QString { return myAvatar->getAnimGraphOverrideUrl().toString(); }; + auto setter = [myAvatar](const QString& value) { myAvatar->setAnimGraphOverrideUrl(QUrl(value)); }; auto preference = new EditPreference(AVATAR_TUNING, "Avatar animation JSON", getter, setter); preference->setPlaceholderText("default"); preferences->addPreference(preference); } { - auto getter = [=]()->bool { return myAvatar->getCollisionsEnabled(); }; - auto setter = [=](bool value) { myAvatar->setCollisionsEnabled(value); }; + auto getter = [myAvatar]()->bool { return myAvatar->getCollisionsEnabled(); }; + auto setter = [myAvatar](bool value) { myAvatar->setCollisionsEnabled(value); }; auto preference = new CheckPreference(AVATAR_TUNING, "Enable Avatar collisions", getter, setter); preferences->addPreference(preference); } @@ -270,15 +269,15 @@ void setupPreferences() { { static const QString movementsControlChannel = QStringLiteral("Hifi-Advanced-Movement-Disabler"); - auto getter = [=]()->bool { return myAvatar->useAdvancedMovementControls(); }; - auto setter = [=](bool value) { myAvatar->setUseAdvancedMovementControls(value); }; + auto getter = [myAvatar]()->bool { return myAvatar->useAdvancedMovementControls(); }; + auto setter = [myAvatar](bool value) { myAvatar->setUseAdvancedMovementControls(value); }; preferences->addPreference(new CheckPreference(MOVEMENT, QStringLiteral("Advanced movement for hand controllers"), getter, setter)); } { - auto getter = [=]()->int { return myAvatar->getSnapTurn() ? 0 : 1; }; - auto setter = [=](int value) { myAvatar->setSnapTurn(value == 0); }; + auto getter = [myAvatar]()->int { return myAvatar->getSnapTurn() ? 0 : 1; }; + auto setter = [myAvatar](int value) { myAvatar->setSnapTurn(value == 0); }; auto preference = new RadioButtonsPreference(MOVEMENT, "Snap turn / Smooth turn", getter, setter); QStringList items; items << "Snap turn" << "Smooth turn"; @@ -306,20 +305,20 @@ void setupPreferences() { { static const QString movementsControlChannel = QStringLiteral("Hifi-Advanced-Movement-Disabler"); - auto getter = [=]()->bool { return myAvatar->useAdvancedMovementControls(); }; - auto setter = [=](bool value) { myAvatar->setUseAdvancedMovementControls(value); }; + auto getter = [myAvatar]()->bool { return myAvatar->useAdvancedMovementControls(); }; + auto setter = [myAvatar](bool value) { myAvatar->setUseAdvancedMovementControls(value); }; preferences->addPreference(new CheckPreference(VR_MOVEMENT, QStringLiteral("Advanced movement for hand controllers"), getter, setter)); } { - auto getter = [=]()->bool { return myAvatar->getFlyingHMDPref(); }; - auto setter = [=](bool value) { myAvatar->setFlyingHMDPref(value); }; + auto getter = [myAvatar]()->bool { return myAvatar->getFlyingHMDPref(); }; + auto setter = [myAvatar](bool value) { myAvatar->setFlyingHMDPref(value); }; preferences->addPreference(new CheckPreference(VR_MOVEMENT, "Flying & jumping", getter, setter)); } { - auto getter = [=]()->int { return myAvatar->getSnapTurn() ? 0 : 1; }; - auto setter = [=](int value) { myAvatar->setSnapTurn(value == 0); }; + auto getter = [myAvatar]()->int { return myAvatar->getSnapTurn() ? 0 : 1; }; + auto setter = [myAvatar](int value) { myAvatar->setSnapTurn(value == 0); }; auto preference = new RadioButtonsPreference(VR_MOVEMENT, "Snap turn / Smooth turn", getter, setter); QStringList items; items << "Snap turn" << "Smooth turn"; @@ -345,8 +344,8 @@ void setupPreferences() { static const QString AVATAR_CAMERA{ "Mouse Sensitivity" }; { - auto getter = [=]()->float { return myAvatar->getPitchSpeed(); }; - auto setter = [=](float value) { myAvatar->setPitchSpeed(value); }; + auto getter = [myAvatar]()->float { return myAvatar->getPitchSpeed(); }; + auto setter = [myAvatar](float value) { myAvatar->setPitchSpeed(value); }; auto preference = new SpinnerSliderPreference(AVATAR_CAMERA, "Y input:", getter, setter); preference->setMin(1.0f); preference->setMax(360.0f); @@ -355,8 +354,8 @@ void setupPreferences() { preferences->addPreference(preference); } { - auto getter = [=]()->float { return myAvatar->getYawSpeed(); }; - auto setter = [=](float value) { myAvatar->setYawSpeed(value); }; + auto getter = [myAvatar]()->float { return myAvatar->getYawSpeed(); }; + auto setter = [myAvatar](float value) { myAvatar->setYawSpeed(value); }; auto preference = new SpinnerSliderPreference(AVATAR_CAMERA, "X input:", getter, setter); preference->setMin(1.0f); preference->setMax(360.0f); @@ -421,8 +420,8 @@ void setupPreferences() { { static const int MIN_PORT_NUMBER { 0 }; static const int MAX_PORT_NUMBER { 65535 }; - auto getter = [nodelist] { return static_cast(nodelist->getSocketLocalPort()); }; - auto setter = [nodelist](int preset) { nodelist->setSocketLocalPort(static_cast(preset)); }; + auto getter = [&nodelist] { return static_cast(nodelist->getSocketLocalPort()); }; + auto setter = [&nodelist](int preset) { nodelist->setSocketLocalPort(static_cast(preset)); }; auto preference = new IntSpinnerPreference(NETWORKING, "Listening Port", getter, setter); preference->setMin(MIN_PORT_NUMBER); preference->setMax(MAX_PORT_NUMBER); diff --git a/interface/src/ui/Stats.cpp b/interface/src/ui/Stats.cpp index 1f6b540ad6..107de97d33 100644 --- a/interface/src/ui/Stats.cpp +++ b/interface/src/ui/Stats.cpp @@ -174,7 +174,7 @@ void Stats::updateStats(bool force) { int octreeServerCount = 0; int pingOctreeMax = 0; int totalEntityKbps = 0; - nodeList->eachNode([&](const SharedNodePointer& node) { + nodeList->eachNode([&totalPingOctree, &totalEntityKbps, &octreeServerCount, &pingOctreeMax](const SharedNodePointer& node) { // TODO: this should also support entities if (node->getType() == NodeType::EntityServer) { totalPingOctree += node->getPingMs(); diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index abdac838b6..a7579dac2b 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -1908,7 +1908,7 @@ void AvatarData::sendIdentityPacket() { auto packetList = NLPacketList::create(PacketType::AvatarIdentity, QByteArray(), true, true); packetList->write(identityData); nodeList->eachMatchingNode( - [&](const SharedNodePointer& node)->bool { + [](const SharedNodePointer& node)->bool { return node->getType() == NodeType::AvatarMixer && node->getActiveSocket(); }, [&](const SharedNodePointer& node) { diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index 8fd87e068a..0fac5e55e8 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -1249,7 +1249,7 @@ bool EntityScriptingInterface::actionWorker(const QUuid& entityID, EntityItemPointer entity; bool doTransmit = false; - _entityTree->withWriteLock([&] { + _entityTree->withWriteLock([this, &entity, entityID, myNodeID, &doTransmit, actor, &properties] { EntitySimulationPointer simulation = _entityTree->getSimulation(); entity = _entityTree->findEntityByEntityItemID(entityID); if (!entity) { diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 66dd6adfb5..377e192bb1 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -1435,7 +1435,7 @@ void EntityTree::validatePop(const QString& certID, const EntityItemID& entityIt QNetworkReply* networkReply = networkAccessManager.put(networkRequest, QJsonDocument(request).toJson()); - connect(networkReply, &QNetworkReply::finished, [=]() { + connect(networkReply, &QNetworkReply::finished, [this, networkReply, entityItemID, certID, senderNode]() { QJsonObject jsonObject = QJsonDocument::fromJson(networkReply->readAll()).object(); jsonObject = jsonObject["data"].toObject(); From f0d058cb61d0a18dadb4e31b8e62a69a692940b2 Mon Sep 17 00:00:00 2001 From: Gabriel Date: Thu, 2 Aug 2018 11:29:56 -0300 Subject: [PATCH 033/144] Set buffer to 10 ms for android (CALLBACK_ACCELERATOR_RATIO = 1.0) --- libraries/audio-client/src/AudioClient.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index 2a07d2898a..85ceece702 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -1849,7 +1849,7 @@ const float AudioClient::CALLBACK_ACCELERATOR_RATIO = 2.0f; #endif #ifdef Q_OS_ANDROID -const float AudioClient::CALLBACK_ACCELERATOR_RATIO = 0.25f; +const float AudioClient::CALLBACK_ACCELERATOR_RATIO = 1.0f; #elif defined(Q_OS_LINUX) const float AudioClient::CALLBACK_ACCELERATOR_RATIO = 2.0f; #endif From a06f85faa8e5ac46ca7f2688ec7713fb3cc3133e Mon Sep 17 00:00:00 2001 From: sabrina-shanman Date: Tue, 31 Jul 2018 09:25:51 -0700 Subject: [PATCH 034/144] Consolidate intersection information in CollisionPickResult to one list, and move variant conversion code to source file --- interface/src/raypick/CollisionPick.cpp | 55 +++++++++++++++++++++++++ interface/src/raypick/CollisionPick.h | 30 +------------- 2 files changed, 56 insertions(+), 29 deletions(-) diff --git a/interface/src/raypick/CollisionPick.cpp b/interface/src/raypick/CollisionPick.cpp index c1fc8e9c87..0531a5a67d 100644 --- a/interface/src/raypick/CollisionPick.cpp +++ b/interface/src/raypick/CollisionPick.cpp @@ -14,6 +14,61 @@ #include "ScriptEngineLogging.h" #include "model-networking/ModelCache.h" +#include "UUIDHasher.h" + +QVariantMap CollisionPickResult::toVariantMap() const { + QVariantMap variantMap; + + variantMap["intersects"] = intersects; + + std::unordered_map intersections; + std::unordered_map collisionPointPairs; + + IntersectionType intersectionTypesToCheck[] = { ENTITY, AVATAR }; + for (int i = 0; i < 2; i++) { + IntersectionType intersectionType = intersectionTypesToCheck[i]; + + const std::vector* objectIntersections; + if (intersectionType == ENTITY) { + objectIntersections = &entityIntersections; + } + else { + objectIntersections = &avatarIntersections; + } + + 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["pick"] = vec3toVariant(objectIntersection.testCollisionPoint); + collisionPointPair["object"] = vec3toVariant(objectIntersection.foundCollisionPoint); + + collisionPointPairs[objectIntersection.foundID].append(collisionPointPair); + } + } + + QVariantList qIntersectingObjects; + for (auto& intersectionKeyVal : intersections) { + const QUuid& id = intersectionKeyVal.first; + QVariantMap& intersection = intersectionKeyVal.second; + + intersection["collisionPointPairs"] = collisionPointPairs[id]; + qIntersectingObjects.append(intersection); + } + + variantMap["intersectingObjects"] = qIntersectingObjects; + variantMap["collisionRegion"] = pickVariant; + + return variantMap; +} bool CollisionPick::isShapeInfoReady(CollisionRegion& pick) { if (pick.shouldComputeShapeInfo()) { diff --git a/interface/src/raypick/CollisionPick.h b/interface/src/raypick/CollisionPick.h index 29990f21a7..48b3173f19 100644 --- a/interface/src/raypick/CollisionPick.h +++ b/interface/src/raypick/CollisionPick.h @@ -35,35 +35,7 @@ public: std::vector entityIntersections; std::vector avatarIntersections; - virtual QVariantMap toVariantMap() const override { - QVariantMap variantMap; - - variantMap["intersects"] = intersects; - - QVariantList qEntityIntersections; - for (auto entityIntersection : entityIntersections) { - QVariantMap qEntityIntersection; - qEntityIntersection["objectID"] = entityIntersection.foundID; - qEntityIntersection["pickCollisionPoint"] = vec3toVariant(entityIntersection.testCollisionPoint); - qEntityIntersection["entityCollisionPoint"] = vec3toVariant(entityIntersection.foundCollisionPoint); - qEntityIntersections.append(qEntityIntersection); - } - variantMap["entityIntersections"] = qEntityIntersections; - - QVariantList qAvatarIntersections; - for (auto avatarIntersection : avatarIntersections) { - QVariantMap qAvatarIntersection; - qAvatarIntersection["objectID"] = avatarIntersection.foundID; - qAvatarIntersection["pickCollisionPoint"] = vec3toVariant(avatarIntersection.testCollisionPoint); - qAvatarIntersection["entityCollisionPoint"] = vec3toVariant(avatarIntersection.foundCollisionPoint); - qAvatarIntersections.append(qAvatarIntersection); - } - variantMap["avatarIntersections"] = qAvatarIntersections; - - variantMap["collisionRegion"] = pickVariant; - - return variantMap; - } + QVariantMap CollisionPickResult::toVariantMap() const override; bool doesIntersect() const override { return intersects; } bool checkOrFilterAgainstMaxDistance(float maxDistance) override { return true; } From eda80fb36fe5cea974f3ab4d9c94cb74cc8e38ed Mon Sep 17 00:00:00 2001 From: Alexia Mandeville Date: Thu, 2 Aug 2018 13:26:08 -0700 Subject: [PATCH 035/144] updating emote app icon --- .../icons/tablet-icons/EmoteAppIcon.svg | 54 ++++++++++++++----- 1 file changed, 40 insertions(+), 14 deletions(-) diff --git a/interface/resources/icons/tablet-icons/EmoteAppIcon.svg b/interface/resources/icons/tablet-icons/EmoteAppIcon.svg index 340f0fcd2f..382f61467f 100644 --- a/interface/resources/icons/tablet-icons/EmoteAppIcon.svg +++ b/interface/resources/icons/tablet-icons/EmoteAppIcon.svg @@ -1,21 +1,47 @@ - + - - - - + + + + + + + + + + + + + + + + + + + From 5a2506e94f6838b42a5394c1c0ea39326c6d8ca1 Mon Sep 17 00:00:00 2001 From: Alexander Ivash Date: Fri, 3 Aug 2018 17:10:41 +0300 Subject: [PATCH 036/144] FB16831 qml-related warnings in log fix is based on exposing C++ objects to QML before root object is created (as root object might already require C++ objects) --- interface/src/Application.cpp | 9 +++++++++ interface/src/ui/overlays/Web3DOverlay.cpp | 8 +++++--- interface/src/ui/overlays/Web3DOverlay.h | 2 +- libraries/ui/src/ui/OffscreenQmlSurfaceCache.cpp | 9 +++++++++ libraries/ui/src/ui/OffscreenQmlSurfaceCache.h | 3 +++ 5 files changed, 27 insertions(+), 4 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index ba7c15ad33..d9a84981a7 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2926,6 +2926,15 @@ void Application::initializeUi() { // Pre-create a couple of Web3D overlays to speed up tablet UI auto offscreenSurfaceCache = DependencyManager::get(); + offscreenSurfaceCache->setOnRootContextCreated([&](const QString& rootObject, QQmlContext* surfaceContext) { + if (rootObject == TabletScriptingInterface::QML) { + // in Qt 5.10.0 there is already an "Audio" object in the QML context + // though I failed to find it (from QtMultimedia??). So.. let it be "AudioScriptingInterface" + surfaceContext->setContextProperty("AudioScriptingInterface", DependencyManager::get().data()); + surfaceContext->setContextProperty("Account", AccountServicesScriptingInterface::getInstance()); // DEPRECATED - TO BE REMOVED + } + }); + offscreenSurfaceCache->reserve(TabletScriptingInterface::QML, 1); offscreenSurfaceCache->reserve(Web3DOverlay::QML, 2); diff --git a/interface/src/ui/overlays/Web3DOverlay.cpp b/interface/src/ui/overlays/Web3DOverlay.cpp index 12100e026c..c16b4c016d 100644 --- a/interface/src/ui/overlays/Web3DOverlay.cpp +++ b/interface/src/ui/overlays/Web3DOverlay.cpp @@ -184,9 +184,11 @@ void Web3DOverlay::buildWebSurface() { _webSurface->getRootItem()->setProperty("scriptURL", _scriptURL); } else { _webSurface = QSharedPointer(new OffscreenQmlSurface(), qmlSurfaceDeleter); + connect(_webSurface.data(), &hifi::qml::OffscreenSurface::rootContextCreated, [this](QQmlContext* surfaceContext) { + setupQmlSurface(_url == TabletScriptingInterface::QML); + }); _webSurface->load(_url); _cachedWebSurface = false; - setupQmlSurface(); } _webSurface->getSurfaceContext()->setContextProperty("globalPosition", vec3toVariant(getWorldPosition())); onResizeWebSurface(); @@ -214,7 +216,7 @@ bool Web3DOverlay::isWebContent() const { return false; } -void Web3DOverlay::setupQmlSurface() { +void Web3DOverlay::setupQmlSurface(bool isTablet) { _webSurface->getSurfaceContext()->setContextProperty("Users", DependencyManager::get().data()); _webSurface->getSurfaceContext()->setContextProperty("HMD", DependencyManager::get().data()); _webSurface->getSurfaceContext()->setContextProperty("UserActivityLogger", DependencyManager::get().data()); @@ -225,7 +227,7 @@ void Web3DOverlay::setupQmlSurface() { _webSurface->getSurfaceContext()->setContextProperty("Entities", DependencyManager::get().data()); _webSurface->getSurfaceContext()->setContextProperty("Snapshot", DependencyManager::get().data()); - if (_webSurface->getRootItem() && _webSurface->getRootItem()->objectName() == "tabletRoot") { + if (isTablet) { auto tabletScriptingInterface = DependencyManager::get(); auto flags = tabletScriptingInterface->getFlags(); diff --git a/interface/src/ui/overlays/Web3DOverlay.h b/interface/src/ui/overlays/Web3DOverlay.h index 233f4e0d21..4137ed8680 100644 --- a/interface/src/ui/overlays/Web3DOverlay.h +++ b/interface/src/ui/overlays/Web3DOverlay.h @@ -79,7 +79,7 @@ protected: Transform evalRenderTransform() override; private: - void setupQmlSurface(); + void setupQmlSurface(bool isTablet); void rebuildWebSurface(); bool isWebContent() const; diff --git a/libraries/ui/src/ui/OffscreenQmlSurfaceCache.cpp b/libraries/ui/src/ui/OffscreenQmlSurfaceCache.cpp index 51fe11fdc7..aad90d0806 100644 --- a/libraries/ui/src/ui/OffscreenQmlSurfaceCache.cpp +++ b/libraries/ui/src/ui/OffscreenQmlSurfaceCache.cpp @@ -46,8 +46,17 @@ void OffscreenQmlSurfaceCache::release(const QString& rootSource, const QSharedP QSharedPointer OffscreenQmlSurfaceCache::buildSurface(const QString& rootSource) { auto surface = QSharedPointer(new OffscreenQmlSurface()); + + QObject::connect(surface.data(), &hifi::qml::OffscreenSurface::rootContextCreated, [this, rootSource](QQmlContext* surfaceContext) { + if (_onRootContextCreated) + _onRootContextCreated(rootSource, surfaceContext); + }); + surface->load(rootSource); surface->resize(QSize(100, 100)); return surface; } +void OffscreenQmlSurfaceCache::setOnRootContextCreated(const std::function& onRootContextCreated) { + _onRootContextCreated = onRootContextCreated; +} \ No newline at end of file diff --git a/libraries/ui/src/ui/OffscreenQmlSurfaceCache.h b/libraries/ui/src/ui/OffscreenQmlSurfaceCache.h index 02382a791b..6dc8b79ca3 100644 --- a/libraries/ui/src/ui/OffscreenQmlSurfaceCache.h +++ b/libraries/ui/src/ui/OffscreenQmlSurfaceCache.h @@ -13,6 +13,7 @@ #include +class QQmlContext; class OffscreenQmlSurface; class OffscreenQmlSurfaceCache : public Dependency { @@ -25,10 +26,12 @@ public: QSharedPointer acquire(const QString& rootSource); void release(const QString& rootSource, const QSharedPointer& surface); void reserve(const QString& rootSource, int count = 1); + void setOnRootContextCreated(const std::function & onRootContextCreated); private: QSharedPointer buildSurface(const QString& rootSource); QHash>> _cache; + std::function _onRootContextCreated; }; #endif From 0b40608650ae7b43cd185910ea00d61be44779e2 Mon Sep 17 00:00:00 2001 From: sabrina-shanman Date: Fri, 3 Aug 2018 10:23:22 -0700 Subject: [PATCH 037/144] Fix collision picks not working with MyAvatar --- libraries/physics/src/PhysicsEngine.cpp | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/libraries/physics/src/PhysicsEngine.cpp b/libraries/physics/src/PhysicsEngine.cpp index 32708f6ead..5ba6501006 100644 --- a/libraries/physics/src/PhysicsEngine.cpp +++ b/libraries/physics/src/PhysicsEngine.cpp @@ -841,11 +841,12 @@ void PhysicsEngine::setShowBulletConstraintLimits(bool value) { } struct AllContactsCallback : public btCollisionWorld::ContactResultCallback { - AllContactsCallback(MotionStateType desiredObjectType, const ShapeInfo& shapeInfo, const Transform& transform) : + AllContactsCallback(MotionStateType desiredObjectType, const ShapeInfo& shapeInfo, const Transform& transform, btCollisionObject* myAvatarCollisionObject) : desiredObjectType(desiredObjectType), collisionObject(), contacts(), - btCollisionWorld::ContactResultCallback() { + btCollisionWorld::ContactResultCallback(), + myAvatarCollisionObject(myAvatarCollisionObject) { const btCollisionShape* collisionShape = ObjectMotionState::getShapeManager()->getShape(shapeInfo); collisionObject.setCollisionShape(const_cast(collisionShape)); @@ -864,6 +865,7 @@ struct AllContactsCallback : public btCollisionWorld::ContactResultCallback { MotionStateType desiredObjectType; btCollisionObject collisionObject; std::vector contacts; + btCollisionObject* myAvatarCollisionObject; bool needsCollision(btBroadphaseProxy* proxy) const override { return true; @@ -884,12 +886,17 @@ struct AllContactsCallback : public btCollisionWorld::ContactResultCallback { otherPenetrationPoint = getWorldPoint(cp.m_localPointB, colObj1->getWorldTransform()); } + // TODO: Give MyAvatar a motion state so we don't have to do this + if (desiredObjectType == MOTIONSTATE_TYPE_AVATAR && myAvatarCollisionObject && myAvatarCollisionObject == otherBody) { + contacts.emplace_back(Physics::getSessionUUID(), bulletToGLM(penetrationPoint), bulletToGLM(otherPenetrationPoint)); + } + if (!(otherBody->getInternalType() & btCollisionObject::CO_RIGID_BODY)) { return 0; } const btRigidBody* collisionCandidate = static_cast(otherBody); - const btMotionState* motionStateCandidate = collisionCandidate->getMotionState(); + const btMotionState* motionStateCandidate = collisionCandidate->getMotionState(); const ObjectMotionState* candidate = dynamic_cast(motionStateCandidate); if (!candidate || candidate->getType() != desiredObjectType) { return 0; @@ -908,7 +915,13 @@ protected: }; const std::vector PhysicsEngine::getCollidingInRegion(MotionStateType desiredObjectType, const ShapeInfo& regionShapeInfo, const Transform& regionTransform) const { - auto contactCallback = AllContactsCallback(desiredObjectType, regionShapeInfo, regionTransform); + // TODO: Give MyAvatar a motion state so we don't have to do this + btCollisionObject* myAvatarCollisionObject = nullptr; + if (desiredObjectType == MOTIONSTATE_TYPE_AVATAR && _myAvatarController) { + myAvatarCollisionObject = _myAvatarController->getCollisionObject(); + } + + auto contactCallback = AllContactsCallback(desiredObjectType, regionShapeInfo, regionTransform, myAvatarCollisionObject); _dynamicsWorld->contactTest(&contactCallback.collisionObject, contactCallback); return contactCallback.contacts; From b7d5804edbceeb6bdd96f3915d8f69ab01fa49de Mon Sep 17 00:00:00 2001 From: sabrina-shanman Date: Fri, 3 Aug 2018 12:55:11 -0700 Subject: [PATCH 038/144] Format if..else statements --- libraries/physics/src/PhysicsEngine.cpp | 3 +-- libraries/shared/src/RegisteredMetaTypes.h | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/libraries/physics/src/PhysicsEngine.cpp b/libraries/physics/src/PhysicsEngine.cpp index 5ba6501006..d55fbf7e34 100644 --- a/libraries/physics/src/PhysicsEngine.cpp +++ b/libraries/physics/src/PhysicsEngine.cpp @@ -879,8 +879,7 @@ struct AllContactsCallback : public btCollisionWorld::ContactResultCallback { otherBody = colObj1->m_collisionObject; penetrationPoint = getWorldPoint(cp.m_localPointB, colObj1->getWorldTransform()); otherPenetrationPoint = getWorldPoint(cp.m_localPointA, colObj0->getWorldTransform()); - } - else { + } else { otherBody = colObj0->m_collisionObject; penetrationPoint = getWorldPoint(cp.m_localPointA, colObj0->getWorldTransform()); otherPenetrationPoint = getWorldPoint(cp.m_localPointB, colObj1->getWorldTransform()); diff --git a/libraries/shared/src/RegisteredMetaTypes.h b/libraries/shared/src/RegisteredMetaTypes.h index 69fa5b2b5d..a96903100e 100644 --- a/libraries/shared/src/RegisteredMetaTypes.h +++ b/libraries/shared/src/RegisteredMetaTypes.h @@ -235,8 +235,7 @@ public: if (shapeType >= SHAPE_TYPE_COMPOUND && shapeType <= SHAPE_TYPE_STATIC_MESH && shape["modelURL"].isValid()) { QString newURL = shape["modelURL"].toString(); modelURL.setUrl(newURL); - } - else { + } else { modelURL.setUrl(""); } From c58322387a9cb1bb976d85ea9e581917cfdf80b8 Mon Sep 17 00:00:00 2001 From: Alexia Mandeville Date: Fri, 3 Aug 2018 13:05:20 -0700 Subject: [PATCH 039/144] Increase stroke width on icon --- .../icons/tablet-icons/EmoteAppIcon.svg | 132 ++++++++++++++---- 1 file changed, 102 insertions(+), 30 deletions(-) diff --git a/interface/resources/icons/tablet-icons/EmoteAppIcon.svg b/interface/resources/icons/tablet-icons/EmoteAppIcon.svg index 382f61467f..4882337358 100644 --- a/interface/resources/icons/tablet-icons/EmoteAppIcon.svg +++ b/interface/resources/icons/tablet-icons/EmoteAppIcon.svg @@ -1,5 +1,5 @@ - + From b3426f695bb4a8a371322c8834a13d6d6330cb4e Mon Sep 17 00:00:00 2001 From: Alexia Mandeville Date: Fri, 3 Aug 2018 15:12:18 -0700 Subject: [PATCH 040/144] Making icon bigger --- .../icons/tablet-icons/EmoteAppIcon.svg | 142 ++++-------------- 1 file changed, 31 insertions(+), 111 deletions(-) diff --git a/interface/resources/icons/tablet-icons/EmoteAppIcon.svg b/interface/resources/icons/tablet-icons/EmoteAppIcon.svg index 4882337358..cef6e8771b 100644 --- a/interface/resources/icons/tablet-icons/EmoteAppIcon.svg +++ b/interface/resources/icons/tablet-icons/EmoteAppIcon.svg @@ -5,115 +5,35 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + From f9d9324a14848cf2979654d35075859791ccb81f Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sat, 4 Aug 2018 10:55:57 +1200 Subject: [PATCH 041/144] Improve text in Avatar app's pop-ups --- interface/resources/qml/hifi/avatarapp/MessageBoxes.qml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/interface/resources/qml/hifi/avatarapp/MessageBoxes.qml b/interface/resources/qml/hifi/avatarapp/MessageBoxes.qml index 90f55fd8bb..bdb0462f42 100644 --- a/interface/resources/qml/hifi/avatarapp/MessageBoxes.qml +++ b/interface/resources/qml/hifi/avatarapp/MessageBoxes.qml @@ -41,8 +41,8 @@ MessageBox { popup.button1text = 'CANCEL' popup.titleText = 'Get Wearables' popup.bodyText = 'Buy wearables from Marketplace.' + '
' + - 'Wear wearables from My Purchases.' + '
' + '
' + - 'Visit “AvatarIsland” to get wearables' + 'Use wearables in My Purchases.' + '
' + '
' + + '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 Marketplace.' + '
' + - 'Wear avatars from My Purchases.' + '
' + '
' + + 'Wear avatars in My Purchases.' + '
' + '
' + 'Visit “BodyMart” to get free avatars.' popup.imageSource = getAvatarsUrl; From 59271881baada477fd970bd26fb5d3d3de45992a Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Fri, 3 Aug 2018 17:45:06 -0700 Subject: [PATCH 042/144] Create & destroy ScriptEngines on AC thread but move to main after creation --- assignment-client/src/Agent.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index f1bdaaad12..b9d1db2b3e 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -82,8 +82,6 @@ Agent::Agent(ReceivedMessage& message) : DependencyManager::set(); DependencyManager::set(); - DependencyManager::set(ScriptEngine::AGENT_SCRIPT); - DependencyManager::set(); DependencyManager::set(); @@ -162,6 +160,8 @@ void Agent::handleAudioPacket(QSharedPointer message) { static const QString AGENT_LOGGING_NAME = "agent"; void Agent::run() { + // Create ScriptEngines on threaded-assignment thread then move to main thread. + DependencyManager::set(ScriptEngine::AGENT_SCRIPT)->moveToThread(qApp->thread()); // make sure we request our script once the agent connects to the domain auto nodeList = DependencyManager::get(); @@ -497,7 +497,6 @@ void Agent::executeScript() { Frame::clearFrameHandler(AUDIO_FRAME_TYPE); Frame::clearFrameHandler(AVATAR_FRAME_TYPE); - DependencyManager::destroy(); setFinished(true); } @@ -848,7 +847,8 @@ void Agent::aboutToFinish() { DependencyManager::destroy(); DependencyManager::destroy(); DependencyManager::destroy(); - + DependencyManager::destroy(); + DependencyManager::destroy(); QMetaObject::invokeMethod(&_avatarAudioTimer, "stop"); // cleanup codec & encoder From cf34a2cffd5dc5833ed486c3729a40367c1dab02 Mon Sep 17 00:00:00 2001 From: sabrina-shanman Date: Fri, 3 Aug 2018 13:43:19 -0700 Subject: [PATCH 043/144] Do not const cast CollisionRegion --- interface/src/raypick/CollisionPick.cpp | 21 ++++++++------------- interface/src/raypick/CollisionPick.h | 5 ++++- libraries/shared/src/RegisteredMetaTypes.h | 20 ++++++++++---------- 3 files changed, 22 insertions(+), 24 deletions(-) diff --git a/interface/src/raypick/CollisionPick.cpp b/interface/src/raypick/CollisionPick.cpp index 0531a5a67d..d3411a77ca 100644 --- a/interface/src/raypick/CollisionPick.cpp +++ b/interface/src/raypick/CollisionPick.cpp @@ -13,7 +13,6 @@ #include #include "ScriptEngineLogging.h" -#include "model-networking/ModelCache.h" #include "UUIDHasher.h" QVariantMap CollisionPickResult::toVariantMap() const { @@ -70,14 +69,10 @@ QVariantMap CollisionPickResult::toVariantMap() const { return variantMap; } -bool CollisionPick::isShapeInfoReady(CollisionRegion& pick) { - if (pick.shouldComputeShapeInfo()) { - if (!_cachedResource || _cachedResource->getURL() != pick.modelURL) { - _cachedResource = DependencyManager::get()->getCollisionGeometryResource(pick.modelURL); - } - - if (_cachedResource->isLoaded()) { - computeShapeInfo(pick, pick.shapeInfo, _cachedResource); +bool CollisionPick::isShapeInfoReady() { + if (_mathPick.shouldComputeShapeInfo()) { + if (_cachedResource && _cachedResource->isLoaded()) { + computeShapeInfo(_mathPick, *_mathPick.shapeInfo, _cachedResource); return true; } @@ -321,12 +316,12 @@ CollisionRegion CollisionPick::getMathematicalPick() const { } PickResultPointer CollisionPick::getEntityIntersection(const CollisionRegion& pick) { - if (!isShapeInfoReady(*const_cast(&pick))) { + if (!isShapeInfoReady()) { // Cannot compute result return std::make_shared(); } - const auto& entityIntersections = _physicsEngine->getCollidingInRegion(MOTIONSTATE_TYPE_ENTITY, pick.shapeInfo, pick.transform); + const auto& entityIntersections = _physicsEngine->getCollidingInRegion(MOTIONSTATE_TYPE_ENTITY, *pick.shapeInfo, pick.transform); return std::make_shared(pick, entityIntersections, std::vector()); } @@ -335,12 +330,12 @@ PickResultPointer CollisionPick::getOverlayIntersection(const CollisionRegion& p } PickResultPointer CollisionPick::getAvatarIntersection(const CollisionRegion& pick) { - if (!isShapeInfoReady(*const_cast(&pick))) { + if (!isShapeInfoReady()) { // Cannot compute result return std::make_shared(); } - const auto& avatarIntersections = _physicsEngine->getCollidingInRegion(MOTIONSTATE_TYPE_AVATAR, pick.shapeInfo, pick.transform); + const auto& avatarIntersections = _physicsEngine->getCollidingInRegion(MOTIONSTATE_TYPE_AVATAR, *pick.shapeInfo, pick.transform); return std::make_shared(pick, std::vector(), avatarIntersections); } diff --git a/interface/src/raypick/CollisionPick.h b/interface/src/raypick/CollisionPick.h index 48b3173f19..72f5450c41 100644 --- a/interface/src/raypick/CollisionPick.h +++ b/interface/src/raypick/CollisionPick.h @@ -62,6 +62,9 @@ public: Pick(filter, maxDistance, enabled), _mathPick(collisionRegion), _physicsEngine(physicsEngine) { + if (collisionRegion.shouldComputeShapeInfo()) { + _cachedResource = DependencyManager::get()->getCollisionGeometryResource(collisionRegion.modelURL); + } } CollisionRegion getMathematicalPick() const override; @@ -73,7 +76,7 @@ public: protected: // Returns true if pick.shapeInfo is valid. Otherwise, attempts to get the shapeInfo ready for use. - bool isShapeInfoReady(CollisionRegion& pick); + bool isShapeInfoReady(); void computeShapeInfo(CollisionRegion& pick, ShapeInfo& shapeInfo, QSharedPointer resource); CollisionRegion _mathPick; diff --git a/libraries/shared/src/RegisteredMetaTypes.h b/libraries/shared/src/RegisteredMetaTypes.h index a96903100e..151526adbd 100644 --- a/libraries/shared/src/RegisteredMetaTypes.h +++ b/libraries/shared/src/RegisteredMetaTypes.h @@ -243,7 +243,7 @@ public: transform.setScale(vec3FromVariant(shape["dimensions"])); } - shapeInfo.setParams(shapeType, transform.getScale() / 2.0f, modelURL.toString()); + shapeInfo->setParams(shapeType, transform.getScale() / 2.0f, modelURL.toString()); } } @@ -259,7 +259,7 @@ public: QVariantMap collisionRegion; QVariantMap shape; - shape["shapeType"] = ShapeInfo::getNameForShapeType(shapeInfo.getType()); + shape["shapeType"] = ShapeInfo::getNameForShapeType(shapeInfo->getType()); shape["modelURL"] = modelURL.toString(); shape["dimensions"] = vec3toVariant(transform.getScale()); @@ -274,33 +274,33 @@ public: operator bool() const override { return !(glm::any(glm::isnan(transform.getTranslation())) || glm::any(glm::isnan(transform.getRotation())) || - shapeInfo.getType() == SHAPE_TYPE_NONE); + shapeInfo->getType() == SHAPE_TYPE_NONE); } bool operator==(const CollisionRegion& other) const { return glm::all(glm::equal(transform.getTranslation(), other.transform.getTranslation())) && glm::all(glm::equal(transform.getRotation(), other.transform.getRotation())) && glm::all(glm::equal(transform.getScale(), other.transform.getScale())) && - shapeInfo.getType() == other.shapeInfo.getType() && + shapeInfo->getType() == other.shapeInfo->getType() && modelURL == other.modelURL; } bool shouldComputeShapeInfo() const { - if (!(shapeInfo.getType() == SHAPE_TYPE_HULL || - (shapeInfo.getType() >= SHAPE_TYPE_COMPOUND && - shapeInfo.getType() <= SHAPE_TYPE_STATIC_MESH) + if (!(shapeInfo->getType() == SHAPE_TYPE_HULL || + (shapeInfo->getType() >= SHAPE_TYPE_COMPOUND && + shapeInfo->getType() <= SHAPE_TYPE_STATIC_MESH) )) { return false; } - return !shapeInfo.getPointCollection().size(); + return !shapeInfo->getPointCollection().size(); } // We can't load the model here because it would create a circular dependency, so we delegate that responsibility to the owning CollisionPick QUrl modelURL; // We can't compute the shapeInfo here without loading the model first, so we delegate that responsibility to the owning CollisionPick - ShapeInfo shapeInfo; + std::shared_ptr shapeInfo = std::make_shared(); Transform transform; }; @@ -371,7 +371,7 @@ namespace std { struct hash { size_t operator()(const CollisionRegion& a) const { size_t result = 0; - hash_combine(result, a.transform, (int)a.shapeInfo.getType(), qHash(a.modelURL)); + hash_combine(result, a.transform, (int)a.shapeInfo->getType(), qHash(a.modelURL)); return result; } }; From e7766039b9e66ca8c22a768d22be6f0bfcc668d2 Mon Sep 17 00:00:00 2001 From: sabrina-shanman Date: Fri, 3 Aug 2018 14:08:47 -0700 Subject: [PATCH 044/144] Pass pick variant map into unsuccessful/default collision results --- interface/src/raypick/CollisionPick.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/interface/src/raypick/CollisionPick.cpp b/interface/src/raypick/CollisionPick.cpp index d3411a77ca..eac71ab6ce 100644 --- a/interface/src/raypick/CollisionPick.cpp +++ b/interface/src/raypick/CollisionPick.cpp @@ -318,7 +318,7 @@ CollisionRegion CollisionPick::getMathematicalPick() const { PickResultPointer CollisionPick::getEntityIntersection(const CollisionRegion& pick) { if (!isShapeInfoReady()) { // Cannot compute result - return std::make_shared(); + return std::make_shared(pick.toVariantMap()); } const auto& entityIntersections = _physicsEngine->getCollidingInRegion(MOTIONSTATE_TYPE_ENTITY, *pick.shapeInfo, pick.transform); @@ -326,13 +326,13 @@ PickResultPointer CollisionPick::getEntityIntersection(const CollisionRegion& pi } PickResultPointer CollisionPick::getOverlayIntersection(const CollisionRegion& pick) { - return getDefaultResult(QVariantMap()); + return getDefaultResult(pick.toVariantMap()); } PickResultPointer CollisionPick::getAvatarIntersection(const CollisionRegion& pick) { if (!isShapeInfoReady()) { // Cannot compute result - return std::make_shared(); + return std::make_shared(pick.toVariantMap()); } const auto& avatarIntersections = _physicsEngine->getCollidingInRegion(MOTIONSTATE_TYPE_AVATAR, *pick.shapeInfo, pick.transform); @@ -340,5 +340,5 @@ PickResultPointer CollisionPick::getAvatarIntersection(const CollisionRegion& pi } PickResultPointer CollisionPick::getHUDIntersection(const CollisionRegion& pick) { - return getDefaultResult(QVariantMap()); + return getDefaultResult(pick.toVariantMap()); } \ No newline at end of file From c6b24496c85296ef461d2f765186d0e2fd758efa Mon Sep 17 00:00:00 2001 From: sabrina-shanman Date: Fri, 3 Aug 2018 14:51:08 -0700 Subject: [PATCH 045/144] Add 'loaded' parameter to CollisionPickResult to indicate if the collision region is loaded --- interface/src/raypick/CollisionPick.cpp | 13 +++++++------ interface/src/raypick/CollisionPick.h | 18 ++++++++++++++++-- interface/src/raypick/PickScriptingInterface.h | 1 + 3 files changed, 24 insertions(+), 8 deletions(-) diff --git a/interface/src/raypick/CollisionPick.cpp b/interface/src/raypick/CollisionPick.cpp index eac71ab6ce..e96ccb1580 100644 --- a/interface/src/raypick/CollisionPick.cpp +++ b/interface/src/raypick/CollisionPick.cpp @@ -64,6 +64,7 @@ QVariantMap CollisionPickResult::toVariantMap() const { } variantMap["intersectingObjects"] = qIntersectingObjects; + variantMap["loaded"] = (loadState == LOAD_STATE_LOADED); variantMap["collisionRegion"] = pickVariant; return variantMap; @@ -318,27 +319,27 @@ CollisionRegion CollisionPick::getMathematicalPick() const { PickResultPointer CollisionPick::getEntityIntersection(const CollisionRegion& pick) { if (!isShapeInfoReady()) { // Cannot compute result - return std::make_shared(pick.toVariantMap()); + return std::make_shared(pick.toVariantMap(), CollisionPickResult::LOAD_STATE_NOT_LOADED, std::vector(), std::vector()); } const auto& entityIntersections = _physicsEngine->getCollidingInRegion(MOTIONSTATE_TYPE_ENTITY, *pick.shapeInfo, pick.transform); - return std::make_shared(pick, entityIntersections, std::vector()); + return std::make_shared(pick, CollisionPickResult::LOAD_STATE_LOADED, entityIntersections, std::vector()); } PickResultPointer CollisionPick::getOverlayIntersection(const CollisionRegion& pick) { - return getDefaultResult(pick.toVariantMap()); + return std::make_shared(pick.toVariantMap(), isShapeInfoReady() ? CollisionPickResult::LOAD_STATE_LOADED : CollisionPickResult::LOAD_STATE_NOT_LOADED, std::vector(), std::vector()); } PickResultPointer CollisionPick::getAvatarIntersection(const CollisionRegion& pick) { if (!isShapeInfoReady()) { // Cannot compute result - return std::make_shared(pick.toVariantMap()); + return std::make_shared(pick.toVariantMap(), CollisionPickResult::LOAD_STATE_NOT_LOADED, std::vector(), std::vector()); } const auto& avatarIntersections = _physicsEngine->getCollidingInRegion(MOTIONSTATE_TYPE_AVATAR, *pick.shapeInfo, pick.transform); - return std::make_shared(pick, std::vector(), avatarIntersections); + return std::make_shared(pick, CollisionPickResult::LOAD_STATE_LOADED, std::vector(), avatarIntersections); } PickResultPointer CollisionPick::getHUDIntersection(const CollisionRegion& pick) { - return getDefaultResult(pick.toVariantMap()); + return std::make_shared(pick.toVariantMap(), isShapeInfoReady() ? CollisionPickResult::LOAD_STATE_LOADED : CollisionPickResult::LOAD_STATE_NOT_LOADED, std::vector(), std::vector()); } \ No newline at end of file diff --git a/interface/src/raypick/CollisionPick.h b/interface/src/raypick/CollisionPick.h index 72f5450c41..5a3260b981 100644 --- a/interface/src/raypick/CollisionPick.h +++ b/interface/src/raypick/CollisionPick.h @@ -15,11 +15,18 @@ 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, const std::vector& entityIntersections, const std::vector& avatarIntersections) : + CollisionPickResult(const CollisionRegion& searchRegion, LoadState loadState, const std::vector& entityIntersections, const std::vector& avatarIntersections) : PickResult(searchRegion.toVariantMap()), + loadState(loadState), intersects(entityIntersections.size() || avatarIntersections.size()), entityIntersections(entityIntersections), avatarIntersections(avatarIntersections) { @@ -29,8 +36,10 @@ public: avatarIntersections = collisionPickResult.avatarIntersections; entityIntersections = collisionPickResult.entityIntersections; intersects = avatarIntersections.size() || entityIntersections.size(); + loadState = collisionPickResult.loadState; } + LoadState loadState { LOAD_STATE_UNKNOWN }; bool intersects { false }; std::vector entityIntersections; std::vector avatarIntersections; @@ -51,6 +60,9 @@ public: } intersects = entityIntersections.size() || avatarIntersections.size(); + if (newCollisionResult->loadState == LOAD_STATE_NOT_LOADED || loadState == LOAD_STATE_UNKNOWN) { + loadState = (LoadState)newCollisionResult->loadState; + } return std::make_shared(*this); } @@ -68,7 +80,9 @@ public: } CollisionRegion getMathematicalPick() const override; - PickResultPointer getDefaultResult(const QVariantMap& pickVariant) const override { return std::make_shared(pickVariant); } + PickResultPointer getDefaultResult(const QVariantMap& pickVariant) const override { + return std::make_shared(pickVariant, CollisionPickResult::LOAD_STATE_UNKNOWN, std::vector(), std::vector()); + } PickResultPointer getEntityIntersection(const CollisionRegion& pick) override; PickResultPointer getOverlayIntersection(const CollisionRegion& pick) override; PickResultPointer getAvatarIntersection(const CollisionRegion& pick) override; diff --git a/interface/src/raypick/PickScriptingInterface.h b/interface/src/raypick/PickScriptingInterface.h index 4c857585be..35196663ab 100644 --- a/interface/src/raypick/PickScriptingInterface.h +++ b/interface/src/raypick/PickScriptingInterface.h @@ -135,6 +135,7 @@ public: * @property {EntityItersection[]} entityIntersections The collision information of entities which intersect with the CollisionRegion. There may be multiple intersections with the same entity which represent distinct collision points. * @property {EntityItersection[]} avatarIntersections The collision information of avatars which intersect with the CollisionRegion. There may be multiple intersections with the same entity which represent distinct collision points. * @property {CollisionRegion} collisionRegion The CollisionRegion that was used. Valid even if there was no intersection. + * @property {boolean} loaded If the CollisionRegion was successfully loaded (may be false if a model was used) */ /**jsdoc From a460cfac29ed226251369dbc1e45722dfd27dc05 Mon Sep 17 00:00:00 2001 From: sabrina-shanman Date: Fri, 3 Aug 2018 15:35:59 -0700 Subject: [PATCH 046/144] Do not store physics engine pointer in PickScriptingInterface --- interface/src/Application.cpp | 1 - interface/src/raypick/PickScriptingInterface.cpp | 3 ++- interface/src/raypick/PickScriptingInterface.h | 8 -------- 3 files changed, 2 insertions(+), 10 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index bc2055bd06..698a571b66 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -6656,7 +6656,6 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEnginePointe auto pickScriptingInterface = DependencyManager::get(); pickScriptingInterface->registerMetaTypes(scriptEngine.data()); - pickScriptingInterface->setPhysicsEngine(_physicsEngine); // connect this script engines printedMessage signal to the global ScriptEngines these various messages connect(scriptEngine.data(), &ScriptEngine::printedMessage, diff --git a/interface/src/raypick/PickScriptingInterface.cpp b/interface/src/raypick/PickScriptingInterface.cpp index 384a193f25..5e2a26712e 100644 --- a/interface/src/raypick/PickScriptingInterface.cpp +++ b/interface/src/raypick/PickScriptingInterface.cpp @@ -11,6 +11,7 @@ #include #include "GLMHelpers.h" +#include "Application.h" #include #include "StaticRayPick.h" @@ -174,7 +175,7 @@ unsigned int PickScriptingInterface::createCollisionPick(const QVariant& propert CollisionRegion collisionRegion(propMap); - return DependencyManager::get()->addPick(PickQuery::Collision, std::make_shared(filter, maxDistance, enabled, collisionRegion, _physicsEngine)); + return DependencyManager::get()->addPick(PickQuery::Collision, std::make_shared(filter, maxDistance, enabled, collisionRegion, qApp->getPhysicsEngine())); } void PickScriptingInterface::enablePick(unsigned int uid) { diff --git a/interface/src/raypick/PickScriptingInterface.h b/interface/src/raypick/PickScriptingInterface.h index 35196663ab..001f6cd20c 100644 --- a/interface/src/raypick/PickScriptingInterface.h +++ b/interface/src/raypick/PickScriptingInterface.h @@ -295,14 +295,6 @@ public slots: * @returns {number} */ static constexpr unsigned int INTERSECTED_HUD() { return IntersectionType::HUD; } - - // Set to allow CollisionPicks to have access to the physics engine - void setPhysicsEngine(PhysicsEnginePointer physicsEngine) { - _physicsEngine = physicsEngine; - } - -protected: - PhysicsEnginePointer _physicsEngine; }; #endif // hifi_PickScriptingInterface_h From c4e8205a797a05b563de9b5a4cf6452e78d7bfde Mon Sep 17 00:00:00 2001 From: Robin Wilson Date: Sat, 4 Aug 2018 14:02:05 -0700 Subject: [PATCH 047/144] reordered build.md instructions --- BUILD.md | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/BUILD.md b/BUILD.md index ba38f4b51d..4a0274cea6 100644 --- a/BUILD.md +++ b/BUILD.md @@ -1,3 +1,10 @@ +### OS Specific Build Guides + +* [BUILD_WIN.md](BUILD_WIN.md) - complete instructions for Windows. +* [BUILD_OSX.md](BUILD_OSX.md) - additional instructions for OS X. +* [BUILD_LINUX.md](BUILD_LINUX.md) - additional instructions for Linux. +* [BUILD_ANDROID.md](BUILD_ANDROID.md) - additional instructions for Android + ### Dependencies - [cmake](https://cmake.org/download/): 3.9 @@ -27,14 +34,7 @@ These are not placed in your normal build tree when doing an out of source build If you would like to use a specific install of a dependency instead of the version that would be grabbed as a CMake ExternalProject, you can pass -DUSE\_LOCAL\_$NAME=0 (where $NAME is the name of the subfolder in [cmake/externals](cmake/externals)) when you run CMake to tell it not to get that dependency as an external project. -### OS Specific Build Guides - -* [BUILD_OSX.md](BUILD_OSX.md) - additional instructions for OS X. -* [BUILD_LINUX.md](BUILD_LINUX.md) - additional instructions for Linux. -* [BUILD_WIN.md](BUILD_WIN.md) - additional instructions for Windows. -* [BUILD_ANDROID.md](BUILD_ANDROID.md) - additional instructions for Android - -### CMake +#### CMake Hifi uses CMake to generate build files and project files for your platform. @@ -80,6 +80,7 @@ In the examples below the variable $NAME would be replaced by the name of the de * $NAME_ROOT_DIR - set this variable in your ENV * HIFI_LIB_DIR - set this variable in your ENV to your High Fidelity lib folder, should contain a folder '$name' + ### Optional Components #### Devices From 658ef4e9e69b87e57fb7286b5569bf914d0af12a Mon Sep 17 00:00:00 2001 From: sabrina-shanman Date: Mon, 6 Aug 2018 08:54:55 -0700 Subject: [PATCH 048/144] Remove redundant namespace qualifier on CollisionPickResult::toVariantMap() in header file --- interface/src/raypick/CollisionPick.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/raypick/CollisionPick.h b/interface/src/raypick/CollisionPick.h index 5a3260b981..45e0617343 100644 --- a/interface/src/raypick/CollisionPick.h +++ b/interface/src/raypick/CollisionPick.h @@ -44,7 +44,7 @@ public: std::vector entityIntersections; std::vector avatarIntersections; - QVariantMap CollisionPickResult::toVariantMap() const override; + QVariantMap toVariantMap() const override; bool doesIntersect() const override { return intersects; } bool checkOrFilterAgainstMaxDistance(float maxDistance) override { return true; } From c40db2e8f0c71dc90ba02285c620f180e50a2529 Mon Sep 17 00:00:00 2001 From: sabrina-shanman Date: Mon, 6 Aug 2018 10:50:23 -0700 Subject: [PATCH 049/144] Add pick filtering to CollisionPick --- interface/src/raypick/CollisionPick.cpp | 20 ++++++++++++++++++-- interface/src/raypick/CollisionPick.h | 1 + 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/interface/src/raypick/CollisionPick.cpp b/interface/src/raypick/CollisionPick.cpp index e96ccb1580..d5b1949f31 100644 --- a/interface/src/raypick/CollisionPick.cpp +++ b/interface/src/raypick/CollisionPick.cpp @@ -316,13 +316,29 @@ CollisionRegion CollisionPick::getMathematicalPick() const { return _mathPick; } +const std::vector CollisionPick::filterIntersections(const std::vector& intersections) const { + std::vector filteredIntersections; + + const QVector& ignoreItems = getIgnoreItems(); + const QVector& 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(pick.toVariantMap(), CollisionPickResult::LOAD_STATE_NOT_LOADED, std::vector(), std::vector()); } - const auto& entityIntersections = _physicsEngine->getCollidingInRegion(MOTIONSTATE_TYPE_ENTITY, *pick.shapeInfo, pick.transform); + const auto& entityIntersections = filterIntersections(_physicsEngine->getCollidingInRegion(MOTIONSTATE_TYPE_ENTITY, *pick.shapeInfo, pick.transform)); return std::make_shared(pick, CollisionPickResult::LOAD_STATE_LOADED, entityIntersections, std::vector()); } @@ -336,7 +352,7 @@ PickResultPointer CollisionPick::getAvatarIntersection(const CollisionRegion& pi return std::make_shared(pick.toVariantMap(), CollisionPickResult::LOAD_STATE_NOT_LOADED, std::vector(), std::vector()); } - const auto& avatarIntersections = _physicsEngine->getCollidingInRegion(MOTIONSTATE_TYPE_AVATAR, *pick.shapeInfo, pick.transform); + const auto& avatarIntersections = filterIntersections(_physicsEngine->getCollidingInRegion(MOTIONSTATE_TYPE_AVATAR, *pick.shapeInfo, pick.transform)); return std::make_shared(pick, CollisionPickResult::LOAD_STATE_LOADED, std::vector(), avatarIntersections); } diff --git a/interface/src/raypick/CollisionPick.h b/interface/src/raypick/CollisionPick.h index 45e0617343..c6f5b8755f 100644 --- a/interface/src/raypick/CollisionPick.h +++ b/interface/src/raypick/CollisionPick.h @@ -92,6 +92,7 @@ protected: // Returns true if pick.shapeInfo is valid. Otherwise, attempts to get the shapeInfo ready for use. bool isShapeInfoReady(); void computeShapeInfo(CollisionRegion& pick, ShapeInfo& shapeInfo, QSharedPointer resource); + const std::vector filterIntersections(const std::vector& intersections) const; CollisionRegion _mathPick; PhysicsEnginePointer _physicsEngine; From 577379dcb4651c02413b95604db2b6cde95d2544 Mon Sep 17 00:00:00 2001 From: sabrina-shanman Date: Mon, 6 Aug 2018 10:53:03 -0700 Subject: [PATCH 050/144] Do not re-calculate intersects flag in CollisionPickResult copy constructor --- interface/src/raypick/CollisionPick.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/raypick/CollisionPick.h b/interface/src/raypick/CollisionPick.h index c6f5b8755f..b3a7186893 100644 --- a/interface/src/raypick/CollisionPick.h +++ b/interface/src/raypick/CollisionPick.h @@ -35,7 +35,7 @@ public: CollisionPickResult(const CollisionPickResult& collisionPickResult) : PickResult(collisionPickResult.pickVariant) { avatarIntersections = collisionPickResult.avatarIntersections; entityIntersections = collisionPickResult.entityIntersections; - intersects = avatarIntersections.size() || entityIntersections.size(); + intersects = collisionPickResult.intersects; loadState = collisionPickResult.loadState; } From 9806bce403a57fa1f75b7646c7e8160338a2675a Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Mon, 6 Aug 2018 11:21:32 -0700 Subject: [PATCH 051/144] weak pointers to nodeList --- domain-server/src/DomainGatekeeper.cpp | 6 ++++-- domain-server/src/DomainServer.cpp | 22 ++++++++++++--------- interface/src/Application.cpp | 6 ++++-- interface/src/avatar/AvatarManager.cpp | 21 ++++++++++++-------- interface/src/ui/PreferencesDialog.cpp | 18 ++++++++++++++--- libraries/ui/src/ui/OffscreenQmlSurface.cpp | 2 +- 6 files changed, 50 insertions(+), 25 deletions(-) diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index 39ec5463dc..2307b1be3b 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -264,7 +264,8 @@ void DomainGatekeeper::updateNodePermissions() { QList nodesToKill; auto limitedNodeList = DependencyManager::get(); - limitedNodeList->eachNode([this, &limitedNodeList, &nodesToKill](const SharedNodePointer& node){ + QWeakPointer limitedNodeListWeak = limitedNodeList; + limitedNodeList->eachNode([this, limitedNodeListWeak, &nodesToKill](const SharedNodePointer& node){ // the id and the username in NodePermissions will often be the same, but id is set before // authentication and verifiedUsername is only set once they user's key has been confirmed. QString verifiedUsername = node->getPermissions().getVerifiedUserName(); @@ -296,7 +297,8 @@ void DomainGatekeeper::updateNodePermissions() { machineFingerprint = nodeData->getMachineFingerprint(); auto sendingAddress = nodeData->getSendingSockAddr().getAddress(); - isLocalUser = (sendingAddress == limitedNodeList->getLocalSockAddr().getAddress() || + auto nodeList = limitedNodeListWeak.lock(); + isLocalUser = ((nodeList && sendingAddress == nodeList->getLocalSockAddr().getAddress()) || sendingAddress == QHostAddress::LocalHost); } diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 05555314f3..7bf8720c86 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -1198,6 +1198,7 @@ QUuid DomainServer::connectionSecretForNodes(const SharedNodePointer& nodeA, con void DomainServer::broadcastNewNode(const SharedNodePointer& addedNode) { auto limitedNodeList = DependencyManager::get(); + QWeakPointer limitedNodeListWeak = limitedNodeList; auto addNodePacket = NLPacket::create(PacketType::DomainServerAddedNode); @@ -1217,16 +1218,19 @@ void DomainServer::broadcastNewNode(const SharedNodePointer& addedNode) { return false; } }, - [this, &addNodePacket, connectionSecretIndex, addedNode, &limitedNodeList](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); + } } ); } diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index ca2d9a919e..ed92a3bd7d 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1138,8 +1138,10 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo // setup a timer for domain-server check ins QTimer* domainCheckInTimer = new QTimer(this); - connect(domainCheckInTimer, &QTimer::timeout, [this, &nodeList] { - if (!isServerlessMode()) { + QWeakPointer nodeListWeak = nodeList; + connect(domainCheckInTimer, &QTimer::timeout, [this, nodeListWeak] { + auto nodeList = nodeListWeak.lock(); + if (!isServerlessMode() && nodeList) { nodeList->sendDomainServerCheckIn(); } }); diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index eb96554cb2..01c9872cd7 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -333,16 +333,21 @@ void AvatarManager::postUpdate(float deltaTime, const render::ScenePointer& scen void AvatarManager::sendIdentityRequest(const QUuid& avatarID) const { auto nodeList = DependencyManager::get(); + QWeakPointer nodeListWeak = nodeList; nodeList->eachMatchingNode( [](const SharedNodePointer& node)->bool { - return node->getType() == NodeType::AvatarMixer && node->getActiveSocket(); - }, - [this, avatarID, &nodeList](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; - }); + 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) { diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index 37f94f5b2a..438ea775af 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -416,12 +416,24 @@ void setupPreferences() { { static const QString NETWORKING("Networking"); - auto nodelist = DependencyManager::get(); + QWeakPointer nodeListWeak = DependencyManager::get(); { static const int MIN_PORT_NUMBER { 0 }; static const int MAX_PORT_NUMBER { 65535 }; - auto getter = [&nodelist] { return static_cast(nodelist->getSocketLocalPort()); }; - auto setter = [&nodelist](int preset) { nodelist->setSocketLocalPort(static_cast(preset)); }; + auto getter = [nodeListWeak] { + auto nodeList = nodeListWeak.lock(); + if (nodeList) { + return static_cast(nodeList->getSocketLocalPort()); + } else { + return -1; + } + }; + auto setter = [nodeListWeak](int preset) { + auto nodeList = nodeListWeak.lock(); + if (nodeList) { + nodeList->setSocketLocalPort(static_cast(preset)); + } + }; auto preference = new IntSpinnerPreference(NETWORKING, "Listening Port", getter, setter); preference->setMin(MIN_PORT_NUMBER); preference->setMax(MAX_PORT_NUMBER); diff --git a/libraries/ui/src/ui/OffscreenQmlSurface.cpp b/libraries/ui/src/ui/OffscreenQmlSurface.cpp index dfb825411d..2d22f90d1a 100644 --- a/libraries/ui/src/ui/OffscreenQmlSurface.cpp +++ b/libraries/ui/src/ui/OffscreenQmlSurface.cpp @@ -331,7 +331,7 @@ void OffscreenQmlSurface::onRootCreated() { getSurfaceContext()->setContextProperty("offscreenWindow", QVariant::fromValue(getWindow())); // Connect with the audio client and listen for audio device changes - connect(DependencyManager::get().data(), &AudioClient::deviceChanged, this, [&](QAudio::Mode mode, const QAudioDeviceInfo& device) { + connect(DependencyManager::get().data(), &AudioClient::deviceChanged, this, [this](QAudio::Mode mode, const QAudioDeviceInfo& device) { if (mode == QAudio::Mode::AudioOutput) { QMetaObject::invokeMethod(this, "changeAudioOutputDevice", Qt::QueuedConnection, Q_ARG(QString, device.deviceName())); } From 5bb9accd306cd737f03fcb30614eb2984d300637 Mon Sep 17 00:00:00 2001 From: Gabriel Date: Tue, 7 Aug 2018 10:57:51 -0300 Subject: [PATCH 052/144] Fix low volume issue in Samsung Make Qt use QT_ANDROID_PRESET_VOICE_RECOGNITION device in order to set SL_ANDROID_RECORDING_PRESET_VOICE_RECOGNITION in OpenSL ES --- libraries/audio-client/src/AudioClient.cpp | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index 85ceece702..c6c6463cef 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -53,6 +53,7 @@ #include "AudioHelpers.h" #if defined(Q_OS_ANDROID) +#define VOICE_RECOGNITION "voicerecognition" #include #endif @@ -465,7 +466,16 @@ QAudioDeviceInfo defaultAudioDeviceForMode(QAudio::Mode mode) { return getNamedAudioDeviceForMode(mode, deviceName); #endif - +#if defined (Q_OS_ANDROID) + if (mode == QAudio::AudioInput) { + auto inputDevices = QAudioDeviceInfo::availableDevices(QAudio::AudioInput); + for (auto inputDevice : inputDevices) { + if (inputDevice.deviceName() == VOICE_RECOGNITION) { + return inputDevice; + } + } + } +#endif // fallback for failed lookup is the default device return (mode == QAudio::AudioInput) ? QAudioDeviceInfo::defaultInputDevice() : QAudioDeviceInfo::defaultOutputDevice(); } @@ -486,15 +496,6 @@ bool nativeFormatForAudioDevice(const QAudioDeviceInfo& audioDevice, audioFormat.setSampleType(QAudioFormat::SignedInt); audioFormat.setByteOrder(QAudioFormat::LittleEndian); -#if defined(Q_OS_ANDROID) - // Using the HW sample rate (AUDIO_INPUT_FLAG_FAST) in some samsung phones causes a low volume at input stream - // Changing the sample rate forces a resampling that (in samsung) amplifies +18 dB - QAndroidJniObject brand = QAndroidJniObject::getStaticObjectField("android/os/Build", "BRAND"); - if (audioDevice == QAudioDeviceInfo::defaultInputDevice() && brand.toString().contains("samsung", Qt::CaseInsensitive)) { - audioFormat.setSampleRate(24000); - } -#endif - if (!audioDevice.isFormatSupported(audioFormat)) { qCWarning(audioclient) << "The native format is" << audioFormat << "but isFormatSupported() failed."; return false; From cb215d3190971bc7a9343dc136d8ed25efcd7f21 Mon Sep 17 00:00:00 2001 From: Gabriel Date: Tue, 7 Aug 2018 11:49:56 -0300 Subject: [PATCH 053/144] Set audio buffer size to 20 ms for android CALLBACK_ACCELERATOR_RATIO = 0.5 --- libraries/audio-client/src/AudioClient.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index c6c6463cef..6ac30e7f73 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -1850,7 +1850,7 @@ const float AudioClient::CALLBACK_ACCELERATOR_RATIO = 2.0f; #endif #ifdef Q_OS_ANDROID -const float AudioClient::CALLBACK_ACCELERATOR_RATIO = 1.0f; +const float AudioClient::CALLBACK_ACCELERATOR_RATIO = 0.5f; #elif defined(Q_OS_LINUX) const float AudioClient::CALLBACK_ACCELERATOR_RATIO = 2.0f; #endif From 9c96a10f6cf0c60eb39cab3ea3e7460d0dcda0a5 Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Tue, 7 Aug 2018 19:35:49 +0200 Subject: [PATCH 054/144] Fix for angularVelocity zeroing out on duplication or undo --- scripts/system/libraries/entitySelectionTool.js | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/scripts/system/libraries/entitySelectionTool.js b/scripts/system/libraries/entitySelectionTool.js index 61e4a53887..683ef6e1dc 100644 --- a/scripts/system/libraries/entitySelectionTool.js +++ b/scripts/system/libraries/entitySelectionTool.js @@ -28,6 +28,17 @@ Script.include([ SelectionManager = (function() { var that = {}; + /** + * @description Removes known to be broken properties from a properties object + * @param properties + * @return properties + */ + var fixRemoveBrokenProperties = function (properties) { + // Reason: Entity property is always set to 0,0,0 which causes it to override angularVelocity (see MS17131) + delete properties.localAngularVelocity; + return properties; + } + // FUNCTION: SUBSCRIBE TO UPDATE MESSAGES function subscribeToUpdateMessages() { Messages.subscribe("entityToolUpdates"); @@ -118,7 +129,7 @@ SelectionManager = (function() { that.savedProperties = {}; for (var i = 0; i < that.selections.length; i++) { var entityID = that.selections[i]; - that.savedProperties[entityID] = Entities.getEntityProperties(entityID); + that.savedProperties[entityID] = fixRemoveBrokenProperties(Entities.getEntityProperties(entityID)); } }; @@ -246,7 +257,7 @@ SelectionManager = (function() { var originalEntityID = entitiesToDuplicate[i]; var properties = that.savedProperties[originalEntityID]; if (properties === undefined) { - properties = Entities.getEntityProperties(originalEntityID); + properties = fixRemoveBrokenProperties(Entities.getEntityProperties(originalEntityID)); } if (!properties.locked && (!properties.clientOnly || properties.owningAvatarID === MyAvatar.sessionUUID)) { if (nonDynamicEntityIsBeingGrabbedByAvatar(properties)) { From 4cee6b720a10a8ccfe26994140d91f5579fa0d74 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Tue, 7 Aug 2018 11:49:32 -0700 Subject: [PATCH 055/144] Implement auto-scrolling on Places cards (TODO for Connections) --- interface/resources/qml/hifi/Feed.qml | 49 +++++++++++++++++-- .../qml/hifi/tablet/TabletAddressDialog.qml | 2 + 2 files changed, 48 insertions(+), 3 deletions(-) diff --git a/interface/resources/qml/hifi/Feed.qml b/interface/resources/qml/hifi/Feed.qml index 785b586dd2..4ff26b8057 100644 --- a/interface/resources/qml/hifi/Feed.qml +++ b/interface/resources/qml/hifi/Feed.qml @@ -41,6 +41,8 @@ Column { property var goFunction: null; property var http: null; + property bool autoScrollTimerEnabled; + HifiConstants { id: hifi } Component.onCompleted: suggestions.getFirstPage(); HifiModels.PSFListModel { @@ -102,9 +104,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; @@ -137,5 +142,43 @@ Column { hoverThunk: function () { hovered = true } unhoverThunk: function () { hovered = false } } + + onCountChanged: { + if (scroll.currentIndex === -1 && scroll.count > 0 && root.autoScrollTimerEnabled) { + scroll.currentIndex = 0; + autoScrollTimer.start(); + } + } + + MouseArea { + id: feedMouseArea; + enabled: root.autoScrollTimerEnabled; + anchors.fill: parent; + hoverEnabled: true; + onEntered: { + if (autoScrollTimer.running) { + autoScrollTimer.stop(); + } + } + onExited: { + autoScrollTimer.restart(); + } + } + } + + Timer { + id: autoScrollTimer; + interval: 3000; + running: false; + repeat: true; + onTriggered: { + if (scroll.currentIndex !== -1) { + if (scroll.currentIndex === scroll.count - 1) { + scroll.currentIndex = 0; + } else { + scroll.currentIndex++; + } + } + } } } diff --git a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml index 9a472de046..4a3f7ea905 100644 --- a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml +++ b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml @@ -325,6 +325,7 @@ StackView { filter: addressLine.text; goFunction: goCard; http: http; + autoScrollTimerEnabled: true; } Feed { id: places; @@ -337,6 +338,7 @@ StackView { filter: addressLine.text; goFunction: goCard; http: http; + autoScrollTimerEnabled: true; } Feed { id: snapshots; From af9dc8acca516c832808dce51b0175df8580548f Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Tue, 7 Aug 2018 11:51:38 -0700 Subject: [PATCH 056/144] Turn on only for Annoucements --- interface/resources/qml/hifi/Feed.qml | 2 +- interface/resources/qml/hifi/tablet/TabletAddressDialog.qml | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/interface/resources/qml/hifi/Feed.qml b/interface/resources/qml/hifi/Feed.qml index 4ff26b8057..c81e3c2389 100644 --- a/interface/resources/qml/hifi/Feed.qml +++ b/interface/resources/qml/hifi/Feed.qml @@ -41,7 +41,7 @@ Column { property var goFunction: null; property var http: null; - property bool autoScrollTimerEnabled; + property bool autoScrollTimerEnabled: false; HifiConstants { id: hifi } Component.onCompleted: suggestions.getFirstPage(); diff --git a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml index 4a3f7ea905..5dce74f2dd 100644 --- a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml +++ b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml @@ -338,7 +338,6 @@ StackView { filter: addressLine.text; goFunction: goCard; http: http; - autoScrollTimerEnabled: true; } Feed { id: snapshots; From df9c65e41070489a6897407d935225d050b28b23 Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Tue, 7 Aug 2018 12:08:10 -0700 Subject: [PATCH 057/144] Move back RecordingScriptingInterface destruction --- assignment-client/src/Agent.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index b9d1db2b3e..8c94cc1148 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -497,7 +497,7 @@ void Agent::executeScript() { Frame::clearFrameHandler(AUDIO_FRAME_TYPE); Frame::clearFrameHandler(AVATAR_FRAME_TYPE); - + DependencyManager::destroy(); setFinished(true); } @@ -847,7 +847,6 @@ void Agent::aboutToFinish() { DependencyManager::destroy(); DependencyManager::destroy(); DependencyManager::destroy(); - DependencyManager::destroy(); DependencyManager::destroy(); QMetaObject::invokeMethod(&_avatarAudioTimer, "stop"); From 69812d04ae148e1080f6cf2cb8cb411f856683ca Mon Sep 17 00:00:00 2001 From: sabrina-shanman Date: Tue, 7 Aug 2018 17:42:38 -0700 Subject: [PATCH 058/144] Reduce scope of CollisionPick shapeType to primitive shapes --- interface/src/raypick/PickScriptingInterface.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/interface/src/raypick/PickScriptingInterface.cpp b/interface/src/raypick/PickScriptingInterface.cpp index 5d6151fe47..72606c8da6 100644 --- a/interface/src/raypick/PickScriptingInterface.cpp +++ b/interface/src/raypick/PickScriptingInterface.cpp @@ -242,11 +242,13 @@ unsigned int PickScriptingInterface::createParabolaPick(const QVariant& properti * A Shape defines a physical volume. * * @typedef {object} Shape -* @property {ShapeType} shapeType The type of shape to use. -* @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. +* @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", "plane" * @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. From 4ffe164569b2b1d26194cef187403ce76ac14679 Mon Sep 17 00:00:00 2001 From: sabrina-shanman Date: Wed, 8 Aug 2018 08:56:35 -0700 Subject: [PATCH 059/144] Address Mac/Linux build warnings --- libraries/physics/src/PhysicsEngine.cpp | 4 ++-- libraries/shared/src/ShapeInfo.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/libraries/physics/src/PhysicsEngine.cpp b/libraries/physics/src/PhysicsEngine.cpp index d55fbf7e34..fe12350124 100644 --- a/libraries/physics/src/PhysicsEngine.cpp +++ b/libraries/physics/src/PhysicsEngine.cpp @@ -845,8 +845,8 @@ struct AllContactsCallback : public btCollisionWorld::ContactResultCallback { desiredObjectType(desiredObjectType), collisionObject(), contacts(), - btCollisionWorld::ContactResultCallback(), - myAvatarCollisionObject(myAvatarCollisionObject) { + myAvatarCollisionObject(myAvatarCollisionObject), + btCollisionWorld::ContactResultCallback() { const btCollisionShape* collisionShape = ObjectMotionState::getShapeManager()->getShape(shapeInfo); collisionObject.setCollisionShape(const_cast(collisionShape)); diff --git a/libraries/shared/src/ShapeInfo.cpp b/libraries/shared/src/ShapeInfo.cpp index 24942c0043..152e305bf2 100644 --- a/libraries/shared/src/ShapeInfo.cpp +++ b/libraries/shared/src/ShapeInfo.cpp @@ -75,7 +75,7 @@ QString ShapeInfo::getNameForShapeType(ShapeType type) { } ShapeType ShapeInfo::getShapeTypeForName(QString string) { - for (int i = 0; i < SHAPETYPE_NAME_COUNT; i++) { + for (int i = 0; i < (int)SHAPETYPE_NAME_COUNT; i++) { auto name = shapeTypeNames[i]; if (name == string) { return (ShapeType)i; From 9ea08f18500ef26b1ce559a27d493a25c86165b7 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Thu, 12 Jul 2018 16:45:04 -0700 Subject: [PATCH 060/144] Add ETC2 support to Oven --- libraries/baking/src/TextureBaker.cpp | 71 +-- libraries/image/src/image/Image.cpp | 538 +++++++++--------- libraries/image/src/image/Image.h | 48 +- .../src/model-networking/TextureCache.cpp | 15 +- 4 files changed, 348 insertions(+), 324 deletions(-) diff --git a/libraries/baking/src/TextureBaker.cpp b/libraries/baking/src/TextureBaker.cpp index ecfe724441..24fc331a3b 100644 --- a/libraries/baking/src/TextureBaker.cpp +++ b/libraries/baking/src/TextureBaker.cpp @@ -138,7 +138,7 @@ void TextureBaker::processTexture() { // IMPORTANT: _originalTexture is empty past this point _originalTexture.clear(); _outputFiles.push_back(originalCopyFilePath); - meta.original = _metaTexturePathPrefix +_textureURL.fileName(); + meta.original = _metaTexturePathPrefix + _textureURL.fileName(); } auto buffer = std::static_pointer_cast(std::make_shared(originalCopyFilePath)); @@ -149,49 +149,56 @@ void TextureBaker::processTexture() { // Compressed KTX if (_compressionEnabled) { - auto processedTexture = image::processImage(buffer, _textureURL.toString().toStdString(), - ABSOLUTE_MAX_TEXTURE_NUM_PIXELS, _textureType, true, _abortProcessing); - if (!processedTexture) { - handleError("Could not process texture " + _textureURL.toString()); - return; - } - processedTexture->setSourceHash(hash); + constexpr std::array BACKEND_TARGETS { + image::BackendTarget::GL, + image::BackendTarget::GLES + }; + for (auto target : BACKEND_TARGETS) { + auto processedTexture = image::processImage(buffer, _textureURL.toString().toStdString(), + ABSOLUTE_MAX_TEXTURE_NUM_PIXELS, _textureType, true, + target, _abortProcessing); + if (!processedTexture) { + handleError("Could not process texture " + _textureURL.toString()); + return; + } + processedTexture->setSourceHash(hash); - if (shouldStop()) { - return; - } + if (shouldStop()) { + return; + } - auto memKTX = gpu::Texture::serialize(*processedTexture); - if (!memKTX) { - handleError("Could not serialize " + _textureURL.toString() + " to KTX"); - return; - } + auto memKTX = gpu::Texture::serialize(*processedTexture); + if (!memKTX) { + handleError("Could not serialize " + _textureURL.toString() + " to KTX"); + return; + } - const char* name = khronos::gl::texture::toString(memKTX->_header.getGLInternaFormat()); - if (name == nullptr) { - handleError("Could not determine internal format for compressed KTX: " + _textureURL.toString()); - return; - } + const char* name = khronos::gl::texture::toString(memKTX->_header.getGLInternaFormat()); + if (name == nullptr) { + handleError("Could not determine internal format for compressed KTX: " + _textureURL.toString()); + return; + } - const char* data = reinterpret_cast(memKTX->_storage->data()); - const size_t length = memKTX->_storage->size(); + const char* data = reinterpret_cast(memKTX->_storage->data()); + const size_t length = memKTX->_storage->size(); - auto fileName = _baseFilename + "_" + name + ".ktx"; - auto filePath = _outputDirectory.absoluteFilePath(fileName); - QFile bakedTextureFile { filePath }; - if (!bakedTextureFile.open(QIODevice::WriteOnly) || bakedTextureFile.write(data, length) == -1) { - handleError("Could not write baked texture for " + _textureURL.toString()); - return; + auto fileName = _baseFilename + "_" + name + ".ktx"; + auto filePath = _outputDirectory.absoluteFilePath(fileName); + QFile bakedTextureFile { filePath }; + if (!bakedTextureFile.open(QIODevice::WriteOnly) || bakedTextureFile.write(data, length) == -1) { + handleError("Could not write baked texture for " + _textureURL.toString()); + return; + } + _outputFiles.push_back(filePath); + meta.availableTextureTypes[memKTX->_header.getGLInternaFormat()] = _metaTexturePathPrefix + fileName; } - _outputFiles.push_back(filePath); - meta.availableTextureTypes[memKTX->_header.getGLInternaFormat()] = _metaTexturePathPrefix + fileName; } // Uncompressed KTX if (_textureType == image::TextureUsage::Type::CUBE_TEXTURE) { buffer->reset(); auto processedTexture = image::processImage(std::move(buffer), _textureURL.toString().toStdString(), - ABSOLUTE_MAX_TEXTURE_NUM_PIXELS, _textureType, false, _abortProcessing); + ABSOLUTE_MAX_TEXTURE_NUM_PIXELS, _textureType, false, image::BackendTarget::GL, _abortProcessing); if (!processedTexture) { handleError("Could not process texture " + _textureURL.toString()); return; diff --git a/libraries/image/src/image/Image.cpp b/libraries/image/src/image/Image.cpp index 7fc3a73f87..eaf8eacfce 100644 --- a/libraries/image/src/image/Image.cpp +++ b/libraries/image/src/image/Image.cpp @@ -31,17 +31,13 @@ using namespace gpu; #define CPU_MIPMAPS 1 #include -#ifdef USE_GLES +#undef _CRT_SECURE_NO_WARNINGS #include #include -#endif static const glm::uvec2 SPARSE_PAGE_SIZE(128); -#ifdef Q_OS_ANDROID -static const glm::uvec2 MAX_TEXTURE_SIZE(2048); -#else -static const glm::uvec2 MAX_TEXTURE_SIZE(4096); -#endif +static const glm::uvec2 MAX_TEXTURE_SIZE_GLES(2048); +static const glm::uvec2 MAX_TEXTURE_SIZE_GL(4096); bool DEV_DECIMATE_TEXTURES = false; std::atomic DECIMATED_TEXTURE_COUNT{ 0 }; std::atomic RECTIFIED_TEXTURE_COUNT{ 0 }; @@ -83,11 +79,12 @@ const QStringList getSupportedFormats() { // On GLES, we don't use HDR skyboxes -#ifndef USE_GLES -QImage::Format QIMAGE_HDR_FORMAT = QImage::Format_RGB30; -#else -QImage::Format QIMAGE_HDR_FORMAT = QImage::Format_RGB32; -#endif +QImage::Format hdrFormatForTarget(BackendTarget target) { + if (target == BackendTarget::GLES) { + return QImage::Format_RGB32; + } + return QImage::Format_RGB30; +} TextureUsage::TextureLoader TextureUsage::getTextureLoaderForType(Type type, const QVariantMap& options) { switch (type) { @@ -123,63 +120,63 @@ TextureUsage::TextureLoader TextureUsage::getTextureLoaderForType(Type type, con } gpu::TexturePointer TextureUsage::createStrict2DTextureFromImage(QImage&& srcImage, const std::string& srcImageName, - bool compress, const std::atomic& abortProcessing) { - return process2DTextureColorFromImage(std::move(srcImage), srcImageName, compress, true, abortProcessing); + bool compress, BackendTarget target, const std::atomic& abortProcessing) { + return process2DTextureColorFromImage(std::move(srcImage), srcImageName, compress, target, true, abortProcessing); } gpu::TexturePointer TextureUsage::create2DTextureFromImage(QImage&& srcImage, const std::string& srcImageName, - bool compress, const std::atomic& abortProcessing) { - return process2DTextureColorFromImage(std::move(srcImage), srcImageName, compress, false, abortProcessing); + bool compress, BackendTarget target, const std::atomic& abortProcessing) { + return process2DTextureColorFromImage(std::move(srcImage), srcImageName, compress, target, false, abortProcessing); } gpu::TexturePointer TextureUsage::createAlbedoTextureFromImage(QImage&& srcImage, const std::string& srcImageName, - bool compress, const std::atomic& abortProcessing) { - return process2DTextureColorFromImage(std::move(srcImage), srcImageName, compress, false, abortProcessing); + bool compress, BackendTarget target, const std::atomic& abortProcessing) { + return process2DTextureColorFromImage(std::move(srcImage), srcImageName, compress, target, false, abortProcessing); } gpu::TexturePointer TextureUsage::createEmissiveTextureFromImage(QImage&& srcImage, const std::string& srcImageName, - bool compress, const std::atomic& abortProcessing) { - return process2DTextureColorFromImage(std::move(srcImage), srcImageName, compress, false, abortProcessing); + bool compress, BackendTarget target, const std::atomic& abortProcessing) { + return process2DTextureColorFromImage(std::move(srcImage), srcImageName, compress, target, false, abortProcessing); } gpu::TexturePointer TextureUsage::createLightmapTextureFromImage(QImage&& srcImage, const std::string& srcImageName, - bool compress, const std::atomic& abortProcessing) { - return process2DTextureColorFromImage(std::move(srcImage), srcImageName, compress, false, abortProcessing); + bool compress, BackendTarget target, const std::atomic& abortProcessing) { + return process2DTextureColorFromImage(std::move(srcImage), srcImageName, compress, target, false, abortProcessing); } gpu::TexturePointer TextureUsage::createNormalTextureFromNormalImage(QImage&& srcImage, const std::string& srcImageName, - bool compress, const std::atomic& abortProcessing) { - return process2DTextureNormalMapFromImage(std::move(srcImage), srcImageName, compress, false, abortProcessing); + bool compress, BackendTarget target, const std::atomic& abortProcessing) { + return process2DTextureNormalMapFromImage(std::move(srcImage), srcImageName, compress, target, false, abortProcessing); } gpu::TexturePointer TextureUsage::createNormalTextureFromBumpImage(QImage&& srcImage, const std::string& srcImageName, - bool compress, const std::atomic& abortProcessing) { - return process2DTextureNormalMapFromImage(std::move(srcImage), srcImageName, compress, true, abortProcessing); + bool compress, BackendTarget target, const std::atomic& abortProcessing) { + return process2DTextureNormalMapFromImage(std::move(srcImage), srcImageName, compress, target, true, abortProcessing); } gpu::TexturePointer TextureUsage::createRoughnessTextureFromImage(QImage&& srcImage, const std::string& srcImageName, - bool compress, const std::atomic& abortProcessing) { - return process2DTextureGrayscaleFromImage(std::move(srcImage), srcImageName, compress, false, abortProcessing); + bool compress, BackendTarget target, const std::atomic& abortProcessing) { + return process2DTextureGrayscaleFromImage(std::move(srcImage), srcImageName, compress, target, false, abortProcessing); } gpu::TexturePointer TextureUsage::createRoughnessTextureFromGlossImage(QImage&& srcImage, const std::string& srcImageName, - bool compress, const std::atomic& abortProcessing) { - return process2DTextureGrayscaleFromImage(std::move(srcImage), srcImageName, compress, true, abortProcessing); + bool compress, BackendTarget target, const std::atomic& abortProcessing) { + return process2DTextureGrayscaleFromImage(std::move(srcImage), srcImageName, compress, target, true, abortProcessing); } gpu::TexturePointer TextureUsage::createMetallicTextureFromImage(QImage&& srcImage, const std::string& srcImageName, - bool compress, const std::atomic& abortProcessing) { - return process2DTextureGrayscaleFromImage(std::move(srcImage), srcImageName, compress, false, abortProcessing); + bool compress, BackendTarget target, const std::atomic& abortProcessing) { + return process2DTextureGrayscaleFromImage(std::move(srcImage), srcImageName, compress, target, false, abortProcessing); } gpu::TexturePointer TextureUsage::createCubeTextureFromImage(QImage&& srcImage, const std::string& srcImageName, - bool compress, const std::atomic& abortProcessing) { - return processCubeTextureColorFromImage(std::move(srcImage), srcImageName, compress, true, abortProcessing); + bool compress, BackendTarget target, const std::atomic& abortProcessing) { + return processCubeTextureColorFromImage(std::move(srcImage), srcImageName, compress, target, true, abortProcessing); } gpu::TexturePointer TextureUsage::createCubeTextureFromImageWithoutIrradiance(QImage&& srcImage, const std::string& srcImageName, - bool compress, const std::atomic& abortProcessing) { - return processCubeTextureColorFromImage(std::move(srcImage), srcImageName, compress, false, abortProcessing); + bool compress, BackendTarget target, const std::atomic& abortProcessing) { + return processCubeTextureColorFromImage(std::move(srcImage), srcImageName, compress, target, false, abortProcessing); } static float denormalize(float value, const float minValue) { @@ -228,7 +225,7 @@ QImage processRawImageData(QIODevice& content, const std::string& filename) { gpu::TexturePointer processImage(std::shared_ptr content, const std::string& filename, int maxNumPixels, TextureUsage::Type textureType, - bool compress, const std::atomic& abortProcessing) { + bool compress, BackendTarget target, const std::atomic& abortProcessing) { QImage image = processRawImageData(*content.get(), filename); // Texture content can take up a lot of memory. Here we release our ownership of that content @@ -259,12 +256,12 @@ gpu::TexturePointer processImage(std::shared_ptr content, const std:: } auto loader = TextureUsage::getTextureLoaderForType(textureType); - auto texture = loader(std::move(image), filename, compress, abortProcessing); + auto texture = loader(std::move(image), filename, compress, target, abortProcessing); return texture; } -QImage processSourceImage(QImage&& srcImage, bool cubemap) { +QImage processSourceImage(QImage&& srcImage, bool cubemap, BackendTarget target) { PROFILE_RANGE(resource_parse, "processSourceImage"); // Take a local copy to force move construction @@ -274,7 +271,8 @@ QImage processSourceImage(QImage&& srcImage, bool cubemap) { const glm::uvec2 srcImageSize = toGlm(localCopy.size()); glm::uvec2 targetSize = srcImageSize; - while (glm::any(glm::greaterThan(targetSize, MAX_TEXTURE_SIZE))) { + const auto maxTextureSize = target == BackendTarget::GL ? MAX_TEXTURE_SIZE_GL : MAX_TEXTURE_SIZE_GLES; + while (glm::any(glm::greaterThan(targetSize, maxTextureSize))) { targetSize /= 2; } if (targetSize != srcImageSize) { @@ -406,12 +404,12 @@ public: } }; -void generateHDRMips(gpu::Texture* texture, QImage&& image, const std::atomic& abortProcessing, int face) { +void generateHDRMips(gpu::Texture* texture, QImage&& image, BackendTarget target, const std::atomic& abortProcessing, int face) { // Take a local copy to force move construction // https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#f18-for-consume-parameters-pass-by-x-and-stdmove-the-parameter QImage localCopy = std::move(image); - assert(localCopy.format() == QIMAGE_HDR_FORMAT); + assert(localCopy.format() == hdrFormatForTarget(target)); const int width = localCopy.width(), height = localCopy.height(); std::vector data; @@ -503,220 +501,219 @@ void generateHDRMips(gpu::Texture* texture, QImage&& image, const std::atomic& abortProcessing, int face) { +void generateLDRMips(gpu::Texture* texture, QImage&& image, BackendTarget target, const std::atomic& abortProcessing, int face) { // Take a local copy to force move construction // https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#f18-for-consume-parameters-pass-by-x-and-stdmove-the-parameter QImage localCopy = std::move(image); - if (localCopy.format() != QImage::Format_ARGB32 && localCopy.format() != QIMAGE_HDR_FORMAT) { + if (localCopy.format() != QImage::Format_ARGB32 && localCopy.format() != hdrFormatForTarget(target)) { localCopy = localCopy.convertToFormat(QImage::Format_ARGB32); } const int width = localCopy.width(), height = localCopy.height(); auto mipFormat = texture->getStoredMipFormat(); -#ifndef USE_GLES - const void* data = static_cast(localCopy.constBits()); - nvtt::TextureType textureType = nvtt::TextureType_2D; - nvtt::InputFormat inputFormat = nvtt::InputFormat_BGRA_8UB; - nvtt::WrapMode wrapMode = nvtt::WrapMode_Mirror; - nvtt::RoundMode roundMode = nvtt::RoundMode_None; - nvtt::AlphaMode alphaMode = nvtt::AlphaMode_None; + if (target != BackendTarget::GLES) { + const void* data = static_cast(localCopy.constBits()); + nvtt::TextureType textureType = nvtt::TextureType_2D; + nvtt::InputFormat inputFormat = nvtt::InputFormat_BGRA_8UB; + nvtt::WrapMode wrapMode = nvtt::WrapMode_Mirror; + nvtt::RoundMode roundMode = nvtt::RoundMode_None; + nvtt::AlphaMode alphaMode = nvtt::AlphaMode_None; - float inputGamma = 2.2f; - float outputGamma = 2.2f; + float inputGamma = 2.2f; + float outputGamma = 2.2f; - nvtt::InputOptions inputOptions; - inputOptions.setTextureLayout(textureType, width, height); + nvtt::InputOptions inputOptions; + inputOptions.setTextureLayout(textureType, width, height); - inputOptions.setMipmapData(data, width, height); - // setMipmapData copies the memory, so free up the memory afterward to avoid bloating the heap - data = nullptr; - localCopy = QImage(); // QImage doesn't have a clear function, so override it with an empty one. + inputOptions.setMipmapData(data, width, height); + // setMipmapData copies the memory, so free up the memory afterward to avoid bloating the heap + data = nullptr; + localCopy = QImage(); // QImage doesn't have a clear function, so override it with an empty one. - inputOptions.setFormat(inputFormat); - inputOptions.setGamma(inputGamma, outputGamma); - inputOptions.setAlphaMode(alphaMode); - inputOptions.setWrapMode(wrapMode); - inputOptions.setRoundMode(roundMode); + inputOptions.setFormat(inputFormat); + inputOptions.setGamma(inputGamma, outputGamma); + inputOptions.setAlphaMode(alphaMode); + inputOptions.setWrapMode(wrapMode); + inputOptions.setRoundMode(roundMode); - inputOptions.setMipmapGeneration(true); - inputOptions.setMipmapFilter(nvtt::MipmapFilter_Box); + inputOptions.setMipmapGeneration(true); + inputOptions.setMipmapFilter(nvtt::MipmapFilter_Box); - nvtt::CompressionOptions compressionOptions; - compressionOptions.setQuality(nvtt::Quality_Production); + nvtt::CompressionOptions compressionOptions; + compressionOptions.setQuality(nvtt::Quality_Production); - if (mipFormat == gpu::Element::COLOR_COMPRESSED_BCX_SRGB) { - compressionOptions.setFormat(nvtt::Format_BC1); - } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_BCX_SRGBA_MASK) { - alphaMode = nvtt::AlphaMode_Transparency; - compressionOptions.setFormat(nvtt::Format_BC1a); - } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_BCX_SRGBA) { - alphaMode = nvtt::AlphaMode_Transparency; - compressionOptions.setFormat(nvtt::Format_BC3); - } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_BCX_RED) { - compressionOptions.setFormat(nvtt::Format_BC4); - } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_BCX_XY) { - compressionOptions.setFormat(nvtt::Format_BC5); - } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_BCX_SRGBA_HIGH) { - alphaMode = nvtt::AlphaMode_Transparency; - compressionOptions.setFormat(nvtt::Format_BC7); - } else if (mipFormat == gpu::Element::COLOR_RGBA_32) { - compressionOptions.setFormat(nvtt::Format_RGBA); - compressionOptions.setPixelType(nvtt::PixelType_UnsignedNorm); - compressionOptions.setPitchAlignment(4); - compressionOptions.setPixelFormat(32, - 0x000000FF, - 0x0000FF00, - 0x00FF0000, - 0xFF000000); - inputGamma = 1.0f; - outputGamma = 1.0f; - } else if (mipFormat == gpu::Element::COLOR_BGRA_32) { - compressionOptions.setFormat(nvtt::Format_RGBA); - compressionOptions.setPixelType(nvtt::PixelType_UnsignedNorm); - compressionOptions.setPitchAlignment(4); - compressionOptions.setPixelFormat(32, - 0x00FF0000, - 0x0000FF00, - 0x000000FF, - 0xFF000000); - inputGamma = 1.0f; - outputGamma = 1.0f; - } else if (mipFormat == gpu::Element::COLOR_SRGBA_32) { - compressionOptions.setFormat(nvtt::Format_RGBA); - compressionOptions.setPixelType(nvtt::PixelType_UnsignedNorm); - compressionOptions.setPitchAlignment(4); - compressionOptions.setPixelFormat(32, - 0x000000FF, - 0x0000FF00, - 0x00FF0000, - 0xFF000000); - } else if (mipFormat == gpu::Element::COLOR_SBGRA_32) { - compressionOptions.setFormat(nvtt::Format_RGBA); - compressionOptions.setPixelType(nvtt::PixelType_UnsignedNorm); - compressionOptions.setPitchAlignment(4); - compressionOptions.setPixelFormat(32, - 0x00FF0000, - 0x0000FF00, - 0x000000FF, - 0xFF000000); - } else if (mipFormat == gpu::Element::COLOR_R_8) { - compressionOptions.setFormat(nvtt::Format_RGB); - compressionOptions.setPixelType(nvtt::PixelType_UnsignedNorm); - compressionOptions.setPitchAlignment(4); - compressionOptions.setPixelFormat(8, 0, 0, 0); - } else if (mipFormat == gpu::Element::VEC2NU8_XY) { - inputOptions.setNormalMap(true); - compressionOptions.setFormat(nvtt::Format_RGBA); - compressionOptions.setPixelType(nvtt::PixelType_UnsignedNorm); - compressionOptions.setPitchAlignment(4); - compressionOptions.setPixelFormat(8, 8, 0, 0); - } else { - qCWarning(imagelogging) << "Unknown mip format"; - Q_UNREACHABLE(); - return; - } - - nvtt::OutputOptions outputOptions; - outputOptions.setOutputHeader(false); - OutputHandler outputHandler(texture, face); - outputOptions.setOutputHandler(&outputHandler); - MyErrorHandler errorHandler; - outputOptions.setErrorHandler(&errorHandler); - - SequentialTaskDispatcher dispatcher(abortProcessing); - nvtt::Compressor compressor; - compressor.setTaskDispatcher(&dispatcher); - compressor.process(inputOptions, compressionOptions, outputOptions); - -#else - int numMips = 1 + (int)log2(std::max(width, height)); - Etc::RawImage *mipMaps = new Etc::RawImage[numMips]; - Etc::Image::Format etcFormat = Etc::Image::Format::DEFAULT; - - if (mipFormat == gpu::Element::COLOR_COMPRESSED_ETC2_RGB) { - etcFormat = Etc::Image::Format::RGB8; - } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_ETC2_SRGB) { - etcFormat = Etc::Image::Format::SRGB8; - } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_ETC2_RGB_PUNCHTHROUGH_ALPHA) { - etcFormat = Etc::Image::Format::RGB8A1; - } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_ETC2_SRGB_PUNCHTHROUGH_ALPHA) { - etcFormat = Etc::Image::Format::SRGB8A1; - } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_ETC2_RGBA) { - etcFormat = Etc::Image::Format::RGBA8; - } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_ETC2_SRGBA) { - etcFormat = Etc::Image::Format::SRGBA8; - } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_EAC_RED) { - etcFormat = Etc::Image::Format::R11; - } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_EAC_RED_SIGNED) { - etcFormat = Etc::Image::Format::SIGNED_R11; - } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_EAC_XY) { - etcFormat = Etc::Image::Format::RG11; - } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_EAC_XY_SIGNED) { - etcFormat = Etc::Image::Format::SIGNED_RG11; - } else { - qCWarning(imagelogging) << "Unknown mip format"; - Q_UNREACHABLE(); - return; - } - - const Etc::ErrorMetric errorMetric = Etc::ErrorMetric::RGBA; - const float effort = 1.0f; - const int numEncodeThreads = 4; - int encodingTime; - const float MAX_COLOR = 255.0f; - - std::vector floatData; - floatData.resize(width * height); - for (int y = 0; y < height; y++) { - QRgb *line = (QRgb *) localCopy.scanLine(y); - for (int x = 0; x < width; x++) { - QRgb &pixel = line[x]; - floatData[x + y * width] = vec4(qRed(pixel), qGreen(pixel), qBlue(pixel), qAlpha(pixel)) / MAX_COLOR; + if (mipFormat == gpu::Element::COLOR_COMPRESSED_BCX_SRGB) { + compressionOptions.setFormat(nvtt::Format_BC1); + } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_BCX_SRGBA_MASK) { + alphaMode = nvtt::AlphaMode_Transparency; + compressionOptions.setFormat(nvtt::Format_BC1a); + } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_BCX_SRGBA) { + alphaMode = nvtt::AlphaMode_Transparency; + compressionOptions.setFormat(nvtt::Format_BC3); + } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_BCX_RED) { + compressionOptions.setFormat(nvtt::Format_BC4); + } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_BCX_XY) { + compressionOptions.setFormat(nvtt::Format_BC5); + } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_BCX_SRGBA_HIGH) { + alphaMode = nvtt::AlphaMode_Transparency; + compressionOptions.setFormat(nvtt::Format_BC7); + } else if (mipFormat == gpu::Element::COLOR_RGBA_32) { + compressionOptions.setFormat(nvtt::Format_RGBA); + compressionOptions.setPixelType(nvtt::PixelType_UnsignedNorm); + compressionOptions.setPitchAlignment(4); + compressionOptions.setPixelFormat(32, + 0x000000FF, + 0x0000FF00, + 0x00FF0000, + 0xFF000000); + inputGamma = 1.0f; + outputGamma = 1.0f; + } else if (mipFormat == gpu::Element::COLOR_BGRA_32) { + compressionOptions.setFormat(nvtt::Format_RGBA); + compressionOptions.setPixelType(nvtt::PixelType_UnsignedNorm); + compressionOptions.setPitchAlignment(4); + compressionOptions.setPixelFormat(32, + 0x00FF0000, + 0x0000FF00, + 0x000000FF, + 0xFF000000); + inputGamma = 1.0f; + outputGamma = 1.0f; + } else if (mipFormat == gpu::Element::COLOR_SRGBA_32) { + compressionOptions.setFormat(nvtt::Format_RGBA); + compressionOptions.setPixelType(nvtt::PixelType_UnsignedNorm); + compressionOptions.setPitchAlignment(4); + compressionOptions.setPixelFormat(32, + 0x000000FF, + 0x0000FF00, + 0x00FF0000, + 0xFF000000); + } else if (mipFormat == gpu::Element::COLOR_SBGRA_32) { + compressionOptions.setFormat(nvtt::Format_RGBA); + compressionOptions.setPixelType(nvtt::PixelType_UnsignedNorm); + compressionOptions.setPitchAlignment(4); + compressionOptions.setPixelFormat(32, + 0x00FF0000, + 0x0000FF00, + 0x000000FF, + 0xFF000000); + } else if (mipFormat == gpu::Element::COLOR_R_8) { + compressionOptions.setFormat(nvtt::Format_RGB); + compressionOptions.setPixelType(nvtt::PixelType_UnsignedNorm); + compressionOptions.setPitchAlignment(4); + compressionOptions.setPixelFormat(8, 0, 0, 0); + } else if (mipFormat == gpu::Element::VEC2NU8_XY) { + inputOptions.setNormalMap(true); + compressionOptions.setFormat(nvtt::Format_RGBA); + compressionOptions.setPixelType(nvtt::PixelType_UnsignedNorm); + compressionOptions.setPitchAlignment(4); + compressionOptions.setPixelFormat(8, 8, 0, 0); + } else { + qCWarning(imagelogging) << "Unknown mip format"; + Q_UNREACHABLE(); + return; } - } - // free up the memory afterward to avoid bloating the heap - localCopy = QImage(); // QImage doesn't have a clear function, so override it with an empty one. + nvtt::OutputOptions outputOptions; + outputOptions.setOutputHeader(false); + OutputHandler outputHandler(texture, face); + outputOptions.setOutputHandler(&outputHandler); + MyErrorHandler errorHandler; + outputOptions.setErrorHandler(&errorHandler); - Etc::EncodeMipmaps( - (float *)floatData.data(), width, height, - etcFormat, errorMetric, effort, - numEncodeThreads, numEncodeThreads, - numMips, Etc::FILTER_WRAP_NONE, - mipMaps, &encodingTime - ); + SequentialTaskDispatcher dispatcher(abortProcessing); + nvtt::Compressor compressor; + compressor.setTaskDispatcher(&dispatcher); + compressor.process(inputOptions, compressionOptions, outputOptions); + } else { + int numMips = 1 + (int)log2(std::max(width, height)); + Etc::RawImage *mipMaps = new Etc::RawImage[numMips]; + Etc::Image::Format etcFormat = Etc::Image::Format::DEFAULT; - for (int i = 0; i < numMips; i++) { - if (mipMaps[i].paucEncodingBits.get()) { - if (face >= 0) { - texture->assignStoredMipFace(i, face, mipMaps[i].uiEncodingBitsBytes, static_cast(mipMaps[i].paucEncodingBits.get())); - } else { - texture->assignStoredMip(i, mipMaps[i].uiEncodingBitsBytes, static_cast(mipMaps[i].paucEncodingBits.get())); + if (mipFormat == gpu::Element::COLOR_COMPRESSED_ETC2_RGB) { + etcFormat = Etc::Image::Format::RGB8; + } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_ETC2_SRGB) { + etcFormat = Etc::Image::Format::SRGB8; + } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_ETC2_RGB_PUNCHTHROUGH_ALPHA) { + etcFormat = Etc::Image::Format::RGB8A1; + } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_ETC2_SRGB_PUNCHTHROUGH_ALPHA) { + etcFormat = Etc::Image::Format::SRGB8A1; + } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_ETC2_RGBA) { + etcFormat = Etc::Image::Format::RGBA8; + } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_ETC2_SRGBA) { + etcFormat = Etc::Image::Format::SRGBA8; + } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_EAC_RED) { + etcFormat = Etc::Image::Format::R11; + } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_EAC_RED_SIGNED) { + etcFormat = Etc::Image::Format::SIGNED_R11; + } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_EAC_XY) { + etcFormat = Etc::Image::Format::RG11; + } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_EAC_XY_SIGNED) { + etcFormat = Etc::Image::Format::SIGNED_RG11; + } else { + qCWarning(imagelogging) << "Unknown mip format"; + Q_UNREACHABLE(); + return; + } + + const Etc::ErrorMetric errorMetric = Etc::ErrorMetric::RGBA; + const float effort = 1.0f; + const int numEncodeThreads = 4; + int encodingTime; + const float MAX_COLOR = 255.0f; + + std::vector floatData; + floatData.resize(width * height); + for (int y = 0; y < height; y++) { + QRgb *line = (QRgb *)localCopy.scanLine(y); + for (int x = 0; x < width; x++) { + QRgb &pixel = line[x]; + floatData[x + y * width] = vec4(qRed(pixel), qGreen(pixel), qBlue(pixel), qAlpha(pixel)) / MAX_COLOR; } } - } - delete[] mipMaps; -#endif + // free up the memory afterward to avoid bloating the heap + localCopy = QImage(); // QImage doesn't have a clear function, so override it with an empty one. + + Etc::EncodeMipmaps( + (float *)floatData.data(), width, height, + etcFormat, errorMetric, effort, + numEncodeThreads, numEncodeThreads, + numMips, Etc::FILTER_WRAP_NONE, + mipMaps, &encodingTime + ); + + for (int i = 0; i < numMips; i++) { + if (mipMaps[i].paucEncodingBits.get()) { + if (face >= 0) { + texture->assignStoredMipFace(i, face, mipMaps[i].uiEncodingBitsBytes, static_cast(mipMaps[i].paucEncodingBits.get())); + } else { + texture->assignStoredMip(i, mipMaps[i].uiEncodingBitsBytes, static_cast(mipMaps[i].paucEncodingBits.get())); + } + } + } + + delete[] mipMaps; + } } #endif -void generateMips(gpu::Texture* texture, QImage&& image, const std::atomic& abortProcessing = false, int face = -1) { +void generateMips(gpu::Texture* texture, QImage&& image, BackendTarget target, const std::atomic& abortProcessing = false, int face = -1) { #if CPU_MIPMAPS PROFILE_RANGE(resource_parse, "generateMips"); -#ifndef USE_GLES - if (image.format() == QIMAGE_HDR_FORMAT) { - generateHDRMips(texture, std::move(image), abortProcessing, face); - } else { - generateLDRMips(texture, std::move(image), abortProcessing, face); + if (target == BackendTarget::GLES) { + generateLDRMips(texture, std::move(image), target, abortProcessing, face); + } else { + if (image.format() == hdrFormatForTarget(target)) { + generateHDRMips(texture, std::move(image), target, abortProcessing, face); + } else { + generateLDRMips(texture, std::move(image), target, abortProcessing, face); + } } -#else - generateLDRMips(texture, std::move(image), abortProcessing, face); -#endif #else texture->setAutoGenerateMips(true); #endif @@ -750,9 +747,9 @@ void processTextureAlpha(const QImage& srcImage, bool& validAlpha, bool& alphaAs } gpu::TexturePointer TextureUsage::process2DTextureColorFromImage(QImage&& srcImage, const std::string& srcImageName, bool compress, - bool isStrict, const std::atomic& abortProcessing) { + BackendTarget target, bool isStrict, const std::atomic& abortProcessing) { PROFILE_RANGE(resource_parse, "process2DTextureColorFromImage"); - QImage image = processSourceImage(std::move(srcImage), false); + QImage image = processSourceImage(std::move(srcImage), false, target); bool validAlpha = image.hasAlphaChannel(); bool alphaAsMask = false; @@ -770,7 +767,11 @@ gpu::TexturePointer TextureUsage::process2DTextureColorFromImage(QImage&& srcIma if ((image.width() > 0) && (image.height() > 0)) { gpu::Element formatMip; gpu::Element formatGPU; - if (compress) { + if (target == BackendTarget::GLES) { + // GLES does not support GL_BGRA + formatGPU = gpu::Element::COLOR_COMPRESSED_ETC2_SRGBA; + formatMip = formatGPU; + } else if (compress) { if (validAlpha) { // NOTE: This disables BC1a compression because it was producing odd artifacts on text textures // for the tutorial. Instead we use BC3 (which is larger) but doesn't produce the same artifacts). @@ -780,14 +781,8 @@ gpu::TexturePointer TextureUsage::process2DTextureColorFromImage(QImage&& srcIma } formatMip = formatGPU; } else { -#ifdef USE_GLES - // GLES does not support GL_BGRA - formatGPU = gpu::Element::COLOR_COMPRESSED_ETC2_SRGBA; - formatMip = formatGPU; -#else formatGPU = gpu::Element::COLOR_SRGBA_32; formatMip = gpu::Element::COLOR_SBGRA_32; -#endif } if (isStrict) { @@ -806,7 +801,7 @@ gpu::TexturePointer TextureUsage::process2DTextureColorFromImage(QImage&& srcIma theTexture->setUsage(usage.build()); theTexture->setStoredMipFormat(formatMip); theTexture->assignStoredMip(0, image.byteCount(), image.constBits()); - generateMips(theTexture.get(), std::move(image), abortProcessing); + generateMips(theTexture.get(), std::move(image), target, abortProcessing); } return theTexture; @@ -887,10 +882,10 @@ QImage processBumpMap(QImage&& image) { return result; } gpu::TexturePointer TextureUsage::process2DTextureNormalMapFromImage(QImage&& srcImage, const std::string& srcImageName, - bool compress, bool isBumpMap, + bool compress, BackendTarget target, bool isBumpMap, const std::atomic& abortProcessing) { PROFILE_RANGE(resource_parse, "process2DTextureNormalMapFromImage"); - QImage image = processSourceImage(std::move(srcImage), false); + QImage image = processSourceImage(std::move(srcImage), false, target); if (isBumpMap) { image = processBumpMap(std::move(image)); @@ -908,11 +903,11 @@ gpu::TexturePointer TextureUsage::process2DTextureNormalMapFromImage(QImage&& sr if (compress) { formatGPU = gpu::Element::COLOR_COMPRESSED_BCX_XY; } else { -#ifdef USE_GLES - formatGPU = gpu::Element::COLOR_COMPRESSED_EAC_XY; -#else - formatGPU = gpu::Element::VEC2NU8_XY; -#endif + if (target == BackendTarget::GLES) { + formatGPU = gpu::Element::COLOR_COMPRESSED_EAC_XY; + } else { + formatGPU = gpu::Element::VEC2NU8_XY; + } } formatMip = formatGPU; @@ -920,17 +915,17 @@ gpu::TexturePointer TextureUsage::process2DTextureNormalMapFromImage(QImage&& sr theTexture->setSource(srcImageName); theTexture->setStoredMipFormat(formatMip); theTexture->assignStoredMip(0, image.byteCount(), image.constBits()); - generateMips(theTexture.get(), std::move(image), abortProcessing); + generateMips(theTexture.get(), std::move(image), target, abortProcessing); } return theTexture; } gpu::TexturePointer TextureUsage::process2DTextureGrayscaleFromImage(QImage&& srcImage, const std::string& srcImageName, - bool compress, bool isInvertedPixels, + bool compress, BackendTarget target, bool isInvertedPixels, const std::atomic& abortProcessing) { PROFILE_RANGE(resource_parse, "process2DTextureGrayscaleFromImage"); - QImage image = processSourceImage(std::move(srcImage), false); + QImage image = processSourceImage(std::move(srcImage), false, target); if (image.format() != QImage::Format_ARGB32) { image = image.convertToFormat(QImage::Format_ARGB32); @@ -948,11 +943,11 @@ gpu::TexturePointer TextureUsage::process2DTextureGrayscaleFromImage(QImage&& sr if (compress) { formatGPU = gpu::Element::COLOR_COMPRESSED_BCX_RED; } else { -#ifdef USE_GLES - formatGPU = gpu::Element::COLOR_COMPRESSED_EAC_RED; -#else - formatGPU = gpu::Element::COLOR_R_8; -#endif + if (target == BackendTarget::GLES) { + formatGPU = gpu::Element::COLOR_COMPRESSED_EAC_RED; + } else { + formatGPU = gpu::Element::COLOR_R_8; + } } formatMip = formatGPU; @@ -960,7 +955,7 @@ gpu::TexturePointer TextureUsage::process2DTextureGrayscaleFromImage(QImage&& sr theTexture->setSource(srcImageName); theTexture->setStoredMipFormat(formatMip); theTexture->assignStoredMip(0, image.byteCount(), image.constBits()); - generateMips(theTexture.get(), std::move(image), abortProcessing); + generateMips(theTexture.get(), std::move(image), target, abortProcessing); } return theTexture; @@ -1233,12 +1228,12 @@ const int CubeLayout::NUM_CUBEMAP_LAYOUTS = sizeof(CubeLayout::CUBEMAP_LAYOUTS) //#define DEBUG_COLOR_PACKING -QImage convertToHDRFormat(QImage&& srcImage, gpu::Element format) { +QImage convertToHDRFormat(QImage&& srcImage, gpu::Element format, BackendTarget target) { // Take a local copy to force move construction // https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#f18-for-consume-parameters-pass-by-x-and-stdmove-the-parameter QImage localCopy = std::move(srcImage); - QImage hdrImage(localCopy.width(), localCopy.height(), (QImage::Format)QIMAGE_HDR_FORMAT); + QImage hdrImage(localCopy.width(), localCopy.height(), hdrFormatForTarget(target)); std::function packFunc; #ifdef DEBUG_COLOR_PACKING std::function unpackFunc; @@ -1292,7 +1287,7 @@ QImage convertToHDRFormat(QImage&& srcImage, gpu::Element format) { } gpu::TexturePointer TextureUsage::processCubeTextureColorFromImage(QImage&& srcImage, const std::string& srcImageName, - bool compress, bool generateIrradiance, + bool compress, BackendTarget target, bool generateIrradiance, const std::atomic& abortProcessing) { PROFILE_RANGE(resource_parse, "processCubeTextureColorFromImage"); @@ -1308,14 +1303,14 @@ gpu::TexturePointer TextureUsage::processCubeTextureColorFromImage(QImage&& srcI gpu::TexturePointer theTexture = nullptr; - QImage image = processSourceImage(std::move(localCopy), true); + QImage image = processSourceImage(std::move(localCopy), true, target); - if (image.format() != QIMAGE_HDR_FORMAT) { -#ifndef USE_GLES - image = convertToHDRFormat(std::move(image), HDR_FORMAT); -#else - image = image.convertToFormat(QImage::Format_RGB32); -#endif + if (image.format() != hdrFormatForTarget(target)) { + if (target == BackendTarget::GLES) { + image = image.convertToFormat(QImage::Format_RGB32); + } else { + image = convertToHDRFormat(std::move(image), HDR_FORMAT, target); + } } gpu::Element formatMip; @@ -1323,11 +1318,11 @@ gpu::TexturePointer TextureUsage::processCubeTextureColorFromImage(QImage&& srcI if (compress) { formatGPU = gpu::Element::COLOR_COMPRESSED_BCX_HDR_RGB; } else { -#ifdef USE_GLES - formatGPU = gpu::Element::COLOR_COMPRESSED_ETC2_SRGB; -#else - formatGPU = HDR_FORMAT; -#endif + if (target == BackendTarget::GLES) { + formatGPU = gpu::Element::COLOR_COMPRESSED_ETC2_SRGB; + } else { + formatGPU = HDR_FORMAT; + } } formatMip = formatGPU; @@ -1378,11 +1373,12 @@ gpu::TexturePointer TextureUsage::processCubeTextureColorFromImage(QImage&& srcI PROFILE_RANGE(resource_parse, "generateIrradiance"); gpu::Element irradianceFormat; // TODO: we could locally compress the irradiance texture on Android, but we don't need to -#ifndef USE_GLES - irradianceFormat = HDR_FORMAT; -#else - irradianceFormat = gpu::Element::COLOR_SRGBA_32; -#endif + if (target == BackendTarget::GLES) { + irradianceFormat = gpu::Element::COLOR_SRGBA_32; + } else { + irradianceFormat = HDR_FORMAT; + } + auto irradianceTexture = gpu::Texture::createCube(irradianceFormat, faces[0].width(), gpu::Texture::MAX_NUM_MIPS, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR, gpu::Sampler::WRAP_CLAMP)); irradianceTexture->setSource(srcImageName); irradianceTexture->setStoredMipFormat(irradianceFormat); @@ -1397,7 +1393,7 @@ gpu::TexturePointer TextureUsage::processCubeTextureColorFromImage(QImage&& srcI } for (uint8 face = 0; face < faces.size(); ++face) { - generateMips(theTexture.get(), std::move(faces[face]), abortProcessing, face); + generateMips(theTexture.get(), std::move(faces[face]), target, abortProcessing, face); } } diff --git a/libraries/image/src/image/Image.h b/libraries/image/src/image/Image.h index ccf4845fca..43b1b613ad 100644 --- a/libraries/image/src/image/Image.h +++ b/libraries/image/src/image/Image.h @@ -21,6 +21,11 @@ class QImage; namespace image { +enum class BackendTarget { + GL, + GLES +}; + namespace TextureUsage { enum Type { @@ -41,42 +46,41 @@ enum Type { UNUSED_TEXTURE }; -using TextureLoader = std::function&)>; +using TextureLoader = std::function&)>; TextureLoader getTextureLoaderForType(Type type, const QVariantMap& options = QVariantMap()); gpu::TexturePointer create2DTextureFromImage(QImage&& image, const std::string& srcImageName, - bool compress, const std::atomic& abortProcessing); + bool compress, BackendTarget target, const std::atomic& abortProcessing); gpu::TexturePointer createStrict2DTextureFromImage(QImage&& image, const std::string& srcImageName, - bool compress, const std::atomic& abortProcessing); + bool compress, BackendTarget target, const std::atomic& abortProcessing); gpu::TexturePointer createAlbedoTextureFromImage(QImage&& image, const std::string& srcImageName, - bool compress, const std::atomic& abortProcessing); + bool compress, BackendTarget target, const std::atomic& abortProcessing); gpu::TexturePointer createEmissiveTextureFromImage(QImage&& image, const std::string& srcImageName, - bool compress, const std::atomic& abortProcessing); + bool compress, BackendTarget target, const std::atomic& abortProcessing); gpu::TexturePointer createNormalTextureFromNormalImage(QImage&& image, const std::string& srcImageName, - bool compress, const std::atomic& abortProcessing); + bool compress, BackendTarget target, const std::atomic& abortProcessing); gpu::TexturePointer createNormalTextureFromBumpImage(QImage&& image, const std::string& srcImageName, - bool compress, const std::atomic& abortProcessing); + bool compress, BackendTarget target, const std::atomic& abortProcessing); gpu::TexturePointer createRoughnessTextureFromImage(QImage&& image, const std::string& srcImageName, - bool compress, const std::atomic& abortProcessing); + bool compress, BackendTarget target, const std::atomic& abortProcessing); gpu::TexturePointer createRoughnessTextureFromGlossImage(QImage&& image, const std::string& srcImageName, - bool compress, const std::atomic& abortProcessing); + bool compress, BackendTarget target, const std::atomic& abortProcessing); gpu::TexturePointer createMetallicTextureFromImage(QImage&& image, const std::string& srcImageName, - bool compress, const std::atomic& abortProcessing); + bool compress, BackendTarget target, const std::atomic& abortProcessing); gpu::TexturePointer createCubeTextureFromImage(QImage&& image, const std::string& srcImageName, - bool compress, const std::atomic& abortProcessing); + bool compress, BackendTarget target, const std::atomic& abortProcessing); gpu::TexturePointer createCubeTextureFromImageWithoutIrradiance(QImage&& image, const std::string& srcImageName, - bool compress, const std::atomic& abortProcessing); + bool compress, BackendTarget target, const std::atomic& abortProcessing); gpu::TexturePointer createLightmapTextureFromImage(QImage&& image, const std::string& srcImageName, - bool compress, const std::atomic& abortProcessing); - + bool compress, BackendTarget target, const std::atomic& abortProcessing); gpu::TexturePointer process2DTextureColorFromImage(QImage&& srcImage, const std::string& srcImageName, bool compress, - bool isStrict, const std::atomic& abortProcessing); + BackendTarget target, bool isStrict, const std::atomic& abortProcessing); gpu::TexturePointer process2DTextureNormalMapFromImage(QImage&& srcImage, const std::string& srcImageName, bool compress, - bool isBumpMap, const std::atomic& abortProcessing); + BackendTarget target, bool isBumpMap, const std::atomic& abortProcessing); gpu::TexturePointer process2DTextureGrayscaleFromImage(QImage&& srcImage, const std::string& srcImageName, bool compress, - bool isInvertedPixels, const std::atomic& abortProcessing); + BackendTarget target, bool isInvertedPixels, const std::atomic& abortProcessing); gpu::TexturePointer processCubeTextureColorFromImage(QImage&& srcImage, const std::string& srcImageName, bool compress, - bool generateIrradiance, const std::atomic& abortProcessing); + BackendTarget target, bool generateIrradiance, const std::atomic& abortProcessing); } // namespace TextureUsage @@ -84,7 +88,13 @@ const QStringList getSupportedFormats(); gpu::TexturePointer processImage(std::shared_ptr content, const std::string& url, int maxNumPixels, TextureUsage::Type textureType, - bool compress = false, const std::atomic& abortProcessing = false); + bool compress = false, +#ifdef USE_GLES + BackendTarget target = BackendTarget::GLES, +#else + BackendTarget target = BackendTarget::GL, +#endif + const std::atomic& abortProcessing = false); } // namespace image diff --git a/libraries/model-networking/src/model-networking/TextureCache.cpp b/libraries/model-networking/src/model-networking/TextureCache.cpp index 40b31cac53..1485766aa2 100644 --- a/libraries/model-networking/src/model-networking/TextureCache.cpp +++ b/libraries/model-networking/src/model-networking/TextureCache.cpp @@ -279,7 +279,12 @@ gpu::TexturePointer TextureCache::getImageTexture(const QString& path, image::Te return nullptr; } auto loader = image::TextureUsage::getTextureLoaderForType(type, options); - return gpu::TexturePointer(loader(std::move(image), path.toStdString(), false, false)); +#ifdef USE_GLES + image::BackendTarget target = image::BackendTarget::GLES; +#else + image::BackendTarget target = image::BackendTarget::GL; +#endif + return gpu::TexturePointer(loader(std::move(image), path.toStdString(), false, target, false)); } QSharedPointer TextureCache::createResource(const QUrl& url, const QSharedPointer& fallback, @@ -1160,7 +1165,13 @@ void ImageReader::read() { // IMPORTANT: _content is empty past this point auto buffer = std::shared_ptr((QIODevice*)new OwningBuffer(std::move(_content))); - texture = image::processImage(std::move(buffer), _url.toString().toStdString(), _maxNumPixels, networkTexture->getTextureType()); + +#ifdef USE_GLES + constexpr bool shouldCompress = true; +#else + constexpr bool shouldCompress = false; +#endif + texture = image::processImage(std::move(buffer), _url.toString().toStdString(), _maxNumPixels, networkTexture->getTextureType(), shouldCompress); if (!texture) { qCWarning(modelnetworking) << "Could not process:" << _url; From f1e63f489b8eae069c21d66d90482398a0066157 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Mon, 16 Jul 2018 08:47:17 -0700 Subject: [PATCH 061/144] Update image::processImage to use compress flag for GLES --- libraries/image/src/image/Image.cpp | 53 ++++++++++++++++------------- libraries/image/src/image/Image.h | 8 +---- 2 files changed, 31 insertions(+), 30 deletions(-) diff --git a/libraries/image/src/image/Image.cpp b/libraries/image/src/image/Image.cpp index eaf8eacfce..a35aa064ae 100644 --- a/libraries/image/src/image/Image.cpp +++ b/libraries/image/src/image/Image.cpp @@ -767,22 +767,28 @@ gpu::TexturePointer TextureUsage::process2DTextureColorFromImage(QImage&& srcIma if ((image.width() > 0) && (image.height() > 0)) { gpu::Element formatMip; gpu::Element formatGPU; - if (target == BackendTarget::GLES) { - // GLES does not support GL_BGRA - formatGPU = gpu::Element::COLOR_COMPRESSED_ETC2_SRGBA; - formatMip = formatGPU; - } else if (compress) { - if (validAlpha) { - // NOTE: This disables BC1a compression because it was producing odd artifacts on text textures - // for the tutorial. Instead we use BC3 (which is larger) but doesn't produce the same artifacts). - formatGPU = gpu::Element::COLOR_COMPRESSED_BCX_SRGBA; + if (compress) { + if (target == BackendTarget::GLES) { + // GLES does not support GL_BGRA + formatGPU = gpu::Element::COLOR_COMPRESSED_ETC2_SRGBA; + formatMip = formatGPU; } else { - formatGPU = gpu::Element::COLOR_COMPRESSED_BCX_SRGB; + if (validAlpha) { + // NOTE: This disables BC1a compression because it was producing odd artifacts on text textures + // for the tutorial. Instead we use BC3 (which is larger) but doesn't produce the same artifacts). + formatGPU = gpu::Element::COLOR_COMPRESSED_BCX_SRGBA; + } else { + formatGPU = gpu::Element::COLOR_COMPRESSED_BCX_SRGB; + } + formatMip = formatGPU; } - formatMip = formatGPU; } else { - formatGPU = gpu::Element::COLOR_SRGBA_32; - formatMip = gpu::Element::COLOR_SBGRA_32; + if (target == BackendTarget::GLES) { + static_assert(false); + } else { + formatGPU = gpu::Element::COLOR_SRGBA_32; + formatMip = gpu::Element::COLOR_SBGRA_32; + } } if (isStrict) { @@ -901,13 +907,13 @@ gpu::TexturePointer TextureUsage::process2DTextureNormalMapFromImage(QImage&& sr gpu::Element formatMip; gpu::Element formatGPU; if (compress) { - formatGPU = gpu::Element::COLOR_COMPRESSED_BCX_XY; - } else { if (target == BackendTarget::GLES) { formatGPU = gpu::Element::COLOR_COMPRESSED_EAC_XY; } else { - formatGPU = gpu::Element::VEC2NU8_XY; + formatGPU = gpu::Element::COLOR_COMPRESSED_BCX_XY; } + } else { + formatGPU = gpu::Element::VEC2NU8_XY; } formatMip = formatGPU; @@ -941,13 +947,13 @@ gpu::TexturePointer TextureUsage::process2DTextureGrayscaleFromImage(QImage&& sr gpu::Element formatMip; gpu::Element formatGPU; if (compress) { - formatGPU = gpu::Element::COLOR_COMPRESSED_BCX_RED; - } else { if (target == BackendTarget::GLES) { formatGPU = gpu::Element::COLOR_COMPRESSED_EAC_RED; } else { - formatGPU = gpu::Element::COLOR_R_8; - } + formatGPU = gpu::Element::COLOR_COMPRESSED_BCX_RED; + } + } else { + formatGPU = gpu::Element::COLOR_R_8; } formatMip = formatGPU; @@ -1316,14 +1322,15 @@ gpu::TexturePointer TextureUsage::processCubeTextureColorFromImage(QImage&& srcI gpu::Element formatMip; gpu::Element formatGPU; if (compress) { - formatGPU = gpu::Element::COLOR_COMPRESSED_BCX_HDR_RGB; - } else { if (target == BackendTarget::GLES) { formatGPU = gpu::Element::COLOR_COMPRESSED_ETC2_SRGB; } else { - formatGPU = HDR_FORMAT; + formatGPU = gpu::Element::COLOR_COMPRESSED_BCX_HDR_RGB; } + } else { + formatGPU = HDR_FORMAT; } + formatMip = formatGPU; // Find the layout of the cubemap in the 2D image diff --git a/libraries/image/src/image/Image.h b/libraries/image/src/image/Image.h index 43b1b613ad..9e56369ad3 100644 --- a/libraries/image/src/image/Image.h +++ b/libraries/image/src/image/Image.h @@ -88,13 +88,7 @@ const QStringList getSupportedFormats(); gpu::TexturePointer processImage(std::shared_ptr content, const std::string& url, int maxNumPixels, TextureUsage::Type textureType, - bool compress = false, -#ifdef USE_GLES - BackendTarget target = BackendTarget::GLES, -#else - BackendTarget target = BackendTarget::GL, -#endif - const std::atomic& abortProcessing = false); + bool compress, BackendTarget target, const std::atomic& abortProcessing = false); } // namespace image From 140b9be1c43b196dfa576941234bd0a1eabbc180 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Mon, 16 Jul 2018 10:00:11 -0700 Subject: [PATCH 062/144] Update BackendTarget to include version --- libraries/baking/src/TextureBaker.cpp | 6 ++--- libraries/image/src/image/Image.cpp | 23 +++++++++---------- libraries/image/src/image/Image.h | 5 ++-- .../src/model-networking/TextureCache.cpp | 11 ++++++--- 4 files changed, 25 insertions(+), 20 deletions(-) diff --git a/libraries/baking/src/TextureBaker.cpp b/libraries/baking/src/TextureBaker.cpp index 24fc331a3b..66c86d8c5f 100644 --- a/libraries/baking/src/TextureBaker.cpp +++ b/libraries/baking/src/TextureBaker.cpp @@ -150,8 +150,8 @@ void TextureBaker::processTexture() { // Compressed KTX if (_compressionEnabled) { constexpr std::array BACKEND_TARGETS { - image::BackendTarget::GL, - image::BackendTarget::GLES + image::BackendTarget::GL45, + image::BackendTarget::GLES32 }; for (auto target : BACKEND_TARGETS) { auto processedTexture = image::processImage(buffer, _textureURL.toString().toStdString(), @@ -198,7 +198,7 @@ void TextureBaker::processTexture() { if (_textureType == image::TextureUsage::Type::CUBE_TEXTURE) { buffer->reset(); auto processedTexture = image::processImage(std::move(buffer), _textureURL.toString().toStdString(), - ABSOLUTE_MAX_TEXTURE_NUM_PIXELS, _textureType, false, image::BackendTarget::GL, _abortProcessing); + ABSOLUTE_MAX_TEXTURE_NUM_PIXELS, _textureType, false, image::BackendTarget::GL45, _abortProcessing); if (!processedTexture) { handleError("Could not process texture " + _textureURL.toString()); return; diff --git a/libraries/image/src/image/Image.cpp b/libraries/image/src/image/Image.cpp index a35aa064ae..2c9255d215 100644 --- a/libraries/image/src/image/Image.cpp +++ b/libraries/image/src/image/Image.cpp @@ -80,7 +80,7 @@ const QStringList getSupportedFormats() { // On GLES, we don't use HDR skyboxes QImage::Format hdrFormatForTarget(BackendTarget target) { - if (target == BackendTarget::GLES) { + if (target == BackendTarget::GLES32) { return QImage::Format_RGB32; } return QImage::Format_RGB30; @@ -271,7 +271,7 @@ QImage processSourceImage(QImage&& srcImage, bool cubemap, BackendTarget target) const glm::uvec2 srcImageSize = toGlm(localCopy.size()); glm::uvec2 targetSize = srcImageSize; - const auto maxTextureSize = target == BackendTarget::GL ? MAX_TEXTURE_SIZE_GL : MAX_TEXTURE_SIZE_GLES; + const auto maxTextureSize = target == BackendTarget::GLES32 ? MAX_TEXTURE_SIZE_GLES : MAX_TEXTURE_SIZE_GL; while (glm::any(glm::greaterThan(targetSize, maxTextureSize))) { targetSize /= 2; } @@ -513,7 +513,7 @@ void generateLDRMips(gpu::Texture* texture, QImage&& image, BackendTarget target const int width = localCopy.width(), height = localCopy.height(); auto mipFormat = texture->getStoredMipFormat(); - if (target != BackendTarget::GLES) { + if (target != BackendTarget::GLES32) { const void* data = static_cast(localCopy.constBits()); nvtt::TextureType textureType = nvtt::TextureType_2D; nvtt::InputFormat inputFormat = nvtt::InputFormat_BGRA_8UB; @@ -705,7 +705,7 @@ void generateMips(gpu::Texture* texture, QImage&& image, BackendTarget target, c #if CPU_MIPMAPS PROFILE_RANGE(resource_parse, "generateMips"); - if (target == BackendTarget::GLES) { + if (target == BackendTarget::GLES32) { generateLDRMips(texture, std::move(image), target, abortProcessing, face); } else { if (image.format() == hdrFormatForTarget(target)) { @@ -768,7 +768,7 @@ gpu::TexturePointer TextureUsage::process2DTextureColorFromImage(QImage&& srcIma gpu::Element formatMip; gpu::Element formatGPU; if (compress) { - if (target == BackendTarget::GLES) { + if (target == BackendTarget::GLES32) { // GLES does not support GL_BGRA formatGPU = gpu::Element::COLOR_COMPRESSED_ETC2_SRGBA; formatMip = formatGPU; @@ -783,8 +783,7 @@ gpu::TexturePointer TextureUsage::process2DTextureColorFromImage(QImage&& srcIma formatMip = formatGPU; } } else { - if (target == BackendTarget::GLES) { - static_assert(false); + if (target == BackendTarget::GLES32) { } else { formatGPU = gpu::Element::COLOR_SRGBA_32; formatMip = gpu::Element::COLOR_SBGRA_32; @@ -907,7 +906,7 @@ gpu::TexturePointer TextureUsage::process2DTextureNormalMapFromImage(QImage&& sr gpu::Element formatMip; gpu::Element formatGPU; if (compress) { - if (target == BackendTarget::GLES) { + if (target == BackendTarget::GLES32) { formatGPU = gpu::Element::COLOR_COMPRESSED_EAC_XY; } else { formatGPU = gpu::Element::COLOR_COMPRESSED_BCX_XY; @@ -947,7 +946,7 @@ gpu::TexturePointer TextureUsage::process2DTextureGrayscaleFromImage(QImage&& sr gpu::Element formatMip; gpu::Element formatGPU; if (compress) { - if (target == BackendTarget::GLES) { + if (target == BackendTarget::GLES32) { formatGPU = gpu::Element::COLOR_COMPRESSED_EAC_RED; } else { formatGPU = gpu::Element::COLOR_COMPRESSED_BCX_RED; @@ -1312,7 +1311,7 @@ gpu::TexturePointer TextureUsage::processCubeTextureColorFromImage(QImage&& srcI QImage image = processSourceImage(std::move(localCopy), true, target); if (image.format() != hdrFormatForTarget(target)) { - if (target == BackendTarget::GLES) { + if (target == BackendTarget::GLES32) { image = image.convertToFormat(QImage::Format_RGB32); } else { image = convertToHDRFormat(std::move(image), HDR_FORMAT, target); @@ -1322,7 +1321,7 @@ gpu::TexturePointer TextureUsage::processCubeTextureColorFromImage(QImage&& srcI gpu::Element formatMip; gpu::Element formatGPU; if (compress) { - if (target == BackendTarget::GLES) { + if (target == BackendTarget::GLES32) { formatGPU = gpu::Element::COLOR_COMPRESSED_ETC2_SRGB; } else { formatGPU = gpu::Element::COLOR_COMPRESSED_BCX_HDR_RGB; @@ -1380,7 +1379,7 @@ gpu::TexturePointer TextureUsage::processCubeTextureColorFromImage(QImage&& srcI PROFILE_RANGE(resource_parse, "generateIrradiance"); gpu::Element irradianceFormat; // TODO: we could locally compress the irradiance texture on Android, but we don't need to - if (target == BackendTarget::GLES) { + if (target == BackendTarget::GLES32) { irradianceFormat = gpu::Element::COLOR_SRGBA_32; } else { irradianceFormat = HDR_FORMAT; diff --git a/libraries/image/src/image/Image.h b/libraries/image/src/image/Image.h index 9e56369ad3..e633cfc600 100644 --- a/libraries/image/src/image/Image.h +++ b/libraries/image/src/image/Image.h @@ -22,8 +22,9 @@ class QImage; namespace image { enum class BackendTarget { - GL, - GLES + GL41, + GL45, + GLES32 }; namespace TextureUsage { diff --git a/libraries/model-networking/src/model-networking/TextureCache.cpp b/libraries/model-networking/src/model-networking/TextureCache.cpp index 1485766aa2..1f536e8f09 100644 --- a/libraries/model-networking/src/model-networking/TextureCache.cpp +++ b/libraries/model-networking/src/model-networking/TextureCache.cpp @@ -280,9 +280,9 @@ gpu::TexturePointer TextureCache::getImageTexture(const QString& path, image::Te } auto loader = image::TextureUsage::getTextureLoaderForType(type, options); #ifdef USE_GLES - image::BackendTarget target = image::BackendTarget::GLES; + image::BackendTarget target = image::BackendTarget::GLES32; #else - image::BackendTarget target = image::BackendTarget::GL; + image::BackendTarget target = image::BackendTarget::GL45; #endif return gpu::TexturePointer(loader(std::move(image), path.toStdString(), false, target, false)); } @@ -1171,7 +1171,12 @@ void ImageReader::read() { #else constexpr bool shouldCompress = false; #endif - texture = image::processImage(std::move(buffer), _url.toString().toStdString(), _maxNumPixels, networkTexture->getTextureType(), shouldCompress); + #ifdef USE_GLES + image::BackendTarget target = image::BackendTarget::GLES32; + #else + image::BackendTarget target = image::BackendTarget::GL45; + #endif + texture = image::processImage(std::move(buffer), _url.toString().toStdString(), _maxNumPixels, networkTexture->getTextureType(), shouldCompress, target); if (!texture) { qCWarning(modelnetworking) << "Could not process:" << _url; From 26e69de81ea0cea5da17d2f24329c0203f6cbda9 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Tue, 24 Jul 2018 16:37:34 -0700 Subject: [PATCH 063/144] Add braces around std::array intialization to suppress warning --- libraries/baking/src/TextureBaker.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/baking/src/TextureBaker.cpp b/libraries/baking/src/TextureBaker.cpp index 66c86d8c5f..17bdf6fa7d 100644 --- a/libraries/baking/src/TextureBaker.cpp +++ b/libraries/baking/src/TextureBaker.cpp @@ -149,10 +149,10 @@ void TextureBaker::processTexture() { // Compressed KTX if (_compressionEnabled) { - constexpr std::array BACKEND_TARGETS { + constexpr std::array BACKEND_TARGETS {{ image::BackendTarget::GL45, image::BackendTarget::GLES32 - }; + }}; for (auto target : BACKEND_TARGETS) { auto processedTexture = image::processImage(buffer, _textureURL.toString().toStdString(), ABSOLUTE_MAX_TEXTURE_NUM_PIXELS, _textureType, true, From 6be0c43fca4f07180901adae8b6c9ba57cbf2ff5 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Thu, 26 Jul 2018 16:40:05 -0700 Subject: [PATCH 064/144] Update generateIrradiance to take a BackendTarget --- libraries/baking/src/TextureBaker.cpp | 8 +-- libraries/gpu/src/gpu/Texture.cpp | 72 +++++++++---------- libraries/gpu/src/gpu/Texture.h | 9 ++- libraries/image/src/image/Image.cpp | 2 +- libraries/image/src/image/Image.h | 42 +++++------ .../src/model-networking/TextureCache.cpp | 8 +-- 6 files changed, 70 insertions(+), 71 deletions(-) diff --git a/libraries/baking/src/TextureBaker.cpp b/libraries/baking/src/TextureBaker.cpp index 17bdf6fa7d..2516323c37 100644 --- a/libraries/baking/src/TextureBaker.cpp +++ b/libraries/baking/src/TextureBaker.cpp @@ -149,9 +149,9 @@ void TextureBaker::processTexture() { // Compressed KTX if (_compressionEnabled) { - constexpr std::array BACKEND_TARGETS {{ - image::BackendTarget::GL45, - image::BackendTarget::GLES32 + constexpr std::array BACKEND_TARGETS {{ + gpu::BackendTarget::GL45, + gpu::BackendTarget::GLES32 }}; for (auto target : BACKEND_TARGETS) { auto processedTexture = image::processImage(buffer, _textureURL.toString().toStdString(), @@ -198,7 +198,7 @@ void TextureBaker::processTexture() { if (_textureType == image::TextureUsage::Type::CUBE_TEXTURE) { buffer->reset(); auto processedTexture = image::processImage(std::move(buffer), _textureURL.toString().toStdString(), - ABSOLUTE_MAX_TEXTURE_NUM_PIXELS, _textureType, false, image::BackendTarget::GL45, _abortProcessing); + ABSOLUTE_MAX_TEXTURE_NUM_PIXELS, _textureType, false, gpu::BackendTarget::GL45, _abortProcessing); if (!processedTexture) { handleError("Could not process texture " + _textureURL.toString()); return; diff --git a/libraries/gpu/src/gpu/Texture.cpp b/libraries/gpu/src/gpu/Texture.cpp index 34262b0cd9..d34fa954c8 100755 --- a/libraries/gpu/src/gpu/Texture.cpp +++ b/libraries/gpu/src/gpu/Texture.cpp @@ -510,7 +510,7 @@ void Texture::setSampler(const Sampler& sampler) { } -bool Texture::generateIrradiance() { +bool Texture::generateIrradiance(gpu::BackendTarget target) { if (getType() != TEX_CUBE) { return false; } @@ -521,7 +521,7 @@ bool Texture::generateIrradiance() { _irradiance = std::make_shared(); } - _irradiance->evalFromTexture(*this); + _irradiance->evalFromTexture(*this, target); return true; } @@ -684,7 +684,7 @@ void sphericalHarmonicsEvaluateDirection(float * result, int order, const glm:: result[8] = P_2_2 * ((double)dir.x * (double)dir.x - (double)dir.y * (double)dir.y); } -bool sphericalHarmonicsFromTexture(const gpu::Texture& cubeTexture, std::vector & output, const uint order) { +bool sphericalHarmonicsFromTexture(const gpu::Texture& cubeTexture, std::vector & output, const uint order, gpu::BackendTarget target) { int width = cubeTexture.getWidth(); if(width != cubeTexture.getHeight()) { return false; @@ -692,22 +692,6 @@ bool sphericalHarmonicsFromTexture(const gpu::Texture& cubeTexture, std::vector< PROFILE_RANGE(render_gpu, "sphericalHarmonicsFromTexture"); -#ifndef USE_GLES - auto mipFormat = cubeTexture.getStoredMipFormat(); - std::function unpackFunc; - switch (mipFormat.getSemantic()) { - case gpu::R11G11B10: - unpackFunc = glm::unpackF2x11_1x10; - break; - case gpu::RGB9E5: - unpackFunc = glm::unpackF3x9_E1x5; - break; - default: - assert(false); - break; - } -#endif - const uint sqOrder = order*order; // allocate memory for calculations @@ -741,11 +725,7 @@ bool sphericalHarmonicsFromTexture(const gpu::Texture& cubeTexture, std::vector< for(int face=0; face < gpu::Texture::NUM_CUBE_FACES; face++) { PROFILE_RANGE(render_gpu, "ProcessFace"); -#ifndef USE_GLES - auto data = reinterpret_cast( cubeTexture.accessStoredMipFace(0, face)->readData() ); -#else auto data = cubeTexture.accessStoredMipFace(0, face)->readData(); -#endif if (data == nullptr) { continue; } @@ -827,20 +807,40 @@ bool sphericalHarmonicsFromTexture(const gpu::Texture& cubeTexture, std::vector< // get color from texture glm::vec3 color{ 0.0f, 0.0f, 0.0f }; - for (int i = 0; i < stride; ++i) { - for (int j = 0; j < stride; ++j) { -#ifndef USE_GLES - int k = (int)(x + i - halfStride + (y + j - halfStride) * width); - color += unpackFunc(data[k]); -#else - const int NUM_COMPONENTS_PER_PIXEL = 4; - int k = NUM_COMPONENTS_PER_PIXEL * (int)(x + i - halfStride + (y + j - halfStride) * width); - // BGRA -> RGBA - color += glm::pow(glm::vec3(data[k + 2], data[k + 1], data[k]) / 255.0f, glm::vec3(2.2f)); -#endif + + if (target != gpu::BackendTarget::GLES32) { + auto mipFormat = cubeTexture.getStoredMipFormat(); + std::function unpackFunc; + switch (mipFormat.getSemantic()) { + case gpu::R11G11B10: + unpackFunc = glm::unpackF2x11_1x10; + break; + case gpu::RGB9E5: + unpackFunc = glm::unpackF3x9_E1x5; + break; + default: + assert(false); + break; + } + auto data32 = reinterpret_cast(data); + for (int i = 0; i < stride; ++i) { + for (int j = 0; j < stride; ++j) { + int k = (int)(x + i - halfStride + (y + j - halfStride) * width); + color += unpackFunc(data32[k]); + } + } + } else { + // BGRA -> RGBA + const int NUM_COMPONENTS_PER_PIXEL = 4; + for (int i = 0; i < stride; ++i) { + for (int j = 0; j < stride; ++j) { + int k = NUM_COMPONENTS_PER_PIXEL * (int)(x + i - halfStride + (y + j - halfStride) * width); + color += glm::pow(glm::vec3(data[k + 2], data[k + 1], data[k]) / 255.0f, glm::vec3(2.2f)); + } } } + // scale color and add to previously accumulated coefficients // red sphericalHarmonicsScale(shBuffB.data(), order, shBuff.data(), color.r * fDiffSolid); @@ -869,10 +869,10 @@ bool sphericalHarmonicsFromTexture(const gpu::Texture& cubeTexture, std::vector< return true; } -void SphericalHarmonics::evalFromTexture(const Texture& texture) { +void SphericalHarmonics::evalFromTexture(const Texture& texture, gpu::BackendTarget target) { if (texture.isDefined()) { std::vector< glm::vec3 > coefs; - sphericalHarmonicsFromTexture(texture, coefs, 3); + sphericalHarmonicsFromTexture(texture, coefs, 3, target); L00 = coefs[0]; L1m1 = coefs[1]; diff --git a/libraries/gpu/src/gpu/Texture.h b/libraries/gpu/src/gpu/Texture.h index 9ad5dc0816..73ed1b15dc 100755 --- a/libraries/gpu/src/gpu/Texture.h +++ b/libraries/gpu/src/gpu/Texture.h @@ -43,6 +43,11 @@ namespace khronos { namespace gl { namespace texture { namespace gpu { +enum class BackendTarget { + GL41, + GL45, + GLES32 +}; const std::string SOURCE_HASH_KEY { "hifi.sourceHash" }; @@ -82,7 +87,7 @@ public: void assignPreset(int p); - void evalFromTexture(const Texture& texture); + void evalFromTexture(const Texture& texture, gpu::BackendTarget target); }; typedef std::shared_ptr< SphericalHarmonics > SHPointer; @@ -541,7 +546,7 @@ public: Usage getUsage() const { return _usage; } // For Cube Texture, it's possible to generate the irradiance spherical harmonics and make them availalbe with the texture - bool generateIrradiance(); + bool generateIrradiance(gpu::BackendTarget target); const SHPointer& getIrradiance(uint16 slice = 0) const { return _irradiance; } void overrideIrradiance(SHPointer irradiance) { _irradiance = irradiance; } bool isIrradianceValid() const { return _isIrradianceValid; } diff --git a/libraries/image/src/image/Image.cpp b/libraries/image/src/image/Image.cpp index 2c9255d215..1355a24bf4 100644 --- a/libraries/image/src/image/Image.cpp +++ b/libraries/image/src/image/Image.cpp @@ -1392,7 +1392,7 @@ gpu::TexturePointer TextureUsage::processCubeTextureColorFromImage(QImage&& srcI irradianceTexture->assignStoredMipFace(0, face, faces[face].byteCount(), faces[face].constBits()); } - irradianceTexture->generateIrradiance(); + irradianceTexture->generateIrradiance(target); auto irradiance = irradianceTexture->getIrradiance(); theTexture->overrideIrradiance(irradiance); diff --git a/libraries/image/src/image/Image.h b/libraries/image/src/image/Image.h index e633cfc600..ae72a183b3 100644 --- a/libraries/image/src/image/Image.h +++ b/libraries/image/src/image/Image.h @@ -21,12 +21,6 @@ class QImage; namespace image { -enum class BackendTarget { - GL41, - GL45, - GLES32 -}; - namespace TextureUsage { enum Type { @@ -47,41 +41,41 @@ enum Type { UNUSED_TEXTURE }; -using TextureLoader = std::function&)>; +using TextureLoader = std::function&)>; TextureLoader getTextureLoaderForType(Type type, const QVariantMap& options = QVariantMap()); gpu::TexturePointer create2DTextureFromImage(QImage&& image, const std::string& srcImageName, - bool compress, BackendTarget target, const std::atomic& abortProcessing); + bool compress, gpu::BackendTarget target, const std::atomic& abortProcessing); gpu::TexturePointer createStrict2DTextureFromImage(QImage&& image, const std::string& srcImageName, - bool compress, BackendTarget target, const std::atomic& abortProcessing); + bool compress, gpu::BackendTarget target, const std::atomic& abortProcessing); gpu::TexturePointer createAlbedoTextureFromImage(QImage&& image, const std::string& srcImageName, - bool compress, BackendTarget target, const std::atomic& abortProcessing); + bool compress, gpu::BackendTarget target, const std::atomic& abortProcessing); gpu::TexturePointer createEmissiveTextureFromImage(QImage&& image, const std::string& srcImageName, - bool compress, BackendTarget target, const std::atomic& abortProcessing); + bool compress, gpu::BackendTarget target, const std::atomic& abortProcessing); gpu::TexturePointer createNormalTextureFromNormalImage(QImage&& image, const std::string& srcImageName, - bool compress, BackendTarget target, const std::atomic& abortProcessing); + bool compress, gpu::BackendTarget target, const std::atomic& abortProcessing); gpu::TexturePointer createNormalTextureFromBumpImage(QImage&& image, const std::string& srcImageName, - bool compress, BackendTarget target, const std::atomic& abortProcessing); + bool compress, gpu::BackendTarget target, const std::atomic& abortProcessing); gpu::TexturePointer createRoughnessTextureFromImage(QImage&& image, const std::string& srcImageName, - bool compress, BackendTarget target, const std::atomic& abortProcessing); + bool compress, gpu::BackendTarget target, const std::atomic& abortProcessing); gpu::TexturePointer createRoughnessTextureFromGlossImage(QImage&& image, const std::string& srcImageName, - bool compress, BackendTarget target, const std::atomic& abortProcessing); + bool compress, gpu::BackendTarget target, const std::atomic& abortProcessing); gpu::TexturePointer createMetallicTextureFromImage(QImage&& image, const std::string& srcImageName, - bool compress, BackendTarget target, const std::atomic& abortProcessing); + bool compress, gpu::BackendTarget target, const std::atomic& abortProcessing); gpu::TexturePointer createCubeTextureFromImage(QImage&& image, const std::string& srcImageName, - bool compress, BackendTarget target, const std::atomic& abortProcessing); + bool compress, gpu::BackendTarget target, const std::atomic& abortProcessing); gpu::TexturePointer createCubeTextureFromImageWithoutIrradiance(QImage&& image, const std::string& srcImageName, - bool compress, BackendTarget target, const std::atomic& abortProcessing); + bool compress, gpu::BackendTarget target, const std::atomic& abortProcessing); gpu::TexturePointer createLightmapTextureFromImage(QImage&& image, const std::string& srcImageName, - bool compress, BackendTarget target, const std::atomic& abortProcessing); + bool compress, gpu::BackendTarget target, const std::atomic& abortProcessing); gpu::TexturePointer process2DTextureColorFromImage(QImage&& srcImage, const std::string& srcImageName, bool compress, - BackendTarget target, bool isStrict, const std::atomic& abortProcessing); + gpu::BackendTarget target, bool isStrict, const std::atomic& abortProcessing); gpu::TexturePointer process2DTextureNormalMapFromImage(QImage&& srcImage, const std::string& srcImageName, bool compress, - BackendTarget target, bool isBumpMap, const std::atomic& abortProcessing); + gpu::BackendTarget target, bool isBumpMap, const std::atomic& abortProcessing); gpu::TexturePointer process2DTextureGrayscaleFromImage(QImage&& srcImage, const std::string& srcImageName, bool compress, - BackendTarget target, bool isInvertedPixels, const std::atomic& abortProcessing); + gpu::BackendTarget target, bool isInvertedPixels, const std::atomic& abortProcessing); gpu::TexturePointer processCubeTextureColorFromImage(QImage&& srcImage, const std::string& srcImageName, bool compress, - BackendTarget target, bool generateIrradiance, const std::atomic& abortProcessing); + gpu::BackendTarget target, bool generateIrradiance, const std::atomic& abortProcessing); } // namespace TextureUsage @@ -89,7 +83,7 @@ const QStringList getSupportedFormats(); gpu::TexturePointer processImage(std::shared_ptr content, const std::string& url, int maxNumPixels, TextureUsage::Type textureType, - bool compress, BackendTarget target, const std::atomic& abortProcessing = false); + bool compress, gpu::BackendTarget target, const std::atomic& abortProcessing = false); } // namespace image diff --git a/libraries/model-networking/src/model-networking/TextureCache.cpp b/libraries/model-networking/src/model-networking/TextureCache.cpp index 1f536e8f09..7f435838b6 100644 --- a/libraries/model-networking/src/model-networking/TextureCache.cpp +++ b/libraries/model-networking/src/model-networking/TextureCache.cpp @@ -280,9 +280,9 @@ gpu::TexturePointer TextureCache::getImageTexture(const QString& path, image::Te } auto loader = image::TextureUsage::getTextureLoaderForType(type, options); #ifdef USE_GLES - image::BackendTarget target = image::BackendTarget::GLES32; + gpu::BackendTarget target = gpu::BackendTarget::GLES32; #else - image::BackendTarget target = image::BackendTarget::GL45; + gpu::BackendTarget target = gpu::BackendTarget::GL45; #endif return gpu::TexturePointer(loader(std::move(image), path.toStdString(), false, target, false)); } @@ -1172,9 +1172,9 @@ void ImageReader::read() { constexpr bool shouldCompress = false; #endif #ifdef USE_GLES - image::BackendTarget target = image::BackendTarget::GLES32; + gpu::BackendTarget target = gpu::BackendTarget::GLES32; #else - image::BackendTarget target = image::BackendTarget::GL45; + gpu::BackendTarget target = gpu::BackendTarget::GL45; #endif texture = image::processImage(std::move(buffer), _url.toString().toStdString(), _maxNumPixels, networkTexture->getTextureType(), shouldCompress, target); From 6249d1c3aa5e9ec802ee5d414598d75ede18cbf3 Mon Sep 17 00:00:00 2001 From: sabrina-shanman Date: Wed, 8 Aug 2018 10:32:26 -0700 Subject: [PATCH 065/144] Initialize btCollisionWorld::ContactResultCallback BEFORE AllContactsCallback variables --- libraries/physics/src/PhysicsEngine.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/physics/src/PhysicsEngine.cpp b/libraries/physics/src/PhysicsEngine.cpp index fe12350124..95004f8b2a 100644 --- a/libraries/physics/src/PhysicsEngine.cpp +++ b/libraries/physics/src/PhysicsEngine.cpp @@ -842,11 +842,11 @@ void PhysicsEngine::setShowBulletConstraintLimits(bool value) { struct AllContactsCallback : public btCollisionWorld::ContactResultCallback { AllContactsCallback(MotionStateType desiredObjectType, const ShapeInfo& shapeInfo, const Transform& transform, btCollisionObject* myAvatarCollisionObject) : + btCollisionWorld::ContactResultCallback(), desiredObjectType(desiredObjectType), collisionObject(), contacts(), - myAvatarCollisionObject(myAvatarCollisionObject), - btCollisionWorld::ContactResultCallback() { + myAvatarCollisionObject(myAvatarCollisionObject) { const btCollisionShape* collisionShape = ObjectMotionState::getShapeManager()->getShape(shapeInfo); collisionObject.setCollisionShape(const_cast(collisionShape)); From 22142a84b89281b491b222ffa6376a08e0cbff01 Mon Sep 17 00:00:00 2001 From: sabrina-shanman Date: Wed, 8 Aug 2018 10:42:26 -0700 Subject: [PATCH 066/144] Remove JS documentation on 'loaded' parameter referencing unsupported model collision pick --- interface/src/raypick/PickScriptingInterface.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/interface/src/raypick/PickScriptingInterface.h b/interface/src/raypick/PickScriptingInterface.h index 908f0eb48a..e1431c3937 100644 --- a/interface/src/raypick/PickScriptingInterface.h +++ b/interface/src/raypick/PickScriptingInterface.h @@ -151,9 +151,11 @@ public: * @property {EntityItersection[]} entityIntersections The collision information of entities which intersect with the CollisionRegion. There may be multiple intersections with the same entity which represent distinct collision points. * @property {EntityItersection[]} avatarIntersections The collision information of avatars which intersect with the CollisionRegion. There may be multiple intersections with the same entity which represent distinct collision points. * @property {CollisionRegion} collisionRegion The CollisionRegion that was used. Valid even if there was no intersection. - * @property {boolean} loaded If the CollisionRegion was successfully loaded (may be false if a model was used) */ + // 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 * A pair of intersection points between a CollisionPick and an entity/avatar. * From 5b6916177c542e053fdd1eb76ad9afcefe8e1db8 Mon Sep 17 00:00:00 2001 From: sabrina-shanman Date: Wed, 8 Aug 2018 10:47:13 -0700 Subject: [PATCH 067/144] Add support for rotation as CollisionRegion parameter and make it the default --- interface/src/raypick/PickScriptingInterface.cpp | 2 +- libraries/shared/src/RegisteredMetaTypes.h | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/interface/src/raypick/PickScriptingInterface.cpp b/interface/src/raypick/PickScriptingInterface.cpp index 72606c8da6..28bffb487e 100644 --- a/interface/src/raypick/PickScriptingInterface.cpp +++ b/interface/src/raypick/PickScriptingInterface.cpp @@ -255,7 +255,7 @@ unsigned int PickScriptingInterface::createParabolaPick(const QVariant& properti * @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. +* @property {Quat} rotation - The orientation of the collision region. */ unsigned int PickScriptingInterface::createCollisionPick(const QVariant& properties) { QVariantMap propMap = properties.toMap(); diff --git a/libraries/shared/src/RegisteredMetaTypes.h b/libraries/shared/src/RegisteredMetaTypes.h index 204ee04baa..5f842ef066 100644 --- a/libraries/shared/src/RegisteredMetaTypes.h +++ b/libraries/shared/src/RegisteredMetaTypes.h @@ -282,7 +282,9 @@ public: if (pickVariant["position"].isValid()) { transform.setTranslation(vec3FromVariant(pickVariant["position"])); } - if (pickVariant["orientation"].isValid()) { + if (pickVariant["rotation"].isValid()) { + transform.setRotation(quatFromVariant(pickVariant["rotation"])); + } else if (pickVariant["orientation"].isValid()) { transform.setRotation(quatFromVariant(pickVariant["orientation"])); } } From 1a261bea04c5c4065d17dcd08e16f9eff5691e68 Mon Sep 17 00:00:00 2001 From: Cristian Luis Duarte Date: Wed, 8 Aug 2018 15:41:28 -0300 Subject: [PATCH 068/144] Android - People screen renaming --- .../io/highfidelity/hifiinterface/MainActivity.java | 12 ++++++------ android/app/src/main/res/menu/menu_navigation.xml | 4 ++-- android/app/src/main/res/values/strings.xml | 1 + 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/MainActivity.java b/android/app/src/main/java/io/highfidelity/hifiinterface/MainActivity.java index d1ac6d5ec8..220a69381d 100644 --- a/android/app/src/main/java/io/highfidelity/hifiinterface/MainActivity.java +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/MainActivity.java @@ -115,8 +115,8 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On case "Privacy Policy": loadPrivacyPolicyFragment(); break; - case "Friends": - loadFriendsFragment(); + case "People": + loadPeopleFragment(); break; default: Log.e(TAG, "Unknown fragment " + fragment); @@ -141,10 +141,10 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On loadFragment(fragment, getString(R.string.privacyPolicy), true); } - private void loadFriendsFragment() { + private void loadPeopleFragment() { Fragment fragment = FriendsFragment.newInstance(); - loadFragment(fragment, getString(R.string.friends), true); + loadFragment(fragment, getString(R.string.people), true); } private void loadFragment(Fragment fragment, String title, boolean addToBackStack) { @@ -212,8 +212,8 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On case R.id.action_home: loadHomeFragment(); return true; - case R.id.action_friends: - loadFriendsFragment(); + case R.id.action_people: + loadPeopleFragment(); return true; } return false; diff --git a/android/app/src/main/res/menu/menu_navigation.xml b/android/app/src/main/res/menu/menu_navigation.xml index de496bc4cb..3cce64f9f5 100644 --- a/android/app/src/main/res/menu/menu_navigation.xml +++ b/android/app/src/main/res/menu/menu_navigation.xml @@ -6,7 +6,7 @@ android:title="@string/home" /> diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml index e29252c2c0..9fe7f0cbee 100644 --- a/android/app/src/main/res/values/strings.xml +++ b/android/app/src/main/res/values/strings.xml @@ -2,6 +2,7 @@ Interface Home Friends + People Open in browser Share link Shared a link From 2ba2c4b68a0b9f730a1d62ca9da98f02bdebd39b Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Wed, 8 Aug 2018 14:06:34 -0700 Subject: [PATCH 069/144] Fix TextureCache not compressing textures on Android --- .../src/model-networking/TextureCache.cpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/libraries/model-networking/src/model-networking/TextureCache.cpp b/libraries/model-networking/src/model-networking/TextureCache.cpp index 7f435838b6..b9def7f5b7 100644 --- a/libraries/model-networking/src/model-networking/TextureCache.cpp +++ b/libraries/model-networking/src/model-networking/TextureCache.cpp @@ -281,10 +281,12 @@ gpu::TexturePointer TextureCache::getImageTexture(const QString& path, image::Te auto loader = image::TextureUsage::getTextureLoaderForType(type, options); #ifdef USE_GLES gpu::BackendTarget target = gpu::BackendTarget::GLES32; + bool shouldCompress = true; #else gpu::BackendTarget target = gpu::BackendTarget::GL45; + bool shouldCompress = false; #endif - return gpu::TexturePointer(loader(std::move(image), path.toStdString(), false, target, false)); + return gpu::TexturePointer(loader(std::move(image), path.toStdString(), shouldCompress, target, false)); } QSharedPointer TextureCache::createResource(const QUrl& url, const QSharedPointer& fallback, @@ -1168,14 +1170,11 @@ void ImageReader::read() { #ifdef USE_GLES constexpr bool shouldCompress = true; + gpu::BackendTarget target = gpu::BackendTarget::GLES32; #else constexpr bool shouldCompress = false; -#endif - #ifdef USE_GLES - gpu::BackendTarget target = gpu::BackendTarget::GLES32; - #else gpu::BackendTarget target = gpu::BackendTarget::GL45; - #endif +#endif texture = image::processImage(std::move(buffer), _url.toString().toStdString(), _maxNumPixels, networkTexture->getTextureType(), shouldCompress, target); if (!texture) { From 8db0711b5b06b73d830e88da0023e8aec73d0ae1 Mon Sep 17 00:00:00 2001 From: David Back Date: Wed, 8 Aug 2018 16:33:47 -0700 Subject: [PATCH 070/144] set grabbable user data in addModelEntity, pass grabbable from asset browser --- interface/resources/qml/hifi/AssetServer.qml | 6 ++++-- interface/resources/qml/hifi/dialogs/TabletAssetServer.qml | 6 ++++-- interface/src/Application.cpp | 1 - libraries/entities/src/EntityScriptingInterface.cpp | 5 ++++- libraries/entities/src/EntityScriptingInterface.h | 5 ++++- 5 files changed, 16 insertions(+), 7 deletions(-) diff --git a/interface/resources/qml/hifi/AssetServer.qml b/interface/resources/qml/hifi/AssetServer.qml index 526ea6aad0..1a7f5bac40 100644 --- a/interface/resources/qml/hifi/AssetServer.qml +++ b/interface/resources/qml/hifi/AssetServer.qml @@ -186,6 +186,8 @@ Windows.ScrollingWindow { return; } + var grabbable = MenuInterface.isOptionChecked("Create Entities As Grabbable (except Zones, Particles, and Lights)"); + if (defaultURL.endsWith(".jpg") || defaultURL.endsWith(".png")) { var name = assetProxyModel.data(treeView.selection.currentIndex); var modelURL = "https://hifi-content.s3.amazonaws.com/DomainContent/production/default-image-model.fbx"; @@ -195,7 +197,7 @@ Windows.ScrollingWindow { var collisionless = true; var position = Vec3.sum(MyAvatar.position, Vec3.multiply(2, Quat.getForward(MyAvatar.orientation))); var gravity = Vec3.multiply(Vec3.fromPolar(Math.PI / 2, 0), 0); - Entities.addModelEntity(name, modelURL, textures, shapeType, dynamic, collisionless, position, gravity); + Entities.addModelEntity(name, modelURL, textures, shapeType, dynamic, collisionless, grabbable, position, gravity); } else { var SHAPE_TYPE_NONE = 0; var SHAPE_TYPE_SIMPLE_HULL = 1; @@ -281,7 +283,7 @@ Windows.ScrollingWindow { print("Asset browser - adding asset " + url + " (" + name + ") to world."); // Entities.addEntity doesn't work from QML, so we use this. - Entities.addModelEntity(name, url, "", shapeType, dynamic, collisionless, addPosition, gravity); + Entities.addModelEntity(name, url, "", shapeType, dynamic, collisionless, grabbable, addPosition, gravity); } } }); diff --git a/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml b/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml index 6bf8f8a5d5..0eeb252049 100644 --- a/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml +++ b/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml @@ -186,6 +186,8 @@ Rectangle { return; } + var grabbable = MenuInterface.isOptionChecked("Create Entities As Grabbable (except Zones, Particles, and Lights)"); + if (defaultURL.endsWith(".jpg") || defaultURL.endsWith(".png")) { var name = assetProxyModel.data(treeView.selection.currentIndex); var modelURL = "https://hifi-content.s3.amazonaws.com/DomainContent/production/default-image-model.fbx"; @@ -195,7 +197,7 @@ Rectangle { var collisionless = true; var position = Vec3.sum(MyAvatar.position, Vec3.multiply(2, Quat.getForward(MyAvatar.orientation))); var gravity = Vec3.multiply(Vec3.fromPolar(Math.PI / 2, 0), 0); - Entities.addModelEntity(name, modelURL, textures, shapeType, dynamic, collisionless, position, gravity); + Entities.addModelEntity(name, modelURL, textures, shapeType, dynamic, collisionless, grabbable, position, gravity); } else { var SHAPE_TYPE_NONE = 0; var SHAPE_TYPE_SIMPLE_HULL = 1; @@ -281,7 +283,7 @@ Rectangle { print("Asset browser - adding asset " + url + " (" + name + ") to world."); // Entities.addEntity doesn't work from QML, so we use this. - Entities.addModelEntity(name, url, "", shapeType, dynamic, collisionless, addPosition, gravity); + Entities.addModelEntity(name, url, "", shapeType, dynamic, collisionless, grabbable, addPosition, gravity); } } }); diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 0bbd2b1c17..a39652cf14 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -7325,7 +7325,6 @@ void Application::addAssetToWorldCheckModelSize() { auto name = properties.getName(); auto dimensions = properties.getDimensions(); - const QString GRABBABLE_USER_DATA = "{\"grabbableKey\":{\"grabbable\":true}}"; bool doResize = false; const glm::vec3 DEFAULT_DIMENSIONS = glm::vec3(0.1f, 0.1f, 0.1f); diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index 8fd87e068a..ff636f0877 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -38,6 +38,8 @@ #include #include +const QString GRABBABLE_USER_DATA = "{\"grabbableKey\":{\"grabbable\":true}}"; +const QString NOT_GRABBABLE_USER_DATA = "{\"grabbableKey\":{\"grabbable\":false}}"; EntityScriptingInterface::EntityScriptingInterface(bool bidOnSimulationOwnership) : _entityTree(NULL), @@ -303,7 +305,7 @@ bool EntityScriptingInterface::addLocalEntityCopy(EntityItemProperties& properti } QUuid EntityScriptingInterface::addModelEntity(const QString& name, const QString& modelUrl, const QString& textures, - const QString& shapeType, bool dynamic, bool collisionless, + const QString& shapeType, bool dynamic, bool collisionless, bool grabbable, const glm::vec3& position, const glm::vec3& gravity) { _activityTracking.addedEntityCount++; @@ -314,6 +316,7 @@ QUuid EntityScriptingInterface::addModelEntity(const QString& name, const QStrin properties.setShapeTypeFromString(shapeType); properties.setDynamic(dynamic); properties.setCollisionless(collisionless); + properties.setUserData(grabbable ? GRABBABLE_USER_DATA : NOT_GRABBABLE_USER_DATA); properties.setPosition(position); properties.setGravity(gravity); if (!textures.isEmpty()) { diff --git a/libraries/entities/src/EntityScriptingInterface.h b/libraries/entities/src/EntityScriptingInterface.h index a166d513d3..3e0f040fd6 100644 --- a/libraries/entities/src/EntityScriptingInterface.h +++ b/libraries/entities/src/EntityScriptingInterface.h @@ -39,6 +39,9 @@ class EntityTree; class MeshProxy; +extern const QString GRABBABLE_USER_DATA; +extern const QString NOT_GRABBABLE_USER_DATA; + // helper factory to compose standardized, async metadata queries for "magic" Entity properties // like .script and .serverScripts. This is used for automated testing of core scripting features // as well as to provide early adopters a self-discoverable, consistent way to diagnose common @@ -237,7 +240,7 @@ public slots: /// temporary method until addEntity can be used from QJSEngine /// Deliberately not adding jsdoc, only used internally. Q_INVOKABLE QUuid addModelEntity(const QString& name, const QString& modelUrl, const QString& textures, const QString& shapeType, bool dynamic, - bool collisionless, const glm::vec3& position, const glm::vec3& gravity); + bool collisionless, bool grabbable, const glm::vec3& position, const glm::vec3& gravity); /**jsdoc * Create a clone of an entity. A clone can be created by a client that doesn't have rez permissions in the current domain. From 8c121a531d3ac70fdaf0ace6cf229ebb03bcd206 Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Thu, 9 Aug 2018 10:35:15 -0700 Subject: [PATCH 071/144] Turn attachments into avatar entities --- interface/src/avatar/MyAvatar.cpp | 175 +++++++++++++++++++++++++---- interface/src/avatar/MyAvatar.h | 18 ++- libraries/avatars/src/AvatarData.h | 10 +- 3 files changed, 176 insertions(+), 27 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 3f738ea4cb..bd05bb284e 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -1688,16 +1688,6 @@ void MyAvatar::useFullAvatarURL(const QUrl& fullAvatarURL, const QString& modelN markIdentityDataChanged(); } -void MyAvatar::setAttachmentData(const QVector& attachmentData) { - if (QThread::currentThread() != thread()) { - BLOCKING_INVOKE_METHOD(this, "setAttachmentData", - Q_ARG(const QVector, attachmentData)); - return; - } - Avatar::setAttachmentData(attachmentData); - emit attachmentsChanged(); -} - glm::vec3 MyAvatar::getSkeletonPosition() const { CameraMode mode = qApp->getCamera().getMode(); if (mode == CAMERA_MODE_THIRD_PERSON || mode == CAMERA_MODE_INDEPENDENT) { @@ -1968,20 +1958,165 @@ void MyAvatar::attach(const QString& modelURL, const QString& jointName, float scale, bool isSoft, bool allowDuplicates, bool useSaved) { if (QThread::currentThread() != thread()) { - Avatar::attach(modelURL, jointName, translation, rotation, scale, isSoft, allowDuplicates, useSaved); + BLOCKING_INVOKE_METHOD(this, "attach", + Q_ARG(const QString&, modelURL), + Q_ARG(const QString&, jointName), + Q_ARG(const glm::vec3&, translation), + Q_ARG(const glm::quat&, rotation), + Q_ARG(float, scale), + Q_ARG(bool, isSoft), + Q_ARG(bool, allowDuplicates), + Q_ARG(bool, useSaved) + ); return; } - if (useSaved) { - AttachmentData attachment = loadAttachmentData(modelURL, jointName); - if (attachment.isValid()) { - Avatar::attach(modelURL, attachment.jointName, - attachment.translation, attachment.rotation, - attachment.scale, attachment.isSoft, - allowDuplicates, useSaved); - return; + AttachmentData data; + data.modelURL = modelURL; + data.jointName = jointName; + data.translation = translation; + data.rotation = rotation; + data.scale = scale; + data.isSoft = isSoft; + EntityItemProperties properties; + attachmentDataToEntityProperties(data, properties); + DependencyManager::get()->addEntity(properties, true); + emit attachmentsChanged(); +} + +void MyAvatar::detachOne(const QString& modelURL, const QString& jointName) { + if (QThread::currentThread() != thread()) { + BLOCKING_INVOKE_METHOD(this, "detachOne", + Q_ARG(const QString&, modelURL), + Q_ARG(const QString&, jointName) + ); + return; + } + QUuid entityID; + if (findAvatarEntity(modelURL, jointName, entityID)) { + DependencyManager::get()->deleteEntity(entityID); + } + emit attachmentsChanged(); +} + +void MyAvatar::detachAll(const QString& modelURL, const QString& jointName) { + if (QThread::currentThread() != thread()) { + BLOCKING_INVOKE_METHOD(this, "detachAll", + Q_ARG(const QString&, modelURL), + Q_ARG(const QString&, jointName) + ); + return; + } + QUuid entityID; + while (findAvatarEntity(modelURL, jointName, entityID)) { + DependencyManager::get()->deleteEntity(entityID); + } + emit attachmentsChanged(); +} + +void MyAvatar::setAttachmentData(const QVector& attachmentData) { + if (QThread::currentThread() != thread()) { + BLOCKING_INVOKE_METHOD(this, "setAttachmentData", + Q_ARG(const QVector&, attachmentData)); + return; + } + std::vector newEntitiesProperties; + for (auto& data : attachmentData) { + QUuid entityID; + EntityItemProperties properties; + if (findAvatarEntity(data.modelURL.toString(), data.jointName, entityID)) { + properties = DependencyManager::get()->getEntityProperties(entityID); + } + attachmentDataToEntityProperties(data, properties); + newEntitiesProperties.push_back(properties); + } + removeAvatarEntities(); + for (auto& properties : newEntitiesProperties) { + DependencyManager::get()->addEntity(properties, true); + } + emit attachmentsChanged(); +} + +QVector MyAvatar::getAttachmentData() const { + QVector avatarData; + auto avatarEntities = getAvatarEntityData(); + AvatarEntityMap::const_iterator dataItr = avatarEntities.begin(); + while (dataItr != avatarEntities.end()) { + QUuid entityID = dataItr.key(); + auto properties = DependencyManager::get()->getEntityProperties(entityID); + AttachmentData data = entityPropertiesToAttachmentData(properties); + avatarData.append(data); + dataItr++; + } + return avatarData; +} + +QVariantList MyAvatar::getAttachmentsVariant() const { + QVariantList result; + for (const auto& attachment : getAttachmentData()) { + result.append(attachment.toVariant()); + } + return result; +} + +void MyAvatar::setAttachmentsVariant(const QVariantList& variant) { + if (QThread::currentThread() != thread()) { + BLOCKING_INVOKE_METHOD(this, "setAttachmentsVariant", + Q_ARG(const QVariantList&, variant)); + return; + } + QVector newAttachments; + newAttachments.reserve(variant.size()); + for (const auto& attachmentVar : variant) { + AttachmentData attachment; + if (attachment.fromVariant(attachmentVar)) { + newAttachments.append(attachment); } } - Avatar::attach(modelURL, jointName, translation, rotation, scale, isSoft, allowDuplicates, useSaved); + setAttachmentData(newAttachments); +} + +bool MyAvatar::findAvatarEntity(const QString& modelURL, const QString& jointName, QUuid& entityID) { + auto avatarEntities = getAvatarEntityData(); + AvatarEntityMap::const_iterator dataItr = avatarEntities.begin(); + while (dataItr != avatarEntities.end()) { + entityID = dataItr.key(); + auto props = DependencyManager::get()->getEntityProperties(entityID); + if (props.getModelURL() == modelURL && + (jointName.isEmpty() || props.getParentJointIndex() == getJointIndex(jointName))) { + return true; + } + dataItr++; + } + return false; +} + +AttachmentData MyAvatar::entityPropertiesToAttachmentData(const EntityItemProperties& properties) const { + AttachmentData data; + data.modelURL = properties.getModelURL(); + data.translation = properties.getLocalPosition(); + data.rotation = properties.getLocalRotation(); + data.isSoft = properties.getRelayParentJoints(); + quint16 jointIndex = 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(getID()); + properties.setOwningAvatarID(getID()); + 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() { diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index d36e43ca25..bda6a817ff 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -865,8 +865,6 @@ public: void resetFullAvatarURL(); - virtual void setAttachmentData(const QVector& attachmentData) override; - MyCharacterController* getCharacterController() { return &_characterController; } const MyCharacterController* getCharacterController() const { return &_characterController; } @@ -1082,6 +1080,12 @@ public: float computeStandingHeightMode(const controller::Pose& head); glm::quat computeAverageHeadRotation(const controller::Pose& head); + virtual void setAttachmentData(const QVector& attachmentData) override; + virtual QVector getAttachmentData() const override; + + virtual QVariantList getAttachmentsVariant() const override; + virtual void setAttachmentsVariant(const QVariantList& variant) override; + public slots: /**jsdoc @@ -1506,11 +1510,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); diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 0f850aaf24..147d303871 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -895,14 +895,14 @@ public: * @returns {object} */ // FIXME: Can this name be improved? Can it be deprecated? - Q_INVOKABLE QVariantList getAttachmentsVariant() const; + Q_INVOKABLE virtual QVariantList getAttachmentsVariant() const; /**jsdoc * @function MyAvatar.setAttachmentsVariant * @param {object} variant */ // FIXME: Can this name be improved? Can it be deprecated? - Q_INVOKABLE void setAttachmentsVariant(const QVariantList& variant); + Q_INVOKABLE virtual void setAttachmentsVariant(const QVariantList& variant); /**jsdoc @@ -969,7 +969,7 @@ public: * print (attachments[i].modelURL); * } */ - Q_INVOKABLE QVector getAttachmentData() const; + Q_INVOKABLE virtual QVector getAttachmentData() const; /**jsdoc * Set all models currently attached to your avatar. For example, if you retrieve attachment data using @@ -1040,7 +1040,7 @@ public: * @param {string} [jointName=""] - The name of the joint to detach the model from. If "", then the most * recently attached model is removed from which ever joint it was attached to. */ - Q_INVOKABLE void detachOne(const QString& modelURL, const QString& jointName = QString()); + Q_INVOKABLE virtual void detachOne(const QString& modelURL, const QString& jointName = QString()); /**jsdoc * Detach all instances of a particular model from either a specific joint or all joints. @@ -1049,7 +1049,7 @@ public: * @param {string} [jointName=""] - The name of the joint to detach the model from. If "", then the model is * detached from all joints. */ - Q_INVOKABLE void detachAll(const QString& modelURL, const QString& jointName = QString()); + Q_INVOKABLE virtual void detachAll(const QString& modelURL, const QString& jointName = QString()); QString getSkeletonModelURLFromScript() const { return _skeletonModelURL.toString(); } void setSkeletonModelURLFromScript(const QString& skeletonModelString) { setSkeletonModelURL(QUrl(skeletonModelString)); } From c0e2b3a24026d7642a66fbf0dfcbadce52877abf Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Thu, 9 Aug 2018 11:49:29 -0700 Subject: [PATCH 072/144] Happening Now -> Featured --- interface/resources/qml/hifi/Pal.qml | 1 + interface/resources/qml/hifi/tablet/TabletAddressDialog.qml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/interface/resources/qml/hifi/Pal.qml b/interface/resources/qml/hifi/Pal.qml index 915213508c..dc5aa9dade 100644 --- a/interface/resources/qml/hifi/Pal.qml +++ b/interface/resources/qml/hifi/Pal.qml @@ -1046,6 +1046,7 @@ 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.", diff --git a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml index 5dce74f2dd..c9d05aea51 100644 --- a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml +++ b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml @@ -320,7 +320,7 @@ StackView { width: parent.width; cardWidth: 312 + (2 * 4); cardHeight: 163 + (2 * 4); - labelText: 'HAPPENING NOW'; + labelText: 'FEATURED'; actions: 'announcement'; filter: addressLine.text; goFunction: goCard; From 9ca0ee05dcf83ee74d3c2af493fa5fe5548ca74d Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Thu, 9 Aug 2018 12:13:03 -0700 Subject: [PATCH 073/144] Fix wanings --- interface/src/avatar/MyAvatar.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index bd05bb284e..7067c1c791 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -2096,7 +2096,7 @@ AttachmentData MyAvatar::entityPropertiesToAttachmentData(const EntityItemProper data.translation = properties.getLocalPosition(); data.rotation = properties.getLocalRotation(); data.isSoft = properties.getRelayParentJoints(); - quint16 jointIndex = properties.getParentJointIndex(); + int jointIndex = (int)properties.getParentJointIndex(); if (jointIndex > -1 && jointIndex < getJointNames().size()) { data.jointName = getJointNames()[jointIndex]; } From 44a9b1df0eb6be8f3d1cf72516ad46d4bce0d8b1 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Thu, 9 Aug 2018 12:30:42 -0700 Subject: [PATCH 074/144] Fix choice of target backend in TextureCache on OSX --- .../model-networking/src/model-networking/TextureCache.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/libraries/model-networking/src/model-networking/TextureCache.cpp b/libraries/model-networking/src/model-networking/TextureCache.cpp index b9def7f5b7..aa00116200 100644 --- a/libraries/model-networking/src/model-networking/TextureCache.cpp +++ b/libraries/model-networking/src/model-networking/TextureCache.cpp @@ -1173,7 +1173,11 @@ void ImageReader::read() { gpu::BackendTarget target = gpu::BackendTarget::GLES32; #else constexpr bool shouldCompress = false; +#ifdef Q_OS_MAC + gpu::BackendTarget target = gpu::BackendTarget::GL41; +#else gpu::BackendTarget target = gpu::BackendTarget::GL45; +#endif #endif texture = image::processImage(std::move(buffer), _url.toString().toStdString(), _maxNumPixels, networkTexture->getTextureType(), shouldCompress, target); From a0dfd76f064dd5f53d8c9bf2aa387755cfa03f32 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Mon, 6 Aug 2018 15:13:00 -0700 Subject: [PATCH 075/144] variable texture support --- .../src/gpu/gles/GLESBackendTexture.cpp | 157 +++--------------- 1 file changed, 23 insertions(+), 134 deletions(-) diff --git a/libraries/gpu-gles/src/gpu/gles/GLESBackendTexture.cpp b/libraries/gpu-gles/src/gpu/gles/GLESBackendTexture.cpp index 911dfb8bb8..23dc271af9 100644 --- a/libraries/gpu-gles/src/gpu/gles/GLESBackendTexture.cpp +++ b/libraries/gpu-gles/src/gpu/gles/GLESBackendTexture.cpp @@ -65,20 +65,24 @@ GLTexture* GLESBackend::syncGPUObject(const TexturePointer& texturePointer) { object = new GLESAttachmentTexture(shared_from_this(), texture); break; - case TextureUsageType::RESOURCE: -// FIXME disabling variable allocation textures for now, while debugging android rendering -// and crashes -#if 0 - qCDebug(gpugllogging) << "variable / Strict texture " << texture.source().c_str(); - object = new GLESResourceTexture(shared_from_this(), texture); - GLVariableAllocationSupport::addMemoryManagedTexture(texturePointer); - break; -#endif case TextureUsageType::STRICT_RESOURCE: qCDebug(gpugllogging) << "Strict texture " << texture.source().c_str(); object = new GLESStrictResourceTexture(shared_from_this(), texture); break; + case TextureUsageType::RESOURCE: { + auto &transferEngine = _textureManagement._transferEngine; + if (transferEngine->allowCreate()) { + object = new GLESResourceTexture(shared_from_this(), texture); + transferEngine->addMemoryManagedTexture(texturePointer); + } else { + auto fallback = texturePointer->getFallbackTexture(); + if (fallback) { + object = static_cast(syncGPUObject(fallback)); + } + } + break; + } default: Q_UNREACHABLE(); } @@ -195,7 +199,6 @@ Size GLESTexture::copyMipFaceLinesFromTexture(uint16_t mip, uint8_t face, const glTexSubImage2D(target, mip, 0, yOffset, size.x, size.y, format, type, sourcePointer); } } else { - // TODO: implement for android assert(false); amountCopied = 0; } @@ -385,7 +388,6 @@ void GLESVariableAllocationTexture::allocateStorage(uint16 allocatedMip) { const auto totalMips = _gpuObject.getNumMips(); const auto mips = totalMips - _allocatedMip; withPreservedTexture([&] { - // FIXME technically GL 4.2, but OSX includes the ARB_texture_storage extension glTexStorage2D(_target, mips, texelFormat.internalFormat, dimensions.x, dimensions.y); CHECK_GL_ERROR(); }); auto mipLevels = _gpuObject.getNumMips(); @@ -426,139 +428,26 @@ void GLESVariableAllocationTexture::syncSampler() const { }); } - -void copyUncompressedTexGPUMem(const gpu::Texture& texture, GLenum texTarget, GLuint srcId, GLuint destId, uint16_t numMips, uint16_t srcMipOffset, uint16_t destMipOffset, uint16_t populatedMips) { - // DestID must be bound to the GLESBackend::RESOURCE_TRANSFER_TEX_UNIT - - GLuint fbo { 0 }; - glGenFramebuffers(1, &fbo); - glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo); - - uint16_t mips = numMips; - // copy pre-existing mips - for (uint16_t mip = populatedMips; mip < mips; ++mip) { +void copyTexGPUMem(const gpu::Texture& texture, GLenum texTarget, GLuint srcId, GLuint destId, uint16_t numMips, uint16_t srcMipOffset, uint16_t destMipOffset, uint16_t populatedMips) { + for (uint16_t mip = populatedMips; mip < numMips; ++mip) { auto mipDimensions = texture.evalMipDimensions(mip); uint16_t targetMip = mip - destMipOffset; uint16_t sourceMip = mip - srcMipOffset; - for (GLenum target : GLTexture::getFaceTargets(texTarget)) { - glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, target, srcId, sourceMip); - (void)CHECK_GL_ERROR(); - glCopyTexSubImage2D(target, targetMip, 0, 0, 0, 0, mipDimensions.x, mipDimensions.y); + auto faces = GLTexture::getFaceCount(texTarget); + for (uint8_t face = 0; face < faces; ++face) { + glCopyImageSubData( + srcId, texTarget, sourceMip, 0, 0, face, + destId, texTarget, targetMip, 0, 0, face, + mipDimensions.x, mipDimensions.y, 1 + ); (void)CHECK_GL_ERROR(); } } - - // destroy the transfer framebuffer - glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); - glDeleteFramebuffers(1, &fbo); -} - -void copyCompressedTexGPUMem(const gpu::Texture& texture, GLenum texTarget, GLuint srcId, GLuint destId, uint16_t numMips, uint16_t srcMipOffset, uint16_t destMipOffset, uint16_t populatedMips) { - // DestID must be bound to the GLESBackend::RESOURCE_TRANSFER_TEX_UNIT - - struct MipDesc { - GLint _faceSize; - GLint _size; - GLint _offset; - GLint _width; - GLint _height; - }; - std::vector sourceMips(numMips); - - std::vector bytes; - - glActiveTexture(GL_TEXTURE0 + GLESBackend::RESOURCE_TRANSFER_EXTRA_TEX_UNIT); - glBindTexture(texTarget, srcId); - const auto& faceTargets = GLTexture::getFaceTargets(texTarget); - GLint internalFormat { 0 }; - - // Collect the mip description from the source texture - GLint bufferOffset { 0 }; - for (uint16_t mip = populatedMips; mip < numMips; ++mip) { - auto& sourceMip = sourceMips[mip]; - - uint16_t sourceLevel = mip - srcMipOffset; - - // Grab internal format once - if (internalFormat == 0) { - glGetTexLevelParameteriv(faceTargets[0], sourceLevel, GL_TEXTURE_INTERNAL_FORMAT, &internalFormat); - } - - // Collect the size of the first face, and then compute the total size offset needed for this mip level - auto mipDimensions = texture.evalMipDimensions(mip); - sourceMip._width = mipDimensions.x; - sourceMip._height = mipDimensions.y; -#ifdef DEBUG_COPY - glGetTexLevelParameteriv(faceTargets.front(), sourceLevel, GL_TEXTURE_WIDTH, &sourceMip._width); - glGetTexLevelParameteriv(faceTargets.front(), sourceLevel, GL_TEXTURE_HEIGHT, &sourceMip._height); -#endif - // TODO: retrieve the size of a compressed image - assert(false); - //glGetTexLevelParameteriv(faceTargets.front(), sourceLevel, GL_TEXTURE_COMPRESSED_IMAGE_SIZE, &sourceMip._faceSize); - sourceMip._size = (GLint)faceTargets.size() * sourceMip._faceSize; - sourceMip._offset = bufferOffset; - bufferOffset += sourceMip._size; - } - (void)CHECK_GL_ERROR(); - - // Allocate the PBO to accomodate for all the mips to copy - GLuint pbo { 0 }; - glGenBuffers(1, &pbo); - glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo); - glBufferData(GL_PIXEL_PACK_BUFFER, bufferOffset, nullptr, GL_STATIC_COPY); - (void)CHECK_GL_ERROR(); - - // Transfer from source texture to pbo - for (uint16_t mip = populatedMips; mip < numMips; ++mip) { - auto& sourceMip = sourceMips[mip]; - - uint16_t sourceLevel = mip - srcMipOffset; - - for (GLint f = 0; f < (GLint)faceTargets.size(); f++) { - // TODO: implement for android - //glGetCompressedTexImage(faceTargets[f], sourceLevel, BUFFER_OFFSET(sourceMip._offset + f * sourceMip._faceSize)); - } - (void)CHECK_GL_ERROR(); - } - - // Now populate the new texture from the pbo - glBindTexture(texTarget, 0); - glBindBuffer(GL_PIXEL_PACK_BUFFER, 0); - glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo); - - glActiveTexture(GL_TEXTURE0 + GLESBackend::RESOURCE_TRANSFER_TEX_UNIT); - - // Transfer from pbo to new texture - for (uint16_t mip = populatedMips; mip < numMips; ++mip) { - auto& sourceMip = sourceMips[mip]; - - uint16_t destLevel = mip - destMipOffset; - - for (GLint f = 0; f < (GLint)faceTargets.size(); f++) { -#ifdef DEBUG_COPY - GLint destWidth, destHeight, destSize; - glGetTexLevelParameteriv(faceTargets.front(), destLevel, GL_TEXTURE_WIDTH, &destWidth); - glGetTexLevelParameteriv(faceTargets.front(), destLevel, GL_TEXTURE_HEIGHT, &destHeight); - glGetTexLevelParameteriv(faceTargets.front(), destLevel, GL_TEXTURE_COMPRESSED_IMAGE_SIZE, &destSize); -#endif - glCompressedTexSubImage2D(faceTargets[f], destLevel, 0, 0, sourceMip._width, sourceMip._height, internalFormat, - sourceMip._faceSize, BUFFER_OFFSET(sourceMip._offset + f * sourceMip._faceSize)); - } - } - - glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); - glDeleteBuffers(1, &pbo); } void GLESVariableAllocationTexture::copyTextureMipsInGPUMem(GLuint srcId, GLuint destId, uint16_t srcMipOffset, uint16_t destMipOffset, uint16_t populatedMips) { uint16_t numMips = _gpuObject.getNumMips(); - withPreservedTexture([&] { - if (_texelFormat.isCompressed()) { - copyCompressedTexGPUMem(_gpuObject, _target, srcId, destId, numMips, srcMipOffset, destMipOffset, populatedMips); - } else { - copyUncompressedTexGPUMem(_gpuObject, _target, srcId, destId, numMips, srcMipOffset, destMipOffset, populatedMips); - } - }); + copyTexGPUMem(_gpuObject, _target, srcId, destId, numMips, srcMipOffset, destMipOffset, populatedMips); } size_t GLESVariableAllocationTexture::promote() { From 60338f98991e6de42957942a7fe28062d0d757d8 Mon Sep 17 00:00:00 2001 From: Cristian Luis Duarte Date: Thu, 9 Aug 2018 17:54:07 -0300 Subject: [PATCH 076/144] Android - People - Swipe to reveal delete + layout adaptations for design --- .../hifiinterface/view/SwipeRevealLayout.java | 729 ++++++++++++++++++ .../hifiinterface/view/UserListAdapter.java | 14 +- android/app/src/main/res/layout/user_item.xml | 78 +- android/app/src/main/res/values/attrs.xml | 9 + 4 files changed, 810 insertions(+), 20 deletions(-) create mode 100644 android/app/src/main/java/io/highfidelity/hifiinterface/view/SwipeRevealLayout.java create mode 100644 android/app/src/main/res/values/attrs.xml diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/view/SwipeRevealLayout.java b/android/app/src/main/java/io/highfidelity/hifiinterface/view/SwipeRevealLayout.java new file mode 100644 index 0000000000..06ac4ac3ec --- /dev/null +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/view/SwipeRevealLayout.java @@ -0,0 +1,729 @@ +package io.highfidelity.hifiinterface.view; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.graphics.Rect; +import android.os.Bundle; +import android.os.Parcelable; +import android.support.annotation.Nullable; +import android.support.v4.view.GestureDetectorCompat; +import android.support.v4.view.ViewCompat; +import android.support.v4.widget.ViewDragHelper; +import android.util.AttributeSet; +import android.util.DisplayMetrics; +import android.view.GestureDetector; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; + +import io.highfidelity.hifiinterface.R; + +/** + * Created by Mark O'Sullivan on 25th February 2018. + + MIT License + + Copyright (c) 2018 Mark O'Sullivan + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + + */ + +@SuppressLint("RtlHardcoded") +public class SwipeRevealLayout extends ViewGroup { + + private static final String SUPER_INSTANCE_STATE = "saved_instance_state_parcelable"; + + private static final int DEFAULT_MIN_FLING_VELOCITY = 300; // dp per second + private static final int DEFAULT_MIN_DIST_REQUEST_DISALLOW_PARENT = 1; // dp + + public static final int DRAG_EDGE_LEFT = 0x1; + public static final int DRAG_EDGE_RIGHT = 0x1 << 1; + + /** + * The secondary view will be under the main view. + */ + public static final int MODE_NORMAL = 0; + + /** + * The secondary view will stick the edge of the main view. + */ + public static final int MODE_SAME_LEVEL = 1; + + /** + * Main view is the view which is shown when the layout is closed. + */ + private View mMainView; + + /** + * Secondary view is the view which is shown when the layout is opened. + */ + private View mSecondaryView; + + /** + * The rectangle position of the main view when the layout is closed. + */ + private Rect mRectMainClose = new Rect(); + + /** + * The rectangle position of the main view when the layout is opened. + */ + private Rect mRectMainOpen = new Rect(); + + /** + * The rectangle position of the secondary view when the layout is closed. + */ + private Rect mRectSecClose = new Rect(); + + /** + * The rectangle position of the secondary view when the layout is opened. + */ + private Rect mRectSecOpen = new Rect(); + + /** + * The minimum distance (px) to the closest drag edge that the SwipeRevealLayout + * will disallow the parent to intercept touch event. + */ + private int mMinDistRequestDisallowParent = 0; + + private boolean mIsOpenBeforeInit = false; + private volatile boolean mIsScrolling = false; + private volatile boolean mLockDrag = false; + + private int mMinFlingVelocity = DEFAULT_MIN_FLING_VELOCITY; + private int mMode = MODE_NORMAL; + + private int mDragEdge = DRAG_EDGE_LEFT; + + private float mDragDist = 0; + private float mPrevX = -1; + + private ViewDragHelper mDragHelper; + private GestureDetectorCompat mGestureDetector; + + public SwipeRevealLayout(Context context) { + super(context); + init(context, null); + } + + public SwipeRevealLayout(Context context, AttributeSet attrs) { + super(context, attrs); + init(context, attrs); + } + + public SwipeRevealLayout(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + @Nullable + @Override + protected Parcelable onSaveInstanceState() { + Bundle bundle = new Bundle(); + bundle.putParcelable(SUPER_INSTANCE_STATE, super.onSaveInstanceState()); + return super.onSaveInstanceState(); + } + + @Override + protected void onRestoreInstanceState(Parcelable state) { + Bundle bundle = (Bundle) state; + state = bundle.getParcelable(SUPER_INSTANCE_STATE); + super.onRestoreInstanceState(state); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + mGestureDetector.onTouchEvent(event); + mDragHelper.processTouchEvent(event); + return true; + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + if (isDragLocked()) { + return super.onInterceptTouchEvent(ev); + } + + mDragHelper.processTouchEvent(ev); + mGestureDetector.onTouchEvent(ev); + accumulateDragDist(ev); + + boolean couldBecomeClick = couldBecomeClick(ev); + boolean settling = mDragHelper.getViewDragState() == ViewDragHelper.STATE_SETTLING; + boolean idleAfterScrolled = mDragHelper.getViewDragState() == ViewDragHelper.STATE_IDLE + && mIsScrolling; + + // must be placed as the last statement + mPrevX = ev.getX(); + + // return true => intercept, cannot trigger onClick event + return !couldBecomeClick && (settling || idleAfterScrolled); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + + // get views + if (getChildCount() >= 2) { + mSecondaryView = getChildAt(0); + mMainView = getChildAt(1); + } + else if (getChildCount() == 1) { + mMainView = getChildAt(0); + } + } + + /** + * {@inheritDoc} + */ + @SuppressWarnings("ConstantConditions") + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + for (int index = 0; index < getChildCount(); index++) { + final View child = getChildAt(index); + + int left, right, top, bottom; + left = right = top = bottom = 0; + + final int minLeft = getPaddingLeft(); + final int maxRight = Math.max(r - getPaddingRight() - l, 0); + final int minTop = getPaddingTop(); + final int maxBottom = Math.max(b - getPaddingBottom() - t, 0); + + int measuredChildHeight = child.getMeasuredHeight(); + int measuredChildWidth = child.getMeasuredWidth(); + + // need to take account if child size is match_parent + final LayoutParams childParams = child.getLayoutParams(); + boolean matchParentHeight = false; + boolean matchParentWidth = false; + + if (childParams != null) { + matchParentHeight = (childParams.height == LayoutParams.MATCH_PARENT) || + (childParams.height == LayoutParams.FILL_PARENT); + matchParentWidth = (childParams.width == LayoutParams.MATCH_PARENT) || + (childParams.width == LayoutParams.FILL_PARENT); + } + + if (matchParentHeight) { + measuredChildHeight = maxBottom - minTop; + childParams.height = measuredChildHeight; + } + + if (matchParentWidth) { + measuredChildWidth = maxRight - minLeft; + childParams.width = measuredChildWidth; + } + + switch (mDragEdge) { + case DRAG_EDGE_RIGHT: + left = Math.max(r - measuredChildWidth - getPaddingRight() - l, minLeft); + top = Math.min(getPaddingTop(), maxBottom); + right = Math.max(r - getPaddingRight() - l, minLeft); + bottom = Math.min(measuredChildHeight + getPaddingTop(), maxBottom); + break; + + case DRAG_EDGE_LEFT: + left = Math.min(getPaddingLeft(), maxRight); + top = Math.min(getPaddingTop(), maxBottom); + right = Math.min(measuredChildWidth + getPaddingLeft(), maxRight); + bottom = Math.min(measuredChildHeight + getPaddingTop(), maxBottom); + break; + } + + child.layout(left, top, right, bottom); + } + + // taking account offset when mode is SAME_LEVEL + if (mMode == MODE_SAME_LEVEL) { + switch (mDragEdge) { + case DRAG_EDGE_LEFT: + mSecondaryView.offsetLeftAndRight(-mSecondaryView.getWidth()); + break; + + case DRAG_EDGE_RIGHT: + mSecondaryView.offsetLeftAndRight(mSecondaryView.getWidth()); + break; + } + } + + initRects(); + + if (mIsOpenBeforeInit) { + open(false); + } else { + close(false); + } + + } + + /** + * {@inheritDoc} + */ + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + if (getChildCount() < 2) { + throw new RuntimeException("Layout must have two children"); + } + + final LayoutParams params = getLayoutParams(); + + final int widthMode = MeasureSpec.getMode(widthMeasureSpec); + final int heightMode = MeasureSpec.getMode(heightMeasureSpec); + + int desiredWidth = 0; + int desiredHeight = 0; + + // first find the largest child + for (int i = 0; i < getChildCount(); i++) { + final View child = getChildAt(i); + measureChild(child, widthMeasureSpec, heightMeasureSpec); + desiredWidth = Math.max(child.getMeasuredWidth(), desiredWidth); + desiredHeight = Math.max(child.getMeasuredHeight(), desiredHeight); + } + // create new measure spec using the largest child width + widthMeasureSpec = MeasureSpec.makeMeasureSpec(desiredWidth, widthMode); + heightMeasureSpec = MeasureSpec.makeMeasureSpec(desiredHeight, heightMode); + + final int measuredWidth = MeasureSpec.getSize(widthMeasureSpec); + final int measuredHeight = MeasureSpec.getSize(heightMeasureSpec); + + for (int i = 0; i < getChildCount(); i++) { + final View child = getChildAt(i); + final LayoutParams childParams = child.getLayoutParams(); + + if (childParams != null) { + if (childParams.height == LayoutParams.MATCH_PARENT) { + child.setMinimumHeight(measuredHeight); + } + + if (childParams.width == LayoutParams.MATCH_PARENT) { + child.setMinimumWidth(measuredWidth); + } + } + + measureChild(child, widthMeasureSpec, heightMeasureSpec); + desiredWidth = Math.max(child.getMeasuredWidth(), desiredWidth); + desiredHeight = Math.max(child.getMeasuredHeight(), desiredHeight); + } + + // taking accounts of padding + desiredWidth += getPaddingLeft() + getPaddingRight(); + desiredHeight += getPaddingTop() + getPaddingBottom(); + + // adjust desired width + if (widthMode == MeasureSpec.EXACTLY) { + desiredWidth = measuredWidth; + } else { + if (params.width == LayoutParams.MATCH_PARENT) { + desiredWidth = measuredWidth; + } + + if (widthMode == MeasureSpec.AT_MOST) { + desiredWidth = (desiredWidth > measuredWidth)? measuredWidth : desiredWidth; + } + } + + // adjust desired height + if (heightMode == MeasureSpec.EXACTLY) { + desiredHeight = measuredHeight; + } else { + if (params.height == LayoutParams.MATCH_PARENT) { + desiredHeight = measuredHeight; + } + + if (heightMode == MeasureSpec.AT_MOST) { + desiredHeight = (desiredHeight > measuredHeight)? measuredHeight : desiredHeight; + } + } + + setMeasuredDimension(desiredWidth, desiredHeight); + } + + @Override + public void computeScroll() { + if (mDragHelper.continueSettling(true)) { + ViewCompat.postInvalidateOnAnimation(this); + } + } + + /** + * Open the panel to show the secondary view + */ + public void open(boolean animation) { + mIsOpenBeforeInit = true; + + if (animation) { + mDragHelper.smoothSlideViewTo(mMainView, mRectMainOpen.left, mRectMainOpen.top); + } else { + mDragHelper.abort(); + + mMainView.layout( + mRectMainOpen.left, + mRectMainOpen.top, + mRectMainOpen.right, + mRectMainOpen.bottom + ); + + mSecondaryView.layout( + mRectSecOpen.left, + mRectSecOpen.top, + mRectSecOpen.right, + mRectSecOpen.bottom + ); + } + + ViewCompat.postInvalidateOnAnimation(this); + } + + /** + * Close the panel to hide the secondary view + */ + public void close(boolean animation) { + mIsOpenBeforeInit = false; + + if (animation) { + mDragHelper.smoothSlideViewTo(mMainView, mRectMainClose.left, mRectMainClose.top); + } else { + mDragHelper.abort(); + mMainView.layout( + mRectMainClose.left, + mRectMainClose.top, + mRectMainClose.right, + mRectMainClose.bottom + ); + mSecondaryView.layout( + mRectSecClose.left, + mRectSecClose.top, + mRectSecClose.right, + mRectSecClose.bottom + ); + } + + ViewCompat.postInvalidateOnAnimation(this); + } + + /** + * @return true if the drag/swipe motion is currently locked. + */ + public boolean isDragLocked() { + return mLockDrag; + } + + private int getMainOpenLeft() { + switch (mDragEdge) { + case DRAG_EDGE_LEFT: + return mRectMainClose.left + mSecondaryView.getWidth(); + + case DRAG_EDGE_RIGHT: + return mRectMainClose.left - mSecondaryView.getWidth(); + + + default: + return 0; + } + } + + private int getMainOpenTop() { + switch (mDragEdge) { + case DRAG_EDGE_LEFT: + return mRectMainClose.top; + + case DRAG_EDGE_RIGHT: + return mRectMainClose.top; + + + default: + return 0; + } + } + + private int getSecOpenLeft() { + return mRectSecClose.left; + } + + private int getSecOpenTop() { + return mRectSecClose.top; + } + + private void initRects() { + // close position of main view + mRectMainClose.set( + mMainView.getLeft(), + mMainView.getTop(), + mMainView.getRight(), + mMainView.getBottom() + ); + + // close position of secondary view + mRectSecClose.set( + mSecondaryView.getLeft(), + mSecondaryView.getTop(), + mSecondaryView.getRight(), + mSecondaryView.getBottom() + ); + + // open position of the main view + mRectMainOpen.set( + getMainOpenLeft(), + getMainOpenTop(), + getMainOpenLeft() + mMainView.getWidth(), + getMainOpenTop() + mMainView.getHeight() + ); + + // open position of the secondary view + mRectSecOpen.set( + getSecOpenLeft(), + getSecOpenTop(), + getSecOpenLeft() + mSecondaryView.getWidth(), + getSecOpenTop() + mSecondaryView.getHeight() + ); + } + + private boolean couldBecomeClick(MotionEvent ev) { + return isInMainView(ev) && !shouldInitiateADrag(); + } + + private boolean isInMainView(MotionEvent ev) { + float x = ev.getX(); + float y = ev.getY(); + + boolean withinVertical = mMainView.getTop() <= y && y <= mMainView.getBottom(); + boolean withinHorizontal = mMainView.getLeft() <= x && x <= mMainView.getRight(); + + return withinVertical && withinHorizontal; + } + + private boolean shouldInitiateADrag() { + float minDistToInitiateDrag = mDragHelper.getTouchSlop(); + return mDragDist >= minDistToInitiateDrag; + } + + private void accumulateDragDist(MotionEvent ev) { + final int action = ev.getAction(); + if (action == MotionEvent.ACTION_DOWN) { + mDragDist = 0; + return; + } + + float dragged = Math.abs(ev.getX() - mPrevX); + + mDragDist += dragged; + } + + private void init(Context context, AttributeSet attrs) { + if (attrs != null && context != null) { + TypedArray a = context.getTheme().obtainStyledAttributes( + attrs, + R.styleable.SwipeRevealLayout, + 0, 0 + ); + + mDragEdge = a.getInteger(R.styleable.SwipeRevealLayout_dragFromEdge, DRAG_EDGE_LEFT); + mMode = MODE_NORMAL; + mMinFlingVelocity = DEFAULT_MIN_FLING_VELOCITY; + mMinDistRequestDisallowParent = DEFAULT_MIN_DIST_REQUEST_DISALLOW_PARENT; + } + + mDragHelper = ViewDragHelper.create(this, 1.0f, mDragHelperCallback); + mDragHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_ALL); + + mGestureDetector = new GestureDetectorCompat(context, mGestureListener); + } + + private final GestureDetector.OnGestureListener mGestureListener = new GestureDetector.SimpleOnGestureListener() { + boolean hasDisallowed = false; + + @Override + public boolean onDown(MotionEvent e) { + mIsScrolling = false; + hasDisallowed = false; + return true; + } + + @Override + public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { + mIsScrolling = true; + return false; + } + + @Override + public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { + mIsScrolling = true; + + if (getParent() != null) { + boolean shouldDisallow; + + if (!hasDisallowed) { + shouldDisallow = getDistToClosestEdge() >= mMinDistRequestDisallowParent; + if (shouldDisallow) { + hasDisallowed = true; + } + } else { + shouldDisallow = true; + } + + // disallow parent to intercept touch event so that the layout will work + // properly on RecyclerView or view that handles scroll gesture. + getParent().requestDisallowInterceptTouchEvent(shouldDisallow); + } + + return false; + } + }; + + private int getDistToClosestEdge() { + switch (mDragEdge) { + case DRAG_EDGE_LEFT: + final int pivotRight = mRectMainClose.left + mSecondaryView.getWidth(); + + return Math.min( + mMainView.getLeft() - mRectMainClose.left, + pivotRight - mMainView.getLeft() + ); + + case DRAG_EDGE_RIGHT: + final int pivotLeft = mRectMainClose.right - mSecondaryView.getWidth(); + + return Math.min( + mMainView.getRight() - pivotLeft, + mRectMainClose.right - mMainView.getRight() + ); + } + + return 0; + } + + private int getHalfwayPivotHorizontal() { + if (mDragEdge == DRAG_EDGE_LEFT) { + return mRectMainClose.left + mSecondaryView.getWidth() / 2; + } else { + return mRectMainClose.right - mSecondaryView.getWidth() / 2; + } + } + + private final ViewDragHelper.Callback mDragHelperCallback = new ViewDragHelper.Callback() { + @Override + public boolean tryCaptureView(View child, int pointerId) { + + if (mLockDrag) + return false; + + mDragHelper.captureChildView(mMainView, pointerId); + return false; + } + + @Override + public int clampViewPositionHorizontal(View child, int left, int dx) { + switch (mDragEdge) { + case DRAG_EDGE_RIGHT: + return Math.max( + Math.min(left, mRectMainClose.left), + mRectMainClose.left - mSecondaryView.getWidth() + ); + + case DRAG_EDGE_LEFT: + return Math.max( + Math.min(left, mRectMainClose.left + mSecondaryView.getWidth()), + mRectMainClose.left + ); + + default: + return child.getLeft(); + } + } + + @Override + public void onViewReleased(View releasedChild, float xvel, float yvel) { + final boolean velRightExceeded = pxToDp((int) xvel) >= mMinFlingVelocity; + final boolean velLeftExceeded = pxToDp((int) xvel) <= -mMinFlingVelocity; + + final int pivotHorizontal = getHalfwayPivotHorizontal(); + + switch (mDragEdge) { + case DRAG_EDGE_RIGHT: + if (velRightExceeded) { + close(true); + } else if (velLeftExceeded) { + open(true); + } else { + if (mMainView.getRight() < pivotHorizontal) { + open(true); + } else { + close(true); + } + } + break; + + case DRAG_EDGE_LEFT: + if (velRightExceeded) { + open(true); + } else if (velLeftExceeded) { + close(true); + } else { + if (mMainView.getLeft() < pivotHorizontal) { + close(true); + } else { + open(true); + } + } + break; + } + } + + @Override + public void onEdgeDragStarted(int edgeFlags, int pointerId) { + super.onEdgeDragStarted(edgeFlags, pointerId); + + if (mLockDrag) { + return; + } + + boolean edgeStartLeft = (mDragEdge == DRAG_EDGE_RIGHT) + && edgeFlags == ViewDragHelper.EDGE_LEFT; + + boolean edgeStartRight = (mDragEdge == DRAG_EDGE_LEFT) + && edgeFlags == ViewDragHelper.EDGE_RIGHT; + + if (edgeStartLeft || edgeStartRight) { + mDragHelper.captureChildView(mMainView, pointerId); + } + } + + @Override + public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) { + super.onViewPositionChanged(changedView, left, top, dx, dy); + if (mMode == MODE_SAME_LEVEL) { + if (mDragEdge == DRAG_EDGE_LEFT || mDragEdge == DRAG_EDGE_RIGHT) { + mSecondaryView.offsetLeftAndRight(dx); + } else { + mSecondaryView.offsetTopAndBottom(dy); + } + } + ViewCompat.postInvalidateOnAnimation(SwipeRevealLayout.this); + } + }; + + private int pxToDp(int px) { + Resources resources = getContext().getResources(); + DisplayMetrics metrics = resources.getDisplayMetrics(); + return (int) (px / ((float)metrics.densityDpi / DisplayMetrics.DENSITY_DEFAULT)); + } +} \ No newline at end of file diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/view/UserListAdapter.java b/android/app/src/main/java/io/highfidelity/hifiinterface/view/UserListAdapter.java index 32993500fe..79a931d93b 100644 --- a/android/app/src/main/java/io/highfidelity/hifiinterface/view/UserListAdapter.java +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/view/UserListAdapter.java @@ -7,8 +7,11 @@ import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.widget.Button; +import android.widget.ImageButton; import android.widget.ImageView; import android.widget.TextView; +import android.widget.Toast; import com.squareup.picasso.Picasso; @@ -62,7 +65,14 @@ public class UserListAdapter extends RecyclerView.Adapter - + android:layout_gravity="center_vertical" + app:dragFromEdge="right"> - - + android:layout_height="match_parent" + android:background="@android:color/holo_red_dark"> + - + + + - \ No newline at end of file + + + + + + + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/values/attrs.xml b/android/app/src/main/res/values/attrs.xml new file mode 100644 index 0000000000..c12f28ccde --- /dev/null +++ b/android/app/src/main/res/values/attrs.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file From 4d31863484cbe2b85f9e4fc0a54ad74562177db5 Mon Sep 17 00:00:00 2001 From: sabrina-shanman Date: Thu, 9 Aug 2018 15:08:27 -0700 Subject: [PATCH 077/144] Fix missing jsdoc note for getPrevPickResult on existence of ParabolaPickResult --- interface/src/raypick/PickScriptingInterface.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/raypick/PickScriptingInterface.h b/interface/src/raypick/PickScriptingInterface.h index e1431c3937..982210e8e7 100644 --- a/interface/src/raypick/PickScriptingInterface.h +++ b/interface/src/raypick/PickScriptingInterface.h @@ -169,7 +169,7 @@ public: * 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|CollisionPickResult} 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); From 4f077080b02ad7e3508db7baf938ae7402089de1 Mon Sep 17 00:00:00 2001 From: sabrina-shanman Date: Thu, 9 Aug 2018 15:29:21 -0700 Subject: [PATCH 078/144] Change API naming for intersectingObjects in CollisionPickResult and update docs --- interface/src/raypick/CollisionPick.cpp | 6 +++--- .../src/raypick/PickScriptingInterface.h | 21 ++++++++++++------- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/interface/src/raypick/CollisionPick.cpp b/interface/src/raypick/CollisionPick.cpp index d5b1949f31..bef93f8efc 100644 --- a/interface/src/raypick/CollisionPick.cpp +++ b/interface/src/raypick/CollisionPick.cpp @@ -47,8 +47,8 @@ QVariantMap CollisionPickResult::toVariantMap() const { } QVariantMap collisionPointPair; - collisionPointPair["pick"] = vec3toVariant(objectIntersection.testCollisionPoint); - collisionPointPair["object"] = vec3toVariant(objectIntersection.foundCollisionPoint); + collisionPointPair["pickContactPoint"] = vec3toVariant(objectIntersection.testCollisionPoint); + collisionPointPair["objectContactPoint"] = vec3toVariant(objectIntersection.foundCollisionPoint); collisionPointPairs[objectIntersection.foundID].append(collisionPointPair); } @@ -59,7 +59,7 @@ QVariantMap CollisionPickResult::toVariantMap() const { const QUuid& id = intersectionKeyVal.first; QVariantMap& intersection = intersectionKeyVal.second; - intersection["collisionPointPairs"] = collisionPointPairs[id]; + intersection["contactPointPairs"] = collisionPointPairs[id]; qIntersectingObjects.append(intersection); } diff --git a/interface/src/raypick/PickScriptingInterface.h b/interface/src/raypick/PickScriptingInterface.h index 982210e8e7..a13e7551d9 100644 --- a/interface/src/raypick/PickScriptingInterface.h +++ b/interface/src/raypick/PickScriptingInterface.h @@ -148,21 +148,28 @@ public: * * @typedef {object} CollisionPickResult * @property {boolean} intersects If there was at least one valid intersection (entityIntersections.length + avatarIntersections.length > 0) - * @property {EntityItersection[]} entityIntersections The collision information of entities which intersect with the CollisionRegion. There may be multiple intersections with the same entity which represent distinct collision points. - * @property {EntityItersection[]} avatarIntersections The collision information of avatars which intersect with the CollisionRegion. There may be multiple intersections with the same entity which represent distinct collision points. + * @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 {ContactPointPair[]} contactPointPairs Pairs of points representing penetration information between the pick and the object + */ + /**jsdoc - * A pair of intersection points between a CollisionPick and an entity/avatar. + * 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} EntityIntersection - * @property {QUuid} id The ID of the object. - * @property {Vec3} pickCollisionPoint A point within the volume of the CollisionPick which corresponds to a point on the surface of the collided entity, in world space. - * @property {Vec3} entityCollisionPoint A point within the volume of the collided entity which corresponds to a point on the surface of the CollisionPick, in world space. + * @typedef {object} ContactPointPair + * @property {Vec3} pickContactPoint A point representing a penetration of the object's surface into the volume of the pick, in world space. + * @property {Vec3} objectContactPoint A point representing a penetration of the pick's surface into the volume of the found object, in world space. */ /**jsdoc From 38973f0840441148e4a1dfded2ddd30b86df003a Mon Sep 17 00:00:00 2001 From: Alexia Mandeville Date: Thu, 9 Aug 2018 16:00:36 -0700 Subject: [PATCH 079/144] Adding icon states to emote app, active state --- .../icons/tablet-icons/EmoteAppIcon.svg | 39 ------------------- .../resources/icons/tablet-icons/emote-a.svg | 30 ++++++++++++++ .../resources/icons/tablet-icons/emote-i.svg | 33 ++++++++++++++++ scripts/system/emote.js | 3 +- 4 files changed, 65 insertions(+), 40 deletions(-) delete mode 100644 interface/resources/icons/tablet-icons/EmoteAppIcon.svg create mode 100644 interface/resources/icons/tablet-icons/emote-a.svg create mode 100644 interface/resources/icons/tablet-icons/emote-i.svg diff --git a/interface/resources/icons/tablet-icons/EmoteAppIcon.svg b/interface/resources/icons/tablet-icons/EmoteAppIcon.svg deleted file mode 100644 index cef6e8771b..0000000000 --- a/interface/resources/icons/tablet-icons/EmoteAppIcon.svg +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - - - - - diff --git a/interface/resources/icons/tablet-icons/emote-a.svg b/interface/resources/icons/tablet-icons/emote-a.svg new file mode 100644 index 0000000000..981bb77566 --- /dev/null +++ b/interface/resources/icons/tablet-icons/emote-a.svg @@ -0,0 +1,30 @@ + + + + + + + + + + + diff --git a/interface/resources/icons/tablet-icons/emote-i.svg b/interface/resources/icons/tablet-icons/emote-i.svg new file mode 100644 index 0000000000..57a957052e --- /dev/null +++ b/interface/resources/icons/tablet-icons/emote-i.svg @@ -0,0 +1,33 @@ + + + + + + + + + + + + diff --git a/scripts/system/emote.js b/scripts/system/emote.js index d484078b7b..6dfd1ae1ef 100644 --- a/scripts/system/emote.js +++ b/scripts/system/emote.js @@ -46,7 +46,8 @@ var activeTimer = false; // Used to cancel active timer if a user plays an anima var activeEmote = false; // To keep track of the currently playing emote button = tablet.addButton({ - icon: "icons/tablet-icons/EmoteAppIcon.svg", + icon: "icons/tablet-icons/emote-i.svg", + activeIcon: "icons/tablet-icons/emote-a.svg", text: EMOTE_LABEL, sortOrder: EMOTE_APP_SORT_ORDER }); From c5e9f02372c25bea79823bf0f01f974ffcc414f4 Mon Sep 17 00:00:00 2001 From: sabrina-shanman Date: Thu, 9 Aug 2018 16:59:23 -0700 Subject: [PATCH 080/144] Finalize Collision Pick API naming convention for intersectingObjects --- interface/src/raypick/CollisionPick.cpp | 6 +++--- interface/src/raypick/PickScriptingInterface.h | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/interface/src/raypick/CollisionPick.cpp b/interface/src/raypick/CollisionPick.cpp index bef93f8efc..73d2dfe67a 100644 --- a/interface/src/raypick/CollisionPick.cpp +++ b/interface/src/raypick/CollisionPick.cpp @@ -47,8 +47,8 @@ QVariantMap CollisionPickResult::toVariantMap() const { } QVariantMap collisionPointPair; - collisionPointPair["pickContactPoint"] = vec3toVariant(objectIntersection.testCollisionPoint); - collisionPointPair["objectContactPoint"] = vec3toVariant(objectIntersection.foundCollisionPoint); + collisionPointPair["pointOnPick"] = vec3toVariant(objectIntersection.testCollisionPoint); + collisionPointPair["pointOnObject"] = vec3toVariant(objectIntersection.foundCollisionPoint); collisionPointPairs[objectIntersection.foundID].append(collisionPointPair); } @@ -59,7 +59,7 @@ QVariantMap CollisionPickResult::toVariantMap() const { const QUuid& id = intersectionKeyVal.first; QVariantMap& intersection = intersectionKeyVal.second; - intersection["contactPointPairs"] = collisionPointPairs[id]; + intersection["collisionContacts"] = collisionPointPairs[id]; qIntersectingObjects.append(intersection); } diff --git a/interface/src/raypick/PickScriptingInterface.h b/interface/src/raypick/PickScriptingInterface.h index a13e7551d9..1ba1c248bc 100644 --- a/interface/src/raypick/PickScriptingInterface.h +++ b/interface/src/raypick/PickScriptingInterface.h @@ -161,15 +161,15 @@ public: * @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 {ContactPointPair[]} contactPointPairs Pairs of points representing penetration information between the pick and the object + * @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} ContactPointPair - * @property {Vec3} pickContactPoint A point representing a penetration of the object's surface into the volume of the pick, in world space. - * @property {Vec3} objectContactPoint A point representing a penetration of the pick's surface into the volume of the found object, in world space. + * @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 From ad36e23c34bb4e002d2e44562c50202a2f10b7e3 Mon Sep 17 00:00:00 2001 From: sabrina-shanman Date: Thu, 9 Aug 2018 17:00:54 -0700 Subject: [PATCH 081/144] Return early when colliding MyAvatar is found in AllContactsCallback --- libraries/physics/src/PhysicsEngine.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/libraries/physics/src/PhysicsEngine.cpp b/libraries/physics/src/PhysicsEngine.cpp index 95004f8b2a..bcd717b9e9 100644 --- a/libraries/physics/src/PhysicsEngine.cpp +++ b/libraries/physics/src/PhysicsEngine.cpp @@ -888,6 +888,7 @@ struct AllContactsCallback : public btCollisionWorld::ContactResultCallback { // TODO: Give MyAvatar a motion state so we don't have to do this if (desiredObjectType == MOTIONSTATE_TYPE_AVATAR && myAvatarCollisionObject && myAvatarCollisionObject == otherBody) { contacts.emplace_back(Physics::getSessionUUID(), bulletToGLM(penetrationPoint), bulletToGLM(otherPenetrationPoint)); + return 0; } if (!(otherBody->getInternalType() & btCollisionObject::CO_RIGID_BODY)) { From 26ae688d593bec38aff76a44ff176254186bee0b Mon Sep 17 00:00:00 2001 From: sabrina-shanman Date: Thu, 9 Aug 2018 17:11:32 -0700 Subject: [PATCH 082/144] Refactor CollisionPickResult::toVariantMap --- interface/src/raypick/CollisionPick.cpp | 52 +++++++++++-------------- 1 file changed, 22 insertions(+), 30 deletions(-) diff --git a/interface/src/raypick/CollisionPick.cpp b/interface/src/raypick/CollisionPick.cpp index 73d2dfe67a..503832c0eb 100644 --- a/interface/src/raypick/CollisionPick.cpp +++ b/interface/src/raypick/CollisionPick.cpp @@ -23,36 +23,8 @@ QVariantMap CollisionPickResult::toVariantMap() const { std::unordered_map intersections; std::unordered_map collisionPointPairs; - IntersectionType intersectionTypesToCheck[] = { ENTITY, AVATAR }; - for (int i = 0; i < 2; i++) { - IntersectionType intersectionType = intersectionTypesToCheck[i]; - - const std::vector* objectIntersections; - if (intersectionType == ENTITY) { - objectIntersections = &entityIntersections; - } - else { - objectIntersections = &avatarIntersections; - } - - 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); - } - } + buildObjectIntersectionsMap(ENTITY, entityIntersections, intersections, collisionPointPairs); + buildObjectIntersectionsMap(AVATAR, avatarIntersections, intersections, collisionPointPairs); QVariantList qIntersectingObjects; for (auto& intersectionKeyVal : intersections) { @@ -70,6 +42,26 @@ QVariantMap CollisionPickResult::toVariantMap() const { return variantMap; } +void buildObjectIntersectionsMap(IntersectionType intersectionType, const std::vector& objectIntersections, std::unordered_map& intersections, std::unordered_map& collisionPointPairs) { + for (auto& objectIntersection : objectIntersections) { + auto at = intersections.find(objectIntersection.foundID); + if (at == intersections.end()) { + QVariantMap intersectingObject; + intersectingObject["id"] = objectIntersection.foundID; + intersectingObject["type"] = intersectionType; + intersections[objectIntersection.foundID] = intersectingObject; + + collisionPointPairs[objectIntersection.foundID] = QVariantList(); + } + + QVariantMap collisionPointPair; + collisionPointPair["pointOnPick"] = vec3toVariant(objectIntersection.testCollisionPoint); + collisionPointPair["pointOnObject"] = vec3toVariant(objectIntersection.foundCollisionPoint); + + collisionPointPairs[objectIntersection.foundID].append(collisionPointPair); + } +} + bool CollisionPick::isShapeInfoReady() { if (_mathPick.shouldComputeShapeInfo()) { if (_cachedResource && _cachedResource->isLoaded()) { From b32b811fff967bada1eb41a7d5cb14769f0286fe Mon Sep 17 00:00:00 2001 From: sabrina-shanman Date: Thu, 9 Aug 2018 17:24:09 -0700 Subject: [PATCH 083/144] Move up buildObjectIntersectionsMap in CollisionPick.cpp --- interface/src/raypick/CollisionPick.cpp | 40 ++++++++++++------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/interface/src/raypick/CollisionPick.cpp b/interface/src/raypick/CollisionPick.cpp index 503832c0eb..87c8c91e6d 100644 --- a/interface/src/raypick/CollisionPick.cpp +++ b/interface/src/raypick/CollisionPick.cpp @@ -15,6 +15,26 @@ #include "ScriptEngineLogging.h" #include "UUIDHasher.h" +void buildObjectIntersectionsMap(IntersectionType intersectionType, const std::vector& objectIntersections, std::unordered_map& intersections, std::unordered_map& collisionPointPairs) { + for (auto& objectIntersection : objectIntersections) { + auto at = intersections.find(objectIntersection.foundID); + if (at == intersections.end()) { + QVariantMap intersectingObject; + intersectingObject["id"] = objectIntersection.foundID; + intersectingObject["type"] = intersectionType; + intersections[objectIntersection.foundID] = intersectingObject; + + collisionPointPairs[objectIntersection.foundID] = QVariantList(); + } + + QVariantMap collisionPointPair; + collisionPointPair["pointOnPick"] = vec3toVariant(objectIntersection.testCollisionPoint); + collisionPointPair["pointOnObject"] = vec3toVariant(objectIntersection.foundCollisionPoint); + + collisionPointPairs[objectIntersection.foundID].append(collisionPointPair); + } +} + QVariantMap CollisionPickResult::toVariantMap() const { QVariantMap variantMap; @@ -42,26 +62,6 @@ QVariantMap CollisionPickResult::toVariantMap() const { return variantMap; } -void buildObjectIntersectionsMap(IntersectionType intersectionType, const std::vector& objectIntersections, std::unordered_map& intersections, std::unordered_map& collisionPointPairs) { - for (auto& objectIntersection : objectIntersections) { - auto at = intersections.find(objectIntersection.foundID); - if (at == intersections.end()) { - QVariantMap intersectingObject; - intersectingObject["id"] = objectIntersection.foundID; - intersectingObject["type"] = intersectionType; - intersections[objectIntersection.foundID] = intersectingObject; - - collisionPointPairs[objectIntersection.foundID] = QVariantList(); - } - - QVariantMap collisionPointPair; - collisionPointPair["pointOnPick"] = vec3toVariant(objectIntersection.testCollisionPoint); - collisionPointPair["pointOnObject"] = vec3toVariant(objectIntersection.foundCollisionPoint); - - collisionPointPairs[objectIntersection.foundID].append(collisionPointPair); - } -} - bool CollisionPick::isShapeInfoReady() { if (_mathPick.shouldComputeShapeInfo()) { if (_cachedResource && _cachedResource->isLoaded()) { From 4c367fa44309c9d23b84fe16a8b836529506d738 Mon Sep 17 00:00:00 2001 From: sabrina-shanman Date: Thu, 9 Aug 2018 17:26:54 -0700 Subject: [PATCH 084/144] Fix outdated explanation of when CollisionPickResult.intersects is true --- interface/src/raypick/PickScriptingInterface.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/raypick/PickScriptingInterface.h b/interface/src/raypick/PickScriptingInterface.h index 1ba1c248bc..b039a8c616 100644 --- a/interface/src/raypick/PickScriptingInterface.h +++ b/interface/src/raypick/PickScriptingInterface.h @@ -147,7 +147,7 @@ public: * An intersection result for a Collision Pick. * * @typedef {object} CollisionPickResult - * @property {boolean} intersects If there was at least one valid intersection (entityIntersections.length + avatarIntersections.length > 0) + * @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. */ From 660178e5bb03c193224d4b514149a824402cbd57 Mon Sep 17 00:00:00 2001 From: sabrina-shanman Date: Thu, 9 Aug 2018 17:41:46 -0700 Subject: [PATCH 085/144] Remove 'plane' from list of supported Collision Pick shapeTypes, since it turns out ShapeFactory does not support it --- interface/src/raypick/PickScriptingInterface.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/raypick/PickScriptingInterface.cpp b/interface/src/raypick/PickScriptingInterface.cpp index 28bffb487e..e120010935 100644 --- a/interface/src/raypick/PickScriptingInterface.cpp +++ b/interface/src/raypick/PickScriptingInterface.cpp @@ -242,7 +242,7 @@ unsigned int PickScriptingInterface::createParabolaPick(const QVariant& properti * 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", "plane" +* @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. */ From 925cfc9b23a21f4721c467e319c18e8e583d6489 Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Fri, 10 Aug 2018 16:45:27 +0200 Subject: [PATCH 086/144] Set a max focus distance value of 14km to not lose the edit handles by far-clipping --- scripts/system/libraries/entityCameraTool.js | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/scripts/system/libraries/entityCameraTool.js b/scripts/system/libraries/entityCameraTool.js index f554f45722..01030c9d7d 100644 --- a/scripts/system/libraries/entityCameraTool.js +++ b/scripts/system/libraries/entityCameraTool.js @@ -28,7 +28,7 @@ var FOCUS_MIN_ZOOM = 0.5; var ZOOM_SCALING = 0.02; var MIN_ZOOM_DISTANCE = 0.01; -var MAX_ZOOM_DISTANCE = 200; +var MAX_ZOOM_DISTANCE = 14000; var MODE_INACTIVE = 'inactive'; var MODE_ORBIT = 'orbit'; @@ -255,14 +255,6 @@ CameraManager = function() { that.updateCamera(); } - that.getZoomPercentage = function() { - return (that.zoomDistance - MIN_ZOOM_DISTANCE) / MAX_ZOOM_DISTANCE; - } - - that.setZoomPercentage = function(pct) { - that.targetZoomDistance = pct * (MAX_ZOOM_DISTANCE - MIN_ZOOM_DISTANCE); - } - that.pan = function(offset) { var up = Quat.getUp(Camera.getOrientation()); var right = Quat.getRight(Camera.getOrientation()); From f902552309a726f1855d8e98c2a641eacae6bf4d Mon Sep 17 00:00:00 2001 From: sabrina-shanman Date: Fri, 10 Aug 2018 08:55:29 -0700 Subject: [PATCH 087/144] Finalize on using 'rotation' in Collision Pick API and add missing jsdoc for CollisionRegion --- libraries/shared/src/RegisteredMetaTypes.h | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/libraries/shared/src/RegisteredMetaTypes.h b/libraries/shared/src/RegisteredMetaTypes.h index 5f842ef066..a8f22ce691 100644 --- a/libraries/shared/src/RegisteredMetaTypes.h +++ b/libraries/shared/src/RegisteredMetaTypes.h @@ -253,6 +253,14 @@ public: } }; +/**jsdoc +* A CollisionPick defines a volume for checking collisions in the physics simulation. + +* @typedef {object} CollisionPick +* @property {Shape} shape - The information about the collision region's size and shape. +* @property {Vec3} position - The position of the collision region. +* @property {Quat} rotation - The orientation of the collision region. +*/ class CollisionRegion : public MathPick { public: CollisionRegion() { } @@ -284,8 +292,6 @@ public: } if (pickVariant["rotation"].isValid()) { transform.setRotation(quatFromVariant(pickVariant["rotation"])); - } else if (pickVariant["orientation"].isValid()) { - transform.setRotation(quatFromVariant(pickVariant["orientation"])); } } @@ -300,7 +306,7 @@ public: collisionRegion["shape"] = shape; collisionRegion["position"] = vec3toVariant(transform.getTranslation()); - collisionRegion["orientation"] = quatToVariant(transform.getRotation()); + collisionRegion["rotation"] = quatToVariant(transform.getRotation()); return collisionRegion; } From 1699d3ed6a23c196b64e2eac1a2cc4d553ae81d3 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Fri, 10 Aug 2018 09:35:40 -0700 Subject: [PATCH 088/144] Wording change in Availability --- interface/resources/qml/hifi/Pal.qml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/interface/resources/qml/hifi/Pal.qml b/interface/resources/qml/hifi/Pal.qml index dc5aa9dade..cbab83ea28 100644 --- a/interface/resources/qml/hifi/Pal.qml +++ b/interface/resources/qml/hifi/Pal.qml @@ -1050,9 +1050,9 @@ Rectangle { 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; From e39dd250cd1e5ddcd308482ce19c527c79aa020f Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Fri, 10 Aug 2018 10:01:09 -0700 Subject: [PATCH 089/144] Things will 'just work' once backend is in place --- interface/resources/qml/hifi/Feed.qml | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/interface/resources/qml/hifi/Feed.qml b/interface/resources/qml/hifi/Feed.qml index c81e3c2389..593ebdcbea 100644 --- a/interface/resources/qml/hifi/Feed.qml +++ b/interface/resources/qml/hifi/Feed.qml @@ -90,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 }; } @@ -146,6 +148,14 @@ Column { 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(); } } @@ -170,7 +180,7 @@ Column { id: autoScrollTimer; interval: 3000; running: false; - repeat: true; + repeat: false; onTriggered: { if (scroll.currentIndex !== -1) { if (scroll.currentIndex === scroll.count - 1) { From d61aaa3bd76b907794dd3abccf8ecfcfad7b92b1 Mon Sep 17 00:00:00 2001 From: Cristian Luis Duarte Date: Fri, 10 Aug 2018 14:16:44 -0300 Subject: [PATCH 090/144] Android - People - Round profile image --- .../hifiinterface/view/UserListAdapter.java | 29 +++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/view/UserListAdapter.java b/android/app/src/main/java/io/highfidelity/hifiinterface/view/UserListAdapter.java index 79a931d93b..f9df28d5ca 100644 --- a/android/app/src/main/java/io/highfidelity/hifiinterface/view/UserListAdapter.java +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/view/UserListAdapter.java @@ -1,18 +1,22 @@ 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.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.Button; import android.widget.ImageButton; import android.widget.ImageView; import android.widget.TextView; import android.widget.Toast; +import com.squareup.picasso.Callback; import com.squareup.picasso.Picasso; import java.util.ArrayList; @@ -74,7 +78,28 @@ public class UserListAdapter extends RecyclerView.Adapter Date: Fri, 10 Aug 2018 10:52:21 -0700 Subject: [PATCH 091/144] Use 'orientation' for Collision Pick API, not 'rotation' --- interface/src/raypick/PickScriptingInterface.cpp | 2 +- libraries/shared/src/RegisteredMetaTypes.h | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/interface/src/raypick/PickScriptingInterface.cpp b/interface/src/raypick/PickScriptingInterface.cpp index e120010935..0ed35e5589 100644 --- a/interface/src/raypick/PickScriptingInterface.cpp +++ b/interface/src/raypick/PickScriptingInterface.cpp @@ -255,7 +255,7 @@ unsigned int PickScriptingInterface::createParabolaPick(const QVariant& properti * @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} rotation - The orientation of the collision region. +* @property {Quat} orientation - The orientation of the collision region. */ unsigned int PickScriptingInterface::createCollisionPick(const QVariant& properties) { QVariantMap propMap = properties.toMap(); diff --git a/libraries/shared/src/RegisteredMetaTypes.h b/libraries/shared/src/RegisteredMetaTypes.h index a8f22ce691..e78dbafd75 100644 --- a/libraries/shared/src/RegisteredMetaTypes.h +++ b/libraries/shared/src/RegisteredMetaTypes.h @@ -259,7 +259,7 @@ public: * @typedef {object} CollisionPick * @property {Shape} shape - The information about the collision region's size and shape. * @property {Vec3} position - The position of the collision region. -* @property {Quat} rotation - The orientation of the collision region. +* @property {Quat} orientation - The orientation of the collision region. */ class CollisionRegion : public MathPick { public: @@ -290,8 +290,8 @@ public: if (pickVariant["position"].isValid()) { transform.setTranslation(vec3FromVariant(pickVariant["position"])); } - if (pickVariant["rotation"].isValid()) { - transform.setRotation(quatFromVariant(pickVariant["rotation"])); + if (pickVariant["orientation"].isValid()) { + transform.setRotation(quatFromVariant(pickVariant["orientation"])); } } @@ -306,7 +306,7 @@ public: collisionRegion["shape"] = shape; collisionRegion["position"] = vec3toVariant(transform.getTranslation()); - collisionRegion["rotation"] = quatToVariant(transform.getRotation()); + collisionRegion["orientation"] = quatToVariant(transform.getRotation()); return collisionRegion; } From 91365aeff4453ffe83780a3e1a2f94cae0d31bfa Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Fri, 10 Aug 2018 11:31:17 -0700 Subject: [PATCH 092/144] make avatar kill packets from mixer reliable --- assignment-client/src/avatars/AvatarMixer.cpp | 13 ++++++++----- .../src/avatars/AvatarMixerClientData.cpp | 4 ++-- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index bbc774eea6..8f1df9c321 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -447,18 +447,21 @@ void AvatarMixer::handleAvatarKilled(SharedNodePointer avatarNode) { // send a kill packet for it to our other nodes nodeList->eachMatchingNode([&](const SharedNodePointer& node) { // we relay avatar kill packets to agents that are not upstream - // and downstream avatar mixers, if the node that was just killed was being replicated - return (node->getType() == NodeType::Agent && !node->isUpstream()) || - (avatarNode->isReplicated() && shouldReplicateTo(*avatarNode, *node)); + // and downstream avatar mixers, if the node that was just killed was being replicatedConnectedAgent + return node->getActiveSocket() && + ((node->getType() == NodeType::Agent && !node->isUpstream()) || + (avatarNode->isReplicated() && shouldReplicateTo(*avatarNode, *node))); }, [&](const SharedNodePointer& node) { if (node->getType() == NodeType::Agent) { if (!killPacket) { - killPacket = NLPacket::create(PacketType::KillAvatar, NUM_BYTES_RFC4122_UUID + sizeof(KillAvatarReason)); + killPacket = NLPacket::create(PacketType::KillAvatar, NUM_BYTES_RFC4122_UUID + sizeof(KillAvatarReason), true); killPacket->write(avatarNode->getUUID().toRfc4122()); killPacket->writePrimitive(KillAvatarReason::AvatarDisconnected); } - nodeList->sendUnreliablePacket(*killPacket, *node); + auto killPacketCopy = NLPacket::createCopy(*killPacket); + + nodeList->sendPacket(std::move(killPacketCopy), *node); } else { // send a replicated kill packet to the downstream avatar mixer if (!replicatedKillPacket) { diff --git a/assignment-client/src/avatars/AvatarMixerClientData.cpp b/assignment-client/src/avatars/AvatarMixerClientData.cpp index e185fe9167..9aa3e88b52 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.cpp +++ b/assignment-client/src/avatars/AvatarMixerClientData.cpp @@ -108,7 +108,7 @@ uint16_t AvatarMixerClientData::getLastBroadcastSequenceNumber(const QUuid& node void AvatarMixerClientData::ignoreOther(SharedNodePointer self, SharedNodePointer other) { if (!isRadiusIgnoring(other->getUUID())) { addToRadiusIgnoringSet(other->getUUID()); - auto killPacket = NLPacket::create(PacketType::KillAvatar, NUM_BYTES_RFC4122_UUID + sizeof(KillAvatarReason)); + auto killPacket = NLPacket::create(PacketType::KillAvatar, NUM_BYTES_RFC4122_UUID + sizeof(KillAvatarReason), true); killPacket->write(other->getUUID().toRfc4122()); if (self->isIgnoreRadiusEnabled()) { killPacket->writePrimitive(KillAvatarReason::TheirAvatarEnteredYourBubble); @@ -116,7 +116,7 @@ void AvatarMixerClientData::ignoreOther(SharedNodePointer self, SharedNodePointe killPacket->writePrimitive(KillAvatarReason::YourAvatarEnteredTheirBubble); } setLastBroadcastTime(other->getUUID(), 0); - DependencyManager::get()->sendUnreliablePacket(*killPacket, *self); + DependencyManager::get()->sendPacket(std::move(killPacket), *self); } } From 45b9871fdb72f1a85dc60497360b7f5312ed5ffd Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 10 Aug 2018 12:10:49 -0700 Subject: [PATCH 093/144] when adding asset to world set grabbable --- interface/src/Application.cpp | 2 ++ interface/src/Menu.h | 1 + 2 files changed, 3 insertions(+) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index c37f973313..3f5d6d7d52 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -7284,6 +7284,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::AnimDebugDrawDefaultPose)); + 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()); diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 1ab7faa82b..a4fc283d54 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -76,6 +76,7 @@ namespace MenuOption { const QString CrashOutOfBoundsVectorAccessThreaded = "Out of Bounds Vector Access (threaded)"; const QString CrashNewFault = "New Fault"; const QString CrashNewFaultThreaded = "New Fault (threaded)"; + const QString CreateEntitiesGrabbable = "Create Entities As Grabbable (except Zones, Particles, and Lights)"; const QString DeadlockInterface = "Deadlock Interface"; const QString UnresponsiveInterface = "Unresponsive Interface"; const QString DecreaseAvatarSize = "Decrease Avatar Size"; From 8c185aa04e3c8e4924fa8d01d3f871e6aa04149c Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 10 Aug 2018 12:13:09 -0700 Subject: [PATCH 094/144] fix menu option --- interface/src/Application.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 3f5d6d7d52..d8a48d51c0 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -7284,7 +7284,7 @@ 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::AnimDebugDrawDefaultPose)); + 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); From ef3ac6bba368b5873b031162ba0cf7db8c0a46b3 Mon Sep 17 00:00:00 2001 From: Cristian Luis Duarte Date: Fri, 10 Aug 2018 17:36:32 -0300 Subject: [PATCH 095/144] Android - Friends - rollback swipe to reveal items --- .../hifiinterface/view/SwipeRevealLayout.java | 729 ------------------ .../hifiinterface/view/UserListAdapter.java | 10 - android/app/src/main/res/layout/user_item.xml | 83 +- android/app/src/main/res/values/attrs.xml | 9 - 4 files changed, 26 insertions(+), 805 deletions(-) delete mode 100644 android/app/src/main/java/io/highfidelity/hifiinterface/view/SwipeRevealLayout.java delete mode 100644 android/app/src/main/res/values/attrs.xml diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/view/SwipeRevealLayout.java b/android/app/src/main/java/io/highfidelity/hifiinterface/view/SwipeRevealLayout.java deleted file mode 100644 index 06ac4ac3ec..0000000000 --- a/android/app/src/main/java/io/highfidelity/hifiinterface/view/SwipeRevealLayout.java +++ /dev/null @@ -1,729 +0,0 @@ -package io.highfidelity.hifiinterface.view; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.content.res.Resources; -import android.content.res.TypedArray; -import android.graphics.Rect; -import android.os.Bundle; -import android.os.Parcelable; -import android.support.annotation.Nullable; -import android.support.v4.view.GestureDetectorCompat; -import android.support.v4.view.ViewCompat; -import android.support.v4.widget.ViewDragHelper; -import android.util.AttributeSet; -import android.util.DisplayMetrics; -import android.view.GestureDetector; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewGroup; - -import io.highfidelity.hifiinterface.R; - -/** - * Created by Mark O'Sullivan on 25th February 2018. - - MIT License - - Copyright (c) 2018 Mark O'Sullivan - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - - */ - -@SuppressLint("RtlHardcoded") -public class SwipeRevealLayout extends ViewGroup { - - private static final String SUPER_INSTANCE_STATE = "saved_instance_state_parcelable"; - - private static final int DEFAULT_MIN_FLING_VELOCITY = 300; // dp per second - private static final int DEFAULT_MIN_DIST_REQUEST_DISALLOW_PARENT = 1; // dp - - public static final int DRAG_EDGE_LEFT = 0x1; - public static final int DRAG_EDGE_RIGHT = 0x1 << 1; - - /** - * The secondary view will be under the main view. - */ - public static final int MODE_NORMAL = 0; - - /** - * The secondary view will stick the edge of the main view. - */ - public static final int MODE_SAME_LEVEL = 1; - - /** - * Main view is the view which is shown when the layout is closed. - */ - private View mMainView; - - /** - * Secondary view is the view which is shown when the layout is opened. - */ - private View mSecondaryView; - - /** - * The rectangle position of the main view when the layout is closed. - */ - private Rect mRectMainClose = new Rect(); - - /** - * The rectangle position of the main view when the layout is opened. - */ - private Rect mRectMainOpen = new Rect(); - - /** - * The rectangle position of the secondary view when the layout is closed. - */ - private Rect mRectSecClose = new Rect(); - - /** - * The rectangle position of the secondary view when the layout is opened. - */ - private Rect mRectSecOpen = new Rect(); - - /** - * The minimum distance (px) to the closest drag edge that the SwipeRevealLayout - * will disallow the parent to intercept touch event. - */ - private int mMinDistRequestDisallowParent = 0; - - private boolean mIsOpenBeforeInit = false; - private volatile boolean mIsScrolling = false; - private volatile boolean mLockDrag = false; - - private int mMinFlingVelocity = DEFAULT_MIN_FLING_VELOCITY; - private int mMode = MODE_NORMAL; - - private int mDragEdge = DRAG_EDGE_LEFT; - - private float mDragDist = 0; - private float mPrevX = -1; - - private ViewDragHelper mDragHelper; - private GestureDetectorCompat mGestureDetector; - - public SwipeRevealLayout(Context context) { - super(context); - init(context, null); - } - - public SwipeRevealLayout(Context context, AttributeSet attrs) { - super(context, attrs); - init(context, attrs); - } - - public SwipeRevealLayout(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - } - - @Nullable - @Override - protected Parcelable onSaveInstanceState() { - Bundle bundle = new Bundle(); - bundle.putParcelable(SUPER_INSTANCE_STATE, super.onSaveInstanceState()); - return super.onSaveInstanceState(); - } - - @Override - protected void onRestoreInstanceState(Parcelable state) { - Bundle bundle = (Bundle) state; - state = bundle.getParcelable(SUPER_INSTANCE_STATE); - super.onRestoreInstanceState(state); - } - - @Override - public boolean onTouchEvent(MotionEvent event) { - mGestureDetector.onTouchEvent(event); - mDragHelper.processTouchEvent(event); - return true; - } - - @Override - public boolean onInterceptTouchEvent(MotionEvent ev) { - if (isDragLocked()) { - return super.onInterceptTouchEvent(ev); - } - - mDragHelper.processTouchEvent(ev); - mGestureDetector.onTouchEvent(ev); - accumulateDragDist(ev); - - boolean couldBecomeClick = couldBecomeClick(ev); - boolean settling = mDragHelper.getViewDragState() == ViewDragHelper.STATE_SETTLING; - boolean idleAfterScrolled = mDragHelper.getViewDragState() == ViewDragHelper.STATE_IDLE - && mIsScrolling; - - // must be placed as the last statement - mPrevX = ev.getX(); - - // return true => intercept, cannot trigger onClick event - return !couldBecomeClick && (settling || idleAfterScrolled); - } - - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - - // get views - if (getChildCount() >= 2) { - mSecondaryView = getChildAt(0); - mMainView = getChildAt(1); - } - else if (getChildCount() == 1) { - mMainView = getChildAt(0); - } - } - - /** - * {@inheritDoc} - */ - @SuppressWarnings("ConstantConditions") - @Override - protected void onLayout(boolean changed, int l, int t, int r, int b) { - for (int index = 0; index < getChildCount(); index++) { - final View child = getChildAt(index); - - int left, right, top, bottom; - left = right = top = bottom = 0; - - final int minLeft = getPaddingLeft(); - final int maxRight = Math.max(r - getPaddingRight() - l, 0); - final int minTop = getPaddingTop(); - final int maxBottom = Math.max(b - getPaddingBottom() - t, 0); - - int measuredChildHeight = child.getMeasuredHeight(); - int measuredChildWidth = child.getMeasuredWidth(); - - // need to take account if child size is match_parent - final LayoutParams childParams = child.getLayoutParams(); - boolean matchParentHeight = false; - boolean matchParentWidth = false; - - if (childParams != null) { - matchParentHeight = (childParams.height == LayoutParams.MATCH_PARENT) || - (childParams.height == LayoutParams.FILL_PARENT); - matchParentWidth = (childParams.width == LayoutParams.MATCH_PARENT) || - (childParams.width == LayoutParams.FILL_PARENT); - } - - if (matchParentHeight) { - measuredChildHeight = maxBottom - minTop; - childParams.height = measuredChildHeight; - } - - if (matchParentWidth) { - measuredChildWidth = maxRight - minLeft; - childParams.width = measuredChildWidth; - } - - switch (mDragEdge) { - case DRAG_EDGE_RIGHT: - left = Math.max(r - measuredChildWidth - getPaddingRight() - l, minLeft); - top = Math.min(getPaddingTop(), maxBottom); - right = Math.max(r - getPaddingRight() - l, minLeft); - bottom = Math.min(measuredChildHeight + getPaddingTop(), maxBottom); - break; - - case DRAG_EDGE_LEFT: - left = Math.min(getPaddingLeft(), maxRight); - top = Math.min(getPaddingTop(), maxBottom); - right = Math.min(measuredChildWidth + getPaddingLeft(), maxRight); - bottom = Math.min(measuredChildHeight + getPaddingTop(), maxBottom); - break; - } - - child.layout(left, top, right, bottom); - } - - // taking account offset when mode is SAME_LEVEL - if (mMode == MODE_SAME_LEVEL) { - switch (mDragEdge) { - case DRAG_EDGE_LEFT: - mSecondaryView.offsetLeftAndRight(-mSecondaryView.getWidth()); - break; - - case DRAG_EDGE_RIGHT: - mSecondaryView.offsetLeftAndRight(mSecondaryView.getWidth()); - break; - } - } - - initRects(); - - if (mIsOpenBeforeInit) { - open(false); - } else { - close(false); - } - - } - - /** - * {@inheritDoc} - */ - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - if (getChildCount() < 2) { - throw new RuntimeException("Layout must have two children"); - } - - final LayoutParams params = getLayoutParams(); - - final int widthMode = MeasureSpec.getMode(widthMeasureSpec); - final int heightMode = MeasureSpec.getMode(heightMeasureSpec); - - int desiredWidth = 0; - int desiredHeight = 0; - - // first find the largest child - for (int i = 0; i < getChildCount(); i++) { - final View child = getChildAt(i); - measureChild(child, widthMeasureSpec, heightMeasureSpec); - desiredWidth = Math.max(child.getMeasuredWidth(), desiredWidth); - desiredHeight = Math.max(child.getMeasuredHeight(), desiredHeight); - } - // create new measure spec using the largest child width - widthMeasureSpec = MeasureSpec.makeMeasureSpec(desiredWidth, widthMode); - heightMeasureSpec = MeasureSpec.makeMeasureSpec(desiredHeight, heightMode); - - final int measuredWidth = MeasureSpec.getSize(widthMeasureSpec); - final int measuredHeight = MeasureSpec.getSize(heightMeasureSpec); - - for (int i = 0; i < getChildCount(); i++) { - final View child = getChildAt(i); - final LayoutParams childParams = child.getLayoutParams(); - - if (childParams != null) { - if (childParams.height == LayoutParams.MATCH_PARENT) { - child.setMinimumHeight(measuredHeight); - } - - if (childParams.width == LayoutParams.MATCH_PARENT) { - child.setMinimumWidth(measuredWidth); - } - } - - measureChild(child, widthMeasureSpec, heightMeasureSpec); - desiredWidth = Math.max(child.getMeasuredWidth(), desiredWidth); - desiredHeight = Math.max(child.getMeasuredHeight(), desiredHeight); - } - - // taking accounts of padding - desiredWidth += getPaddingLeft() + getPaddingRight(); - desiredHeight += getPaddingTop() + getPaddingBottom(); - - // adjust desired width - if (widthMode == MeasureSpec.EXACTLY) { - desiredWidth = measuredWidth; - } else { - if (params.width == LayoutParams.MATCH_PARENT) { - desiredWidth = measuredWidth; - } - - if (widthMode == MeasureSpec.AT_MOST) { - desiredWidth = (desiredWidth > measuredWidth)? measuredWidth : desiredWidth; - } - } - - // adjust desired height - if (heightMode == MeasureSpec.EXACTLY) { - desiredHeight = measuredHeight; - } else { - if (params.height == LayoutParams.MATCH_PARENT) { - desiredHeight = measuredHeight; - } - - if (heightMode == MeasureSpec.AT_MOST) { - desiredHeight = (desiredHeight > measuredHeight)? measuredHeight : desiredHeight; - } - } - - setMeasuredDimension(desiredWidth, desiredHeight); - } - - @Override - public void computeScroll() { - if (mDragHelper.continueSettling(true)) { - ViewCompat.postInvalidateOnAnimation(this); - } - } - - /** - * Open the panel to show the secondary view - */ - public void open(boolean animation) { - mIsOpenBeforeInit = true; - - if (animation) { - mDragHelper.smoothSlideViewTo(mMainView, mRectMainOpen.left, mRectMainOpen.top); - } else { - mDragHelper.abort(); - - mMainView.layout( - mRectMainOpen.left, - mRectMainOpen.top, - mRectMainOpen.right, - mRectMainOpen.bottom - ); - - mSecondaryView.layout( - mRectSecOpen.left, - mRectSecOpen.top, - mRectSecOpen.right, - mRectSecOpen.bottom - ); - } - - ViewCompat.postInvalidateOnAnimation(this); - } - - /** - * Close the panel to hide the secondary view - */ - public void close(boolean animation) { - mIsOpenBeforeInit = false; - - if (animation) { - mDragHelper.smoothSlideViewTo(mMainView, mRectMainClose.left, mRectMainClose.top); - } else { - mDragHelper.abort(); - mMainView.layout( - mRectMainClose.left, - mRectMainClose.top, - mRectMainClose.right, - mRectMainClose.bottom - ); - mSecondaryView.layout( - mRectSecClose.left, - mRectSecClose.top, - mRectSecClose.right, - mRectSecClose.bottom - ); - } - - ViewCompat.postInvalidateOnAnimation(this); - } - - /** - * @return true if the drag/swipe motion is currently locked. - */ - public boolean isDragLocked() { - return mLockDrag; - } - - private int getMainOpenLeft() { - switch (mDragEdge) { - case DRAG_EDGE_LEFT: - return mRectMainClose.left + mSecondaryView.getWidth(); - - case DRAG_EDGE_RIGHT: - return mRectMainClose.left - mSecondaryView.getWidth(); - - - default: - return 0; - } - } - - private int getMainOpenTop() { - switch (mDragEdge) { - case DRAG_EDGE_LEFT: - return mRectMainClose.top; - - case DRAG_EDGE_RIGHT: - return mRectMainClose.top; - - - default: - return 0; - } - } - - private int getSecOpenLeft() { - return mRectSecClose.left; - } - - private int getSecOpenTop() { - return mRectSecClose.top; - } - - private void initRects() { - // close position of main view - mRectMainClose.set( - mMainView.getLeft(), - mMainView.getTop(), - mMainView.getRight(), - mMainView.getBottom() - ); - - // close position of secondary view - mRectSecClose.set( - mSecondaryView.getLeft(), - mSecondaryView.getTop(), - mSecondaryView.getRight(), - mSecondaryView.getBottom() - ); - - // open position of the main view - mRectMainOpen.set( - getMainOpenLeft(), - getMainOpenTop(), - getMainOpenLeft() + mMainView.getWidth(), - getMainOpenTop() + mMainView.getHeight() - ); - - // open position of the secondary view - mRectSecOpen.set( - getSecOpenLeft(), - getSecOpenTop(), - getSecOpenLeft() + mSecondaryView.getWidth(), - getSecOpenTop() + mSecondaryView.getHeight() - ); - } - - private boolean couldBecomeClick(MotionEvent ev) { - return isInMainView(ev) && !shouldInitiateADrag(); - } - - private boolean isInMainView(MotionEvent ev) { - float x = ev.getX(); - float y = ev.getY(); - - boolean withinVertical = mMainView.getTop() <= y && y <= mMainView.getBottom(); - boolean withinHorizontal = mMainView.getLeft() <= x && x <= mMainView.getRight(); - - return withinVertical && withinHorizontal; - } - - private boolean shouldInitiateADrag() { - float minDistToInitiateDrag = mDragHelper.getTouchSlop(); - return mDragDist >= minDistToInitiateDrag; - } - - private void accumulateDragDist(MotionEvent ev) { - final int action = ev.getAction(); - if (action == MotionEvent.ACTION_DOWN) { - mDragDist = 0; - return; - } - - float dragged = Math.abs(ev.getX() - mPrevX); - - mDragDist += dragged; - } - - private void init(Context context, AttributeSet attrs) { - if (attrs != null && context != null) { - TypedArray a = context.getTheme().obtainStyledAttributes( - attrs, - R.styleable.SwipeRevealLayout, - 0, 0 - ); - - mDragEdge = a.getInteger(R.styleable.SwipeRevealLayout_dragFromEdge, DRAG_EDGE_LEFT); - mMode = MODE_NORMAL; - mMinFlingVelocity = DEFAULT_MIN_FLING_VELOCITY; - mMinDistRequestDisallowParent = DEFAULT_MIN_DIST_REQUEST_DISALLOW_PARENT; - } - - mDragHelper = ViewDragHelper.create(this, 1.0f, mDragHelperCallback); - mDragHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_ALL); - - mGestureDetector = new GestureDetectorCompat(context, mGestureListener); - } - - private final GestureDetector.OnGestureListener mGestureListener = new GestureDetector.SimpleOnGestureListener() { - boolean hasDisallowed = false; - - @Override - public boolean onDown(MotionEvent e) { - mIsScrolling = false; - hasDisallowed = false; - return true; - } - - @Override - public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { - mIsScrolling = true; - return false; - } - - @Override - public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { - mIsScrolling = true; - - if (getParent() != null) { - boolean shouldDisallow; - - if (!hasDisallowed) { - shouldDisallow = getDistToClosestEdge() >= mMinDistRequestDisallowParent; - if (shouldDisallow) { - hasDisallowed = true; - } - } else { - shouldDisallow = true; - } - - // disallow parent to intercept touch event so that the layout will work - // properly on RecyclerView or view that handles scroll gesture. - getParent().requestDisallowInterceptTouchEvent(shouldDisallow); - } - - return false; - } - }; - - private int getDistToClosestEdge() { - switch (mDragEdge) { - case DRAG_EDGE_LEFT: - final int pivotRight = mRectMainClose.left + mSecondaryView.getWidth(); - - return Math.min( - mMainView.getLeft() - mRectMainClose.left, - pivotRight - mMainView.getLeft() - ); - - case DRAG_EDGE_RIGHT: - final int pivotLeft = mRectMainClose.right - mSecondaryView.getWidth(); - - return Math.min( - mMainView.getRight() - pivotLeft, - mRectMainClose.right - mMainView.getRight() - ); - } - - return 0; - } - - private int getHalfwayPivotHorizontal() { - if (mDragEdge == DRAG_EDGE_LEFT) { - return mRectMainClose.left + mSecondaryView.getWidth() / 2; - } else { - return mRectMainClose.right - mSecondaryView.getWidth() / 2; - } - } - - private final ViewDragHelper.Callback mDragHelperCallback = new ViewDragHelper.Callback() { - @Override - public boolean tryCaptureView(View child, int pointerId) { - - if (mLockDrag) - return false; - - mDragHelper.captureChildView(mMainView, pointerId); - return false; - } - - @Override - public int clampViewPositionHorizontal(View child, int left, int dx) { - switch (mDragEdge) { - case DRAG_EDGE_RIGHT: - return Math.max( - Math.min(left, mRectMainClose.left), - mRectMainClose.left - mSecondaryView.getWidth() - ); - - case DRAG_EDGE_LEFT: - return Math.max( - Math.min(left, mRectMainClose.left + mSecondaryView.getWidth()), - mRectMainClose.left - ); - - default: - return child.getLeft(); - } - } - - @Override - public void onViewReleased(View releasedChild, float xvel, float yvel) { - final boolean velRightExceeded = pxToDp((int) xvel) >= mMinFlingVelocity; - final boolean velLeftExceeded = pxToDp((int) xvel) <= -mMinFlingVelocity; - - final int pivotHorizontal = getHalfwayPivotHorizontal(); - - switch (mDragEdge) { - case DRAG_EDGE_RIGHT: - if (velRightExceeded) { - close(true); - } else if (velLeftExceeded) { - open(true); - } else { - if (mMainView.getRight() < pivotHorizontal) { - open(true); - } else { - close(true); - } - } - break; - - case DRAG_EDGE_LEFT: - if (velRightExceeded) { - open(true); - } else if (velLeftExceeded) { - close(true); - } else { - if (mMainView.getLeft() < pivotHorizontal) { - close(true); - } else { - open(true); - } - } - break; - } - } - - @Override - public void onEdgeDragStarted(int edgeFlags, int pointerId) { - super.onEdgeDragStarted(edgeFlags, pointerId); - - if (mLockDrag) { - return; - } - - boolean edgeStartLeft = (mDragEdge == DRAG_EDGE_RIGHT) - && edgeFlags == ViewDragHelper.EDGE_LEFT; - - boolean edgeStartRight = (mDragEdge == DRAG_EDGE_LEFT) - && edgeFlags == ViewDragHelper.EDGE_RIGHT; - - if (edgeStartLeft || edgeStartRight) { - mDragHelper.captureChildView(mMainView, pointerId); - } - } - - @Override - public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) { - super.onViewPositionChanged(changedView, left, top, dx, dy); - if (mMode == MODE_SAME_LEVEL) { - if (mDragEdge == DRAG_EDGE_LEFT || mDragEdge == DRAG_EDGE_RIGHT) { - mSecondaryView.offsetLeftAndRight(dx); - } else { - mSecondaryView.offsetTopAndBottom(dy); - } - } - ViewCompat.postInvalidateOnAnimation(SwipeRevealLayout.this); - } - }; - - private int pxToDp(int px) { - Resources resources = getContext().getResources(); - DisplayMetrics metrics = resources.getDisplayMetrics(); - return (int) (px / ((float)metrics.densityDpi / DisplayMetrics.DENSITY_DEFAULT)); - } -} \ No newline at end of file diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/view/UserListAdapter.java b/android/app/src/main/java/io/highfidelity/hifiinterface/view/UserListAdapter.java index f9df28d5ca..3489210b62 100644 --- a/android/app/src/main/java/io/highfidelity/hifiinterface/view/UserListAdapter.java +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/view/UserListAdapter.java @@ -11,10 +11,8 @@ import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.widget.ImageButton; import android.widget.ImageView; import android.widget.TextView; -import android.widget.Toast; import com.squareup.picasso.Callback; import com.squareup.picasso.Picasso; @@ -71,12 +69,6 @@ public class UserListAdapter extends RecyclerView.Adapter - + android:background="?attr/selectableItemBackground" + android:clickable="true"> - + - + - - - - - - - - - + - - - + android:layout_height="wrap_content" /> + - - - \ No newline at end of file + \ No newline at end of file diff --git a/android/app/src/main/res/values/attrs.xml b/android/app/src/main/res/values/attrs.xml deleted file mode 100644 index c12f28ccde..0000000000 --- a/android/app/src/main/res/values/attrs.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - \ No newline at end of file From 9fadf58e7b561f555dda08e03407990a33a0cf1a Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Fri, 10 Aug 2018 13:56:48 -0700 Subject: [PATCH 096/144] Add threads lib to image lib --- libraries/image/CMakeLists.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/libraries/image/CMakeLists.txt b/libraries/image/CMakeLists.txt index 6bc5c762f5..4db39f2152 100644 --- a/libraries/image/CMakeLists.txt +++ b/libraries/image/CMakeLists.txt @@ -3,3 +3,9 @@ setup_hifi_library() link_hifi_libraries(shared gpu) target_nvtt() target_etc2comp() + +if (UNIX AND NOT APPLE) + set(THREADS_PREFER_PTHREAD_FLAG ON) + find_package(Threads REQUIRED) + target_link_libraries(image Threads::Threads) +endif() From b65f2d46a64d5bcda85938edd621bfec162c8c29 Mon Sep 17 00:00:00 2001 From: Cristian Luis Duarte Date: Fri, 10 Aug 2018 18:18:24 -0300 Subject: [PATCH 097/144] Android - People - Letters styling in row --- .../hifiinterface/view/UserListAdapter.java | 8 +++++-- android/app/src/main/res/layout/user_item.xml | 21 +++++++++++++++---- android/app/src/main/res/values/colors.xml | 1 + android/app/src/main/res/values/strings.xml | 2 +- 4 files changed, 25 insertions(+), 7 deletions(-) diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/view/UserListAdapter.java b/android/app/src/main/java/io/highfidelity/hifiinterface/view/UserListAdapter.java index 3489210b62..202fd82c8b 100644 --- a/android/app/src/main/java/io/highfidelity/hifiinterface/view/UserListAdapter.java +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/view/UserListAdapter.java @@ -67,9 +67,9 @@ public class UserListAdapter extends RecyclerView.Adapter - + + android:layout_height="wrap_content"> + + + \ No newline at end of file diff --git a/android/app/src/main/res/values/colors.xml b/android/app/src/main/res/values/colors.xml index 7e6cf52d36..9de0ae5347 100644 --- a/android/app/src/main/res/values/colors.xml +++ b/android/app/src/main/res/values/colors.xml @@ -18,4 +18,5 @@ #99000000 #292929 #23B2E7 + #62D5C6
diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml index 9fe7f0cbee..d5da2f857b 100644 --- a/android/app/src/main/res/values/strings.xml +++ b/android/app/src/main/res/values/strings.xml @@ -23,5 +23,5 @@ No places exist with that name Privacy Policy Your Last Location - + Online From 3a356de49fe08f58c89e817bd73cceef6d9c9aef Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sat, 11 Aug 2018 09:46:11 +1200 Subject: [PATCH 098/144] Don't auto-action entry in HMD file dialog when single-click on it --- interface/resources/qml/dialogs/TabletFileDialog.qml | 1 - 1 file changed, 1 deletion(-) diff --git a/interface/resources/qml/dialogs/TabletFileDialog.qml b/interface/resources/qml/dialogs/TabletFileDialog.qml index 4de0460796..6848c230e3 100644 --- a/interface/resources/qml/dialogs/TabletFileDialog.qml +++ b/interface/resources/qml/dialogs/TabletFileDialog.qml @@ -471,7 +471,6 @@ TabletModalWindow { bottomMargin: hifi.dimensions.contentSpacing.y + currentSelection.controlHeight - currentSelection.height } headerVisible: !selectDirectory - onClicked: navigateToRow(row); onDoubleClicked: navigateToRow(row); focus: true Keys.onReturnPressed: navigateToCurrentRow(); From 2e323efe1b0858c263aa01300ff8196e3f2f4291 Mon Sep 17 00:00:00 2001 From: Gabriel Calero Date: Fri, 10 Aug 2018 18:57:41 -0300 Subject: [PATCH 099/144] Test if this fixes the choppy audio --- libraries/audio-client/src/AudioClient.cpp | 91 +++++++++++++++++++++- libraries/audio-client/src/AudioClient.h | 12 +++ 2 files changed, 100 insertions(+), 3 deletions(-) diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index 6ac30e7f73..52ad6751e7 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -57,6 +57,11 @@ #include #endif +Q_DECLARE_METATYPE(const void*); +Q_DECLARE_METATYPE(quint16); +Q_DECLARE_METATYPE(Transform); + + const int AudioClient::MIN_BUFFER_FRAMES = 1; const int AudioClient::MAX_BUFFER_FRAMES = 20; @@ -208,10 +213,18 @@ AudioClient::AudioClient() : _audioOutputIODevice(_localInjectorsStream, _receivedAudioStream, this), _stats(&_receivedAudioStream), _positionGetter(DEFAULT_POSITION_GETTER), + _audioSender(new AudioSender()), #if defined(Q_OS_ANDROID) _checkInputTimer(this), #endif _orientationGetter(DEFAULT_ORIENTATION_GETTER) { + + qRegisterMetaType("const void*"); + qRegisterMetaType("quint16"); + qRegisterMetaType("Transform"); + + _audioSender->start(); + // avoid putting a lock in the device callback assert(_localSamplesAvailable.is_lock_free()); @@ -1112,11 +1125,21 @@ void AudioClient::handleAudioInput(QByteArray& audioBuffer) { } else { encodedBuffer = audioBuffer; } + QMetaObject::invokeMethod(_audioSender, "emitAudioPacket", Qt::QueuedConnection, + Q_ARG(const void*, encodedBuffer.data()), +Q_ARG(size_t, encodedBuffer.size()), +Q_ARG(quint16, _outgoingAvatarAudioSequenceNumber++ ), + Q_ARG(bool,_isStereoInput), + Q_ARG(Transform, audioTransform), + Q_ARG(glm::vec3, avatarBoundingBoxCorner), + Q_ARG(glm::vec3, avatarBoundingBoxScale), + Q_ARG(PacketType, packetType), + Q_ARG(QString, _selectedCodecName)); - emitAudioPacket(encodedBuffer.data(), encodedBuffer.size(), _outgoingAvatarAudioSequenceNumber, _isStereoInput, + /* emitAudioPacket(encodedBuffer.data(), encodedBuffer.size(), _outgoingAvatarAudioSequenceNumber, _isStereoInput, audioTransform, avatarBoundingBoxCorner, avatarBoundingBoxScale, - packetType, _selectedCodecName); - _stats.sentPacket(); + packetType, _selectedCodecName); */ + //_stats.sentPacket(); } void AudioClient::handleMicAudioInput() { @@ -2051,3 +2074,65 @@ void AudioClient::setInputVolume(float volume, bool emitSignal) { } } } + +AudioSender::AudioSender() { + +} + +void AudioSender::start() { + moveToNewNamedThread(this, "Audio Sender Thread", [this] { }, QThread::HighestPriority); +} + + +void AudioSender::emitAudioPacket(const void* audioData, size_t bytes, quint16 sequenceNumber, bool isStereo, + const Transform& transform, glm::vec3 avatarBoundingBoxCorner, glm::vec3 avatarBoundingBoxScale, + PacketType packetType, QString codecName) { + static std::mutex _mutex; + using Locker = std::unique_lock; + auto nodeList = DependencyManager::get(); + SharedNodePointer audioMixer = nodeList->soloNodeOfType(NodeType::AudioMixer); + if (audioMixer && audioMixer->getActiveSocket()) { + Locker lock(_mutex); + auto audioPacket = NLPacket::create(packetType); + + // write sequence number + auto sequence = sequenceNumber; + audioPacket->writePrimitive(sequence); + + // write the codec + audioPacket->writeString(codecName); + + if (packetType == PacketType::SilentAudioFrame) { + // pack num silent samples + quint16 numSilentSamples = isStereo ? + AudioConstants::NETWORK_FRAME_SAMPLES_STEREO : + AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL; + audioPacket->writePrimitive(numSilentSamples); + } else { + // set the mono/stereo byte + quint8 channelFlag = isStereo ? 1 : 0; + audioPacket->writePrimitive(channelFlag); + } + + // at this point we'd better be sending the mixer a valid position, or it won't consider us for mixing + assert(!isNaN(transform.getTranslation())); + + // pack the three float positions + audioPacket->writePrimitive(transform.getTranslation()); + // pack the orientation + audioPacket->writePrimitive(transform.getRotation()); + + audioPacket->writePrimitive(avatarBoundingBoxCorner); + audioPacket->writePrimitive(avatarBoundingBoxScale); + + + if (audioPacket->getType() != PacketType::SilentAudioFrame) { + // audio samples have already been packed (written to networkAudioSamples) + int leadingBytes = audioPacket->getPayloadSize(); + audioPacket->setPayloadSize(leadingBytes + bytes); + memcpy(audioPacket->getPayload() + leadingBytes, audioData, bytes); + } + nodeList->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::SendAudioPacket); + nodeList->sendUnreliablePacket(*audioPacket, *audioMixer); + } +} \ No newline at end of file diff --git a/libraries/audio-client/src/AudioClient.h b/libraries/audio-client/src/AudioClient.h index 9ee7bcfeba..c9ef2e5c2c 100644 --- a/libraries/audio-client/src/AudioClient.h +++ b/libraries/audio-client/src/AudioClient.h @@ -71,6 +71,17 @@ class QIODevice; class Transform; class NLPacket; +class AudioSender : public QObject { + Q_OBJECT +public: + AudioSender(); + void start(); +public Q_SLOTS: + void emitAudioPacket(const void* audioData, size_t bytes, quint16 sequenceNumber, bool isStereo, + const Transform& transform, glm::vec3 avatarBoundingBoxCorner, glm::vec3 avatarBoundingBoxScale, + PacketType packetType, QString codecName = QString("")); +}; + class AudioClient : public AbstractAudioInterface, public Dependency { Q_OBJECT SINGLETON_DEPENDENCY @@ -261,6 +272,7 @@ protected: virtual void customDeleter() override; private: + AudioSender *_audioSender; friend class CheckDevicesThread; friend class LocalInjectorsThread; From 610306c49c05353bd3f1c0b90aaa5db411272421 Mon Sep 17 00:00:00 2001 From: Cristian Luis Duarte Date: Fri, 10 Aug 2018 19:02:01 -0300 Subject: [PATCH 100/144] Android - People - add friend star --- android/app/src/main/res/drawable/ic_star.xml | 4 ++++ android/app/src/main/res/layout/user_item.xml | 10 +++++++++- android/app/src/main/res/values/colors.xml | 2 ++ 3 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 android/app/src/main/res/drawable/ic_star.xml diff --git a/android/app/src/main/res/drawable/ic_star.xml b/android/app/src/main/res/drawable/ic_star.xml new file mode 100644 index 0000000000..abd1798942 --- /dev/null +++ b/android/app/src/main/res/drawable/ic_star.xml @@ -0,0 +1,4 @@ + + + diff --git a/android/app/src/main/res/layout/user_item.xml b/android/app/src/main/res/layout/user_item.xml index 703c99945e..dec9f04af8 100644 --- a/android/app/src/main/res/layout/user_item.xml +++ b/android/app/src/main/res/layout/user_item.xml @@ -44,5 +44,13 @@ android:fontFamily="@font/raleway_italic"/> - + \ No newline at end of file diff --git a/android/app/src/main/res/values/colors.xml b/android/app/src/main/res/values/colors.xml index 9de0ae5347..ecca075298 100644 --- a/android/app/src/main/res/values/colors.xml +++ b/android/app/src/main/res/values/colors.xml @@ -19,4 +19,6 @@ #292929 #23B2E7 #62D5C6 + #FBD92A + #8A8A8A From 1a6a55680c169f8a149ec1c098024de58aa13a2a Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Fri, 10 Aug 2018 15:30:32 -0700 Subject: [PATCH 101/144] PropagateComposedEvents --- interface/resources/qml/hifi/Feed.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/interface/resources/qml/hifi/Feed.qml b/interface/resources/qml/hifi/Feed.qml index 593ebdcbea..0605c72f26 100644 --- a/interface/resources/qml/hifi/Feed.qml +++ b/interface/resources/qml/hifi/Feed.qml @@ -165,6 +165,7 @@ Column { enabled: root.autoScrollTimerEnabled; anchors.fill: parent; hoverEnabled: true; + propagateComposedEvents: true; onEntered: { if (autoScrollTimer.running) { autoScrollTimer.stop(); From 096be16bb10423ac3b4df62e07de3944a7164d3b Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Fri, 10 Aug 2018 15:32:31 -0700 Subject: [PATCH 102/144] Only restart timer if feedMouseArea doesn't contain mouse --- interface/resources/qml/hifi/Feed.qml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/interface/resources/qml/hifi/Feed.qml b/interface/resources/qml/hifi/Feed.qml index 0605c72f26..e1c712db1f 100644 --- a/interface/resources/qml/hifi/Feed.qml +++ b/interface/resources/qml/hifi/Feed.qml @@ -156,7 +156,9 @@ Column { onCurrentIndexChanged: { if (root.autoScrollTimerEnabled) { autoScrollTimer.interval = suggestions.get(scroll.currentIndex).time_before_autoscroll_ms; - autoScrollTimer.start(); + if (!feedMouseArea.containsMouse) { + autoScrollTimer.start(); + } } } From 876079bb8139a4a380861150a4ace6709f84359a Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Sat, 11 Aug 2018 00:41:50 +0200 Subject: [PATCH 103/144] deactivate Create App when desktop windows are closed --- scripts/system/edit.js | 8 ++++++++ scripts/system/libraries/entityList.js | 5 +++++ scripts/system/modules/createWindow.js | 16 +++++++++++++++- 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/scripts/system/edit.js b/scripts/system/edit.js index fe790480f6..43d95b108b 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -806,6 +806,14 @@ var toolBar = (function () { addButton("newMaterialButton", createNewEntityDialogButtonCallback("Material")); + var deactiveCreateIfDesktopWindowsHidden = function() { + if (!shouldUseEditTabletApp() && !entityListTool.isVisible() && !createToolsWindow.isVisible()) { + that.setActive(false); + } + }; + entityListTool.interactiveWindowHidden.addListener(this, deactiveCreateIfDesktopWindowsHidden); + createToolsWindow.interactiveWindowHidden.addListener(this, deactiveCreateIfDesktopWindowsHidden); + that.setActive(false); } diff --git a/scripts/system/libraries/entityList.js b/scripts/system/libraries/entityList.js index fb876302dd..678b2eeb0b 100644 --- a/scripts/system/libraries/entityList.js +++ b/scripts/system/libraries/entityList.js @@ -59,6 +59,10 @@ EntityListTool = function(shouldUseEditTabletApp) { entityListWindow.setVisible(!shouldUseEditTabletApp() && visible); }; + that.isVisible = function() { + return entityListWindow.isVisible(); + }; + that.setVisible(false); function emitJSONScriptEvent(data) { @@ -244,6 +248,7 @@ EntityListTool = function(shouldUseEditTabletApp) { webView.webEventReceived.connect(onWebEventReceived); entityListWindow.webEventReceived.addListener(onWebEventReceived); + that.interactiveWindowHidden = entityListWindow.interactiveWindowHidden; return that; }; diff --git a/scripts/system/modules/createWindow.js b/scripts/system/modules/createWindow.js index 185991d2ef..7369cf91f8 100644 --- a/scripts/system/modules/createWindow.js +++ b/scripts/system/modules/createWindow.js @@ -75,6 +75,7 @@ module.exports = (function() { this.settingsKey = settingsKey; this.defaultRect = defaultRect; this.webEventReceived = new CallableEvent(); + this.interactiveWindowHidden = new CallableEvent(); this.fromQml = new CallableEvent(); if (createOnStartup) { this.createWindow(); @@ -108,10 +109,16 @@ module.exports = (function() { this.window.sizeChanged.connect(this, windowRectChanged); this.window.positionChanged.connect(this, windowRectChanged); - this.window.webEventReceived.connect(this, function (data) { + this.window.webEventReceived.connect(this, function(data) { this.webEventReceived.call(data); }); + this.window.visibleChanged.connect(this, function() { + if (!this.window.visible) { + this.interactiveWindowHidden.call(); + } + }); + this.window.fromQml.connect(this, function (data) { this.fromQml.call(data); }); @@ -133,6 +140,12 @@ module.exports = (function() { } } }, + isVisible: function() { + if (this.window) { + return this.window.visible; + } + return false; + }, emitScriptEvent: function(data) { if (this.window) { this.window.emitScriptEvent(data); @@ -144,6 +157,7 @@ module.exports = (function() { } }, webEventReceived: null, + interactiveWindowHidden: null, fromQml: null }; From dd93055d89d83237f02126c7b5739cc8d9d936d5 Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Fri, 10 Aug 2018 16:15:30 -0700 Subject: [PATCH 104/144] Convert and load attachments when rig is ready --- interface/src/avatar/MyAvatar.cpp | 14 +++++++++----- libraries/avatars/src/AvatarData.h | 1 + 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 7067c1c791..837f67d9af 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -139,6 +139,12 @@ MyAvatar::MyAvatar(QThread* thread) : auto geometry = getSkeletonModel()->getFBXGeometry(); qApp->loadAvatarScripts(geometry.scripts); _shouldLoadScripts = false; + } + // Load and convert old attachments to avatar entities + if (_oldAttachmentData.size() > 0) { + setAttachmentData(_oldAttachmentData); + _oldAttachmentData.clear(); + _attachmentData.clear(); } }); connect(_skeletonModel.get(), &Model::rigReady, this, &Avatar::rigReady); @@ -1249,7 +1255,6 @@ void MyAvatar::loadData() { useFullAvatarURL(_fullAvatarURLFromPreferences, _fullAvatarModelName); - QVector attachmentData; int attachmentCount = settings.beginReadArray("attachmentData"); for (int i = 0; i < attachmentCount; i++) { settings.setArrayIndex(i); @@ -1266,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++) { @@ -2107,8 +2112,7 @@ void MyAvatar::attachmentDataToEntityProperties(const AttachmentData& data, Enti QString url = data.modelURL.toString(); properties.setName(QFileInfo(url).baseName()); properties.setType(EntityTypes::Model); - properties.setParentID(getID()); - properties.setOwningAvatarID(getID()); + properties.setParentID(AVATAR_SELF_ID); properties.setLocalPosition(data.translation); properties.setLocalRotation(data.rotation); if (!data.isSoft) { diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 147d303871..e873f88aaa 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -1317,6 +1317,7 @@ protected: bool _firstSkeletonCheck { true }; QUrl _skeletonFBXURL; QVector _attachmentData; + QVector _oldAttachmentData; QString _displayName; QString _sessionDisplayName { }; bool _lookAtSnappingEnabled { true }; From f26188d5b8e193470d5e2ef904c1323852469075 Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Sat, 11 Aug 2018 01:54:13 +0200 Subject: [PATCH 105/144] added comment for limit choice --- scripts/system/libraries/entityCameraTool.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/system/libraries/entityCameraTool.js b/scripts/system/libraries/entityCameraTool.js index 01030c9d7d..ebaeb1acb5 100644 --- a/scripts/system/libraries/entityCameraTool.js +++ b/scripts/system/libraries/entityCameraTool.js @@ -28,6 +28,8 @@ var FOCUS_MIN_ZOOM = 0.5; var ZOOM_SCALING = 0.02; var MIN_ZOOM_DISTANCE = 0.01; + +// The maximum usable zoom level is somewhere around 14km, further than that the edit handles will fade-out. (FIXME: MS17493) var MAX_ZOOM_DISTANCE = 14000; var MODE_INACTIVE = 'inactive'; From 221dfb8979ae7acfc3e35bf5ffe0c36db9f962f7 Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Sat, 11 Aug 2018 05:46:48 +0200 Subject: [PATCH 106/144] typo fix --- scripts/system/edit.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/system/edit.js b/scripts/system/edit.js index 43d95b108b..c4c5561236 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -806,13 +806,13 @@ var toolBar = (function () { addButton("newMaterialButton", createNewEntityDialogButtonCallback("Material")); - var deactiveCreateIfDesktopWindowsHidden = function() { + var deactivateCreateIfDesktopWindowsHidden = function() { if (!shouldUseEditTabletApp() && !entityListTool.isVisible() && !createToolsWindow.isVisible()) { that.setActive(false); } }; - entityListTool.interactiveWindowHidden.addListener(this, deactiveCreateIfDesktopWindowsHidden); - createToolsWindow.interactiveWindowHidden.addListener(this, deactiveCreateIfDesktopWindowsHidden); + entityListTool.interactiveWindowHidden.addListener(this, deactivateCreateIfDesktopWindowsHidden); + createToolsWindow.interactiveWindowHidden.addListener(this, deactivateCreateIfDesktopWindowsHidden); that.setActive(false); } From 8a22d0adc7debeb6bd6611c61b1ffb3709241eb8 Mon Sep 17 00:00:00 2001 From: unknown Date: Sat, 11 Aug 2018 11:04:44 -0700 Subject: [PATCH 107/144] set grabbable user data in addAssetToWorldCheckModelSize --- interface/src/Application.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index d8a48d51c0..d0b6a604ee 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -7371,7 +7371,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); } From 86ba8c3d810a44da48667ea20c04375d7be831f6 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Mon, 13 Aug 2018 10:08:19 -0700 Subject: [PATCH 108/144] fix parabola shader --- libraries/render-utils/src/parabola.slv | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/render-utils/src/parabola.slv b/libraries/render-utils/src/parabola.slv index 0948b2ab47..31b3ab8fae 100644 --- a/libraries/render-utils/src/parabola.slv +++ b/libraries/render-utils/src/parabola.slv @@ -20,7 +20,7 @@ struct ParabolaData { vec4 color; int numSections; ivec3 spare; -} +}; layout(std140, binding=0) uniform parabolaData { ParabolaData _parabolaData; From b381a182bd66d0ee8c4d22dcbdf3042f0975e262 Mon Sep 17 00:00:00 2001 From: Cristian Luis Duarte Date: Mon, 13 Aug 2018 14:31:20 -0300 Subject: [PATCH 109/144] Android - People - add padding to list --- android/app/src/main/res/layout/fragment_friends.xml | 3 +++ android/app/src/main/res/values/dimens.xml | 2 ++ 2 files changed, 5 insertions(+) diff --git a/android/app/src/main/res/layout/fragment_friends.xml b/android/app/src/main/res/layout/fragment_friends.xml index 8129f5d53c..4e4abb7f28 100644 --- a/android/app/src/main/res/layout/fragment_friends.xml +++ b/android/app/src/main/res/layout/fragment_friends.xml @@ -12,6 +12,9 @@ app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" + android:paddingTop="@dimen/list_vertical_padding" + android:paddingBottom="@dimen/list_vertical_padding" + android:clipToPadding="false" android:layout_width="0dp" android:layout_height="0dp" /> diff --git a/android/app/src/main/res/values/dimens.xml b/android/app/src/main/res/values/dimens.xml index bb5be1c070..d40132939b 100644 --- a/android/app/src/main/res/values/dimens.xml +++ b/android/app/src/main/res/values/dimens.xml @@ -37,4 +37,6 @@ 101dp 425dp + 8dp + From 4fa0c96046f63758d09734631b6b25a4f946eeb5 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Mon, 13 Aug 2018 11:57:48 -0700 Subject: [PATCH 110/144] fix desktop press-x handshake --- scripts/system/makeUserConnection.js | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/system/makeUserConnection.js b/scripts/system/makeUserConnection.js index 8642fc5ce6..5dee36d147 100644 --- a/scripts/system/makeUserConnection.js +++ b/scripts/system/makeUserConnection.js @@ -228,6 +228,7 @@ animationData.rightHandPosition.y += verticalOffset; } animationData.rightHandRotation = Quat.fromPitchYawRollDegrees(90, 0, 90); + animationData.rightHandType = 0; // RotationAndPosition, see IKTargets.h } function shakeHandsAnimation() { return animationData; From f910757ec443248b95adddafabc71a4576bb979a Mon Sep 17 00:00:00 2001 From: Alexander Ivash Date: Mon, 13 Aug 2018 22:57:07 +0300 Subject: [PATCH 111/144] FB16914 - Avatar bookmarks missing from Avatar Favorites --- interface/src/AvatarBookmarks.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/interface/src/AvatarBookmarks.cpp b/interface/src/AvatarBookmarks.cpp index 3e0e643bd8..89097f3435 100644 --- a/interface/src/AvatarBookmarks.cpp +++ b/interface/src/AvatarBookmarks.cpp @@ -171,6 +171,13 @@ void AvatarBookmarks::loadBookmark(const QString& bookmarkName) { if (bookmarkEntry != _bookmarks.end()) { QVariantMap bookmark = bookmarkEntry.value().toMap(); + if (bookmark.empty()) { // compatibility with bookmarks like this: "Wooden Doll": "http://mpassets.highfidelity.com/7fe80a1e-f445-4800-9e89-40e677b03bee-v1/mannequin.fst?noDownload=false", + auto avatarUrl = bookmarkEntry.value().toString(); + if (!avatarUrl.isEmpty()) { + bookmark.insert(ENTRY_AVATAR_URL, avatarUrl); + } + } + if (!bookmark.empty()) { auto myAvatar = DependencyManager::get()->getMyAvatar(); auto treeRenderer = DependencyManager::get(); From fd9876cb63551c27bf256ebb6da822bcea157c49 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Mon, 13 Aug 2018 13:30:30 -0700 Subject: [PATCH 112/144] don't attempt physics with invalid modelURL --- libraries/entities/src/ModelEntityItem.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/entities/src/ModelEntityItem.cpp b/libraries/entities/src/ModelEntityItem.cpp index cf89a73214..5d5344c9c8 100644 --- a/libraries/entities/src/ModelEntityItem.cpp +++ b/libraries/entities/src/ModelEntityItem.cpp @@ -371,7 +371,7 @@ void ModelEntityItem::setAnimationFPS(float value) { // virtual bool ModelEntityItem::shouldBePhysical() const { - return !isDead() && getShapeType() != SHAPE_TYPE_NONE; + return !isDead() && getShapeType() != SHAPE_TYPE_NONE && QUrl(_modelURL).isValid(); } void ModelEntityItem::resizeJointArrays(int newSize) { From 4b808fe38ef1c96af00b1c3bcd18fafaee263682 Mon Sep 17 00:00:00 2001 From: Gabriel Calero Date: Mon, 13 Aug 2018 18:29:05 -0300 Subject: [PATCH 113/144] Revert "Test if this fixes the choppy audio" This reverts commit 2e323efe1b0858c263aa01300ff8196e3f2f4291. --- libraries/audio-client/src/AudioClient.cpp | 91 +--------------------- libraries/audio-client/src/AudioClient.h | 12 --- 2 files changed, 3 insertions(+), 100 deletions(-) diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index 52ad6751e7..6ac30e7f73 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -57,11 +57,6 @@ #include #endif -Q_DECLARE_METATYPE(const void*); -Q_DECLARE_METATYPE(quint16); -Q_DECLARE_METATYPE(Transform); - - const int AudioClient::MIN_BUFFER_FRAMES = 1; const int AudioClient::MAX_BUFFER_FRAMES = 20; @@ -213,18 +208,10 @@ AudioClient::AudioClient() : _audioOutputIODevice(_localInjectorsStream, _receivedAudioStream, this), _stats(&_receivedAudioStream), _positionGetter(DEFAULT_POSITION_GETTER), - _audioSender(new AudioSender()), #if defined(Q_OS_ANDROID) _checkInputTimer(this), #endif _orientationGetter(DEFAULT_ORIENTATION_GETTER) { - - qRegisterMetaType("const void*"); - qRegisterMetaType("quint16"); - qRegisterMetaType("Transform"); - - _audioSender->start(); - // avoid putting a lock in the device callback assert(_localSamplesAvailable.is_lock_free()); @@ -1125,21 +1112,11 @@ void AudioClient::handleAudioInput(QByteArray& audioBuffer) { } else { encodedBuffer = audioBuffer; } - QMetaObject::invokeMethod(_audioSender, "emitAudioPacket", Qt::QueuedConnection, - Q_ARG(const void*, encodedBuffer.data()), -Q_ARG(size_t, encodedBuffer.size()), -Q_ARG(quint16, _outgoingAvatarAudioSequenceNumber++ ), - Q_ARG(bool,_isStereoInput), - Q_ARG(Transform, audioTransform), - Q_ARG(glm::vec3, avatarBoundingBoxCorner), - Q_ARG(glm::vec3, avatarBoundingBoxScale), - Q_ARG(PacketType, packetType), - Q_ARG(QString, _selectedCodecName)); - /* emitAudioPacket(encodedBuffer.data(), encodedBuffer.size(), _outgoingAvatarAudioSequenceNumber, _isStereoInput, + emitAudioPacket(encodedBuffer.data(), encodedBuffer.size(), _outgoingAvatarAudioSequenceNumber, _isStereoInput, audioTransform, avatarBoundingBoxCorner, avatarBoundingBoxScale, - packetType, _selectedCodecName); */ - //_stats.sentPacket(); + packetType, _selectedCodecName); + _stats.sentPacket(); } void AudioClient::handleMicAudioInput() { @@ -2074,65 +2051,3 @@ void AudioClient::setInputVolume(float volume, bool emitSignal) { } } } - -AudioSender::AudioSender() { - -} - -void AudioSender::start() { - moveToNewNamedThread(this, "Audio Sender Thread", [this] { }, QThread::HighestPriority); -} - - -void AudioSender::emitAudioPacket(const void* audioData, size_t bytes, quint16 sequenceNumber, bool isStereo, - const Transform& transform, glm::vec3 avatarBoundingBoxCorner, glm::vec3 avatarBoundingBoxScale, - PacketType packetType, QString codecName) { - static std::mutex _mutex; - using Locker = std::unique_lock; - auto nodeList = DependencyManager::get(); - SharedNodePointer audioMixer = nodeList->soloNodeOfType(NodeType::AudioMixer); - if (audioMixer && audioMixer->getActiveSocket()) { - Locker lock(_mutex); - auto audioPacket = NLPacket::create(packetType); - - // write sequence number - auto sequence = sequenceNumber; - audioPacket->writePrimitive(sequence); - - // write the codec - audioPacket->writeString(codecName); - - if (packetType == PacketType::SilentAudioFrame) { - // pack num silent samples - quint16 numSilentSamples = isStereo ? - AudioConstants::NETWORK_FRAME_SAMPLES_STEREO : - AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL; - audioPacket->writePrimitive(numSilentSamples); - } else { - // set the mono/stereo byte - quint8 channelFlag = isStereo ? 1 : 0; - audioPacket->writePrimitive(channelFlag); - } - - // at this point we'd better be sending the mixer a valid position, or it won't consider us for mixing - assert(!isNaN(transform.getTranslation())); - - // pack the three float positions - audioPacket->writePrimitive(transform.getTranslation()); - // pack the orientation - audioPacket->writePrimitive(transform.getRotation()); - - audioPacket->writePrimitive(avatarBoundingBoxCorner); - audioPacket->writePrimitive(avatarBoundingBoxScale); - - - if (audioPacket->getType() != PacketType::SilentAudioFrame) { - // audio samples have already been packed (written to networkAudioSamples) - int leadingBytes = audioPacket->getPayloadSize(); - audioPacket->setPayloadSize(leadingBytes + bytes); - memcpy(audioPacket->getPayload() + leadingBytes, audioData, bytes); - } - nodeList->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::SendAudioPacket); - nodeList->sendUnreliablePacket(*audioPacket, *audioMixer); - } -} \ No newline at end of file diff --git a/libraries/audio-client/src/AudioClient.h b/libraries/audio-client/src/AudioClient.h index c9ef2e5c2c..9ee7bcfeba 100644 --- a/libraries/audio-client/src/AudioClient.h +++ b/libraries/audio-client/src/AudioClient.h @@ -71,17 +71,6 @@ class QIODevice; class Transform; class NLPacket; -class AudioSender : public QObject { - Q_OBJECT -public: - AudioSender(); - void start(); -public Q_SLOTS: - void emitAudioPacket(const void* audioData, size_t bytes, quint16 sequenceNumber, bool isStereo, - const Transform& transform, glm::vec3 avatarBoundingBoxCorner, glm::vec3 avatarBoundingBoxScale, - PacketType packetType, QString codecName = QString("")); -}; - class AudioClient : public AbstractAudioInterface, public Dependency { Q_OBJECT SINGLETON_DEPENDENCY @@ -272,7 +261,6 @@ protected: virtual void customDeleter() override; private: - AudioSender *_audioSender; friend class CheckDevicesThread; friend class LocalInjectorsThread; From 1bfd6fe97dd1e73999f4db9995c1cc4958f14f3b Mon Sep 17 00:00:00 2001 From: Cristian Luis Duarte Date: Mon, 13 Aug 2018 20:32:45 -0300 Subject: [PATCH 114/144] Android - People - Sliding panel (for users actions) --- android/app/build.gradle | 2 + .../hifiinterface/MainActivity.java | 12 +++++- .../fragment/FriendsFragment.java | 39 +++++++++++++++++++ .../hifiinterface/view/UserListAdapter.java | 19 ++++++++- .../src/main/res/layout/fragment_friends.xml | 30 ++++++++------ android/app/src/main/res/values/strings.xml | 3 ++ android/build.gradle | 1 + 7 files changed, 93 insertions(+), 13 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index d5058a7f40..a32ab20f6c 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -144,5 +144,7 @@ dependencies { compile 'com.squareup.retrofit2:converter-gson:2.4.0' implementation 'com.squareup.picasso:picasso:2.71828' + compile 'com.sothree.slidinguppanel:library:3.4.0' + implementation fileTree(include: ['*.jar'], dir: 'libs') } diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/MainActivity.java b/android/app/src/main/java/io/highfidelity/hifiinterface/MainActivity.java index 220a69381d..d259e18ee7 100644 --- a/android/app/src/main/java/io/highfidelity/hifiinterface/MainActivity.java +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/MainActivity.java @@ -150,7 +150,8 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On private void loadFragment(Fragment fragment, String title, boolean addToBackStack) { FragmentManager fragmentManager = getFragmentManager(); FragmentTransaction ft = fragmentManager.beginTransaction(); - ft.replace(R.id.content_frame, fragment); + ft.replace(R.id.content_frame, fragment, getString(R.string.tagFragmentPeople)); + if (addToBackStack) { ft.addToBackStack(title); } @@ -297,6 +298,15 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On @Override public void onBackPressed() { + // if a fragment needs to internally manage back presses.. + FragmentManager fm = getFragmentManager(); + Fragment friendsFragment = fm.findFragmentByTag(getString(R.string.tagFragmentPeople)); + if (friendsFragment != null && friendsFragment instanceof FriendsFragment) { + if (((FriendsFragment) friendsFragment).onBackPressed()) { + return; + } + } + int index = getFragmentManager().getBackStackEntryCount() - 1; if (index > 0) { super.onBackPressed(); diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/FriendsFragment.java b/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/FriendsFragment.java index e70ecfbc57..2cd80bcca9 100644 --- a/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/FriendsFragment.java +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/FriendsFragment.java @@ -10,6 +10,8 @@ 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.view.UserListAdapter; @@ -20,7 +22,9 @@ public class FriendsFragment extends Fragment { public native String nativeGetAccessToken(); private RecyclerView mUsersView; + private View mUserActions; private UserListAdapter mUsersAdapter; + private SlidingUpPanelLayout mSlidingUpPanelLayout; public FriendsFragment() { // Required empty public constructor @@ -45,9 +49,44 @@ public class FriendsFragment extends Fragment { GridLayoutManager gridLayoutMgr = new GridLayoutManager(getContext(), numberOfColumns); mUsersView.setLayoutManager(gridLayoutMgr); mUsersAdapter = new UserListAdapter(getContext(), accessToken); + + mUserActions = rootView.findViewById(R.id.userActionsLayout); + + mSlidingUpPanelLayout = rootView.findViewById(R.id.sliding_layout); + mSlidingUpPanelLayout.setPanelHeight(0); + mUsersAdapter.setClickListener(new UserListAdapter.ItemClickListener() { + @Override + public void onItemClick(View view, int position, UserListAdapter.User user) { + // 1. 'select' user + // .. + // 2. adapt options + // .. + // 3. show + mSlidingUpPanelLayout.setPanelState(SlidingUpPanelLayout.PanelState.EXPANDED); + } + }); mUsersView.setAdapter(mUsersAdapter); + mSlidingUpPanelLayout.setFadeOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + mSlidingUpPanelLayout.setPanelState(SlidingUpPanelLayout.PanelState.COLLAPSED); + } + }); + return rootView; } + /** + * 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); + return true; + } else { + return false; + } + } } diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/view/UserListAdapter.java b/android/app/src/main/java/io/highfidelity/hifiinterface/view/UserListAdapter.java index 202fd82c8b..ad3a5cc136 100644 --- a/android/app/src/main/java/io/highfidelity/hifiinterface/view/UserListAdapter.java +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/view/UserListAdapter.java @@ -34,6 +34,7 @@ public class UserListAdapter extends RecyclerView.Adapter mUsers = new ArrayList<>(); + private ItemClickListener mClickListener; public UserListAdapter(Context c, String accessToken) { mContext = c; @@ -99,7 +100,7 @@ public class UserListAdapter extends RecyclerView.Adapter - + android:gravity="bottom" + sothree:umanoShadowHeight="4dp" + android:background="@color/backgroundLight"> + + android:layout_width="match_parent" + android:layout_height="match_parent" /> - + + + + diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml index d5da2f857b..ea4e59a35a 100644 --- a/android/app/src/main/res/values/strings.xml +++ b/android/app/src/main/res/values/strings.xml @@ -24,4 +24,7 @@ Privacy Policy Your Last Location Online + + + tagFragmentPeople diff --git a/android/build.gradle b/android/build.gradle index bc39c30472..6ecdd34542 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -28,6 +28,7 @@ allprojects { repositories { jcenter() google() + mavenCentral() } } From 0fcf1517a543e19446882ca42b4d2427c04198b8 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Fri, 10 Aug 2018 14:57:00 -0700 Subject: [PATCH 115/144] Fix TextureCache not correctly choosing gl41 when gl45 is disabled --- libraries/model-networking/CMakeLists.txt | 2 +- .../src/model-networking/TextureCache.cpp | 31 +++++++++++++------ 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/libraries/model-networking/CMakeLists.txt b/libraries/model-networking/CMakeLists.txt index 696f4feb9a..9a4bc780a6 100644 --- a/libraries/model-networking/CMakeLists.txt +++ b/libraries/model-networking/CMakeLists.txt @@ -1,4 +1,4 @@ set(TARGET_NAME model-networking) setup_hifi_library() -link_hifi_libraries(shared networking graphics fbx ktx image) +link_hifi_libraries(shared networking graphics fbx ktx image gl) include_hifi_library_headers(gpu) diff --git a/libraries/model-networking/src/model-networking/TextureCache.cpp b/libraries/model-networking/src/model-networking/TextureCache.cpp index aa00116200..e8aec5e60e 100644 --- a/libraries/model-networking/src/model-networking/TextureCache.cpp +++ b/libraries/model-networking/src/model-networking/TextureCache.cpp @@ -31,6 +31,7 @@ #include #include +#include #include #include @@ -271,6 +272,20 @@ gpu::TexturePointer getFallbackTextureForType(image::TextureUsage::Type type) { return result; } +gpu::BackendTarget getBackendTarget() { +#if defined(USE_GLES) + gpu::BackendTarget target = gpu::BackendTarget::GLES32; +#elif defined(Q_OS_MAC) + gpu::BackendTarget target = gpu::BackendTarget::GL41; +#else + gpu::BackendTarget target = gpu::BackendTarget::GL45; + if (gl::disableGl45()) { + target = gpu::BackendTarget::GL41; + } +#endif + return target; +} + /// Returns a texture version of an image file gpu::TexturePointer TextureCache::getImageTexture(const QString& path, image::TextureUsage::Type type, QVariantMap options) { QImage image = QImage(path); @@ -279,13 +294,14 @@ gpu::TexturePointer TextureCache::getImageTexture(const QString& path, image::Te return nullptr; } auto loader = image::TextureUsage::getTextureLoaderForType(type, options); + #ifdef USE_GLES - gpu::BackendTarget target = gpu::BackendTarget::GLES32; - bool shouldCompress = true; + constexpr bool shouldCompress = true; #else - gpu::BackendTarget target = gpu::BackendTarget::GL45; - bool shouldCompress = false; + constexpr bool shouldCompress = false; #endif + auto target = getBackendTarget(); + return gpu::TexturePointer(loader(std::move(image), path.toStdString(), shouldCompress, target, false)); } @@ -1170,15 +1186,10 @@ void ImageReader::read() { #ifdef USE_GLES constexpr bool shouldCompress = true; - gpu::BackendTarget target = gpu::BackendTarget::GLES32; #else constexpr bool shouldCompress = false; -#ifdef Q_OS_MAC - gpu::BackendTarget target = gpu::BackendTarget::GL41; -#else - gpu::BackendTarget target = gpu::BackendTarget::GL45; -#endif #endif + auto target = getBackendTarget(); texture = image::processImage(std::move(buffer), _url.toString().toStdString(), _maxNumPixels, networkTexture->getTextureType(), shouldCompress, target); if (!texture) { From 2576d502ab1426073e20e85b5bc32e0a4cce70bc Mon Sep 17 00:00:00 2001 From: sabrina-shanman Date: Mon, 13 Aug 2018 17:27:57 -0700 Subject: [PATCH 116/144] Remove unused getter for btCollisionWorld in PhysicsEngine --- libraries/physics/src/PhysicsEngine.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/libraries/physics/src/PhysicsEngine.h b/libraries/physics/src/PhysicsEngine.h index 91e4cd4578..c5ab0cfdee 100644 --- a/libraries/physics/src/PhysicsEngine.h +++ b/libraries/physics/src/PhysicsEngine.h @@ -95,8 +95,6 @@ public: const VectorOfMotionStates& getChangedMotionStates(); const VectorOfMotionStates& getDeactivatedMotionStates() const { return _dynamicsWorld->getDeactivatedMotionStates(); } - const ThreadSafeDynamicsWorld* getDynamicsWorld() { return _dynamicsWorld; } - /// \return reference to list of Collision events. The list is only valid until beginning of next simulation loop. const CollisionEvents& getCollisionEvents(); From 36e9c8ba3bceceb4995cd003cf34a3f61d5f80f7 Mon Sep 17 00:00:00 2001 From: Cristian Luis Duarte Date: Tue, 14 Aug 2018 12:44:09 -0300 Subject: [PATCH 117/144] Android - People - UI changes to match design spec --- .../res/drawable/ic_delete_black_24dp.xml | 9 +++ .../app/src/main/res/drawable/ic_visit.xml | 9 +++ .../src/main/res/layout/fragment_friends.xml | 64 +++++++++++++++++-- android/app/src/main/res/layout/user_item.xml | 6 +- 4 files changed, 82 insertions(+), 6 deletions(-) create mode 100644 android/app/src/main/res/drawable/ic_delete_black_24dp.xml create mode 100644 android/app/src/main/res/drawable/ic_visit.xml diff --git a/android/app/src/main/res/drawable/ic_delete_black_24dp.xml b/android/app/src/main/res/drawable/ic_delete_black_24dp.xml new file mode 100644 index 0000000000..39e64d6980 --- /dev/null +++ b/android/app/src/main/res/drawable/ic_delete_black_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/android/app/src/main/res/drawable/ic_visit.xml b/android/app/src/main/res/drawable/ic_visit.xml new file mode 100644 index 0000000000..0ecbf6838c --- /dev/null +++ b/android/app/src/main/res/drawable/ic_visit.xml @@ -0,0 +1,9 @@ + + + diff --git a/android/app/src/main/res/layout/fragment_friends.xml b/android/app/src/main/res/layout/fragment_friends.xml index f0f83089fc..3c8e0af6bc 100644 --- a/android/app/src/main/res/layout/fragment_friends.xml +++ b/android/app/src/main/res/layout/fragment_friends.xml @@ -1,12 +1,13 @@ - - + + + + + + + + diff --git a/android/app/src/main/res/layout/user_item.xml b/android/app/src/main/res/layout/user_item.xml index dec9f04af8..acdc6a8aea 100644 --- a/android/app/src/main/res/layout/user_item.xml +++ b/android/app/src/main/res/layout/user_item.xml @@ -26,7 +26,8 @@ android:id="@+id/userName" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:fontFamily="@font/raleway" /> + android:fontFamily="@font/raleway" + android:textColor="@color/menuOption"/> @@ -41,7 +42,8 @@ android:id="@+id/userLocation" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:fontFamily="@font/raleway_italic"/> + android:fontFamily="@font/raleway_italic" + android:textColor="@color/menuOption"/> Date: Tue, 7 Aug 2018 16:03:35 -0700 Subject: [PATCH 118/144] pseudo-joints for far-grab positions --- interface/src/avatar/MyAvatar.cpp | 133 +++- .../src/avatars-renderer/Avatar.cpp | 18 + libraries/avatars/src/AvatarData.cpp | 203 +++++- libraries/avatars/src/AvatarData.h | 27 +- .../networking/src/udt/PacketHeaders.cpp | 2 +- libraries/networking/src/udt/PacketHeaders.h | 3 +- libraries/shared/src/ThreadSafeValueCache.h | 15 + .../controllers/controllerDispatcher.js | 3 +- .../farActionGrabEntityDynOnly.js | 603 ++++++++++++++++ .../controllerModules/farParentGrabEntity.js | 646 ++++++++++++++++++ .../system/controllers/controllerScripts.js | 10 +- .../libraries/controllerDispatcherUtils.js | 40 +- scripts/system/libraries/pointersUtils.js | 11 +- 13 files changed, 1633 insertions(+), 81 deletions(-) create mode 100644 scripts/system/controllers/controllerModules/farActionGrabEntityDynOnly.js create mode 100644 scripts/system/controllers/controllerModules/farParentGrabEntity.js diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 5eee3f75cc..de0768b7ad 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -1499,50 +1499,126 @@ void MyAvatar::setJointRotations(const QVector& jointRotations) { } void MyAvatar::setJointData(int index, const glm::quat& rotation, const glm::vec3& translation) { - if (QThread::currentThread() != thread()) { - QMetaObject::invokeMethod(this, "setJointData", Q_ARG(int, index), Q_ARG(const glm::quat&, rotation), - Q_ARG(const glm::vec3&, translation)); - return; + switch (index) { + case FARGRAB_RIGHTHAND_INDEX: { + _farGrabRightMatrixCache.set(createMatFromQuatAndPos(rotation, translation)); + break; + } + case FARGRAB_LEFTHAND_INDEX: { + _farGrabLeftMatrixCache.set(createMatFromQuatAndPos(rotation, translation)); + break; + } + case FARGRAB_MOUSE_INDEX: { + _farGrabMouseMatrixCache.set(createMatFromQuatAndPos(rotation, translation)); + break; + } + default: { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "setJointData", Q_ARG(int, index), Q_ARG(const glm::quat&, rotation), + Q_ARG(const glm::vec3&, translation)); + return; + } + // HACK: ATM only JS scripts call setJointData() on MyAvatar so we hardcode the priority + _skeletonModel->getRig().setJointState(index, true, rotation, translation, SCRIPT_PRIORITY); + } } - // HACK: ATM only JS scripts call setJointData() on MyAvatar so we hardcode the priority - _skeletonModel->getRig().setJointState(index, true, rotation, translation, SCRIPT_PRIORITY); } void MyAvatar::setJointRotation(int index, const glm::quat& rotation) { - if (QThread::currentThread() != thread()) { - QMetaObject::invokeMethod(this, "setJointRotation", Q_ARG(int, index), Q_ARG(const glm::quat&, rotation)); - return; + switch (index) { + case FARGRAB_RIGHTHAND_INDEX: { + glm::mat4 prevMat = _farGrabRightMatrixCache.get(); + glm::vec3 previousTranslation = extractTranslation(prevMat); + _farGrabRightMatrixCache.set(createMatFromQuatAndPos(rotation, previousTranslation)); + break; + } + case FARGRAB_LEFTHAND_INDEX: { + glm::mat4 prevMat = _farGrabLeftMatrixCache.get(); + glm::vec3 previousTranslation = extractTranslation(prevMat); + _farGrabLeftMatrixCache.set(createMatFromQuatAndPos(rotation, previousTranslation)); + break; + } + case FARGRAB_MOUSE_INDEX: { + glm::mat4 prevMat = _farGrabMouseMatrixCache.get(); + glm::vec3 previousTranslation = extractTranslation(prevMat); + _farGrabMouseMatrixCache.set(createMatFromQuatAndPos(rotation, previousTranslation)); + break; + } + default: { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "setJointRotation", Q_ARG(int, index), Q_ARG(const glm::quat&, rotation)); + return; + } + // HACK: ATM only JS scripts call setJointData() on MyAvatar so we hardcode the priority + _skeletonModel->getRig().setJointRotation(index, true, rotation, SCRIPT_PRIORITY); + } } - // HACK: ATM only JS scripts call setJointData() on MyAvatar so we hardcode the priority - _skeletonModel->getRig().setJointRotation(index, true, rotation, SCRIPT_PRIORITY); } void MyAvatar::setJointTranslation(int index, const glm::vec3& translation) { - if (QThread::currentThread() != thread()) { - QMetaObject::invokeMethod(this, "setJointTranslation", Q_ARG(int, index), Q_ARG(const glm::vec3&, translation)); - return; + switch (index) { + case FARGRAB_RIGHTHAND_INDEX: { + glm::mat4 prevMat = _farGrabRightMatrixCache.get(); + glm::quat previousRotation = extractRotation(prevMat); + _farGrabRightMatrixCache.set(createMatFromQuatAndPos(previousRotation, translation)); + break; + } + case FARGRAB_LEFTHAND_INDEX: { + glm::mat4 prevMat = _farGrabLeftMatrixCache.get(); + glm::quat previousRotation = extractRotation(prevMat); + _farGrabLeftMatrixCache.set(createMatFromQuatAndPos(previousRotation, translation)); + break; + } + case FARGRAB_MOUSE_INDEX: { + glm::mat4 prevMat = _farGrabMouseMatrixCache.get(); + glm::quat previousRotation = extractRotation(prevMat); + _farGrabMouseMatrixCache.set(createMatFromQuatAndPos(previousRotation, translation)); + break; + } + default: { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "setJointTranslation", + Q_ARG(int, index), Q_ARG(const glm::vec3&, translation)); + return; + } + // HACK: ATM only JS scripts call setJointData() on MyAvatar so we hardcode the priority + _skeletonModel->getRig().setJointTranslation(index, true, translation, SCRIPT_PRIORITY); + } } - // HACK: ATM only JS scripts call setJointData() on MyAvatar so we hardcode the priority - _skeletonModel->getRig().setJointTranslation(index, true, translation, SCRIPT_PRIORITY); } void MyAvatar::clearJointData(int index) { - if (QThread::currentThread() != thread()) { - QMetaObject::invokeMethod(this, "clearJointData", Q_ARG(int, index)); - return; + switch (index) { + case FARGRAB_RIGHTHAND_INDEX: { + _farGrabRightMatrixCache.invalidate(); + break; + } + case FARGRAB_LEFTHAND_INDEX: { + _farGrabLeftMatrixCache.invalidate(); + break; + } + case FARGRAB_MOUSE_INDEX: { + _farGrabMouseMatrixCache.invalidate(); + break; + } + default: { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "clearJointData", Q_ARG(int, index)); + return; + } + _skeletonModel->getRig().clearJointAnimationPriority(index); + } } - _skeletonModel->getRig().clearJointAnimationPriority(index); } void MyAvatar::setJointData(const QString& name, const glm::quat& rotation, const glm::vec3& translation) { if (QThread::currentThread() != thread()) { QMetaObject::invokeMethod(this, "setJointData", Q_ARG(QString, name), Q_ARG(const glm::quat&, rotation), - Q_ARG(const glm::vec3&, translation)); + Q_ARG(const glm::vec3&, translation)); return; } writeLockWithNamedJointIndex(name, [&](int index) { - // HACK: ATM only JS scripts call setJointData() on MyAvatar so we hardcode the priority - _skeletonModel->getRig().setJointState(index, true, rotation, translation, SCRIPT_PRIORITY); + setJointData(index, rotation, translation); }); } @@ -1552,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); }); } @@ -1563,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); }); } @@ -1574,7 +1648,7 @@ void MyAvatar::clearJointData(const QString& name) { return; } writeLockWithNamedJointIndex(name, [&](int index) { - _skeletonModel->getRig().clearJointAnimationPriority(index); + clearJointData(index); }); } @@ -1583,6 +1657,9 @@ void MyAvatar::clearJointsData() { QMetaObject::invokeMethod(this, "clearJointsData"); return; } + _farGrabRightMatrixCache.invalidate(); + _farGrabLeftMatrixCache.invalidate(); + _farGrabMouseMatrixCache.invalidate(); _skeletonModel->getRig().clearJointStates(); } diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index 873fc94021..0b43fd5433 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -1184,6 +1184,15 @@ glm::quat Avatar::getAbsoluteJointRotationInObjectFrame(int index) const { } return Quaternions::Y_180 * rotation * Quaternions::Y_180; } + case FARGRAB_RIGHTHAND_INDEX: { + return extractRotation(_farGrabRightMatrixCache.get()); + } + case FARGRAB_LEFTHAND_INDEX: { + return extractRotation(_farGrabLeftMatrixCache.get()); + } + case FARGRAB_MOUSE_INDEX: { + return extractRotation(_farGrabMouseMatrixCache.get()); + } default: { glm::quat rotation; _skeletonModel->getAbsoluteJointRotationInRigFrame(index, rotation); @@ -1224,6 +1233,15 @@ glm::vec3 Avatar::getAbsoluteJointTranslationInObjectFrame(int index) const { } return Quaternions::Y_180 * translation * Quaternions::Y_180; } + case FARGRAB_RIGHTHAND_INDEX: { + return extractTranslation(_farGrabRightMatrixCache.get()); + } + case FARGRAB_LEFTHAND_INDEX: { + return extractTranslation(_farGrabLeftMatrixCache.get()); + } + case FARGRAB_MOUSE_INDEX: { + return extractTranslation(_farGrabMouseMatrixCache.get()); + } default: { glm::vec3 translation; _skeletonModel->getAbsoluteJointTranslationInRigFrame(index, translation); diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index abdac838b6..a2006d3503 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -62,7 +62,7 @@ size_t AvatarDataPacket::maxFaceTrackerInfoSize(size_t numBlendshapeCoefficients return FACE_TRACKER_INFO_SIZE + numBlendshapeCoefficients * sizeof(float); } -size_t AvatarDataPacket::maxJointDataSize(size_t numJoints) { +size_t AvatarDataPacket::maxJointDataSize(size_t numJoints, bool hasGrabJoints) { const size_t validityBitsSize = (size_t)std::ceil(numJoints / (float)BITS_IN_BYTE); size_t totalSize = sizeof(uint8_t); // numJoints @@ -73,7 +73,8 @@ size_t AvatarDataPacket::maxJointDataSize(size_t numJoints) { totalSize += numJoints * sizeof(SixByteTrans); // Translations size_t NUM_FAUX_JOINT = 2; - totalSize += NUM_FAUX_JOINT * (sizeof(SixByteQuat) + sizeof(SixByteTrans)); // faux joints + size_t num_grab_joints = (hasGrabJoints ? 2 : 0); + totalSize += (NUM_FAUX_JOINT + num_grab_joints) * (sizeof(SixByteQuat) + sizeof(SixByteTrans)); // faux joints return totalSize; } @@ -227,7 +228,8 @@ QByteArray AvatarData::toByteArrayStateful(AvatarDataDetail dataDetail, bool dro &_outboundDataRate); } -QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSentTime, const QVector& lastSentJointData, +QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSentTime, + const QVector& lastSentJointData, AvatarDataPacket::HasFlags& hasFlagsOut, bool dropFaceTracking, bool distanceAdjust, glm::vec3 viewerPosition, QVector* sentJointDataOut, AvatarDataRate* outboundDataRateOut) const { @@ -284,6 +286,11 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent bool hasFaceTrackerInfo = false; bool hasJointData = false; bool hasJointDefaultPoseFlags = false; + bool hasGrabJoints = false; + + glm::mat4 leftFarGrabMatrix; + glm::mat4 rightFarGrabMatrix; + glm::mat4 mouseFarGrabMatrix; if (sendPALMinimum) { hasAudioLoudness = true; @@ -304,12 +311,30 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent (sendAll || faceTrackerInfoChangedSince(lastSentTime)); hasJointData = sendAll || !sendMinimum; hasJointDefaultPoseFlags = hasJointData; + if (hasJointData) { + bool leftValid; + leftFarGrabMatrix = _farGrabLeftMatrixCache.get(leftValid); + if (!leftValid) { + leftFarGrabMatrix = glm::mat4(); + } + bool rightValid; + rightFarGrabMatrix = _farGrabRightMatrixCache.get(rightValid); + if (!rightValid) { + rightFarGrabMatrix = glm::mat4(); + } + bool mouseValid; + mouseFarGrabMatrix = _farGrabMouseMatrixCache.get(mouseValid); + if (!mouseValid) { + mouseFarGrabMatrix = glm::mat4(); + } + hasGrabJoints = (leftValid || rightValid || mouseValid); + } } const size_t byteArraySize = AvatarDataPacket::MAX_CONSTANT_HEADER_SIZE + (hasFaceTrackerInfo ? AvatarDataPacket::maxFaceTrackerInfoSize(_headData->getBlendshapeCoefficients().size()) : 0) + - (hasJointData ? AvatarDataPacket::maxJointDataSize(_jointData.size()) : 0) + + (hasJointData ? AvatarDataPacket::maxJointDataSize(_jointData.size(), hasGrabJoints) : 0) + (hasJointDefaultPoseFlags ? AvatarDataPacket::maxJointDefaultPoseFlagsSize(_jointData.size()) : 0); QByteArray avatarDataByteArray((int)byteArraySize, 0); @@ -330,7 +355,8 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent | (hasAvatarLocalPosition ? AvatarDataPacket::PACKET_HAS_AVATAR_LOCAL_POSITION : 0) | (hasFaceTrackerInfo ? AvatarDataPacket::PACKET_HAS_FACE_TRACKER_INFO : 0) | (hasJointData ? AvatarDataPacket::PACKET_HAS_JOINT_DATA : 0) - | (hasJointDefaultPoseFlags ? AvatarDataPacket::PACKET_HAS_JOINT_DEFAULT_POSE_FLAGS : 0); + | (hasJointDefaultPoseFlags ? AvatarDataPacket::PACKET_HAS_JOINT_DEFAULT_POSE_FLAGS : 0) + | (hasGrabJoints ? AvatarDataPacket::PACKET_HAS_GRAB_JOINTS : 0); memcpy(destinationBuffer, &packetStateFlags, sizeof(packetStateFlags)); destinationBuffer += sizeof(packetStateFlags); @@ -668,6 +694,53 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent destinationBuffer += packFloatVec3ToSignedTwoByteFixed(destinationBuffer, controllerRightHandTransform.getTranslation(), TRANSLATION_COMPRESSION_RADIX); + if (hasGrabJoints) { + // the far-grab joints may range further than 3 meters, so we can't use packFloatVec3ToSignedTwoByteFixed etc + auto startSection = destinationBuffer; + auto data = reinterpret_cast(destinationBuffer); + glm::vec3 leftFarGrabPosition = extractTranslation(leftFarGrabMatrix); + glm::quat leftFarGrabRotation = extractRotation(leftFarGrabMatrix); + glm::vec3 rightFarGrabPosition = extractTranslation(rightFarGrabMatrix); + glm::quat rightFarGrabRotation = extractRotation(rightFarGrabMatrix); + glm::vec3 mouseFarGrabPosition = extractTranslation(mouseFarGrabMatrix); + glm::quat mouseFarGrabRotation = extractRotation(mouseFarGrabMatrix); + + data->leftFarGrabPosition[0] = leftFarGrabPosition.x; + data->leftFarGrabPosition[1] = leftFarGrabPosition.y; + data->leftFarGrabPosition[2] = leftFarGrabPosition.z; + + data->leftFarGrabRotation[0] = leftFarGrabRotation.w; + data->leftFarGrabRotation[1] = leftFarGrabRotation.x; + data->leftFarGrabRotation[2] = leftFarGrabRotation.y; + data->leftFarGrabRotation[3] = leftFarGrabRotation.z; + + data->rightFarGrabPosition[0] = rightFarGrabPosition.x; + data->rightFarGrabPosition[1] = rightFarGrabPosition.y; + data->rightFarGrabPosition[2] = rightFarGrabPosition.z; + + data->rightFarGrabRotation[0] = rightFarGrabRotation.w; + data->rightFarGrabRotation[1] = rightFarGrabRotation.x; + data->rightFarGrabRotation[2] = rightFarGrabRotation.y; + data->rightFarGrabRotation[3] = rightFarGrabRotation.z; + + data->mouseFarGrabPosition[0] = mouseFarGrabPosition.x; + data->mouseFarGrabPosition[1] = mouseFarGrabPosition.y; + data->mouseFarGrabPosition[2] = mouseFarGrabPosition.z; + + data->mouseFarGrabRotation[0] = mouseFarGrabRotation.w; + data->mouseFarGrabRotation[1] = mouseFarGrabRotation.x; + data->mouseFarGrabRotation[2] = mouseFarGrabRotation.y; + data->mouseFarGrabRotation[3] = mouseFarGrabRotation.z; + + destinationBuffer += sizeof(AvatarDataPacket::FarGrabJoints); + + int numBytes = destinationBuffer - startSection; + + if (outboundDataRateOut) { + outboundDataRateOut->farGrabJointRate.increment(numBytes); + } + } + #ifdef WANT_DEBUG if (sendAll) { qCDebug(avatars) << "AvatarData::toByteArray" << cullSmallChanges << sendAll @@ -834,6 +907,7 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { bool hasFaceTrackerInfo = HAS_FLAG(packetStateFlags, AvatarDataPacket::PACKET_HAS_FACE_TRACKER_INFO); bool hasJointData = HAS_FLAG(packetStateFlags, AvatarDataPacket::PACKET_HAS_JOINT_DATA); bool hasJointDefaultPoseFlags = HAS_FLAG(packetStateFlags, AvatarDataPacket::PACKET_HAS_JOINT_DEFAULT_POSE_FLAGS); + bool hasGrabJoints = HAS_FLAG(packetStateFlags, AvatarDataPacket::PACKET_HAS_GRAB_JOINTS); quint64 now = usecTimestampNow(); @@ -1195,6 +1269,34 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { int numBytesRead = sourceBuffer - startSection; _jointDataRate.increment(numBytesRead); _jointDataUpdateRate.increment(); + + if (hasGrabJoints) { + auto startSection = sourceBuffer; + + PACKET_READ_CHECK(FarGrabJoints, sizeof(AvatarDataPacket::FarGrabJoints)); + auto data = reinterpret_cast(sourceBuffer); + glm::vec3 leftFarGrabPosition = glm::vec3(data->leftFarGrabPosition[0], data->leftFarGrabPosition[1], + data->leftFarGrabPosition[2]); + glm::quat leftFarGrabRotation = glm::quat(data->leftFarGrabRotation[0], data->leftFarGrabRotation[1], + data->leftFarGrabRotation[2], data->leftFarGrabRotation[3]); + glm::vec3 rightFarGrabPosition = glm::vec3(data->rightFarGrabPosition[0], data->rightFarGrabPosition[1], + data->rightFarGrabPosition[2]); + glm::quat rightFarGrabRotation = glm::quat(data->rightFarGrabRotation[0], data->rightFarGrabRotation[1], + data->rightFarGrabRotation[2], data->rightFarGrabRotation[3]); + glm::vec3 mouseFarGrabPosition = glm::vec3(data->mouseFarGrabPosition[0], data->mouseFarGrabPosition[1], + data->mouseFarGrabPosition[2]); + glm::quat mouseFarGrabRotation = glm::quat(data->mouseFarGrabRotation[0], data->mouseFarGrabRotation[1], + data->mouseFarGrabRotation[2], data->mouseFarGrabRotation[3]); + + _farGrabLeftMatrixCache.set(createMatFromQuatAndPos(leftFarGrabRotation, leftFarGrabPosition)); + _farGrabRightMatrixCache.set(createMatFromQuatAndPos(rightFarGrabRotation, rightFarGrabPosition)); + _farGrabMouseMatrixCache.set(createMatFromQuatAndPos(mouseFarGrabRotation, mouseFarGrabPosition)); + + sourceBuffer += sizeof(AvatarDataPacket::AvatarGlobalPosition); + int numBytesRead = sourceBuffer - startSection; + _farGrabJointRate.increment(numBytesRead); + _farGrabJointUpdateRate.increment(); + } } if (hasJointDefaultPoseFlags) { @@ -1261,6 +1363,8 @@ float AvatarData::getDataRate(const QString& rateName) const { return _jointDataRate.rate() / BYTES_PER_KILOBIT; } else if (rateName == "jointDefaultPoseFlagsRate") { return _jointDefaultPoseFlagsRate.rate() / BYTES_PER_KILOBIT; + } else if (rateName == "farGrabJointRate") { + return _farGrabJointRate.rate() / BYTES_PER_KILOBIT; } else if (rateName == "globalPositionOutbound") { return _outboundDataRate.globalPositionRate.rate() / BYTES_PER_KILOBIT; } else if (rateName == "localPositionOutbound") { @@ -1318,6 +1422,8 @@ float AvatarData::getUpdateRate(const QString& rateName) const { return _faceTrackerUpdateRate.rate(); } else if (rateName == "jointData") { return _jointDataUpdateRate.rate(); + } else if (rateName == "farGrabJointData") { + return _farGrabJointUpdateRate.rate(); } return 0.0f; } @@ -1344,7 +1450,7 @@ void AvatarData::setRawJointData(QVector data) { } void AvatarData::setJointData(int index, const glm::quat& rotation, const glm::vec3& translation) { - if (index == -1) { + if (index < 0 || index >= LOWEST_PSEUDO_JOINT_INDEX) { return; } QWriteLocker writeLock(&_jointDataLock); @@ -1359,7 +1465,7 @@ void AvatarData::setJointData(int index, const glm::quat& rotation, const glm::v } void AvatarData::clearJointData(int index) { - if (index == -1) { + if (index < 0 || index >= LOWEST_PSEUDO_JOINT_INDEX) { return; } QWriteLocker writeLock(&_jointDataLock); @@ -1371,27 +1477,72 @@ void AvatarData::clearJointData(int index) { } bool AvatarData::isJointDataValid(int index) const { - if (index == -1) { - return false; + switch (index) { + case FARGRAB_RIGHTHAND_INDEX: { + bool valid; + _farGrabRightMatrixCache.get(valid); + return valid; + } + case FARGRAB_LEFTHAND_INDEX: { + bool valid; + _farGrabLeftMatrixCache.get(valid); + return valid; + } + case FARGRAB_MOUSE_INDEX: { + bool valid; + _farGrabMouseMatrixCache.get(valid); + return valid; + } + default: { + if (index < 0 || index >= LOWEST_PSEUDO_JOINT_INDEX) { + return false; + } + QReadLocker readLock(&_jointDataLock); + return index < _jointData.size(); + } } - QReadLocker readLock(&_jointDataLock); - return index < _jointData.size(); } glm::quat AvatarData::getJointRotation(int index) const { - if (index == -1) { - return glm::quat(); + switch (index) { + case FARGRAB_RIGHTHAND_INDEX: { + return extractRotation(_farGrabRightMatrixCache.get()); + } + case FARGRAB_LEFTHAND_INDEX: { + return extractRotation(_farGrabLeftMatrixCache.get()); + } + case FARGRAB_MOUSE_INDEX: { + return extractRotation(_farGrabMouseMatrixCache.get()); + } + default: { + if (index < 0 || index >= LOWEST_PSEUDO_JOINT_INDEX) { + return glm::quat(); + } + QReadLocker readLock(&_jointDataLock); + return index < _jointData.size() ? _jointData.at(index).rotation : glm::quat(); + } } - QReadLocker readLock(&_jointDataLock); - return index < _jointData.size() ? _jointData.at(index).rotation : glm::quat(); } glm::vec3 AvatarData::getJointTranslation(int index) const { - if (index == -1) { - return glm::vec3(); + switch (index) { + case FARGRAB_RIGHTHAND_INDEX: { + return extractTranslation(_farGrabRightMatrixCache.get()); + } + case FARGRAB_LEFTHAND_INDEX: { + return extractTranslation(_farGrabLeftMatrixCache.get()); + } + case FARGRAB_MOUSE_INDEX: { + return extractTranslation(_farGrabMouseMatrixCache.get()); + } + default: { + if (index < 0 || index >= LOWEST_PSEUDO_JOINT_INDEX) { + return glm::vec3(); + } + QReadLocker readLock(&_jointDataLock); + return index < _jointData.size() ? _jointData.at(index).translation : glm::vec3(); + } } - QReadLocker readLock(&_jointDataLock); - return index < _jointData.size() ? _jointData.at(index).translation : glm::vec3(); } glm::vec3 AvatarData::getJointTranslation(const QString& name) const { @@ -1400,6 +1551,7 @@ glm::vec3 AvatarData::getJointTranslation(const QString& name) const { // return getJointTranslation(getJointIndex(name)); return readLockWithNamedJointIndex(name, [this](int index) { return _jointData.at(index).translation; + return getJointTranslation(index); }); } @@ -1437,7 +1589,7 @@ void AvatarData::setJointTranslation(const QString& name, const glm::vec3& trans } void AvatarData::setJointRotation(int index, const glm::quat& rotation) { - if (index == -1) { + if (index < 0 || index >= LOWEST_PSEUDO_JOINT_INDEX) { return; } QWriteLocker writeLock(&_jointDataLock); @@ -1450,7 +1602,7 @@ void AvatarData::setJointRotation(int index, const glm::quat& rotation) { } void AvatarData::setJointTranslation(int index, const glm::vec3& translation) { - if (index == -1) { + if (index < 0 || index >= LOWEST_PSEUDO_JOINT_INDEX) { return; } QWriteLocker writeLock(&_jointDataLock); @@ -1567,6 +1719,15 @@ int AvatarData::getFauxJointIndex(const QString& name) const { if (name == "_CAMERA_MATRIX") { return CAMERA_MATRIX_INDEX; } + if (name == "_FARGRAB_RIGHTHAND") { + return FARGRAB_RIGHTHAND_INDEX; + } + if (name == "_FARGRAB_LEFTHAND") { + return FARGRAB_LEFTHAND_INDEX; + } + if (name == "_FARGRAB_MOUSE") { + return FARGRAB_MOUSE_INDEX; + } return -1; } diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index a54f7927a3..fcc63fdc98 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -138,6 +138,7 @@ namespace AvatarDataPacket { const HasFlags PACKET_HAS_FACE_TRACKER_INFO = 1U << 10; const HasFlags PACKET_HAS_JOINT_DATA = 1U << 11; const HasFlags PACKET_HAS_JOINT_DEFAULT_POSE_FLAGS = 1U << 12; + const HasFlags PACKET_HAS_GRAB_JOINTS = 1U << 13; const size_t AVATAR_HAS_FLAGS_SIZE = 2; using SixByteQuat = uint8_t[6]; @@ -273,7 +274,7 @@ namespace AvatarDataPacket { SixByteTrans rightHandControllerTranslation; }; */ - size_t maxJointDataSize(size_t numJoints); + size_t maxJointDataSize(size_t numJoints, bool hasGrabJoints); /* struct JointDefaultPoseFlags { @@ -283,6 +284,17 @@ namespace AvatarDataPacket { }; */ size_t maxJointDefaultPoseFlagsSize(size_t numJoints); + + PACKED_BEGIN struct FarGrabJoints { + float leftFarGrabPosition[3]; // left controller far-grab joint position + float leftFarGrabRotation[4]; // left controller far-grab joint rotation + float rightFarGrabPosition[3]; // right controller far-grab joint position + float rightFarGrabRotation[4]; // right controller far-grab joint rotation + float mouseFarGrabPosition[3]; // mouse far-grab joint position + float mouseFarGrabRotation[4]; // mouse far-grab joint rotation + } PACKED_END; + const size_t FAR_GRAB_JOINTS_SIZE = 84; + static_assert(sizeof(FarGrabJoints) == FAR_GRAB_JOINTS_SIZE, "AvatarDataPacket::FarGrabJoints size doesn't match."); } const float MAX_AUDIO_LOUDNESS = 1000.0f; // close enough for mouth animation @@ -347,6 +359,7 @@ public: RateCounter<> faceTrackerRate; RateCounter<> jointDataRate; RateCounter<> jointDefaultPoseFlagsRate; + RateCounter<> farGrabJointRate; }; class AvatarPriority { @@ -1370,6 +1383,7 @@ protected: RateCounter<> _faceTrackerRate; RateCounter<> _jointDataRate; RateCounter<> _jointDefaultPoseFlagsRate; + RateCounter<> _farGrabJointRate; // Some rate data for incoming data updates RateCounter<> _parseBufferUpdateRate; @@ -1386,6 +1400,7 @@ protected: RateCounter<> _faceTrackerUpdateRate; RateCounter<> _jointDataUpdateRate; RateCounter<> _jointDefaultPoseFlagsUpdateRate; + RateCounter<> _farGrabJointUpdateRate; // Some rate data for outgoing data AvatarDataRate _outboundDataRate; @@ -1404,6 +1419,10 @@ protected: ThreadSafeValueCache _controllerLeftHandMatrixCache { glm::mat4() }; ThreadSafeValueCache _controllerRightHandMatrixCache { glm::mat4() }; + ThreadSafeValueCache _farGrabRightMatrixCache { glm::mat4() }; + ThreadSafeValueCache _farGrabLeftMatrixCache { glm::mat4() }; + ThreadSafeValueCache _farGrabMouseMatrixCache { glm::mat4() }; + int getFauxJointIndex(const QString& name) const; float _audioLoudness { 0.0f }; @@ -1561,5 +1580,11 @@ const int CONTROLLER_LEFTHAND_INDEX = 65532; // -4 const int CAMERA_RELATIVE_CONTROLLER_RIGHTHAND_INDEX = 65531; // -5 const int CAMERA_RELATIVE_CONTROLLER_LEFTHAND_INDEX = 65530; // -6 const int CAMERA_MATRIX_INDEX = 65529; // -7 +const int FARGRAB_RIGHTHAND_INDEX = 65528; // -8 +const int FARGRAB_LEFTHAND_INDEX = 65527; // -9 +const int FARGRAB_MOUSE_INDEX = 65526; // -10 + +const int LOWEST_PSEUDO_JOINT_INDEX = 65526; + #endif // hifi_AvatarData_h diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index 82e9820509..e902bb1954 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -40,7 +40,7 @@ PacketVersion versionForPacketType(PacketType packetType) { case PacketType::AvatarData: case PacketType::BulkAvatarData: case PacketType::KillAvatar: - return static_cast(AvatarMixerPacketVersion::ProceduralFaceMovementFlagsAndBlendshapes); + return static_cast(AvatarMixerPacketVersion::FarGrabJoints); case PacketType::MessagesData: return static_cast(MessageDataVersion::TextOrBinaryData); // ICE packets diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index 64c5bfe534..126dac7c8f 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -288,7 +288,8 @@ enum class AvatarMixerPacketVersion : PacketVersion { AvatarJointDefaultPoseFlags, FBXReaderNodeReparenting, FixMannequinDefaultAvatarFeet, - ProceduralFaceMovementFlagsAndBlendshapes + ProceduralFaceMovementFlagsAndBlendshapes, + FarGrabJoints }; enum class DomainConnectRequestVersion : PacketVersion { diff --git a/libraries/shared/src/ThreadSafeValueCache.h b/libraries/shared/src/ThreadSafeValueCache.h index 37a1258aa1..192300abbb 100644 --- a/libraries/shared/src/ThreadSafeValueCache.h +++ b/libraries/shared/src/ThreadSafeValueCache.h @@ -32,15 +32,30 @@ public: return _value; } + // returns atomic copy of the cached value and indicates validity + T get(bool& valid) const { + std::lock_guard guard(_mutex); + valid = _valid; + return _value; + } + // will reflect copy of value into the cache. void set(const T& v) { std::lock_guard guard(_mutex); _value = v; + _valid = true; + } + + // indicate that the value is not longer valid + void invalidate() { + std::lock_guard guard(_mutex); + _valid = false; } private: mutable std::mutex _mutex; T _value; + bool _valid { false }; // no copies ThreadSafeValueCache(const ThreadSafeValueCache&) = delete; diff --git a/scripts/system/controllers/controllerDispatcher.js b/scripts/system/controllers/controllerDispatcher.js index 4002fd297b..7a916392b9 100644 --- a/scripts/system/controllers/controllerDispatcher.js +++ b/scripts/system/controllers/controllerDispatcher.js @@ -12,7 +12,8 @@ LEFT_HAND, RIGHT_HAND, NEAR_GRAB_PICK_RADIUS, DEFAULT_SEARCH_SPHERE_DISTANCE, DISPATCHER_PROPERTIES, getGrabPointSphereOffset, HMD, MyAvatar, Messages, findHandChildEntities, Picks, PickType, Pointers, COLORS_GRAB_SEARCHING_HALF_SQUEEZE COLORS_GRAB_SEARCHING_FULL_SQUEEZE, COLORS_GRAB_DISTANCE_HOLD, TRIGGER_ON_VALUE, PointerManager, print - Selection, DISPATCHER_HOVERING_LIST, DISPATCHER_HOVERING_STYLE + getGrabPointSphereOffset, HMD, MyAvatar, Messages, findHandChildEntities, Picks, PickType, Pointers, + PointerManager, print, Selection, DISPATCHER_HOVERING_LIST, DISPATCHER_HOVERING_STYLE */ controllerDispatcherPlugins = {}; diff --git a/scripts/system/controllers/controllerModules/farActionGrabEntityDynOnly.js b/scripts/system/controllers/controllerModules/farActionGrabEntityDynOnly.js new file mode 100644 index 0000000000..a080e75325 --- /dev/null +++ b/scripts/system/controllers/controllerModules/farActionGrabEntityDynOnly.js @@ -0,0 +1,603 @@ +"use strict"; + +// farActionGrabEntity.js +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html + +/* jslint bitwise: true */ + +/* global Script, Controller, RIGHT_HAND, LEFT_HAND, Mat4, MyAvatar, Vec3, Camera, Quat, getEnabledModuleByName, + makeRunningValues, Entities, enableDispatcherModule, disableDispatcherModule, entityIsGrabbable, + makeDispatcherModuleParameters, MSECS_PER_SEC, HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, TRIGGER_OFF_VALUE, + TRIGGER_ON_VALUE, ZERO_VEC, getControllerWorldLocation, projectOntoEntityXYPlane, ContextOverlay, HMD, + Picks, makeLaserLockInfo, Xform, makeLaserParams, AddressManager, getEntityParents, Selection, DISPATCHER_HOVERING_LIST, + Uuid +*/ + +Script.include("/~/system/libraries/controllerDispatcherUtils.js"); +Script.include("/~/system/libraries/controllers.js"); +Script.include("/~/system/libraries/Xform.js"); + +(function() { + var GRABBABLE_PROPERTIES = [ + "position", + "registrationPoint", + "rotation", + "gravity", + "collidesWith", + "dynamic", + "collisionless", + "locked", + "name", + "shapeType", + "parentID", + "parentJointIndex", + "density", + "dimensions", + "userData" + ]; + + var MARGIN = 25; + + function TargetObject(entityID, entityProps) { + this.entityID = entityID; + this.entityProps = entityProps; + this.targetEntityID = null; + this.targetEntityProps = null; + this.previousCollisionStatus = null; + + this.getTargetEntity = function() { + var parentPropsLength = this.parentProps.length; + if (parentPropsLength !== 0) { + var targetEntity = { + id: this.parentProps[parentPropsLength - 1].id, + props: this.parentProps[parentPropsLength - 1]}; + this.targetEntityID = targetEntity.id; + this.targetEntityProps = targetEntity.props; + return targetEntity; + } + this.targetEntityID = this.entityID; + this.targetEntityProps = this.entityProps; + return { + id: this.entityID, + props: this.entityProps}; + }; + } + + function FarActionGrabEntity(hand) { + this.hand = hand; + this.grabbedThingID = null; + this.targetObject = null; + this.actionID = null; // action this script created... + this.entityToLockOnto = null; + this.potentialEntityWithContextOverlay = false; + this.entityWithContextOverlay = false; + this.contextOverlayTimer = false; + this.previousCollisionStatus = false; + this.locked = false; + this.highlightedEntity = null; + this.reticleMinX = MARGIN; + this.reticleMaxX = 0; + this.reticleMinY = MARGIN; + this.reticleMaxY = 0; + + var ACTION_TTL = 15; // seconds + + var DISTANCE_HOLDING_RADIUS_FACTOR = 3.5; // multiplied by distance between hand and object + var DISTANCE_HOLDING_ACTION_TIMEFRAME = 0.1; // how quickly objects move to their new position + var DISTANCE_HOLDING_UNITY_MASS = 1200; // The mass at which the distance holding action timeframe is unmodified + var DISTANCE_HOLDING_UNITY_DISTANCE = 6; // The distance at which the distance holding action timeframe is unmodified + + this.parameters = makeDispatcherModuleParameters( + 550, + this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"], + [], + 100, + makeLaserParams(this.hand, false)); + + + this.handToController = function() { + return (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand; + }; + + this.distanceGrabTimescale = function(mass, distance) { + var timeScale = DISTANCE_HOLDING_ACTION_TIMEFRAME * mass / + DISTANCE_HOLDING_UNITY_MASS * distance / + DISTANCE_HOLDING_UNITY_DISTANCE; + if (timeScale < DISTANCE_HOLDING_ACTION_TIMEFRAME) { + timeScale = DISTANCE_HOLDING_ACTION_TIMEFRAME; + } + return timeScale; + }; + + this.getMass = function(dimensions, density) { + return (dimensions.x * dimensions.y * dimensions.z) * density; + }; + + this.startFarGrabAction = function (controllerData, grabbedProperties) { + var controllerLocation = controllerData.controllerLocations[this.hand]; + var worldControllerPosition = controllerLocation.position; + var worldControllerRotation = controllerLocation.orientation; + + // transform the position into room space + var worldToSensorMat = Mat4.inverse(MyAvatar.getSensorToWorldMatrix()); + var roomControllerPosition = Mat4.transformPoint(worldToSensorMat, worldControllerPosition); + + var now = Date.now(); + + // add the action and initialize some variables + this.currentObjectPosition = grabbedProperties.position; + this.currentObjectRotation = grabbedProperties.rotation; + this.currentObjectTime = now; + this.currentCameraOrientation = Camera.orientation; + + this.grabRadius = this.grabbedDistance; + this.grabRadialVelocity = 0.0; + + // offset between controller vector at the grab radius and the entity position + var targetPosition = Vec3.multiply(this.grabRadius, Quat.getUp(worldControllerRotation)); + targetPosition = Vec3.sum(targetPosition, worldControllerPosition); + this.offsetPosition = Vec3.subtract(this.currentObjectPosition, targetPosition); + + // compute a constant based on the initial conditions which we use below to exaggerate hand motion + // onto the held object + this.radiusScalar = Math.log(this.grabRadius + 1.0); + if (this.radiusScalar < 1.0) { + this.radiusScalar = 1.0; + } + + // compute the mass for the purpose of energy and how quickly to move object + this.mass = this.getMass(grabbedProperties.dimensions, grabbedProperties.density); + var distanceToObject = Vec3.length(Vec3.subtract(MyAvatar.position, grabbedProperties.position)); + var timeScale = this.distanceGrabTimescale(this.mass, distanceToObject); + this.linearTimeScale = timeScale; + this.actionID = Entities.addAction("far-grab", this.grabbedThingID, { + targetPosition: this.currentObjectPosition, + linearTimeScale: timeScale, + targetRotation: this.currentObjectRotation, + angularTimeScale: timeScale, + tag: "far-grab-" + MyAvatar.sessionUUID, + ttl: ACTION_TTL + }); + if (this.actionID === Uuid.NULL) { + this.actionID = null; + } + + if (this.actionID !== null) { + var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID]; + Entities.callEntityMethod(this.grabbedThingID, "startDistanceGrab", args); + } + + Controller.triggerHapticPulse(HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, this.hand); + this.previousRoomControllerPosition = roomControllerPosition; + }; + + this.continueDistanceHolding = function(controllerData) { + var controllerLocation = controllerData.controllerLocations[this.hand]; + var worldControllerPosition = controllerLocation.position; + var worldControllerRotation = controllerLocation.orientation; + + // also transform the position into room space + var worldToSensorMat = Mat4.inverse(MyAvatar.getSensorToWorldMatrix()); + var roomControllerPosition = Mat4.transformPoint(worldToSensorMat, worldControllerPosition); + + var grabbedProperties = Entities.getEntityProperties(this.grabbedThingID, GRABBABLE_PROPERTIES); + var now = Date.now(); + var deltaObjectTime = (now - this.currentObjectTime) / MSECS_PER_SEC; // convert to seconds + this.currentObjectTime = now; + + // the action was set up when this.distanceHolding was called. update the targets. + var radius = Vec3.distance(this.currentObjectPosition, worldControllerPosition) * + this.radiusScalar * DISTANCE_HOLDING_RADIUS_FACTOR; + if (radius < 1.0) { + radius = 1.0; + } + + var roomHandDelta = Vec3.subtract(roomControllerPosition, this.previousRoomControllerPosition); + var worldHandDelta = Mat4.transformVector(MyAvatar.getSensorToWorldMatrix(), roomHandDelta); + var handMoved = Vec3.multiply(worldHandDelta, radius); + this.currentObjectPosition = Vec3.sum(this.currentObjectPosition, handMoved); + + var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID]; + Entities.callEntityMethod(this.grabbedThingID, "continueDistanceGrab", args); + + // Update radialVelocity + var lastVelocity = Vec3.multiply(worldHandDelta, 1.0 / deltaObjectTime); + var delta = Vec3.normalize(Vec3.subtract(grabbedProperties.position, worldControllerPosition)); + var newRadialVelocity = Vec3.dot(lastVelocity, delta); + + var VELOCITY_AVERAGING_TIME = 0.016; + var blendFactor = deltaObjectTime / VELOCITY_AVERAGING_TIME; + if (blendFactor < 0.0) { + blendFactor = 0.0; + } else if (blendFactor > 1.0) { + blendFactor = 1.0; + } + this.grabRadialVelocity = blendFactor * newRadialVelocity + (1.0 - blendFactor) * this.grabRadialVelocity; + + var RADIAL_GRAB_AMPLIFIER = 10.0; + if (Math.abs(this.grabRadialVelocity) > 0.0) { + this.grabRadius = this.grabRadius + (this.grabRadialVelocity * deltaObjectTime * + this.grabRadius * RADIAL_GRAB_AMPLIFIER); + } + + // don't let grabRadius go all the way to zero, because it can't come back from that + var MINIMUM_GRAB_RADIUS = 0.1; + if (this.grabRadius < MINIMUM_GRAB_RADIUS) { + this.grabRadius = MINIMUM_GRAB_RADIUS; + } + var newTargetPosition = Vec3.multiply(this.grabRadius, Quat.getUp(worldControllerRotation)); + newTargetPosition = Vec3.sum(newTargetPosition, worldControllerPosition); + newTargetPosition = Vec3.sum(newTargetPosition, this.offsetPosition); + + // XXX + // this.maybeScale(grabbedProperties); + + var distanceToObject = Vec3.length(Vec3.subtract(MyAvatar.position, this.currentObjectPosition)); + + this.linearTimeScale = (this.linearTimeScale / 2); + if (this.linearTimeScale <= DISTANCE_HOLDING_ACTION_TIMEFRAME) { + this.linearTimeScale = DISTANCE_HOLDING_ACTION_TIMEFRAME; + } + var success = Entities.updateAction(this.grabbedThingID, this.actionID, { + targetPosition: newTargetPosition, + linearTimeScale: this.linearTimeScale, + targetRotation: this.currentObjectRotation, + angularTimeScale: this.distanceGrabTimescale(this.mass, distanceToObject), + ttl: ACTION_TTL + }); + if (!success) { + print("farActionGrabEntity continueDistanceHolding -- updateAction failed: " + this.actionID); + this.actionID = null; + } + + this.previousRoomControllerPosition = roomControllerPosition; + }; + + this.endFarGrabAction = function () { + this.distanceHolding = false; + this.distanceRotating = false; + Entities.deleteAction(this.grabbedThingID, this.actionID); + + var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID]; + Entities.callEntityMethod(this.grabbedThingID, "releaseGrab", args); + this.actionID = null; + this.grabbedThingID = null; + this.targetObject = null; + this.potentialEntityWithContextOverlay = false; + }; + + this.updateRecommendedArea = function() { + var dims = Controller.getViewportDimensions(); + this.reticleMaxX = dims.x - MARGIN; + this.reticleMaxY = dims.y - MARGIN; + }; + + this.calculateNewReticlePosition = function(intersection) { + this.updateRecommendedArea(); + var point2d = HMD.overlayFromWorldPoint(intersection); + point2d.x = Math.max(this.reticleMinX, Math.min(point2d.x, this.reticleMaxX)); + point2d.y = Math.max(this.reticleMinY, Math.min(point2d.y, this.reticleMaxY)); + return point2d; + }; + + this.notPointingAtEntity = function(controllerData) { + var intersection = controllerData.rayPicks[this.hand]; + var entityProperty = Entities.getEntityProperties(intersection.objectID); + var entityType = entityProperty.type; + var hudRayPick = controllerData.hudRayPicks[this.hand]; + var point2d = this.calculateNewReticlePosition(hudRayPick.intersection); + if ((intersection.type === Picks.INTERSECTED_ENTITY && entityType === "Web") || + intersection.type === Picks.INTERSECTED_OVERLAY || Window.isPointOnDesktopWindow(point2d)) { + return true; + } + return false; + }; + + this.distanceRotate = function(otherFarGrabModule) { + this.distanceRotating = true; + this.distanceHolding = false; + + var worldControllerRotation = getControllerWorldLocation(this.handToController(), true).orientation; + var controllerRotationDelta = + Quat.multiply(worldControllerRotation, Quat.inverse(this.previousWorldControllerRotation)); + // Rotate entity by twice the delta rotation. + controllerRotationDelta = Quat.multiply(controllerRotationDelta, controllerRotationDelta); + + // Perform the rotation in the translation controller's action update. + otherFarGrabModule.currentObjectRotation = Quat.multiply(controllerRotationDelta, + otherFarGrabModule.currentObjectRotation); + + this.previousWorldControllerRotation = worldControllerRotation; + }; + + this.prepareDistanceRotatingData = function(controllerData) { + var intersection = controllerData.rayPicks[this.hand]; + + var controllerLocation = getControllerWorldLocation(this.handToController(), true); + var worldControllerPosition = controllerLocation.position; + var worldControllerRotation = controllerLocation.orientation; + + var grabbedProperties = Entities.getEntityProperties(intersection.objectID, GRABBABLE_PROPERTIES); + this.currentObjectPosition = grabbedProperties.position; + this.grabRadius = intersection.distance; + + // Offset between controller vector at the grab radius and the entity position. + var targetPosition = Vec3.multiply(this.grabRadius, Quat.getUp(worldControllerRotation)); + targetPosition = Vec3.sum(targetPosition, worldControllerPosition); + this.offsetPosition = Vec3.subtract(this.currentObjectPosition, targetPosition); + + // Initial controller rotation. + this.previousWorldControllerRotation = worldControllerRotation; + }; + + this.destroyContextOverlay = function(controllerData) { + if (this.entityWithContextOverlay) { + ContextOverlay.destroyContextOverlay(this.entityWithContextOverlay); + this.entityWithContextOverlay = false; + this.potentialEntityWithContextOverlay = false; + } + }; + + this.targetIsNull = function() { + var properties = Entities.getEntityProperties(this.grabbedThingID); + if (Object.keys(properties).length === 0 && this.distanceHolding) { + return true; + } + return false; + }; + + this.getTargetProps = function (controllerData) { + var targetEntityID = controllerData.rayPicks[this.hand].objectID; + if (targetEntityID) { + return Entities.getEntityProperties(targetEntityID); + } + return null; + }; + + this.isReady = function (controllerData) { + if (HMD.active) { + if (this.notPointingAtEntity(controllerData)) { + return makeRunningValues(false, [], []); + } + + this.distanceHolding = false; + this.distanceRotating = false; + + if (controllerData.triggerValues[this.hand] > TRIGGER_ON_VALUE) { + this.prepareDistanceRotatingData(controllerData); + return makeRunningValues(true, [], []); + } else { + this.destroyContextOverlay(); + return makeRunningValues(false, [], []); + } + } + return makeRunningValues(false, [], []); + }; + + this.run = function (controllerData) { + if (controllerData.triggerValues[this.hand] < TRIGGER_OFF_VALUE || + this.notPointingAtEntity(controllerData) || this.targetIsNull()) { + this.endFarGrabAction(); + Selection.removeFromSelectedItemsList(DISPATCHER_HOVERING_LIST, "entity", + this.highlightedEntity); + this.highlightedEntity = null; + return makeRunningValues(false, [], []); + } + this.intersectionDistance = controllerData.rayPicks[this.hand].distance; + + var otherModuleName = this.hand === RIGHT_HAND ? "LeftFarActionGrabEntity" : "RightFarActionGrabEntity"; + var otherFarGrabModule = getEnabledModuleByName(otherModuleName); + + // gather up the readiness of the near-grab modules + var nearGrabNames = [ + this.hand === RIGHT_HAND ? "RightScaleAvatar" : "LeftScaleAvatar", + this.hand === RIGHT_HAND ? "RightFarTriggerEntity" : "LeftFarTriggerEntity", + this.hand === RIGHT_HAND ? "RightNearActionGrabEntity" : "LeftNearActionGrabEntity", + this.hand === RIGHT_HAND ? "RightNearParentingGrabEntity" : "LeftNearParentingGrabEntity", + this.hand === RIGHT_HAND ? "RightNearParentingGrabOverlay" : "LeftNearParentingGrabOverlay" + ]; + + var nearGrabReadiness = []; + for (var i = 0; i < nearGrabNames.length; i++) { + var nearGrabModule = getEnabledModuleByName(nearGrabNames[i]); + var ready = nearGrabModule ? nearGrabModule.isReady(controllerData) : makeRunningValues(false, [], []); + nearGrabReadiness.push(ready); + } + + if (this.actionID) { + // if we are doing a distance grab and the object or tablet gets close enough to the controller, + // stop the far-grab so the near-grab or equip can take over. + for (var k = 0; k < nearGrabReadiness.length; k++) { + if (nearGrabReadiness[k].active && (nearGrabReadiness[k].targets[0] === this.grabbedThingID || + HMD.tabletID && nearGrabReadiness[k].targets[0] === HMD.tabletID)) { + this.endFarGrabAction(); + return makeRunningValues(false, [], []); + } + } + + this.continueDistanceHolding(controllerData); + } else { + // if we are doing a distance search and this controller moves into a position + // where it could near-grab something, stop searching. + for (var j = 0; j < nearGrabReadiness.length; j++) { + if (nearGrabReadiness[j].active) { + this.endFarGrabAction(); + return makeRunningValues(false, [], []); + } + } + + var rayPickInfo = controllerData.rayPicks[this.hand]; + if (rayPickInfo.type === Picks.INTERSECTED_ENTITY) { + if (controllerData.triggerClicks[this.hand]) { + var entityID = rayPickInfo.objectID; + Selection.removeFromSelectedItemsList(DISPATCHER_HOVERING_LIST, "entity", + this.highlightedEntity); + this.highlightedEntity = null; + var targetProps = Entities.getEntityProperties(entityID, [ + "dynamic", "shapeType", "position", + "rotation", "dimensions", "density", + "userData", "locked", "type", "href" + ]); + if (targetProps.href !== "") { + AddressManager.handleLookupString(targetProps.href); + return makeRunningValues(false, [], []); + } + + this.targetObject = new TargetObject(entityID, targetProps); + this.targetObject.parentProps = getEntityParents(targetProps); + + if (this.contextOverlayTimer) { + Script.clearTimeout(this.contextOverlayTimer); + } + this.contextOverlayTimer = false; + if (entityID === this.entityWithContextOverlay) { + this.destroyContextOverlay(); + } else { + Selection.removeFromSelectedItemsList("contextOverlayHighlightList", "entity", entityID); + } + + var targetEntity = this.targetObject.getTargetEntity(); + entityID = targetEntity.id; + targetProps = targetEntity.props; + + if (!targetProps.dynamic && !this.targetObject.entityProps.dynamic) { + // let farParentGrabEntity handle it + return makeRunningValues(false, [], []); + } + + if (entityIsGrabbable(targetProps) || entityIsGrabbable(this.targetObject.entityProps)) { + if (!this.distanceRotating) { + this.grabbedThingID = entityID; + this.grabbedDistance = rayPickInfo.distance; + } + + if (otherFarGrabModule.grabbedThingID === this.grabbedThingID && + otherFarGrabModule.distanceHolding) { + this.prepareDistanceRotatingData(controllerData); + this.distanceRotate(otherFarGrabModule); + } else { + this.distanceHolding = true; + this.distanceRotating = false; + this.startFarGrabAction(controllerData, targetProps); + } + } + } else { + var targetEntityID = rayPickInfo.objectID; + if (this.highlightedEntity !== targetEntityID) { + Selection.removeFromSelectedItemsList(DISPATCHER_HOVERING_LIST, "entity", + this.highlightedEntity); + var selectionTargetProps = Entities.getEntityProperties(targetEntityID, [ + "dynamic", "shapeType", "position", + "rotation", "dimensions", "density", + "userData", "locked", "type", "href" + ]); + + var selectionTargetObject = new TargetObject(targetEntityID, selectionTargetProps); + selectionTargetObject.parentProps = getEntityParents(selectionTargetProps); + var selectionTargetEntity = selectionTargetObject.getTargetEntity(); + + if (entityIsGrabbable(selectionTargetEntity.props) || + entityIsGrabbable(selectionTargetObject.entityProps)) { + + Selection.addToSelectedItemsList(DISPATCHER_HOVERING_LIST, "entity", rayPickInfo.objectID); + } + this.highlightedEntity = rayPickInfo.objectID; + } + + if (!this.entityWithContextOverlay) { + var _this = this; + + if (_this.potentialEntityWithContextOverlay !== rayPickInfo.objectID) { + if (_this.contextOverlayTimer) { + Script.clearTimeout(_this.contextOverlayTimer); + } + _this.contextOverlayTimer = false; + _this.potentialEntityWithContextOverlay = rayPickInfo.objectID; + } + + if (!_this.contextOverlayTimer) { + _this.contextOverlayTimer = Script.setTimeout(function () { + if (!_this.entityWithContextOverlay && + _this.contextOverlayTimer && + _this.potentialEntityWithContextOverlay === rayPickInfo.objectID) { + var pEvProps = Entities.getEntityProperties(rayPickInfo.objectID); + var pointerEvent = { + type: "Move", + id: _this.hand + 1, // 0 is reserved for hardware mouse + pos2D: projectOntoEntityXYPlane(rayPickInfo.objectID, + rayPickInfo.intersection, pEvProps), + pos3D: rayPickInfo.intersection, + normal: rayPickInfo.surfaceNormal, + direction: Vec3.subtract(ZERO_VEC, rayPickInfo.surfaceNormal), + button: "Secondary" + }; + if (ContextOverlay.createOrDestroyContextOverlay(rayPickInfo.objectID, pointerEvent)) { + _this.entityWithContextOverlay = rayPickInfo.objectID; + } + } + _this.contextOverlayTimer = false; + }, 500); + } + } + } + } else if (this.distanceRotating) { + this.distanceRotate(otherFarGrabModule); + } else if (this.highlightedEntity) { + Selection.removeFromSelectedItemsList(DISPATCHER_HOVERING_LIST, "entity", this.highlightedEntity); + this.highlightedEntity = null; + } + } + return this.exitIfDisabled(controllerData); + }; + + this.exitIfDisabled = function(controllerData) { + var moduleName = this.hand === RIGHT_HAND ? "RightDisableModules" : "LeftDisableModules"; + var disableModule = getEnabledModuleByName(moduleName); + if (disableModule) { + if (disableModule.disableModules) { + this.endFarGrabAction(); + Selection.removeFromSelectedItemsList(DISPATCHER_HOVERING_LIST, "entity", + this.highlightedEntity); + this.highlightedEntity = null; + return makeRunningValues(false, [], []); + } + } + var grabbedThing = (this.distanceHolding || this.distanceRotating) ? this.targetObject.entityID : null; + var offset = this.calculateOffset(controllerData); + var laserLockInfo = makeLaserLockInfo(grabbedThing, false, this.hand, offset); + return makeRunningValues(true, [], [], laserLockInfo); + }; + + this.calculateOffset = function(controllerData) { + if (this.distanceHolding || this.distanceRotating) { + var targetProps = Entities.getEntityProperties(this.targetObject.entityID, [ + "position", + "rotation" + ]); + var zeroVector = { x: 0, y: 0, z:0, w: 0 }; + var intersection = controllerData.rayPicks[this.hand].intersection; + var intersectionMat = new Xform(zeroVector, intersection); + var modelMat = new Xform(targetProps.rotation, targetProps.position); + var modelMatInv = modelMat.inv(); + var xformMat = Xform.mul(modelMatInv, intersectionMat); + var offsetMat = Mat4.createFromRotAndTrans(xformMat.rot, xformMat.pos); + return offsetMat; + } + return undefined; + }; + } + + var leftFarActionGrabEntity = new FarActionGrabEntity(LEFT_HAND); + var rightFarActionGrabEntity = new FarActionGrabEntity(RIGHT_HAND); + + enableDispatcherModule("LeftFarActionGrabEntity", leftFarActionGrabEntity); + enableDispatcherModule("RightFarActionGrabEntity", rightFarActionGrabEntity); + + function cleanup() { + disableDispatcherModule("LeftFarActionGrabEntity"); + disableDispatcherModule("RightFarActionGrabEntity"); + } + Script.scriptEnding.connect(cleanup); +}()); diff --git a/scripts/system/controllers/controllerModules/farParentGrabEntity.js b/scripts/system/controllers/controllerModules/farParentGrabEntity.js new file mode 100644 index 0000000000..439b5e5f51 --- /dev/null +++ b/scripts/system/controllers/controllerModules/farParentGrabEntity.js @@ -0,0 +1,646 @@ +"use strict"; + +// farParentGrabEntity.js +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html + +/* jslint bitwise: true */ + +/* global Script, Controller, RIGHT_HAND, LEFT_HAND, Mat4, MyAvatar, Vec3, Quat, getEnabledModuleByName, makeRunningValues, + Entities, enableDispatcherModule, disableDispatcherModule, entityIsGrabbable, makeDispatcherModuleParameters, MSECS_PER_SEC, + HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, TRIGGER_OFF_VALUE, TRIGGER_ON_VALUE, ZERO_VEC, getControllerWorldLocation, + projectOntoEntityXYPlane, ContextOverlay, HMD, Picks, makeLaserLockInfo, Xform, makeLaserParams, AddressManager, + getEntityParents, Selection, DISPATCHER_HOVERING_LIST, unhighlightTargetEntity, Messages, Uuid, findGroupParent +*/ + +Script.include("/~/system/libraries/controllerDispatcherUtils.js"); +Script.include("/~/system/libraries/controllers.js"); +Script.include("/~/system/libraries/Xform.js"); + +(function() { + var GRABBABLE_PROPERTIES = [ + "position", + "registrationPoint", + "rotation", + "gravity", + "collidesWith", + "dynamic", + "collisionless", + "locked", + "name", + "shapeType", + "parentID", + "parentJointIndex", + "density", + "dimensions", + "userData" + ]; + + var MARGIN = 25; + + function TargetObject(entityID, entityProps) { + this.entityID = entityID; + this.entityProps = entityProps; + this.targetEntityID = null; + this.targetEntityProps = null; + + this.getTargetEntity = function() { + var parentPropsLength = this.parentProps.length; + if (parentPropsLength !== 0) { + var targetEntity = { + id: this.parentProps[parentPropsLength - 1].id, + props: this.parentProps[parentPropsLength - 1]}; + this.targetEntityID = targetEntity.id; + this.targetEntityProps = targetEntity.props; + return targetEntity; + } + this.targetEntityID = this.entityID; + this.targetEntityProps = this.entityProps; + return { + id: this.entityID, + props: this.entityProps}; + }; + } + + function FarParentGrabEntity(hand) { + this.hand = hand; + this.targetEntityID = null; + this.targetObject = null; + this.previousParentID = {}; + this.previousParentJointIndex = {}; + this.potentialEntityWithContextOverlay = false; + this.entityWithContextOverlay = false; + this.contextOverlayTimer = false; + this.highlightedEntity = null; + this.reticleMinX = MARGIN; + this.reticleMaxX = 0; + this.reticleMinY = MARGIN; + this.reticleMaxY = 0; + + var FAR_GRAB_JOINTS = [65527, 65528]; // FARGRAB_LEFTHAND_INDEX, FARGRAB_RIGHTHAND_INDEX + + var DISTANCE_HOLDING_RADIUS_FACTOR = 3.5; // multiplied by distance between hand and object + var DISTANCE_HOLDING_ACTION_TIMEFRAME = 0.1; // how quickly objects move to their new position + var DISTANCE_HOLDING_UNITY_MASS = 1200; // The mass at which the distance holding action timeframe is unmodified + var DISTANCE_HOLDING_UNITY_DISTANCE = 6; // The distance at which the distance holding action timeframe is unmodified + + this.parameters = makeDispatcherModuleParameters( + 540, + this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"], + [], + 100, + makeLaserParams(this.hand, false)); + + + this.handToController = function() { + return (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand; + }; + + this.distanceGrabTimescale = function(mass, distance) { + var timeScale = DISTANCE_HOLDING_ACTION_TIMEFRAME * mass / + DISTANCE_HOLDING_UNITY_MASS * distance / + DISTANCE_HOLDING_UNITY_DISTANCE; + if (timeScale < DISTANCE_HOLDING_ACTION_TIMEFRAME) { + timeScale = DISTANCE_HOLDING_ACTION_TIMEFRAME; + } + return timeScale; + }; + + this.getMass = function(dimensions, density) { + return (dimensions.x * dimensions.y * dimensions.z) * density; + }; + + this.thisFarGrabJointIsParent = function(isParentProps) { + if (!isParentProps) { + return false; + } + + if (isParentProps.parentID !== MyAvatar.sessionUUID && isParentProps.parentID !== MyAvatar.SELF_ID) { + return false; + } + + if (isParentProps.parentJointIndex === FAR_GRAB_JOINTS[this.hand]) { + return true; + } + + return false; + }; + + this.startFarParentGrab = function (controllerData, grabbedProperties) { + var controllerLocation = controllerData.controllerLocations[this.hand]; + var worldControllerPosition = controllerLocation.position; + var worldControllerRotation = controllerLocation.orientation; + // transform the position into room space + var worldToSensorMat = Mat4.inverse(MyAvatar.getSensorToWorldMatrix()); + var roomControllerPosition = Mat4.transformPoint(worldToSensorMat, worldControllerPosition); + + var now = Date.now(); + + // add the action and initialize some variables + this.currentObjectPosition = grabbedProperties.position; + this.currentObjectRotation = grabbedProperties.rotation; + this.currentObjectTime = now; + + this.grabRadius = this.grabbedDistance; + this.grabRadialVelocity = 0.0; + + // offset between controller vector at the grab radius and the entity position + var targetPosition = Vec3.multiply(this.grabRadius, Quat.getUp(worldControllerRotation)); + targetPosition = Vec3.sum(targetPosition, worldControllerPosition); + this.offsetPosition = Vec3.subtract(this.currentObjectPosition, targetPosition); + + // compute a constant based on the initial conditions which we use below to exaggerate hand motion + // onto the held object + this.radiusScalar = Math.log(this.grabRadius + 1.0); + if (this.radiusScalar < 1.0) { + this.radiusScalar = 1.0; + } + + // compute the mass for the purpose of energy and how quickly to move object + this.mass = this.getMass(grabbedProperties.dimensions, grabbedProperties.density); + + Controller.triggerHapticPulse(HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, this.hand); + unhighlightTargetEntity(this.targetEntityID); + var message = { + hand: this.hand, + entityID: this.targetEntityID + }; + + Messages.sendLocalMessage('Hifi-unhighlight-entity', JSON.stringify(message)); + + var newTargetPosLocal = MyAvatar.worldToJointPoint(grabbedProperties.position); + MyAvatar.setJointTranslation(FAR_GRAB_JOINTS[this.hand], newTargetPosLocal); + + var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID]; + Entities.callEntityMethod(grabbedProperties.id, "startNearGrab", args); + + var reparentProps = { + parentID: MyAvatar.SELF_ID, + parentJointIndex: FAR_GRAB_JOINTS[this.hand], + localVelocity: {x: 0, y: 0, z: 0}, + localAngularVelocity: {x: 0, y: 0, z: 0} + }; + + if (this.thisFarGrabJointIsParent(grabbedProperties)) { + // this should never happen, but if it does, don't set previous parent to be this hand. + this.previousParentID[grabbedProperties.id] = null; + this.previousParentJointIndex[grabbedProperties.id] = -1; + } else { + this.previousParentID[grabbedProperties.id] = grabbedProperties.parentID; + this.previousParentJointIndex[grabbedProperties.id] = grabbedProperties.parentJointIndex; + } + + this.targetEntityID = grabbedProperties.id; + Entities.editEntity(grabbedProperties.id, reparentProps); + + Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({ + action: 'grab', + grabbedEntity: grabbedProperties.id, + joint: this.hand === RIGHT_HAND ? "RightHand" : "LeftHand" + })); + this.grabbing = true; + + this.previousRoomControllerPosition = roomControllerPosition; + }; + + this.continueDistanceHolding = function(controllerData) { + var controllerLocation = controllerData.controllerLocations[this.hand]; + var worldControllerPosition = controllerLocation.position; + var worldControllerRotation = controllerLocation.orientation; + + // also transform the position into room space + var worldToSensorMat = Mat4.inverse(MyAvatar.getSensorToWorldMatrix()); + var roomControllerPosition = Mat4.transformPoint(worldToSensorMat, worldControllerPosition); + + var grabbedProperties = Entities.getEntityProperties(this.targetEntityID, GRABBABLE_PROPERTIES); + var now = Date.now(); + var deltaObjectTime = (now - this.currentObjectTime) / MSECS_PER_SEC; // convert to seconds + this.currentObjectTime = now; + + // the action was set up when this.distanceHolding was called. update the targets. + var radius = Vec3.distance(this.currentObjectPosition, worldControllerPosition) * + this.radiusScalar * DISTANCE_HOLDING_RADIUS_FACTOR; + if (radius < 1.0) { + radius = 1.0; + } + + var roomHandDelta = Vec3.subtract(roomControllerPosition, this.previousRoomControllerPosition); + var worldHandDelta = Mat4.transformVector(MyAvatar.getSensorToWorldMatrix(), roomHandDelta); + var handMoved = Vec3.multiply(worldHandDelta, radius); + this.currentObjectPosition = Vec3.sum(this.currentObjectPosition, handMoved); + + var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID]; + Entities.callEntityMethod(this.targetEntityID, "continueDistanceGrab", args); + + // Update radialVelocity + var lastVelocity = Vec3.multiply(worldHandDelta, 1.0 / deltaObjectTime); + var delta = Vec3.normalize(Vec3.subtract(grabbedProperties.position, worldControllerPosition)); + var newRadialVelocity = Vec3.dot(lastVelocity, delta); + + var VELOCITY_AVERAGING_TIME = 0.016; + var blendFactor = deltaObjectTime / VELOCITY_AVERAGING_TIME; + if (blendFactor < 0.0) { + blendFactor = 0.0; + } else if (blendFactor > 1.0) { + blendFactor = 1.0; + } + this.grabRadialVelocity = blendFactor * newRadialVelocity + (1.0 - blendFactor) * this.grabRadialVelocity; + + var RADIAL_GRAB_AMPLIFIER = 10.0; + if (Math.abs(this.grabRadialVelocity) > 0.0) { + this.grabRadius = this.grabRadius + (this.grabRadialVelocity * deltaObjectTime * + this.grabRadius * RADIAL_GRAB_AMPLIFIER); + } + + // don't let grabRadius go all the way to zero, because it can't come back from that + var MINIMUM_GRAB_RADIUS = 0.1; + if (this.grabRadius < MINIMUM_GRAB_RADIUS) { + this.grabRadius = MINIMUM_GRAB_RADIUS; + } + var newTargetPosition = Vec3.multiply(this.grabRadius, Quat.getUp(worldControllerRotation)); + newTargetPosition = Vec3.sum(newTargetPosition, worldControllerPosition); + newTargetPosition = Vec3.sum(newTargetPosition, this.offsetPosition); + + // MyAvatar.setJointTranslation(FAR_GRAB_JOINTS[this.hand], MyAvatar.worldToJointPoint(newTargetPosition)); + + // var newTargetPosLocal = Mat4.transformPoint(MyAvatar.getSensorToWorldMatrix(), newTargetPosition); + var newTargetPosLocal = MyAvatar.worldToJointPoint(newTargetPosition); + MyAvatar.setJointTranslation(FAR_GRAB_JOINTS[this.hand], newTargetPosLocal); + + this.previousRoomControllerPosition = roomControllerPosition; + }; + + this.endFarParentGrab = function (controllerData) { + this.hapticTargetID = null; + // var endProps = controllerData.nearbyEntityPropertiesByID[this.targetEntityID]; + var endProps = Entities.getEntityProperties(this.targetEntityID, GRABBABLE_PROPERTIES); + if (this.thisFarGrabJointIsParent(endProps)) { + Entities.editEntity(this.targetEntityID, { + parentID: this.previousParentID[this.targetEntityID], + parentJointIndex: this.previousParentJointIndex[this.targetEntityID] + }); + } + + var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID]; + Entities.callEntityMethod(this.targetEntityID, "releaseGrab", args); + Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({ + action: 'release', + grabbedEntity: this.targetEntityID, + joint: this.hand === RIGHT_HAND ? "RightHand" : "LeftHand" + })); + unhighlightTargetEntity(this.targetEntityID); + this.grabbing = false; + this.targetEntityID = null; + this.potentialEntityWithContextOverlay = false; + MyAvatar.clearJointData(FAR_GRAB_JOINTS[this.hand]); + }; + + this.updateRecommendedArea = function() { + var dims = Controller.getViewportDimensions(); + this.reticleMaxX = dims.x - MARGIN; + this.reticleMaxY = dims.y - MARGIN; + }; + + this.calculateNewReticlePosition = function(intersection) { + this.updateRecommendedArea(); + var point2d = HMD.overlayFromWorldPoint(intersection); + point2d.x = Math.max(this.reticleMinX, Math.min(point2d.x, this.reticleMaxX)); + point2d.y = Math.max(this.reticleMinY, Math.min(point2d.y, this.reticleMaxY)); + return point2d; + }; + + this.notPointingAtEntity = function(controllerData) { + var intersection = controllerData.rayPicks[this.hand]; + var entityProperty = Entities.getEntityProperties(intersection.objectID); + var entityType = entityProperty.type; + var hudRayPick = controllerData.hudRayPicks[this.hand]; + var point2d = this.calculateNewReticlePosition(hudRayPick.intersection); + if ((intersection.type === Picks.INTERSECTED_ENTITY && entityType === "Web") || + intersection.type === Picks.INTERSECTED_OVERLAY || Window.isPointOnDesktopWindow(point2d)) { + return true; + } + return false; + }; + + this.distanceRotate = function(otherFarGrabModule) { + this.distanceRotating = true; + this.distanceHolding = false; + + var worldControllerRotation = getControllerWorldLocation(this.handToController(), true).orientation; + var controllerRotationDelta = + Quat.multiply(worldControllerRotation, Quat.inverse(this.previousWorldControllerRotation)); + // Rotate entity by twice the delta rotation. + controllerRotationDelta = Quat.multiply(controllerRotationDelta, controllerRotationDelta); + + // Perform the rotation in the translation controller's action update. + otherFarGrabModule.currentObjectRotation = Quat.multiply(controllerRotationDelta, + otherFarGrabModule.currentObjectRotation); + + this.previousWorldControllerRotation = worldControllerRotation; + }; + + this.prepareDistanceRotatingData = function(controllerData) { + var intersection = controllerData.rayPicks[this.hand]; + + var controllerLocation = getControllerWorldLocation(this.handToController(), true); + var worldControllerPosition = controllerLocation.position; + var worldControllerRotation = controllerLocation.orientation; + + var grabbedProperties = Entities.getEntityProperties(intersection.objectID, GRABBABLE_PROPERTIES); + this.currentObjectPosition = grabbedProperties.position; + this.grabRadius = intersection.distance; + + // Offset between controller vector at the grab radius and the entity position. + var targetPosition = Vec3.multiply(this.grabRadius, Quat.getUp(worldControllerRotation)); + targetPosition = Vec3.sum(targetPosition, worldControllerPosition); + this.offsetPosition = Vec3.subtract(this.currentObjectPosition, targetPosition); + + // Initial controller rotation. + this.previousWorldControllerRotation = worldControllerRotation; + }; + + this.destroyContextOverlay = function(controllerData) { + if (this.entityWithContextOverlay) { + ContextOverlay.destroyContextOverlay(this.entityWithContextOverlay); + this.entityWithContextOverlay = false; + this.potentialEntityWithContextOverlay = false; + } + }; + + this.targetIsNull = function() { + var properties = Entities.getEntityProperties(this.targetEntityID, GRABBABLE_PROPERTIES); + if (Object.keys(properties).length === 0 && this.distanceHolding) { + return true; + } + return false; + }; + + this.getTargetProps = function (controllerData) { + var targetEntity = controllerData.rayPicks[this.hand].objectID; + if (targetEntity) { + var gtProps = Entities.getEntityProperties(targetEntity, GRABBABLE_PROPERTIES); + if (entityIsGrabbable(gtProps)) { + // give haptic feedback + if (gtProps.id !== this.hapticTargetID) { + Controller.triggerHapticPulse(HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, this.hand); + this.hapticTargetID = gtProps.id; + } + // if we've attempted to grab a child, roll up to the root of the tree + var groupRootProps = findGroupParent(controllerData, gtProps); + if (entityIsGrabbable(groupRootProps)) { + return groupRootProps; + } + return gtProps; + } + } + return null; + }; + + this.isReady = function (controllerData) { + if (HMD.active) { + if (this.notPointingAtEntity(controllerData)) { + return makeRunningValues(false, [], []); + } + + this.distanceHolding = false; + this.distanceRotating = false; + + if (controllerData.triggerValues[this.hand] > TRIGGER_ON_VALUE) { + var targetProps = this.getTargetProps(controllerData); + if (targetProps && (targetProps.dynamic && targetProps.parentID === Uuid.NULL)) { + return makeRunningValues(false, [], []); // let farActionGrabEntity handle it + } else { + this.prepareDistanceRotatingData(controllerData); + return makeRunningValues(true, [], []); + } + } else { + this.destroyContextOverlay(); + return makeRunningValues(false, [], []); + } + } + return makeRunningValues(false, [], []); + }; + + this.run = function (controllerData) { + if (controllerData.triggerValues[this.hand] < TRIGGER_OFF_VALUE || + this.notPointingAtEntity(controllerData) || this.targetIsNull()) { + this.endFarParentGrab(controllerData); + Selection.removeFromSelectedItemsList(DISPATCHER_HOVERING_LIST, "entity", this.highlightedEntity); + this.highlightedEntity = null; + return makeRunningValues(false, [], []); + } + this.intersectionDistance = controllerData.rayPicks[this.hand].distance; + + var otherModuleName = this.hand === RIGHT_HAND ? "LeftFarParentGrabEntity" : "RightFarParentGrabEntity"; + var otherFarGrabModule = getEnabledModuleByName(otherModuleName); + + // gather up the readiness of the near-grab modules + var nearGrabNames = [ + this.hand === RIGHT_HAND ? "RightScaleAvatar" : "LeftScaleAvatar", + this.hand === RIGHT_HAND ? "RightFarTriggerEntity" : "LeftFarTriggerEntity", + this.hand === RIGHT_HAND ? "RightNearActionGrabEntity" : "LeftNearActionGrabEntity", + this.hand === RIGHT_HAND ? "RightNearParentingGrabEntity" : "LeftNearParentingGrabEntity", + this.hand === RIGHT_HAND ? "RightNearParentingGrabOverlay" : "LeftNearParentingGrabOverlay" + ]; + + var nearGrabReadiness = []; + for (var i = 0; i < nearGrabNames.length; i++) { + var nearGrabModule = getEnabledModuleByName(nearGrabNames[i]); + var ready = nearGrabModule ? nearGrabModule.isReady(controllerData) : makeRunningValues(false, [], []); + nearGrabReadiness.push(ready); + } + + if (this.targetEntityID) { + // if we are doing a distance grab and the object or tablet gets close enough to the controller, + // stop the far-grab so the near-grab or equip can take over. + for (var k = 0; k < nearGrabReadiness.length; k++) { + if (nearGrabReadiness[k].active && (nearGrabReadiness[k].targets[0] === this.targetEntityID || + HMD.tabletID && nearGrabReadiness[k].targets[0] === HMD.tabletID)) { + this.endFarParentGrab(controllerData); + return makeRunningValues(false, [], []); + } + } + + this.continueDistanceHolding(controllerData); + } else { + // if we are doing a distance search and this controller moves into a position + // where it could near-grab something, stop searching. + for (var j = 0; j < nearGrabReadiness.length; j++) { + if (nearGrabReadiness[j].active) { + this.endFarParentGrab(controllerData); + return makeRunningValues(false, [], []); + } + } + + var rayPickInfo = controllerData.rayPicks[this.hand]; + if (rayPickInfo.type === Picks.INTERSECTED_ENTITY) { + if (controllerData.triggerClicks[this.hand]) { + var entityID = rayPickInfo.objectID; + Selection.removeFromSelectedItemsList(DISPATCHER_HOVERING_LIST, "entity", this.highlightedEntity); + this.highlightedEntity = null; + var targetProps = Entities.getEntityProperties(entityID, [ + "dynamic", "shapeType", "position", + "rotation", "dimensions", "density", + "userData", "locked", "type", "href" + ]); + if (targetProps.href !== "") { + AddressManager.handleLookupString(targetProps.href); + return makeRunningValues(false, [], []); + } + + this.targetObject = new TargetObject(entityID, targetProps); + this.targetObject.parentProps = getEntityParents(targetProps); + + if (this.contextOverlayTimer) { + Script.clearTimeout(this.contextOverlayTimer); + } + this.contextOverlayTimer = false; + if (entityID === this.entityWithContextOverlay) { + this.destroyContextOverlay(); + } else { + Selection.removeFromSelectedItemsList("contextOverlayHighlightList", "entity", entityID); + } + + var targetEntity = this.targetObject.getTargetEntity(); + entityID = targetEntity.id; + targetProps = targetEntity.props; + + if (targetProps.dynamic || this.targetObject.entityProps.dynamic) { + // let farActionGrabEntity handle it + return makeRunningValues(false, [], []); + } + + if (entityIsGrabbable(targetProps) || entityIsGrabbable(this.targetObject.entityProps)) { + + if (!this.distanceRotating) { + this.targetEntityID = entityID; + this.grabbedDistance = rayPickInfo.distance; + } + + if (otherFarGrabModule.targetEntityID === this.targetEntityID && + otherFarGrabModule.distanceHolding) { + this.prepareDistanceRotatingData(controllerData); + this.distanceRotate(otherFarGrabModule); + } else { + this.distanceHolding = true; + this.distanceRotating = false; + this.startFarParentGrab(controllerData, targetProps); + } + } + } else { + var targetEntityID = rayPickInfo.objectID; + if (this.highlightedEntity !== targetEntityID) { + Selection.removeFromSelectedItemsList(DISPATCHER_HOVERING_LIST, "entity", this.highlightedEntity); + var selectionTargetProps = Entities.getEntityProperties(targetEntityID, [ + "dynamic", "shapeType", "position", + "rotation", "dimensions", "density", + "userData", "locked", "type", "href" + ]); + + var selectionTargetObject = new TargetObject(targetEntityID, selectionTargetProps); + selectionTargetObject.parentProps = getEntityParents(selectionTargetProps); + var selectionTargetEntity = selectionTargetObject.getTargetEntity(); + + if (entityIsGrabbable(selectionTargetEntity.props) || + entityIsGrabbable(selectionTargetObject.entityProps)) { + + Selection.addToSelectedItemsList(DISPATCHER_HOVERING_LIST, "entity", rayPickInfo.objectID); + } + this.highlightedEntity = rayPickInfo.objectID; + } + + if (!this.entityWithContextOverlay) { + var _this = this; + + if (_this.potentialEntityWithContextOverlay !== rayPickInfo.objectID) { + if (_this.contextOverlayTimer) { + Script.clearTimeout(_this.contextOverlayTimer); + } + _this.contextOverlayTimer = false; + _this.potentialEntityWithContextOverlay = rayPickInfo.objectID; + } + + if (!_this.contextOverlayTimer) { + _this.contextOverlayTimer = Script.setTimeout(function () { + if (!_this.entityWithContextOverlay && + _this.contextOverlayTimer && + _this.potentialEntityWithContextOverlay === rayPickInfo.objectID) { + var cotProps = Entities.getEntityProperties(rayPickInfo.objectID); + var pointerEvent = { + type: "Move", + id: _this.hand + 1, // 0 is reserved for hardware mouse + pos2D: projectOntoEntityXYPlane(rayPickInfo.objectID, + rayPickInfo.intersection, cotProps), + pos3D: rayPickInfo.intersection, + normal: rayPickInfo.surfaceNormal, + direction: Vec3.subtract(ZERO_VEC, rayPickInfo.surfaceNormal), + button: "Secondary" + }; + if (ContextOverlay.createOrDestroyContextOverlay(rayPickInfo.objectID, pointerEvent)) { + _this.entityWithContextOverlay = rayPickInfo.objectID; + } + } + _this.contextOverlayTimer = false; + }, 500); + } + } + } + } else if (this.distanceRotating) { + this.distanceRotate(otherFarGrabModule); + } else if (this.highlightedEntity) { + Selection.removeFromSelectedItemsList(DISPATCHER_HOVERING_LIST, "entity", this.highlightedEntity); + this.highlightedEntity = null; + } + } + return this.exitIfDisabled(controllerData); + }; + + this.exitIfDisabled = function(controllerData) { + var moduleName = this.hand === RIGHT_HAND ? "RightDisableModules" : "LeftDisableModules"; + var disableModule = getEnabledModuleByName(moduleName); + if (disableModule) { + if (disableModule.disableModules) { + this.endFarParentGrab(controllerData); + Selection.removeFromSelectedItemsList(DISPATCHER_HOVERING_LIST, "entity", this.highlightedEntity); + this.highlightedEntity = null; + return makeRunningValues(false, [], []); + } + } + var grabbedThing = (this.distanceHolding || this.distanceRotating) ? this.targetObject.entityID : null; + var offset = this.calculateOffset(controllerData); + var laserLockInfo = makeLaserLockInfo(grabbedThing, false, this.hand, offset); + return makeRunningValues(true, [], [], laserLockInfo); + }; + + this.calculateOffset = function(controllerData) { + if (this.distanceHolding || this.distanceRotating) { + var targetProps = Entities.getEntityProperties(this.targetObject.entityID, [ + "position", + "rotation" + ]); + var zeroVector = { x: 0, y: 0, z:0, w: 0 }; + var intersection = controllerData.rayPicks[this.hand].intersection; + var intersectionMat = new Xform(zeroVector, intersection); + var modelMat = new Xform(targetProps.rotation, targetProps.position); + var modelMatInv = modelMat.inv(); + var xformMat = Xform.mul(modelMatInv, intersectionMat); + var offsetMat = Mat4.createFromRotAndTrans(xformMat.rot, xformMat.pos); + return offsetMat; + } + return undefined; + }; + } + + var leftFarParentGrabEntity = new FarParentGrabEntity(LEFT_HAND); + var rightFarParentGrabEntity = new FarParentGrabEntity(RIGHT_HAND); + + enableDispatcherModule("LeftFarParentGrabEntity", leftFarParentGrabEntity); + enableDispatcherModule("RightFarParentGrabEntity", rightFarParentGrabEntity); + + function cleanup() { + disableDispatcherModule("LeftFarParentGrabEntity"); + disableDispatcherModule("RightFarParentGrabEntity"); + } + Script.scriptEnding.connect(cleanup); +}()); diff --git a/scripts/system/controllers/controllerScripts.js b/scripts/system/controllers/controllerScripts.js index ce93c6a010..6899577de2 100644 --- a/scripts/system/controllers/controllerScripts.js +++ b/scripts/system/controllers/controllerScripts.js @@ -19,7 +19,8 @@ var CONTOLLER_SCRIPTS = [ "controllerModules/nearParentGrabEntity.js", "controllerModules/nearParentGrabOverlay.js", "controllerModules/nearActionGrabEntity.js", - "controllerModules/farActionGrabEntity.js", + // "controllerModules/farActionGrabEntity.js", + // "controllerModules/farParentGrabEntity.js", "controllerModules/stylusInput.js", "controllerModules/equipEntity.js", "controllerModules/nearTrigger.js", @@ -37,6 +38,13 @@ var CONTOLLER_SCRIPTS = [ "controllerModules/mouseHighlightEntities.js" ]; +if (Settings.getValue("useFarGrabJoints", false)) { + CONTOLLER_SCRIPTS.push("controllerModules/farActionGrabEntityDynOnly.js"); + CONTOLLER_SCRIPTS.push("controllerModules/farParentGrabEntity.js"); +} else { + CONTOLLER_SCRIPTS.push("controllerModules/farActionGrabEntity.js"); +} + var DEBUG_MENU_ITEM = "Debug defaultScripts.js"; diff --git a/scripts/system/libraries/controllerDispatcherUtils.js b/scripts/system/libraries/controllerDispatcherUtils.js index 60c4553da7..a386dcf5b4 100644 --- a/scripts/system/libraries/controllerDispatcherUtils.js +++ b/scripts/system/libraries/controllerDispatcherUtils.js @@ -5,7 +5,6 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html - /* global module, Camera, HMD, MyAvatar, controllerDispatcherPlugins:true, Quat, Vec3, Overlays, Xform, Selection, Uuid, MSECS_PER_SEC:true , LEFT_HAND:true, RIGHT_HAND:true, FORBIDDEN_GRAB_TYPES:true, @@ -203,15 +202,15 @@ getEnabledModuleByName = function (moduleName) { return null; }; -getGrabbableData = function (props) { +getGrabbableData = function (ggdProps) { // look in userData for a "grabbable" key, return the value or some defaults var grabbableData = {}; var userDataParsed = null; try { - if (!props.userDataParsed) { - props.userDataParsed = JSON.parse(props.userData); + if (!ggdProps.userDataParsed) { + ggdProps.userDataParsed = JSON.parse(ggdProps.userData); } - userDataParsed = props.userDataParsed; + userDataParsed = ggdProps.userDataParsed; } catch (err) { userDataParsed = {}; } @@ -237,11 +236,11 @@ getGrabbableData = function (props) { return grabbableData; }; -entityIsGrabbable = function (props) { - var grabbable = getGrabbableData(props).grabbable; +entityIsGrabbable = function (eigProps) { + var grabbable = getGrabbableData(eigProps).grabbable; if (!grabbable || - props.locked || - FORBIDDEN_GRAB_TYPES.indexOf(props.type) >= 0) { + eigProps.locked || + FORBIDDEN_GRAB_TYPES.indexOf(eigProps.type) >= 0) { return false; } return true; @@ -259,13 +258,13 @@ unhighlightTargetEntity = function(entityID) { Selection.removeFromSelectedItemsList(DISPATCHER_HOVERING_LIST, "entity", entityID); }; -entityIsDistanceGrabbable = function(props) { - if (!entityIsGrabbable(props)) { +entityIsDistanceGrabbable = function(eidgProps) { + if (!entityIsGrabbable(eidgProps)) { return false; } // we can't distance-grab non-physical - var isPhysical = propsArePhysical(props); + var isPhysical = propsArePhysical(eidgProps); if (!isPhysical) { return false; } @@ -304,11 +303,11 @@ getControllerJointIndex = function (hand) { return -1; }; -propsArePhysical = function (props) { - if (!props.dynamic) { +propsArePhysical = function (papProps) { + if (!papProps.dynamic) { return false; } - var isPhysical = (props.shapeType && props.shapeType !== 'none'); + var isPhysical = (papProps.shapeType && papProps.shapeType !== 'none'); return isPhysical; }; @@ -328,8 +327,9 @@ projectOntoXYPlane = function (worldPos, position, rotation, dimensions, registr }; }; -projectOntoEntityXYPlane = function (entityID, worldPos, props) { - return projectOntoXYPlane(worldPos, props.position, props.rotation, props.dimensions, props.registrationPoint); +projectOntoEntityXYPlane = function (entityID, worldPos, popProps) { + return projectOntoXYPlane(worldPos, popProps.position, popProps.rotation, + popProps.dimensions, popProps.registrationPoint); }; projectOntoOverlayXYPlane = function projectOntoOverlayXYPlane(overlayID, worldPos) { @@ -348,9 +348,9 @@ entityHasActions = function (entityID) { ensureDynamic = function (entityID) { // if we distance hold something and keep it very still before releasing it, it ends up // non-dynamic in bullet. If it's too still, give it a little bounce so it will fall. - var props = Entities.getEntityProperties(entityID, ["velocity", "dynamic", "parentID"]); - if (props.dynamic && props.parentID === Uuid.NULL) { - var velocity = props.velocity; + var edProps = Entities.getEntityProperties(entityID, ["velocity", "dynamic", "parentID"]); + if (edProps.dynamic && edProps.parentID === Uuid.NULL) { + var velocity = edProps.velocity; if (Vec3.length(velocity) < 0.05) { // see EntityMotionState.cpp DYNAMIC_LINEAR_VELOCITY_THRESHOLD velocity = { x: 0.0, y: 0.2, z: 0.0 }; Entities.editEntity(entityID, { velocity: velocity }); diff --git a/scripts/system/libraries/pointersUtils.js b/scripts/system/libraries/pointersUtils.js index 53959b91f8..a2a1e674b1 100644 --- a/scripts/system/libraries/pointersUtils.js +++ b/scripts/system/libraries/pointersUtils.js @@ -7,17 +7,14 @@ /* jslint bitwise: true */ -/* global Script, Entities, Overlays, Controller, Vec3, Quat, getControllerWorldLocation, RayPick, - controllerDispatcherPlugins:true, controllerDispatcherPluginsNeedSort:true, - LEFT_HAND, RIGHT_HAND, NEAR_GRAB_PICK_RADIUS, DEFAULT_SEARCH_SPHERE_DISTANCE, DISPATCHER_PROPERTIES, - getGrabPointSphereOffset, HMD, MyAvatar, Messages, findHandChildEntities, Pointers, PickType, COLORS_GRAB_SEARCHING_HALF_SQUEEZE - COLORS_GRAB_SEARCHING_FULL_SQUEEZE, COLORS_GRAB_DISTANCE_HOLD, Picks, TRIGGER_ON_VALUE +/* global Script, Pointers, + DEFAULT_SEARCH_SPHERE_DISTANCE, COLORS_GRAB_SEARCHING_HALF_SQUEEZE, COLORS_GRAB_SEARCHING_FULL_SQUEEZE, + COLORS_GRAB_DISTANCE_HOLD, TRIGGER_ON_VALUE, + Pointer:true, PointerManager:true */ - Script.include("/~/system/libraries/controllerDispatcherUtils.js"); Pointer = function(hudLayer, pickType, pointerData) { - var _this = this; this.SEARCH_SPHERE_SIZE = 0.0132; this.dim = {x: this.SEARCH_SPHERE_SIZE, y: this.SEARCH_SPHERE_SIZE, z: this.SEARCH_SPHERE_SIZE}; this.halfPath = { From 955c15b6830f17cf57932a635a799d62de002e0e Mon Sep 17 00:00:00 2001 From: Cristian Luis Duarte Date: Tue, 14 Aug 2018 13:15:21 -0300 Subject: [PATCH 119/144] Android - People - swap-fix actions icons --- android/app/src/main/res/layout/fragment_friends.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/android/app/src/main/res/layout/fragment_friends.xml b/android/app/src/main/res/layout/fragment_friends.xml index 3c8e0af6bc..cc541e569e 100644 --- a/android/app/src/main/res/layout/fragment_friends.xml +++ b/android/app/src/main/res/layout/fragment_friends.xml @@ -39,7 +39,7 @@ 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:src="@drawable/ic_visit" android:tint="@color/white_opaque"/> Date: Tue, 14 Aug 2018 15:15:24 -0300 Subject: [PATCH 120/144] Android - People - Use designed icon for visit + add fade color --- .../src/main/res/drawable/ic_teleporticon.xml | 31 +++++++++++++++++++ .../app/src/main/res/drawable/ic_visit.xml | 9 ------ .../src/main/res/layout/fragment_friends.xml | 4 +-- android/app/src/main/res/values/colors.xml | 1 + 4 files changed, 34 insertions(+), 11 deletions(-) create mode 100644 android/app/src/main/res/drawable/ic_teleporticon.xml delete mode 100644 android/app/src/main/res/drawable/ic_visit.xml diff --git a/android/app/src/main/res/drawable/ic_teleporticon.xml b/android/app/src/main/res/drawable/ic_teleporticon.xml new file mode 100644 index 0000000000..429e6b795d --- /dev/null +++ b/android/app/src/main/res/drawable/ic_teleporticon.xml @@ -0,0 +1,31 @@ + + + + + + + + + diff --git a/android/app/src/main/res/drawable/ic_visit.xml b/android/app/src/main/res/drawable/ic_visit.xml deleted file mode 100644 index 0ecbf6838c..0000000000 --- a/android/app/src/main/res/drawable/ic_visit.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/android/app/src/main/res/layout/fragment_friends.xml b/android/app/src/main/res/layout/fragment_friends.xml index cc541e569e..6cee738244 100644 --- a/android/app/src/main/res/layout/fragment_friends.xml +++ b/android/app/src/main/res/layout/fragment_friends.xml @@ -6,7 +6,7 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="bottom" - app:umanoFadeColor="@android:color/transparent" + app:umanoFadeColor="@color/slidingUpPanelFadeColor" app:umanoShadowHeight="4dp" android:background="@color/backgroundLight"> @@ -39,7 +39,7 @@ app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toBottomOf="parent" android:layout_marginStart="@dimen/activity_horizontal_margin" - android:src="@drawable/ic_visit" + android:src="@drawable/ic_teleporticon" android:tint="@color/white_opaque"/> #62D5C6 #FBD92A #8A8A8A + #40000000 From 4478733c27c4810775e6eb8ea9feced8357b450b Mon Sep 17 00:00:00 2001 From: Alexander Ivash Date: Tue, 14 Aug 2018 21:18:54 +0300 Subject: [PATCH 121/144] fix coding conventions --- libraries/ui/src/ui/OffscreenQmlSurfaceCache.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libraries/ui/src/ui/OffscreenQmlSurfaceCache.cpp b/libraries/ui/src/ui/OffscreenQmlSurfaceCache.cpp index aad90d0806..6911c34e2a 100644 --- a/libraries/ui/src/ui/OffscreenQmlSurfaceCache.cpp +++ b/libraries/ui/src/ui/OffscreenQmlSurfaceCache.cpp @@ -48,8 +48,9 @@ QSharedPointer OffscreenQmlSurfaceCache::buildSurface(const auto surface = QSharedPointer(new OffscreenQmlSurface()); QObject::connect(surface.data(), &hifi::qml::OffscreenSurface::rootContextCreated, [this, rootSource](QQmlContext* surfaceContext) { - if (_onRootContextCreated) + if (_onRootContextCreated) { _onRootContextCreated(rootSource, surfaceContext); + } }); surface->load(rootSource); From 410873312884b4cb9ad4c24fc5fdc3398c13f105 Mon Sep 17 00:00:00 2001 From: Cristian Luis Duarte Date: Tue, 14 Aug 2018 15:23:09 -0300 Subject: [PATCH 122/144] Android - People - tags spacing --- .../app/src/main/res/layout/fragment_friends.xml | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/android/app/src/main/res/layout/fragment_friends.xml b/android/app/src/main/res/layout/fragment_friends.xml index 6cee738244..62b62b3caf 100644 --- a/android/app/src/main/res/layout/fragment_friends.xml +++ b/android/app/src/main/res/layout/fragment_friends.xml @@ -9,7 +9,7 @@ app:umanoFadeColor="@color/slidingUpPanelFadeColor" app:umanoShadowHeight="4dp" android:background="@color/backgroundLight"> - + + android:background="@color/backgroundDark"> + + android:tint="@color/white_opaque" /> + android:layout_marginStart="32dp" /> + android:tint="@color/white_opaque" /> + android:layout_marginStart="32dp" /> From 3032c7802b5494ef3ac71af7710fd9b1ba985bfa Mon Sep 17 00:00:00 2001 From: Cristian Luis Duarte Date: Tue, 14 Aug 2018 17:56:42 -0300 Subject: [PATCH 123/144] Android - People - Star for friends, toggle UI, use data from API for initial status. Change+Post is pending --- .../provider/EndpointUsersProvider.java | 5 ++- .../hifiinterface/provider/UsersProvider.java | 3 ++ .../hifiinterface/view/UserListAdapter.java | 37 +++++++++++++++++++ android/app/src/main/res/layout/user_item.xml | 3 ++ 4 files changed, 47 insertions(+), 1 deletion(-) diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/provider/EndpointUsersProvider.java b/android/app/src/main/java/io/highfidelity/hifiinterface/provider/EndpointUsersProvider.java index 3e034b654b..d35249a476 100644 --- a/android/app/src/main/java/io/highfidelity/hifiinterface/provider/EndpointUsersProvider.java +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/provider/EndpointUsersProvider.java @@ -1,5 +1,7 @@ package io.highfidelity.hifiinterface.provider; +import android.util.Log; + import java.io.IOException; import java.util.ArrayList; import java.util.List; @@ -47,6 +49,7 @@ public class EndpointUsersProvider implements UsersProvider { return chain.proceed(request); } }); + Log.d("[USERZ]", "Authorization: Bearer " + accessToken);// CLD DELETE THIS LINE! OkHttpClient client = httpClient.build(); mRetrofit = new Retrofit.Builder() @@ -60,7 +63,7 @@ public class EndpointUsersProvider implements UsersProvider { @Override public void retrieve(UsersCallback usersCallback) { Call friendsCall = mEndpointUsersProviderService.getUsers( - "friends", + CONNECTION_FILTER_CONNECTIONS, 400, null); friendsCall.enqueue(new Callback() { diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/provider/UsersProvider.java b/android/app/src/main/java/io/highfidelity/hifiinterface/provider/UsersProvider.java index 13ed812ce6..75f978800f 100644 --- a/android/app/src/main/java/io/highfidelity/hifiinterface/provider/UsersProvider.java +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/provider/UsersProvider.java @@ -10,6 +10,9 @@ import io.highfidelity.hifiinterface.view.UserListAdapter; public interface UsersProvider { + public static String CONNECTION_TYPE_FRIEND = "friend"; + public static String CONNECTION_FILTER_CONNECTIONS = "connections"; + void retrieve(UsersProvider.UsersCallback usersCallback); interface UsersCallback { diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/view/UserListAdapter.java b/android/app/src/main/java/io/highfidelity/hifiinterface/view/UserListAdapter.java index ad3a5cc136..bcb379c501 100644 --- a/android/app/src/main/java/io/highfidelity/hifiinterface/view/UserListAdapter.java +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/view/UserListAdapter.java @@ -2,8 +2,10 @@ package io.highfidelity.hifiinterface.view; import android.content.Context; import android.graphics.Bitmap; +import android.graphics.PorterDuff; 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; @@ -69,6 +71,7 @@ public class UserListAdapter extends RecyclerView.Adapter Date: Tue, 14 Aug 2018 15:28:22 -0700 Subject: [PATCH 124/144] Fix 'hover-to-pause' functionality of AutoScroll Featured Stories list in the goto app --- interface/resources/qml/hifi/Feed.qml | 30 ++++++++------------------- 1 file changed, 9 insertions(+), 21 deletions(-) diff --git a/interface/resources/qml/hifi/Feed.qml b/interface/resources/qml/hifi/Feed.qml index e1c712db1f..135123595b 100644 --- a/interface/resources/qml/hifi/Feed.qml +++ b/interface/resources/qml/hifi/Feed.qml @@ -141,8 +141,14 @@ Column { textSizeSmall: root.textSizeSmall; stackShadowNarrowing: root.stackShadowNarrowing; shadowHeight: root.stackedCardShadowHeight; - hoverThunk: function () { hovered = true } - unhoverThunk: function () { hovered = false } + hoverThunk: function () { + hovered = true; + autoScrollTimer.stop(); + } + unhoverThunk: function () { + hovered = false; + autoScrollTimer.start(); + } } onCountChanged: { @@ -156,25 +162,7 @@ Column { onCurrentIndexChanged: { if (root.autoScrollTimerEnabled) { autoScrollTimer.interval = suggestions.get(scroll.currentIndex).time_before_autoscroll_ms; - if (!feedMouseArea.containsMouse) { - autoScrollTimer.start(); - } - } - } - - MouseArea { - id: feedMouseArea; - enabled: root.autoScrollTimerEnabled; - anchors.fill: parent; - hoverEnabled: true; - propagateComposedEvents: true; - onEntered: { - if (autoScrollTimer.running) { - autoScrollTimer.stop(); - } - } - onExited: { - autoScrollTimer.restart(); + autoScrollTimer.start(); } } } From d20c386102bb8057f62dc3e6e63e8bb22e191d35 Mon Sep 17 00:00:00 2001 From: Cristian Luis Duarte Date: Tue, 14 Aug 2018 20:22:23 -0300 Subject: [PATCH 125/144] Android - People - Befriending working --- .../provider/EndpointUsersProvider.java | 78 +++++++++++++++++++ .../hifiinterface/provider/UsersProvider.java | 12 +++ .../hifiinterface/view/UserListAdapter.java | 51 ++++++++++-- 3 files changed, 135 insertions(+), 6 deletions(-) diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/provider/EndpointUsersProvider.java b/android/app/src/main/java/io/highfidelity/hifiinterface/provider/EndpointUsersProvider.java index d35249a476..102d0995ee 100644 --- a/android/app/src/main/java/io/highfidelity/hifiinterface/provider/EndpointUsersProvider.java +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/provider/EndpointUsersProvider.java @@ -15,7 +15,11 @@ 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; /** @@ -93,11 +97,85 @@ public class EndpointUsersProvider implements UsersProvider { }); } + public class UserActionRetrofitCallback implements Callback { + + UserActionCallback callback; + + public UserActionRetrofitCallback(UserActionCallback callback) { + this.callback = callback; + } + + @Override + public void onResponse(Call call, retrofit2.Response response) { + if (!response.isSuccessful()) { + callback.requestError(new Exception("Error with " + + call.request().url().toString() + " " + + call.request().method() + " call " + response.message()), + response.message()); + return; + } + + if (response.body() == null || !"success".equals(response.body().status)) { + callback.requestError(new Exception("Error with " + + call.request().url().toString() + " " + + call.request().method() + " call " + response.message()), + response.message()); + return; + } + callback.requestOk(); + } + + @Override + public void onFailure(Call call, Throwable t) { + callback.requestError(new Exception(t), t.getMessage()); + } + } + + @Override + public void addFriend(String friendUserName, UserActionCallback callback) { + Call friendCall = mEndpointUsersProviderService.addFriend(new BodyAddFriend(friendUserName)); + friendCall.enqueue(new UserActionRetrofitCallback(callback)); + } + + @Override + public void removeFriend(String friendUserName, UserActionCallback callback) { + Call friendCall = mEndpointUsersProviderService.removeFriend(friendUserName); + friendCall.enqueue(new UserActionRetrofitCallback(callback)); + } + + @Override + public void removeConnection(String connectionUserName, UserActionCallback callback) { + Call connectionCall = mEndpointUsersProviderService.removeConnection(connectionUserName); + connectionCall.enqueue(new UserActionRetrofitCallback(callback)); + } + public interface EndpointUsersProviderService { @GET("api/v1/users") Call getUsers(@Query("filter") String filter, @Query("per_page") int perPage, @Query("online") Boolean online); + + @DELETE("api/v1/user/connections/{connectionUserName}") + Call removeConnection(@Path("connectionUserName") String connectionUserName); + + @DELETE("api/v1/user/friends/{friendUserName}") + Call removeFriend(@Path("friendUserName") String friendUserName); + + @POST("api/v1/user/friends") + Call addFriend(@Body BodyAddFriend friendUserName); + + /* response + { + "status": "success" + } + */ + } + + class BodyAddFriend { + String username; + public BodyAddFriend(String username) { + this.username = username; + } } class UsersResponse { diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/provider/UsersProvider.java b/android/app/src/main/java/io/highfidelity/hifiinterface/provider/UsersProvider.java index 75f978800f..0088506407 100644 --- a/android/app/src/main/java/io/highfidelity/hifiinterface/provider/UsersProvider.java +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/provider/UsersProvider.java @@ -20,4 +20,16 @@ public interface UsersProvider { void retrieveError(Exception e, String message); } + + void addFriend(String friendUserName, UserActionCallback callback); + + void removeFriend(String friendUserName, UserActionCallback callback); + + void removeConnection(String connectionUserName, UserActionCallback callback); + + interface UserActionCallback { + void requestOk(); + void requestError(Exception e, String message); + } + } diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/view/UserListAdapter.java b/android/app/src/main/java/io/highfidelity/hifiinterface/view/UserListAdapter.java index bcb379c501..7e6ccc10d6 100644 --- a/android/app/src/main/java/io/highfidelity/hifiinterface/view/UserListAdapter.java +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/view/UserListAdapter.java @@ -71,7 +71,7 @@ public class UserListAdapter extends RecyclerView.Adapter Date: Tue, 14 Aug 2018 16:32:56 -0700 Subject: [PATCH 126/144] Changes as requested by zfox23 - Start and stop timers on hover only if scroll is enabled. --- interface/resources/qml/hifi/Feed.qml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/interface/resources/qml/hifi/Feed.qml b/interface/resources/qml/hifi/Feed.qml index 135123595b..346481fe1f 100644 --- a/interface/resources/qml/hifi/Feed.qml +++ b/interface/resources/qml/hifi/Feed.qml @@ -143,11 +143,15 @@ Column { shadowHeight: root.stackedCardShadowHeight; hoverThunk: function () { hovered = true; - autoScrollTimer.stop(); + if(root.autoScrollTimerEnabled) { + autoScrollTimer.stop(); + } } unhoverThunk: function () { hovered = false; - autoScrollTimer.start(); + if(root.autoScrollTimerEnabled) { + autoScrollTimer.start(); + } } } From 48e08276bf0711741bb941168f3f1a84245eb7cb Mon Sep 17 00:00:00 2001 From: amantley Date: Wed, 15 Aug 2018 10:20:17 -0700 Subject: [PATCH 127/144] added new jump_takeoff and jump_land to fix the tposing in the transition to and from flying --- .../resources/avatar/animations/jump_land.fbx | Bin 368320 -> 927728 bytes .../avatar/animations/jump_takeoff.fbx | Bin 588176 -> 690272 bytes .../resources/avatar/animations/walk_fwd.fbx | Bin 1158512 -> 1157312 bytes .../avatar/animations/walk_fwd_fast.fbx | Bin 990832 -> 1008352 bytes scripts/developer/spin2App.js | 175 ++++++++++++++++++ 5 files changed, 175 insertions(+) create mode 100644 scripts/developer/spin2App.js diff --git a/interface/resources/avatar/animations/jump_land.fbx b/interface/resources/avatar/animations/jump_land.fbx index 03b1d2c5f60222810b52f11fc8672de1b3d21235..c0034bd091c6c28848b5942acee3c06387e21b5c 100644 GIT binary patch literal 927728 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𝔄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 literal 368320 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%0AC|381!{H57g)DL!Ja5VCI}?X;aY*Ul&(od~@t; zI=g{p#K8s%Z+XH8r&I3o!VURKzuRE?nUYFrmb0sm?W6D5HcCf3N|mx5{$4&A`tt8o zP4(nx*5sP?gL(ta3|^>VU-wrs)z=%9AYThsX776IYsG5rzSgfC%iT@~^5m&(R{WyerLf}6IScVOk@3Ucs>r@g)pKUi8STg9NSAv{o@udAwL8(`-6 zdXqOLXY*@tRg-+pP%gxilzcT5!(6w{uS|K17jD>rLsd<6;7(P@fuhy)IdG%~bD)uC z&T9E9bD)I!VP5ns%WJ$gHw9+aKom%?W`Y7)E+{{^BBcrzg9`NHf%+6!WAT9kcX(5B zra<@VCMi%=SpvGceXBXkvVs?GbjnJumB61V`1t*X%&b0z8pwb0Vht=Y-A=CXv5{eI zS%ZNvtm}jtrt11p4M^7%{;s!EmZgsSesI&p7yCiwv-ikpmZ~*b3*2PAP#cjouBJh) z*6~1nT0O1l1FbsMVmahYs};3O(&`aA&9e55texArz{k9Bqgq*xwH&}svqaVYM=?P9 z=z2}LSM6*#UtamW{JVA~=yrA`>z#Lbp@y|OUfWcyGHOFwm8zp}7W|<;(@J}(A@&!h zRo3@TVCPvT@XFl0no}R~>Utg4Ge*v{T%OjvLD1LGd6uem4ca=82kNtRW8G}S%ba!K z1KyOJ*&0&MBwO<+H?`P8r7T<1Q&#iBjoK=g>dC&&$zr*0VPAWFeqc4}2`|>bB&goP zlC@pSdZ%4|2Ewqduh%!#))n<3TfgD&dOHbDZ_I4fegV^AzHiI=0w%$Umbwj)iLhN` zMA#1-7#FsD%I`66fhSsiY+z8>ybW2PK4D*J=mTMsc~f#G?0>vzIX}_Do}AWeDLc{P zI)8F?%D_e}Z==Fm3N>SopOIEw{aN{1Cs$Ct6Z9MuYoooPA8>vNl7I|d-ac5h-_*sR@U%Y(TWxaL)m zf+uc0^w&o<%F3=L28G?l1N8}8xTz0>?cbE;kTYSo@}}ijSpF^b(Kpza#E|!QJPLfk z3pXr5vj9^i7#ILaFpA@a-f*Y=kH_4787T zSONl?5kLB`lz*Ts&ZC+kgS}ESmM;BfUcb<=g7i{@Gu$2>mgtNNqt5gm7!|`lhM@dF z2Y85W+sPU9p%f!{um<)3N2DX(5gYFCssQ};9nZLKwv0pUlO6FPmf?HuDUa8uUmZ-Z zh$wHfl$9TqR%Uy-nlpej&5~IFyClU=aL_w`LZsDgh>Jq3ObCgLijEoMoHTH9oTFzj zD>mz2Dn%zWM`%ViXEf;~lY{e-|!^Sz|6P1#}XhEHZWozq8uU;)!(_14= zcO4(hnqHF-6738h-!nEcI*E3{5J%M5aft~~20<;53<6qY8~*fjWf4=MQk74pbk6!@ zijoJNB?Yg%EybNc*68VkCs+!1c?xEB!jHV^XeU%K&4Yh5%{O6GJFy8;a|4~wza5f6a68|1 zLWTC?PH_3_>4YNfkrXm{3TB#cl{XzTL3&`I6V6~69BJ>nPFT@F+zC>;o=$kL1JVhz zI+$<5xIhmkTrkiH!vm2F`Ud*06KV#DJAu5RrxVHsAt@9LGQSgk=S{~Fhfb+H?_jf@_@~6B=|uQmE3!{7&!-7I%UaYoHSzVHw=y8Ti746rKrs z?r2wQ9p{d=@jE2xE87!BJ_|O#6W;DB?gZjA&>)flesU3+aTXJOf{t zaDr!onINkNR#F zq!Z@$^<5{7?k6@uuBE3F2J}Nx=-SVG6Y7VGJHd6!KqpiVMKUNJ>bp+3#xp@pn5d@{ z&SEKi&r>kdgsuI>oj{fw=!8}Mkxp3L-*=r5HNb-jNqRbA!~i6Pegn)mp+%U7PPk*B z6Y3~e=7IHHg)rZB!hN0zW`gTiJ)Q6ymcnVCf|(}l9w_bv*Ifgh@cBSw7_AuSyG}?L zBsM|1s;3ho2O%j88)Uu-L4(DeAl)<22?2wV3~CPcT_-%@nP4W!H}!PFT`YymJOwjN z_;!f66Uapao$%!lq!Ts_@m(iO8!9$IRu8ORZPwz3A}NFqHQ$8p!#s4tWdohiei)KL z<6*w*gj}z9=md2*dDRJzu@r9c6wEZ?7;iduQO9-3KqsWVf^@=;SA5qAvxj@=gmZch zM^lC)DZ~sn--P}nJeZ(1UC-ss;1Ng$ZASR66N-!!cY^D@o=(WbQuu?XV5SLYc+)Wx zx7<8C>4&RkT2Z)PPoIHj+*e3fll}Z%isjhz!w*F z)<%dsLHx!OE_-riF2}{PB-wBh)iA|7>8aNz9k3%vT zJ8;4}@glFIj6OQvtFcV~5CdgO{JLAmngr%>EJ3&e_ z&cS)-MT3p>vY? zCe)cA?gVnxz$f}EOh7UyGQoG9@EgwrGr{$np2N{;EQLcn1v5?fe4@A$vGr>%db#=mJ zEQOOi1v5?fa*Bsem|$Q|*f0g@giogUt`p*37n>l@)iW`NzmB9Z_;vG5Xg^il3DRu? zozQqHl7VHa?>gZz&jd5U^{$>yxP_%~k*8p$32D>Boghy#&LN=F;m+v}Z)(o);(x+_*v3vUryoocA6keTSz6pKb^w0@&dDHb=S?lyB zl0nNieb)&EXNo&P{%Wv}PI!i;@PMaarU|Eb)3J*>L~XjBPB=Id>4ZHqeb)&KW{EpN zYBoeiC(M|Iq>wnvd=rMgB{o5B&YP~M6MDaeWYF;~-*rN%x5b^{3eeLD`QAoS_=l%p zrU}3Breh|^O$~Iy2`q#CZ~LwjmdzG-0#P&Z>fF)0vyo1CW48GwjF}@gL26>469&ye zGUzeKcb!n>9dReP8t5^>{~aWS-0zs*2{(DuF%w*k4Rpcss(g}0sn%@c0?|Lwyp@B{q{VtNhfOmaoLfv`dPLS*C>4b{&kQ9o}Grtoa@}^@Z z$m;mfGcjMoGC0dK@P!GR=Ziam=<0-(^N~(?f4=#hFkyiQ6Y3c-A!-4V!H5OEGojf+ zaVNN{>oK9$LL`N93(fC@r@ZNy2`)R|;^;BqK9<36JOf{tuxpXH6X+uqz27l!T!eJO z@MoipA%BC6t;h8 zekUwmD((c?NGHr*igd!1rM@#^;4+p8J*fkIor|joA5IkrA%mk5qQ*o;M+#u+hnXc+yHu)aw#V;t7!Kgvk@Ar2-~|kPK_%kM%K8g2q5WVkQ}aakA(W zF%!9%hRFbk$;?j>CPh90(i1R|GfM|ZouDAWII#>3Y#>d5f&@&YAFI}r@WHZXxtNI* zqhSKNvvfJa#Ijr;6Xi7s3KB4po1dsD&4q#lOgy}He=24|N@$pX?tK0!!ldP=I+&~H2caMV z6GF(H6r9PUWHA$0am_dZ-MN{JFo{dn!9@LH&$Unx#zg(H?F#>BHb1wIoqkw;d7ich3c{GspytQkvfhV+FecQU1HG@O;p3$8YB3Y(9Ssw3 z-YLEsVY2KqeN2e${*sjAP>_g8NZ}e%uGK(I0w#ozX99Nu)`*$Ni!@9?cWSOdm~4hx zimgvr85!2!pdgINGsS}k*0O7KFj0lW$4T&7F%xOLh6(6So3#iN8EPrUgtl;`t&;Q@ z3c{EWLXuk@+-q&HRtFP8i2Qc%p9Ss=T_lvn2L{+==EYIu8kTdpgW`2BTQ~VEybAp%QdL7UI!C3XaE$1G4VX_ zxHgEHNSicFKz9;0AWWV?Eyb8n4Xh4}+-!pmCbX*`ZOtI5c zUrYNnlVYGCj0qvc5>UUr{23I4F`?sQVr(tJxq=g$#Z2fo4l5^X(f!W;%?Oi_&H9)S zLT;tZfPw@}NSiawq+L*uhzTJXdvJHIZ4onZE!Qvs=bf`#5GJ-Qx|w8Hmq0-xCiLXo z_fU`rCW1BFldWPV@(K+T(4D(m5hjUSbu%GkBNQZNLdaz(NX$g^DzaV7#PzDDc_(ul z!eq`ieM~H0tSlg>LO}v1uFcj8(jQQefQc>F#Oi|MiRFJ{Ch~gCI04-$^*@Bk^6mPV zC>orEf&@&IpnU%WY7#I}g7A^ja)$>dr8RRg=uX`o2$StlO9kr_<#inj5;GyB;tt(R zM6Vt@Jur#)R;++ zLdak!NX$g^nzc*JgdEl!W5NB-#9aszvI|I0z$E?CK_#SCP>^7pSbp77TY41=5-|y| z?GT(RSoW2eiL_M11a#-!uMj4sztX`(-G%gof-oje_z8wLpdgHiS`$7}Ht+VptI5!?{toACXIxGFeY@{pFXp`^ga}XF`?(EzngqVz(n37W+Hps@9f-z zFbUkFkBOYH^KRg4P>_I$yts5tX&n?KU_#y;xlwSA`t;XgCem2V`UG_6(ANl)(64nc zQAhM!P!PsMJ;Crb6ofHhtrhKeZtWE_p^pa4nu|eqF78E`MC{eUgl_x0&kL45f`Tw6 znVFesv#vCeet?27Ce+f8QyU1@_RmtpOr(wFUN8aYod+ohlSwK1n7BfE4oKYs1z}7m z$f7^CldeEP0w!ehy5WL($A6y(CWSn?lY1Y+WL~NcCaOE%KtTc~%9`yjC|M_7zG4YQmV%n$|A8E0}lM${v_Z z(i~4fcN)qFlP{o_3f64oMEl^N+fa~zNpi;)?WL-+J|?ool@NrlPeQ&CGm)!k#tAs@ zSieD-9EMsdVnR=u{RagJm?%NbztP7;3BsA!z7;beS|*@7Vc#N5E&jNZ?NT>w_u?=AFd-VkS~4%{T$w8M_~0k^!|;z=U4FN~yhH9}`Q) zq3Y5rP>_I$ZO9)-1n$f^AZFsKs$l}U^ZEgVNuC2hdKeQ{MuxQ=6oicvH7FJe!kDN* zxI4?g6El%^X^yd=I}5)QXeP?V?vi-!vc<2--LoNCX~g5&Z7GY zwjUHTksX?G0=l#MAi|{4L48at>nC1Gu|q)uCi0QM!qSIOkbp^Yi{5($?i@HIW&jETBB#N9b}SjPOsEDW zLxQDupdgG1O>6n#zpVS9AdCsMv}fUhg8K^Y9T77jbu>&scYZyBFd2759}{{1s(eA8 zLO}v1vMV~T^b-^$U_wrQFjC+SIVxu2s-tmv~tFxd^Y6kD^gk(^=u9SRaKQG~1UgFYr6_B)|JdSFscGfu#Hr^}BBlVeaz z#Y_nC`w>V_%!H8EP>^7pcz8ve@W3QY!vu6^$O(i=I@D4Dlk|1P^T}mS=wo6DZ{a8P zf`SB0q=bSS1@25bDP|&7(=Y+&o$)6TCXb+&3YaJ(y8cOhOq8HeP>_I$2Y2TEBxd4T z>*@N=^q&wW1%3k36EXSxVk;>K3KF;@z0#tY91jHvnAraQt(0KRmV8RggnkE0)?5s_ z^T8>EN##>InGBisQ(%86NW^55t+4bq6eM6GZ%mvfaOaEDVkWZ3{m%N+2$O)*I+@V( zYz`<$z(fi97zz?F@!-zkpFJ?~IPdKJ8DSFqvpy!avhOsICPP6O6IyY?^UBgzC`iD> zHvVZt!FjezXT(fgZ+UX($1@0%p=b0lk&bqJnlc{>!kExYzfHc%Cw&VA37EJp?OY|; z?_``6Gm)lgn1J)n)w2kb=(GBmC?k3m6ofHRxBX|JAORB(?&LWyX5yNzVFIq%{yv8= zah=o0#PwuXCHYGz2xCG&iu35JdeR*zNWetey0nbIo$?pNOz4*?iszle7Z4_k&+B7C z(qjKkIRXU=iJAFd8g4uF%$ZwVp&W;cdA`Pm~6P9lL?)7eu07n zOq8IK7lE1tOgy+FT@o`P$23epcUoLRn4~~06|LFm>md{*W~NA z`RRqE2~d!LNpfUJDZ!d;&2M5RE}N%w1xtTJm{@+(!9+cCJO~QHn5b*Exlj}*WpYZBGc?8G2XDg!Ixd0p00+7h!T5YN>#UGNKFK z1=7Qqu!=LR9ibr19nbSl^gS^XS<3{RcShesnB0O|Dqte#THZ#ga!&^ny5H%(r;IcJ z3c{FB_WMqi5v%7!x&U7ZfC5;$h8p?GG^%(n-Svbm#0J2ou{MI+)P?&i8&qUIGPSOz6XP zcD@l~{T>R!m}F*VPTzO%p5S=$vV34} z=`s|AF`@VPT`MfQ7qrM@F%wDBj1$nE%tr{5Igj))QATts6eM7x1pNU837B|r$MTm4 zCP5k|pgX1hLYORnteXiTC!ruQ6GHO+1=J*FB6_v_Tg=2|)i43wsrxs=WINPS!TLn% zyS%V;9SRaKu`QomMXLC>J|-5wr9TPQY(1WcnGo%>mqB+rJVBTofLe+%QFkH#KtUK2 zR&j>4$rBw+)SB>-GU6XG6Ipvc7IdfIKM0d^P)ji;bP6eVsHs%+A0R!92|ZkXG_ID^ z84AMO$;`~G*0x_0!G0(1UojKcWX-up(4Fvq5hnMbmI|22x7XiFss67%Cbkn>3Q2>Z zAdCr}_3!ogM&Qn@r(!0u`h7L@nRTE$6Q3eX$WtIa0TX35Xaxmf<3x4mRVWB!q6Xpa zEPLjG$qLP9^FepseTFb8{Y)Pd`NF#HQcoxdV?yuADf#+y>l;uI#)JkH*dRJru=zhR z6XNmO=*s^PCiVW)$At8s_)p46C`iC0eP6ds>-$iUfC)*zIYO}Ck)Mm1NHaCZ6VRQV z&k-hp&vh_SXM@+EAdJa>O4I}EIw%NZqCSWhcjt7bn2D_YY(D7Dp-hBHXr>M(bVLuC z(N1~`3c{GsdFMdf73)p|ExbiK>{Wg%iDtl^UgCr z4@?3zOh9)Y_#sRt`RQY#%sX44AQ2OKedh`kBw*rUzvG`<%*542!vu6EcW#8qyj;4O z5b_NaBxXX$Ur>;kiRe`;kC=(vS;GW$r(7O{NpfzTOqTX5A^!{o379C)XDgHks7b`c zf4~lbJ8kodnUEG5CZIbF^CC>XfLbazo+u;wHWY+0QRkhid37*hGlJ+DQz7}pOr#4M zCZIdkdfbNVffH28` zT8c4IS5mbL=wPCLhsY~X5XM9e!jH0Z3W}LXtu#zPcU~`uFv(L8NDpI@nVGq=^Mig; zJ19srPL?LLmtvtHj0vr2?6b-Oca|3tGa(-5orQ%ECglt1V8VLsNnjr+NWjDuFSnE4 zgn}?8tR{>Aryo$@jUMwC?aO! zYOi4ey7N^Lgo#u{2NOCQ#2>!cF$oG1FiE~qXMe{{P!Pt1KI`MN!+iwqoGa>qiKJly z&O1koB1{Gr)yISo@@L>XP>_I$oVQJTX&)3MU_$qff;HQ{Vjh@W*DwLy`L!6rWLz;F zOw@A)pF%+x6IFwspdgHi8pMt(;LBgkglyF?0p0ntIKpIVaUD$P@~d-dfc!rw2xCGM zDx6+ez5xYcOz0qa7}8YWPU#Y2CQ_WI>pS^NAWRnd>tm8$zuixP-$6kFCboV@s!RVu zK>{Z7(uJQ0=AC*a#Y|jI4HM9vN+l5{Yf9)~qK@eEP!PsM4JuX=s0m}D27%TnufS4b zCge2@6VRQer4S~&p_XE6HhT7}?&4a~?@$oNga&VGEVYJ$uyI0X{jvo=6u1*nM$E(&uNfzx zJ44DKOwys2VocOsNSQJ^n5gFpdO<-L6Ln{WyECb*n2D62VFJ1{zAVDz5!6zQ37vNq zmI{{Ym({_9c6rkBhiRjrAdCqW?&s0D1fM~eS5C}?Bx;y|?o2O-Fey+DNKeFMV~l@l z5ELYEhs=x4E5$=W0w&3>d!lpH$>qgNWGxfWoe#<*Oe&Yx$3)q>^oN2(Oz1i4x1k^b z6D0_D=ZgwrCaxsSI04;RUjbnfP(dG)^sPslNe(DT#N@X{$I?EAf&@%#1KWt)Ib2c9 zM4F&s0=l!eBElrNqCO_BUlR%jO@@L5OyuDu@=9BwAORE0_I$67($;Bw*sfos7z2CbE_Z=+4#32$Sf_x|tBN3JMZ4A><4c zBxWLd<*6!WB2Cte6VRQ%s~}8VRrE1Q9-2^C{t^ljFtH7qQAN4~1qql)-CBMnI9E`< znwW`el7d9+fhWY-P!PsM4Z_`7Q%B6i^@e7gfbJ}< zgD|nw(ZPgHA*+6GCJlmuFecQU|8gHsn+pYDOsJ)s!mbFI?5-$iOoo5>pLGQkBw&(W{c$hBymPFcm7U=>MBw*s|_5MnMI|UkvnYdbYKp&*Qjy8LR;R0k6^2tUdW z4G=St-qJ7u-RT{GFgXpiRKP^8ou`*nFaSsoV?vK7?e{-R>j(u2+;O$z;skruXV3lS%m{8$HZ_O(WfPw@}B#Yc%;Et=gn2D@q0=kpX z9AWYdYAMD6N5cekXIm?TN!wQXm?#>=KtTc~O3-IekcbJrpo-5s zCt8b{$XX_#JNsKBOhQ`gWuQMW+J_-VFJ2yw=KdXv8@g!tc(onMkokl zqE1$qp&*Qj8ibFOBJIUY$UF@b(4EY72$MPObTFa&9p~(fv{WbvV?uA_sW|yc+8 zWPv*^1I0{S^EKlHbf<10!el$tQfz&quB5I*K>{Yq6jCuz2NTcNcX|YgnMeyXOh9)! z1R+cgKrO|X&^24TzAdDGpdbMg+uz4dq%{fB!GxX-4_%mBu-_TcQOtxa)Gz_v>DLiq zat>;#fQf6*MgO3p9f9-&Owxb-G>_C73KF;@RXQrVz7uB^Gm*7SKzG8e2$OqIOED(u zWzOnW9ZXc=217v@6SXFM-kBwdnYb2d#tG=oLDl>6eM8c8vI!;=?WAi zV3M4?wt`^4gj>WL(Mn=-6_`-VUpZK2NQMq^)nQNF;RmG^#p3dn0Q{Zwe2NlB9GB90o`fX3t{pF z)KbBkjT}7PM!F3JVNB?O;rCn?scJ7BOz2sOS@!&b{Z2@4F%v1&lRMVl2$RE5OGQlP zZ}AKI4+;`6NpG<|pVYj!J|sZj8v3(FG8Bj|FOwyBr0_56#bTFZlRid-1{0bC= zG4Ufry1coOz@0gLJusP~Ii7%Pw%7Y2O!D*v(i1W1+O%X)J17VnC(6e0ERkZNAdCsU zKlWDBnSyy|c|S1|>AZ#s=+44^2$S;t^f6IJbRQ^4z(fgp6ABVA@!-z(P%#r?)f`Vi zcUFfYOd5siWt85H%tUlwLA@bjCiHt%#O_oYf-qS#SO*jO)U-W89p&>-5XOWO z>Gu4Fwb&4#CJ~e1S{A{YEpVuqi5#vOC!jk`hayaNLoF4o*$5#|QhtYm1WeM8M0S;G z4AsemkVgV{LWhZ&(C;M58Ykeq(`6XKP;Lf}eVkWM|8YZAS(?=jo z3XA~K!<*Wj6|4J9;t%~y}f4onUYd}C{YOTjy5_+&Mg2%tWfFVFJ3dcQnEzc(i^d*2z$ihzVV@ZH0maOq3vKgs|5o zo0tiGc2L$_49+`0+7Ko~ZMvBdG9L;OGa=+#C`imi^vbY{nMm(z#tG=oRXf5Y+OC^P zRe2Q@Bw*s|_H9M^3>0LXNuF>q6Z$-*tZ@Rm^Y<8piEE4wCh89UODG6qq6Xc8g2YTj z`4}&`)&9rY9tskS6X~PtH3jaB8z*KWYuB-$JHy8zOs+vK6)+)$=W*W1{Zh8%OD2qHbNrKtUK2wI-a& zqF2RCWRKT(X1#=zGN4q?no=hyNZ^jea^E6NfPye4^g9#J53D9QSFmQh zn2FR5Dfgn|T2$oo4U3D#_noMI-fq8cXPymQluFo|>OW|CoD3k3<7DDGT^gpfT@keCS}_n{y$6VYqj|B-hla54P3KJ$OfcRyS6p6~8_ zcl~y4|L^znczERXdCz?I{h4_^XFfCY+1crmZDP*j@eGjPgvq03PMG57G;6y5SQp;& zTwahDkLRXOx1dEw{|dO#a>O)k9(_NwGLOfr=sebE)T~J}X;%L#{y0egWTIa0v2)zV zPVc#^I*%8R>ub|1^&UHC;DpIzCrR$A@_3b`Yx8)#vv|RpJRYwa-N2|xv&MFtG-cFG zAL-({c<~R6MXUB6I|t32IlbGYQR8MzU@l4TH=F5_lvPrN3bPIbJd>sTMBDYN`$f{&4YWNA4)gJb))TFc|n-|vvMeTDcVWp3Xa zILO|JL z$3gb?72*0>O#lAn5FU>=zI3Pac)J2mc+#@}&yb_^-!Joe)uO$T@U7Yu`frapSI*LZ z-+Yez>$P*jrEqngf=WRL#N zhcq2KoQ{6m5UTFUft20E8q|o*+eAMNTtxoWXN&ZzUljSxs7{$rs81D~)~D(Po)PUC z(?c|q(KOuO%$6&S;_)i!@+5!h|NqlJT^`TRUy}44nfyufy^QlV4GTw7{&p|ii=O*E zE!A|y)EiJ98w&<3D&UiAi=y?*9`fuQ|K&36IKUY@DX;hxjG z>u>&MZdTe8%Kii8=^PFoUbmBfys-)3;Gr81vU#|K>ov}U^=t)sI7`;2&Xe`-rjczn zCy@=>Y$tVXcaV_}_K*dh`^a{^_L3$=yU9uUJIPwRcaYUb?I4#hnpQ0jfpe7Mf!ai# zkoq?=eB+6COJ|pAy5!d}1DY<<<>@l7VhfU~?Vh*;&0hYM))l~mTips-%=OY)z8C_Kn@-rbDSUgF5uw72M5_atitvGI37*M z3o0-keaKi8`j0+w(uE!%(c=;GKuOBVk4JmsHI*OlA6qDY`%*tRw(oxYkut~oIXKAP zzOQlpKW<;sg_2F7!1i@!EXuuo1M!l|Z{H-wXZ&qE#N3V2|Cg3Ji{x+Lpcfq5*C2?) zzg9TN-oE{C{XcHsxp+YZw(mB^qTJi}E?!dk?W@12Yp|zt{z6JDtivPtn^0+Ut2*3_g#YPw)2OV0_7rGqu?gNzc`PzU;V6J`Y}B zIqok6;uK}_5QT$m9xRrY6CUQ`L=@oR&Dkm_;%**sxIr2*{PKMwzn=vX-yeXsxGh0v zMO#t5Q<=zgM;2;Wum?TdI1OE6G_6`5E{6Or4-M|r%epbN6{r84T;>8lUwf1c^rOWm zfvy~K8)*G$H-X+|>j^Z|-*jmaP=)h)j=6k`Wq*o?9%1r%2x`s2Lj+DyCJ#Gskj+Eu z&~m~<7*0e19){{968EPx5_B1SRHWT^GC#ZXeq!z?0v+@SLzG@T8vHr|4Y;-z9qZ`(D9WTku_ifVK45QkHg$wMj*vUw1MmlGaha3Tuuu)6K2>%T`-B{p$668Sv+qf>r<=AmCiIpJXsPDBA7X6>9u+&FTcFn_O& z-nBDBFI>%#^{+ETqetn&QH!<3!(RA^-y9DW$63!4dwI1J>#i!>cdF%~1h=f%^+WVi zQK{ej#7D^^4HuSbx@>IQ4QRx-5lD~6(!$g;O-#V}=0ugDK;O}E1G-K{J)r5rZ12jH zgW&Voai;cMUA9gCc74peEK~Mb%-`-;%w8v-hoF@+dx<=}cq$oD4}4h*I)Mb#6x?qM(Py2w(zves8B;nQ(y=d;z6 z(b}?or&=Bi)=64cj0Z;@*V6MrTzw~}kA2O-_=o8?x*Rm#36B3&_VfdOHd*2Vw8e%3 zusplHDq%x_--kp60KK}e6VUASnR+7bIla5o%R^`s2M@*^=ZA|pMVUN2#z8g@QzFX= z4`*>A3h=OfWF=JGsS@%X_mCJ9xSFsuO%!zA`$6=z$x6!1C7Eh4Z!5Jm+nt)SFjREN za=oDOdUf$&9;FNqOh3^@5Y<=2JU)MXJq7qVx8XscZ(pQiMEIPwG+qs1DSe(}?2dBFb)qx_GeJ z_`mQlVxl(Sf$2B8_lalk4-C zpC68Uu6`bNZIaJJ!u)me$4jqpiZXf7i;?U(HV>ibCyO$)_@d_TY(ytlb{9Fvs*4Byrr+h^XV)U) z!H#s`_ce79&`kf)#qRS*is{$dZ7hJFXH7f@;_UTLY}f(XuOBrLX!i14Iu`d_{X86t zm(K(BlH++?2~JTa55}8i^AL~gHO|8~oQMKE5L@|Z)S130s!d`BhmU(xO2;GGb$`%31=q8)@3;d$ zUl}+MjDM`wuLRoWPz2DyO-BJ;oK_3yBm1;~c8<6Wcw*a)z8D}MEN}M#&et> z3=$+eMg2_?kIY`@wUj(#Dc1{vm=Gp6I47YpvEwoR_*olh(u+0VEgOihS{9sr8*;ybKYNy zGmij%ZhO4|Xse^SKtCKG4zxwv?|?sceq1^O_gwuv;qe)=A(s=tBK65<8_G8cPm4Tn*WS=!pF52b&!nJaofBHV>C@y~cU4-l;GT_gBqC zm3H?wO^q4D^foA%bE(R5d((xKyR<^zb zbmD+?py?uy&jQ?Y_4Dv0Nj?vj?>Np6{M{TpxZ@z3heBMhaUMGDQJ9Bx<4BQi;T}=P zTIWR*T(l^gY35YIvR0JC_oh_E28Pt*$2Ua%cJ>#Yen;?&e4_=CjHXq~Lqw7?JZ!cK zDm|XN_bZ}qy}JUA=ic)hm&WNbdWjp*)$cX|n(fy)i!OlUf0LiyrO!L)vZ(bzpkEpl z0iBTb8fbR=OkIq7u6`aW9gxoh<;?N?q3>P}9(-_+&BJS4uW=rF?NgYCAr_M<^7wY@ z;?$GW;#OJI*IjXx^$Tw*pri>^`=kl=nP@_#&$gj@+%~7`e=inoV>GQ=9^&>Z!@~lv z*wXXC0LS`SmXG1}F4J#xX?4vI`18$R6L9=?zFQ6GZqHVL<=O4IJM97dvEvUexN)w} zT>d-mx%zpib67qPmP0sr7;%7u2Y(!7^Y9DTYn%s!*D)cQth(uC(Xpy?uSyrh4) zYm`E;`s`8-fPIe75HDayRR6oP|n9;zRa^h@JB_~Jz91I|C(U;5T)n|Sr$WbuW* zB5|(HIq`($x#C4VuZVS{PKd_@?iHhLVd5@R`Qpn-XDGhG9#J-mH)s1& zza6}hnAJwyyYzfPmrwfEK(9G!0yNv7YXZ)K$9J{h$WmYudynKu5%K z&mUoazFEqj;-TX)`8;?9aPSa_Ql$hI}5V z91b2LaEdZ{*nxv=9-5{}`ZbyPzLBbbb)Coa`UCG73B(D|zy8KLFG$m9gVRx_Tw^o~ z8DO@m?E6K8>5{E=LFW5KqD}_T@ep$pDm#A_lo~?$UiVtW z;q|#VJ(*r#i-T;h*E+6YulwQzRP1$%(X8scz9;j);&mP2pYZw*O1{^r;fLivH}Jvf z$@F?44zj)e3)gGxxb7}0+o&o$uJ2_ut2(boQU4XMzs5aP?{WR<3He@6Kpe+)SFvPA z$n?4|4zj&ojO#V#buvpWUf;lIR&`zvKJj1idI9dKdcA%vN50po;T&G)XLESn9S7N7 zzlrNL=5?EsYVrCKMzgB(x?j$J#p|bVPu1&n>WqA^dyVJty7egzuah{)_WB82uQ9Ki zo>q(3r!tyVo!33i{8zkw1ou?EUf+9OzSoPgIlOLsmc#2dILP+;0bH*!uj`#ti`R!S znpK_GyPf~9cs(BXRJ~r`a7n(`d5<}~u6=>S>!vu!_IfO?*O=Ge;RNW7s={m29v90H zQk~arFa1}%9*%peUav2?%Hj389A1Bg)025^s&`qkm)KrkhU+!v^}9F$6?@(8iaNY* ze)YfN^#I&c^?E(>x_qx&*5mMcK2A@j*WckF+w1dkC3{cfa}93bL=Y^I9a>8` z=W>3VH5)w3t#RWb(Cqe@dKvCHy-ol2tckf@<-YhE~uYZJ>I1it2A`0+e9jrxlem{n)G>Qaq zG{iyn`5_6{Yn%tYJ7uP2T@N!)zjUcu7)-i9Uq%kE7ed;u@gsdJIg<|Fjmd)97G&oi z?Z|O;2aw~J4JQ|ecO#8zR3N<=O{;di?~zD_yFXb}Icmx;#H)1~grm%L@y)gZ9~Rrap>$u6`b_-ILEl z(BA3tc`z;H;K3FL**s+6dX4j7T%<4$Exkj?8p9Wn^=EmLy^psiYuT0{E299^Abbk{ zKej=D>fiJy!$0%_SY^O|Of9 zzO?cM&`-|@fnIgh4`>5VL!bjZz5t$FevAg1oi9_L!aY|%4-X&8=YcnaW4vU2pMwVy z2iZKF!}S{H!Rmp+Jj7Ppj%?o@NAKs~LA=bn==S(S=-P`x=*P$H#IdNSnUm|R5t+Tv zC)!l6i}p9&f`S-LtCokk50&BJ%Gk2s`k_nsmRvAiX;^s_=Xk}#2)pF4Z%eWl z&}JKta{AkJp(Qw9gdTwTG5s$s((}np+;jEw@ZpJk9xNR=z6;;!5eE;hILPLq0M~1r z2m8kg^I-GzM0-=i4XH1#JGW09T4L{7VOc7<;%Q2?QBzV|=sioVX&m2vrT_I*vmwno z1g;J|x|-3nYIz8Iq6`mQ{*o4{-=w9y>LgPeXSUwLyW*p~-UQaoA@Ww$l55>4% z<2-bGrZ5k~*ELN|*EVgRIctG^=IqhQBlr2+$F@40+Vq@$s#Q(hqenKLveyYbkowip z#@^LZyF&t_Y1Q%&^ZdWc!)M%c_48o#Nzw^-AQD#C+Eo~!RN_?jHXq~ z!=9JF%fltl@h76KmVoii^lETC)JJRp;&fr2FSJ&-=)}}Z$@O6TGv@|!X2!c&`2hWK zzYWmr_PKP$S7p0Pz0V&Szm?B}S0u-~<$Q69GVd=1;~<*{{nwIyX`BZioCtlu`G@;U zcK5rG#)F!X)ERwpWYSACLi`wAnD7zpKmQ9kOw}Q+D;khnKh`BH*ES?in4U!xqiNOh zkpAX(dFT}*&N^b&5%^ua*#T&iHD<kQS!DZ(ZvQu0O7Q)dt$*g&WYZPj>;$_IsFP zTk!epI8&Rv{Zl;HeUQ(Ca=t62;*0*A*Yivsntu3GJUD-r&jZh% z<9a#_rzn$$L>y%E(Bfk`;UNSkq5u!>71xTd*awJRL)(a*t0hpj_L~KTNoR@8!RE-Q zrV}z~HUiafo{p9~PeD!I_d-RCrd7*B{-@vN;c$sR`8+lc__sx7J>c)AFFF8yM$Z~( zQyXod9|{kE_Qvk@1KRdi8=#r~rpxi?A)Guim+$cTPw_DDn|vOM)^LoMVsMHwc{q%N zY#us)DJMKc;Y1YRA+!5kl+yh!5_Z3f;SBw48ql7kSS#%`QyWf!fWHo~f@C+hn zdj|dW_YI?I)$;KC>+kX)EzI!`({FUSz6Xw#;$&;_#)+?D}>1=YxCl2fy2STx5Dwz->H^|Z@6W} z=JVM+-6^gueMl2aFdl2D)ds}b>no!N|18_4 z>b;(x$K}eXo4wUlRw2#@X!?wY1wO`enCI zRO46KzEdp^Rd_moO~_P?2W9+57mu7oFy4)-Fi}ggM>T$bDF-K_01xp)SCMh}AKyHfJlC`@ z8R^xKw7z0RzIrp9oKi20q%KF4{_z&%$#50QHEd7vtB@Q{yFl*z+89Axt_Pq&=#a04fz01qB7 z7ZLqlE+YJ2E+WRY^dzdauTB&>rwa65i3HQ^8WZ)F%^;GD7ZKsci->sRMMRql>f*s# zPn|sI9L)#gn}ddLfM)ym=I~o!JY_t%3gDyK#ciDD4|aQ8dKvDy`gzz^Sw0V5K^(so z`y8hzlLzgJl0C=fAsp9h{QU43C!zoklkzrE%knl+Q}Q-ZMOl&5*YLSiACDfC<^4WX zqW)5<$;*w@s@0n)ztx+lsMVV&qLR9J=u}x59uBwMzlS?h<0+T6BEXOHjF%wJUSAme(N`!qD&qPtH|ad7T0T>hfg>W z1$fxz8iNkF#-N?9F{u5QV08AiEo$#s6J6E`MlHjuk~8x9k)xV;l4J3|{`%_Tp;uL9 zc(|SR8Tg&;KP_G|;AM#2dvHB`GCCh<_WEtSPk`}IgJ%`M{iPRVAc$-6k~#BXx6jl& zanIGyLw0rfJWyP}bC+5X+y!3dl`o({DG zI={m_Fn(gMuZ%v0d#-*SsM_**@cP2>TTj-tICvm&kj=vhT(7ZrXP6pEvR3iCGo~_{ zg$yuXtgP(2sXb~-lKuC;oB9ause0d?vA2$VuNUp$c-CfY%;9w#9Ata_0It`V*Y!-) z;`L#SW>x3)Zgu`EUXRB;Rj=1Km~nVri({U)c3lpyo8lnb>#?|AV_tuU6HxJSy@zQT zLaOt+t=WIY>*2Vk>h=0Aa}KY6okmrg zokrcdp(Cy~+)aEnX1Um|Lx^~}QD<@BsOyyO>@=#%>@@0IKpHjBQe8asZJ-PfEtXtY z%74X^?}BU8!tr{X&x}t6*8$JOE^ILY_dNS)!QXG<^1HM!`D8AignO=j9!@uw&jW8H z2M-1fB|AqZ4^}wHK0h45^&01)N~1E<{=+@c^rf$<)TOVflS^MyyUp%U-hpXUM)i2g z;lvgy%_y5ndGM4vxAZl2bLnd;W$9~b45Mk)o*zauR)zB5r#Jld;QT-ruFvO+XW*WzpNG7r@_8uo$Db&fWKEAYX$s&Y{&?pk@r!sJeS|4Mc(J1!#!6&4^OQ* zcv#HAgH1CI9=hQmn}X4rOtUooO2#A#W|1gXk0+_Y^0^IZeCqJJ2wFJx&+S-@``W#vg zG`qca6@!5PJL$9on!UU-x&ZfF{XBeWC7%Z`9gaEJ{1zNMxZ@z3heBMhaUMFgRG5c) zqim7hC|lHGlr1Vg(;cN0j6|LVBT=8cmgv~ImYKbF)D;(XYA3$bsh#*?r*`5*x)798-YImpgPchYfp*sQ!(zj z`gy3-Rz45ZXO8R!gR$4!*spE0ydCyC1anDQK4B}@(|ZXr97~{Lw4%jDsa6JZqp5D zwtu_&`+)J0L+8UlTeWThG<$tz^mp8I_4822PCgGr1&;H>h_)O&_~Rg(hhMl}<2-oS zD$K*#yz1y0{&%>$1&ZI?0qqcVMVCHwKsA396Jz)L678Rn#E@qs;qr_mk{L~_mWTcA zl;NRTptn-vrLPU(JAkjD8l2~g3gPR4-<_U42f9tlNT8MRzqCltNBVYUyGy;_(P`E} zJ`Y9TIj$f4aEdbTFNNSBn}_P{CH>Mk5571N`hfEf_m_BQ9*TTVKNM9s^H3D@xVGr6 zRSjZ#=3XMl@*B~rw>8Q!?2lq+xTB3T+|kk*?kJnlv}$<}+AG6@vi>{nJstQzF8B!0 zb6)HKdax(FpE`S6EQqHER|i_ze3;9db@)>}7JbBgvmNVe9$e@;=wPLiIg_t`VgQNGuCH#xlSi_??o^Fono!4W!{8zmG9`{teUVkBw@AZT|9A0;Klyh!peaYo0fDA%#g9{{laEuVfX6A+ zys&N5weP-EOyw5TgY;EY;Jq7ENzXOZ>&%(d{mhwEMd3_p7o%y_@{rh786JGjIe>fk zZ2xI_d4gvGHA3Hld!9S3jX1AS&#auzc~2*EW-HG1+4*qkU%2P$=b>It`8-gsIC${x z#=*l}9AxvL(_ONX#(5Zl6H(y&&?xsJQGE3xp`Ck?_+tHosDHZ&DtYLDh8;FR2d3x{ zr^1#~pZGf{8^I2$tzZXrjM21ec{tod86LR&MHelvMc^LknO45Qzt+9HfM&NBG#fqx zBMl6nM({YM2uMNU*kj+D_Ugd-b zKb(jHJWN_NkerDBU0Bh9)c)}mbxDashm(&WJAMPw_54`!(E4fQ!S&O~{p+WZrx{JF zmWQm~s^o#`H@b{7-Uj?!XT%Ah*~`Cnm;}Z{dA2%Wyfk^^5)fyvuZ*^E`BOY}=_j8D zUIGUX!8k>kJjCE2n}l)@);NyxwV-0_FgPp5F&F z+rPe9Rl)eGPM6ByeDe8g28it9ZIh`@;`z{7>m6zXPZ3PpvcP%FM}r|z{2qK5f*p~h@=p_&A3ph^yAQn49XRBA>R z6_=4kJ!LelS|08XP=*ICf6;|KUUDuu1^oMVi5~DbyS>Jb~+<;$hez`8;^F<=`O^rzn$$bR1;!;N(_Lc!j%zF+Q?;X3$v|s6%9GvHvTF0eJmf-;YsNrJ-KQ@O5 z+Qx+ljN?KCl?IpXJJs@_Jw%l}#7;f~#xJd6g9Oq!JaqYbG#O|%4{Y7a)D4_pu8r#r zG<$hv^hn%udUvUJPSJc%`8*H{IOf=8;S^=^kdK3G9!3u>Cp-voB8u?f--<~0Z$*sr zZ$3c~|8b z^1B27b{S*~G~2)N_7>p$VKUAYXshN2;CM-iIoHx6<#P(|x%zn+KZ1h?uHOy0ic^%y z!*d*D^DuFkWRGg>*>e_7K*jSc>I|3ki*6R46PQo5m3>c<_6SL<|Nc2ep17y#efB(X zlzguz9OF3l3vqffy?z4+*uz8MK~^n7HY$e=8?$B403?hQ9w_=M+8mk!(Hn%qgNy^T9K* z&9|VYi*$@lm%vUBz&)M46&rzPWF6?3ZS{3S5*?@mSg zE}$0pE}+)(G?)lV9NC4Ng`>y75e$PI8Y!&cc7N6u_;2QUk zC>m&G{4Xt1{@i^36c1A;%jdx|j$TI(cvjfxmrrywNc*eq#G~V97b)|Ikb40Ux6)z~3I%;#CB5T-oh$ z=^41^>gQqg4Ea0+&EXg?-M}fz#NLlA!r1<$4sl|^@Q^SX)P!B)EQ3gx4P%)n}s7VLT zQ#xxeQoq(*q>9#Dq`J&f7Z3LS%J86!-{@k}MgaUfsM}_s+3lSi_!wM2#NFQlcrw|& z8^qb`E2G!po~xgSgLCBbkT9ELyz~jDD3b?+0Lh+X^AL^eHGY5T9Zp069)6_jph_7! zs6vJgdV69wal`wn<6r|GHM`mlBFs#TKAJQpqy1WwYy4W1>-}1jF0<9eL$^7~@NjEx z20~O{W&Az_ytzCU#CENPKm9J~_b(3raAKLo}(k&bE&z!m~T4L!!HTyD@8vJD_ z)#1xfY7(Pq)$-u8P?bDreGCTYk7^rw0RK+gZwEBnzrCi{2j`EmcY6T+F)bF1pLlJS z{PrB|6S(K<=izv;d>)7iZam&79?wmm-WFQOZ^C4!S=0T;y3x`qh=T_{4zhU=;d+hD zDKcIp$y&wFo+mMyg$yt!2`D?K$URt+?7#on^8wsb_0B2UwN$>>gC1~B;oE_Hx-?56ATy zAJad^i70T-=h-t`^2sw>^2IY-vim?|vhQ08WpoTgCzfO)N4)`L)r3vtI=}7Y8o%x2 zTEFci5hm$7-8A$)^XmC`e*3IbxTIzE%qbcb83Ue)`5j3Fnmwjp;Z+IT_)&MB|>Te@veoA)g1!NgU&xZ#YGnJQ%K!%|k4%*EkQKa3Tuu(52TYqJ6JZgiEhe zgl+~8*$y>FXPg_McOkuz*KBLlrP~65z3~`Py73s1wec8H|CQ?Ep;v?|d0>CfxK*QJ z;2GD-33tFXE?cwt82!05;Ac^%=0Nij9De&6cPH+-`gzD+BcBJ}FAg56tdi^;nLISa zLH1ZE3D;|!2ffv0ru~O|pmn`Q3F?g)C3rP*lwd+@9m-*lu~U z9a>u04lOKfhei~(Ltc!gReOH$SfffF*nVEp*cAA;@;Yar*~`ZjZv@AG``ufBW-tH0 z=%cvj>gVBFBnJ-x9OEU^wH!Ry;vk!c3|y~q9*oy1%)?vVM3VmZPB)R3`g|AgAig>VX!iQb=u^1o>gVBMw0s^ctvPtGUeCb;iGyq&&f$8E^I#RFFb|0j z;|bxzcw+Cvc)~j~lvtV8k%-EBFDSTJBDg(e0Ff0PMdanj5nuA-2)%+hB8btnYI&F& ztqc!4A8rEo{Mr7~3hD*MFPj{Hf_wODY8!xi{Ot8>ZijPov}U{YHg(K!Q6|U%?%!yWkGhp++8Ml95ey zBlc0Q83(9ki_TH^mK9LrjqXshjP6j~jP6jY8BME}hp-rBcwqbYpR`Y52jJh+;qW^O z?DZYULNI5@SO9-ZJYZZnh_lyMMnAzlS3eKBaq@Xcc+7GA&?}aM2X7o?^H7ZIHO@n~ zO$zhyJlTMJoNPcoOEw^lYwM8Pa&IGHmGfxcJZ-XJNFQ>6eJDBf`byFh|4U#rty&&p zHmj3|7VY4Bitc{oeoxWFn(#eEK8El;Mf+aN2jivx<^1p&_gwuv7;TZyLs18g^Mgkm z2M^P6kj=w)T(5B++~XDI;hR~6z^h(_z@}b=pvkYPf~)~81r0)K34Y}J2~LkTAe{G4 zCZ5z?NL;GBka$sdA#sq=v}$?Slb{R_ImaV7$4l(vv)AP-;CTL&JODI%dHZW8!TF?W zo)s7`J^ywB#M$dBqbqJH+g*SA?mqK2wld$_mo5uR<8AVJ2x`YMhr$=9DD(bOFb=YL z&`*@~OXEEF;6&&H&Oh8=y5GAg^{RJMO2?%s6+YgCx^e2g$o*@+X!2TP>iNX!lvS1W zlzpEKlv$q*REs_vD2mavYI#WCstgax_>C?e-K>FsL+$DV&2DcU@fBQehl*z+d9AxuQYrCXh8hiHaixW`svuBFYEM(w+ znNzf9hh%I0`{xwt?3DCWz0aP1?3V9!YB`73eQq^&0cK`>wK$$~xvV zU8!u@%V<`0UXR-SU-9~D+*9>>{pnu$UJq(_ME+b+*FBOQA=B%=ILP*TF|OB`*U2Qc zczpw-S=D(xc<+D3>jk){>h=1U1Mm%364be?e2UOY48S(mcMUjlAq3@X& zW4|+}DB^%5>ED}EBs{+ijJYlDL;(Gig6~>qkGZGo_XT78E8Q;x9pwvuA3Wf7V{pwo z()A};e&5F&@I7Vr=P`9L?z#H!`BXY0p9d<4gNMEcIe75FK{gMsalOWQ=ygb89$NE9 zi6+!F6HWh7SrjqfO0?*58_|#ZjYLUSMxqJv6+~T+eGq<3FA;tjXDS+Y(owXH(X?uL zh&%kdJZv76NI1oA<@8_2*qWU8cyhkp0N)!s|Ls1|KmEi&GyP1LIbH67@2Rb-;LG`W z%=N$Ho~xgSI;k8y9OmF*#1Rf2{Be-Y!!KN~aUML973QJf)*PaORSUtwX>F(qHi?w> ztqf{$ZVF`-w4JJRYc)0e>`1CRuUNz%lFtu4(v?VNG_6`5_NV+V5BKIrX1V2_0_TH` zb@u{2|9mH)nSP|px0t5j_+C9J4Cs8_GeE!aYXHse=<2?A{L=+e=tsc{wJRX6_y;p~kW9m;O+wTq_ zYtLOsPB9B1>yHg1T^g<++dW@Sn)n8jdz0PCY(~?n1G10JR> z{|Wp&zUo+@neEY~B%uZHbBx(qpdF{|0Xnbi1)#S)`~Wn&eWq@4{7>=FLnNOEuS^ae zB5;Z_dDwx2Y#v%?mJ=Sra3Tuuuy@P~RQ$3Rs?<&k%_#CDCcdaZIX-wo)!kz$zSpF# zc;2@(YUZ>H{1JVtqLh%G=pmzN)$(vx_`5t5R=bc@T+aabZ(3>-*R+s zpGo!`zMpK**V&i)fzh;Td3YuMT^^+6ci)R5Et*=tch;ZcVf;z?Ja`@BI6v&iDazy_ z2M5_a^gB^bc-VszQGka>!X)@pYjjrZ z2O_JnSM#jg7p=2$4BfK!uUwpEbgFFMsg?(W(~_2z=b>}VIg-sokG;uYJk&ezE6{JR zW&+J#|Mbh|V7ybS{t%$q%X<&61=>Ra@H;{5<(YaS?m4}?)XPKYIr%&!q;c?Y5vM4V zhsQX`=3&a2a>Bz|oQMKE+&pKMH9FrWOZc`?){_PmvmOxHVx#9v#mlxJaZjifEml_3u>y5QN#6Uk;c^zo>`b<3!_gwuvY`7?&hawRN4|j2jGI{uf zgKQoao-Zdn4(6d9oCO4<%ur0yDtSgylGdU z>)IRw>(hnVK2>W4;OEf7BB0sJGj%xbx%zq7bwxf8L8%-(yuvBU*X4`e}{7t;r z8pIEt2?yHw!$zP-r{3gzUe4qp7WZ8JJRG|wp9db-^M?|gqD&r)ugc~j9@lG}hi^C$ z1$a1{hR~Bm+mUYUS!A4-jA~Y!kN8^}qkUTKkk6Qv2u;aDh7)U%ru}Wn;G2AMV6M7& z=zC2W9!@upEgj!PSj=^r>)fqWOAGV(Jua$xDL;P|VMnN0$NPK7w*Y_Z{hAEg-Ps~G6++Cbx`B8- zdJ{2*(X?u>r$^jWhKF^xZA!V+Mlu z1}%OK^mM)!;N`={qagl$h9%JK_L({Z_gwuvn-ZBtTOq)u{Jq>lO=hqZ5wj?z^>$guwmqYFVjdeKZwkIx|m$T zXj-*A1m01Gho>F)m5xVjr%iMkG!>3#=Jka4OUKu_4*aTTu@7ibplm*-oKs>K< z6L9`G6J`r^n`-rePJJN+>(fQ*e`%5GV%&4}^HAxbd>*J<9N+Qpdyj($9~@-!@EX@^ zoQGcb73Se_`UC!gFA;*Vj#mT`H9iSyPO3=kFEJ#NYBwaTwA&NZ>h8q+Iqt-dZzG9; zgGLkE7)`5|hqwpI@bIZpNa=XQa??5_J`+)@>0;Rj0o_&i3Ao>*ckBqzx?fHKt!?@X z=#?w0g7zx>`Uc`B>Kz4o_dNn=x=8&mEmHj*_gwuv)Oo_egB{2Gr4bJ~c<{$THV?mW zy~cU)c%(27$=MCjs)5#M{9^}Hv&0SUALNe$`b3~d!HKAk>v1$XAP?#FdxvadwaGPI z^~hvK)2iiR|6^r%5H>Em|Jft9Hp-d55BPOw=Xzkg(sheB&=yvqK--1E?<+J|yBEZ7 z420vUqcfnkI=>SvpPS_jG#3y0Ps(doG^`B8}tx;D=L`d4DMc2iZJSe=6yh z#(D6?iO>g}f4IN&$T=#r$FgmiJqu4{wwhIv=@8aJSZUc9p?^}OFt<2Icsy5I-6McIs|Rm+3$nKC@&`&yP>Kd-;6FZ`mNB$OU+=~6YWS!tXuH?+cl9`rgB z=*xx0K(Ea(2jiVZd2T>odFluB>uW=Sev)1hXu2@-V`{VKe~O2WFFAN{nqHGwWMfX4o^3_UAH^pU zt@{w+`v((F2R(^vjHXq~!^IcM@X*P^qjWqHUmy?|*0>A4&)L+mdug05+hSG$UF+K( zpq;D2@y@pyS3!Je;9Hb0RjGyN|uQa)O|{8KzMej}d;>MY0kAp)l; zlZPER$mXHxD@ngJ_U?>8oPdhoiFA$8EM$QB_8Db=XHEE8vbFyG??f_rBk8Gn-l&Kbza}|?!V%7o%esj>pwoq_d2gD z$2*aHaC$Pm9*Bc%um8gJ8auALe<<6itnohc2&`<`%V<`0UXS|tU-9~D+*9=)*Q2+TmWPANRuGjcAZr{(6>=hW(>l$rFT1B_fnZ>$fmn{{^0YkLN z<_6!9M`i_b%jr5K&$cc3aZ^t+a@a7kZ|-Pv7o%zDd*U990x3{nX(3_&O z!TRjaXX;gqovJy$;uEq}@9fw;}V!*raY%=1GS4zhWu^|PGt;D-}YfQL#;HxdWj z*NE;c*i2oy8A)x6Z9<*cyjHmGn@chwy-d5oE8#CZ{aN@%*gSeAJ2++)Y zrA5kz#jiibLl0UqWVPCzDwYf;-h2a(&Tb7)D8C+JXhZPK%uA(@+GMSjZfLXK-N zfOIb&Nj}^*mds-`ty&(gYDu=9V&kPIhnANf--9b;k!$DO1OE1UnhW&NU*~{MKYb18 z;|CuB9oQ4jDax*IS^9Z&dC}1V=m`sSfVR$u-%+58)c?{V)vdK9Jy-wv!KH$H9xU&2 z@URxAD3gahILPLqolZI7Ap$3&01tV)>IxV2pCRnwc|aI8?}>2EK_k(gQJqEYe)x)B z38F=_Pl-fxte=X~(sZasUn^5j8BME}hx@w9@G#qKOzAVIlGi%ao#P!#HC>v&=mT`z z*|O#7($@{nNiDgs1uP$vcN6GUJ@vu(C+DaM(6hYtfJUvhf%WMk^}n=8wV=YE;$c`N z`8*Vbay%nT#3{<;Asq+VJUHo<6CUDlA`0+ex33yuy48|ccH5SSyg(8|?zj@In~x^8 z&zeaPo0kvD2`u+B5KtE}69jt$R_&A`sc<5K@Pw_BGUp^0( z4LEo>j8l}!!&w|;^Wavwoba$8C!zokqUcbRePbvR_&K6l$9toXQ>P*Q$*a(Kk8Nm5 zb0OO4l8+J^d_Y4M>yY-XDw36|lXsJ^ zbeDKIXL=TS0K_M{z&S)|-cWB%gL8_$?N)qF(Ma5L^^ce48_4H@H-UqPES#cD9`bRJ z&BN%b<%9(KLH4h54Jx;Umy0&|9P@^1kBEm#|jZx%*2 z1Uff+5YYEirvm+>lPA!6ODh4*&WEX|;GV0WhZQyD^FS@-;NdDxQ6>-1agfc!jOyit zhl@B71$gKguO;|mJC)yJqR8=cWHbKK)Fb=>n+*jc8g&!+cMcY89lTZ0{RSn_zI0vC z@7x_hvl{B+p<&J6*6$$k!& z-zI=_iiE8cpHs9D_gwuv%r=tG1JQxw+4FszqD&sX;UJrbd4`fbsFvv7>We4?%FoFYRbNvr?*H!` z*y}=^fQr3tWULOam*7_a{aznzQnvm6_I(28;X?X5v5Ni$rN1eM*Kc!-bJB5oGQEBk z2iabCtD|AB@4*SE*z3HyW&28X$Mxs9)qlU&olMKNpZdJ+RiDG_=7;6~rr-{oo=mR` zaggox?q-rcY5W;kB2GksF@2oL7$oYRf<_rUKxZaCLlxZe(eQN_(V%O$Q7)-P?mJhP zEE;Z0j_%o&^e%8Ae=wSczW-n66n&~E*?NjSBO~AME`25yQP0om>O;3uO_#*>tH3=Q zx=8QA)1}4Wo}BlHR-MlR>+jkJ=M+tA(h+>#B$Gx!_urQT)@Oe{Q@hod^j!U8`l*)k zdGI>V!9yxeQ6>);agfc!VDoaq!(p6=0zBM~IVl=_AWpRY_!VLAT8H?;-ctl^7wi+b zFMTdpQola2q)RuVt+@}ecwzui)p0&i)uL?Qsg?&lOJ#Uq`iU+->V$%O^zWR7KqtFx z0s2tS=|KPT9t`xCw~K+EbuAj`uJ_>QIZo{e;uD{22iL&te3^PQ?z#GT2x=ss2O^DQ z+@FI}l*z+g9Axt_u0c8BAqyv>01xerV??J-)iDQ=gQ)G4b{biaU*4T2#p?HdVH@pCOnJW0Dp(GWUX^)oG#Pb zm;+rWrW(-WSDypVd|RyQ0yNXVbkTF`3j9BI(^8-}b#(%oy*^XVz&%$#538-@^T2D$ z!NU!lqD&rM;UJrbfX3y7hpRXd1$e09la0vbdgP3ujmgMBdva-t6KT_+KY8cLNYZJ{ zO!AEVQnCfPihQ{?imW$oBiXWvx_Gd%QicZ)^rLiq@-c0elX;EVrJ649ZngtD#I_iW zUlu!2K(C(z=O9IVhjUWL&VqA@dVHx5+OOVD8)ysb4WNCd|D{Dbep`%tu6`a8nse}Q zg5z5GF-}n?4<$Ir=3!aWa>B!XoQMKEP$qf8y_P*hZTRa%1)?LOH^vu4m+L+jbx+ly z<}Wd&o({C5dgr=O>nul7Lwos9U7D$j2m9vA@Q@hSx%BwG(Y~^H&Ka9hO_w?oY6E@g zMILx&F!;)Gplgcrf%fGYf%WrU?SWpt6P`b^=2?RH^B;=8r)VASx%zoH*it?ZMYbHz z1U}&uW%6KPE!lHy9-?u*#;>Q};Y1YR;lku5#MKLZiBHLsiN`MjiKzZdiEDGhh<(de z6TXY138G*#@i8=!c#gIcb(-!VTw17$hi)yE;o(CODn0({*{w!?OP7K1N!Uy~pw}1Y zf${tSTR6TS+G!VvpI!x@p~XIe?=iA+gX_PjI0CHSzQ!%iXV`R+o*%a2o~xe+s;xQCB0UXogGhLN@&97hlE%+pBOYq3&s6v zj9jPmMaByQP}8{eNO=1YYJd40(z$pSRcQGb4P!K|+Usfe*2?g}^wZgg=2_8>7s2tF z8K=wkj{{2ip-aZ<)j(?>fWJ#Sn71Fq7rcaXh*CPkIo;WzyTI}>2fG4I7pecHMXHbB zo~xgS%eL});1zR>myFwR@L+?3Y#xr{dX4j7*j8a4+J(AhYWo;vx>u-}>Gsqy(|ux0 z=7eEyGy64YC9Ll=Ul?)ZkkDb;Q{mm|RYabvYl$W?npQ0jKDNKhL)iQG{E@R(@umKf z7N#aXGz4>qGSPCNuiZKV^p!84fcCp*4aQTe?YaYPQsMw~ZA19pBf2p2Vd@jO=j!L7 zsJ(n1id;B&uxiJ_10M(3JeiG7 z(Pf#-GBBQL;Ft#Vkicx9AGXQ|Iza&6@r-6w=XKZ4{}rz%;hw73>)ZJ9 zz3vsx;dO&99A3A=LAKX-;(CpFy#yzq;^X>2$1;Re=XE~+zvA_1+*9>>JsHXOx@8!L z*T3QPWZv^J6iD_G+v~BoUgKl>PdE{JkoXVxd;(mZsi5{0Vcy6NwFompcUq4p7Ia=B z+VxaNl(2Fo5zw|1+8?_S8H_)J3a{Qq{Rv6m>87FYnRkp3EZ>!)x#dNzhw7tppP5%2KuYP53oKvAEw@k zd#?U5eYUfF9(c<*c&I{3c8*LQ8sZ?Eha_CDaUS%X%1rwYJZ!Vmr~C(89&%?Fu z@_7iF%E5zaR}LO*agfbJ2CmmQ560aT=D{m0M7;SWS?(S&5^DwiREm zXeXYu%T`=}O$%{BlDW9r_Zs5M@m0iA8BME}2jA|$%R{$~DWu;@ILC9*c=+1|O#jj4 z@~}9-!<4=~fo3m%srF}Z4+lM75Bxr7>?xq#JfeVRx5w0{aL?7x!^2)2Jj~+Y!MX?C6_r;Z&T(4_3W z7IZPH8Bp3jT`mmI1UjexRiG!#I0m%e@{vH(Mcy-ko4Dud=ix&i`8*`-o+1A`37vX# z@ZgGrY#s`5y~cU4cTt#!N!>;ZEgr5C?%cXV*zIMEu+Gq6VaB)+q05q6!uQdmL~+}% zi7MA`O0BHuN%f1KO08xzty&(!`Y6N0okwNw8CtbJS+rZ6Q~HcRTA2QeHVNgN)39@` zJy<_?0i07rctwKqM~CQ3KtFQL1={K4a-g|*c!GPbejaoO$mfC3=Quy~>dV1{Hx9CS zD8}^~=b>9ag?X^eFd}p>SP(t?HYJ8HZA3J!U`8ySTaQTTY)_n?FrHY^copHLy_Ily zwwExOe}G6}G_6`5V)`q?!>1Z^O2;Eze=a9l^$Q2%8Mgm^_JHs8V#kM)I$-^HFZf$( zb?Tld{XDv?4L$&LEvJz{r#Hv|?Xf?PsXybMtDgs>fgC)n<>0|%00$4#agfc!cU-S= z9^73O=HXsyO*BbY4;5NgLWAPX(Nb*^EuQC#2JKspjsg}f4IN2;rrCg!WX?WoAq_gEEYv%TK2l0X%b{6w9<7Kew`L2-1YQ= zFs!Ps$Y7qC=$L*J5yfa)wLGM||1J-+21WAe-Od~z6|blcehV#C+zX6{1jjc6-OKe1 z(3|?e_YApDg>!}m)`jma>ipFi%;C5-=QC)JogY)14E|F**m=n3fp?07hq*XKnLMn; zK{gL&L&^yc{x}f@cxaOFRut3xt0?^ZXOUpwGf~GwS4GV$rHT6Z#*3EMTPQMX)LHan z&kx~*d)tNQg-e8&8BME}hqFVK;o)-R?o$8Nzx-Il^cP)L_{{>>3jtPeJT3K-A4 z>q!A^viv5{=U#jQy8SHp-l9b#;GFIJd@ry(J3ppw>hY&|upBO*2TM0wt)Njno|`_M zn-=|+?7v=+?MBNmoT5w~5^<2tLnBW~zclvjc`i;s#m}BEGn$1A{4aBg(uYa5*1vyF zQLW*Uo~rlRb5$?-Ugvpmc-yXo{y63QBlF4A;Fz=ubIMY-2PHeb2l(@jG*h;>Jpn zR(?)VmD}-IHZ9hIXJD6xv;lhQ$^+mYJ%70$(48)X0`2lL1L%HF3xIwZbpz-z^cWb^O~*K3>yk8uj~FemaRvQFEB zk^`2aV^8>;d%AZ}WiuRWk|b9>XpG{bdEz<6QTExKDg{pqc)c z7AYV46Uug%dhdZYnY>2d%b3V!G_6`5gcFtFfq$*+ zx5dsJH#?C%@hk9OYVsk@<8#!5R^XX{?Ib&(_Y8u%?pOF*X;%~BoQ<(l;WGfwNiV@O z2`(PYCjBWMI!=+#1JRL#hd`X7Odg_ekj;a|y=Eib1a&kaea#mod0)*%W79*aQnm%`CCM$@Y0;i8{1JREI%pmfeo!l7p* znRXNSxz44{oPN}KR~PWJe~c;6$DbGgt!HZqw52=LYk%p2c#iQVP9B+jw3zazc<3=* zJ`V|9ICzM_Dazzw2M)4%Xg#%@@DPR*QGka9YflraI_scY^^K8_sWFkiJjCEc6yRa!n|k6i?e|eNGVY5^Y}fIJwY(~5QnfR2 z`BfCr{?J3>+_!qjZHP1S?=}j}3YmmHFq&2^53gn_!voV#mBu|My)E9B@`tH;PKo7YA=UB`Y!QZ~TS1kxE&)a7VH3tv9XZ%R>onS^4`*&np>{!7tu%j*renH3#FFZWZA5 zbnLBmAU-``A87Mf9?(^~!S^D4I~)Yg2f`P3N}p*Fr*3+Uqr; zP{uP$D`#}i@@0H}tYb#0c3m?{JoM+ayRi9C_~B5?=bsN7rf*32W|6F;b=%dlE_}^y zwZ#2;?r)#Q%%3yIi;DREsC5OA539?z6#M52*BASnKRzJzu|6OA(#V6CD`)dz%9!Z+ zkm`$(4?C%gFdq)nL7fi+NBvFmVH~$AU%oo4oIY3Na&9VqMSR84Ih7CP#{AWMNPfiE{PIKX`1^HmYx$3Q zVn2ClMX|s3$3kMi^2|9RUv&JwS?Wa*@7E7LCiY)EeOT--?K4;G>-NI;AEuG#d_D{t z7d;=Ggc$koIdu`{!)ZFG^I^o;&=tLCe|z3Z9k{^rEGoL8aRr-|p9$fA(LUcgMTN(O zTK(^zQ`Cz_nzMgK=^sopeygdfQ1CIo6j^2QF}| zp9^dD!rj*oKN525zdzRFC;um6z00&1v0gmJoT4SvJs9h6(Lo*S&8LLMbkYB2yofrI zjc594gC-@6c`qel-lm=jn>P1OD6@Q6!k2d>C2Xv6PeR{MIww57|L%l?RhlJq{;6z2 z+UI+HXTzG7asThmDLOGVO1@TTq5RBgg-L}x2omp$9!h5>X*;xTCex> z7TzMB+1qx#Rh<9xCiBI<)`#!6ofaB-&Og)ln-M)9vbM%}re956g!!n*8p}n7+^1F#n+QUEi|4Licy~m3jXOU&A+7`KtHW;%oEvF5ir|-uA5? zIM6q4Kyl~NNB_Kb7d9Vq&-knPP#`&XW_WxBw?^sjiQjbUXMG~_p?rLP@g4WU2abv3 z`B!cb`%m04LG0`Eje4ZIi2oCBEE4;g58v-fBhUGK7&S9`KBUf!@%fNRU4;3tpAPDL z=>FK>Bp)_XN3xL*dlp^kJy>Vj#&2K9jDKy(=JPws0j_QT^f zxOJ&lTC7Ve(MG;I^ZR8>#rd;5`ECT=Uikh%8hOs=!=rPe=R;O{jL(OUsEaTkzNLdY zABN8Qo8-ez>PR;7p~sz#Ge7w%Z|1&Rw`OcFye(tIvAmf(zWXub`ub-xj?ZY2+2N+H znav*>lDVPe(9D8g+@D!xc24C(g*ksUAL_nvy!mU3QpNo~Jl=xar}GMB`#1XFmVC<$ z@jSF;&pMGW@o9--e`dih=hc32vwt%iOC!(ud{{6qdOoD?h>;Hmsf#clexZXpA12KG zo8-gi)RAoDLvDAe=k`kX?rJvMOPTjsdWVLO#n(R3Fn-NGH~rZ1NgH>(eyrKk3)?r_ z{#ya(j-S7EZh9i8@}bVW^T~$_9iQZQE&Otyi07p#dB%$S^#cQ67W?7x7~Foo_P3bv zy!zHv;`vMK_5S>lyJMa&oPV4~p7Z&ze17zNaIcP$4=1RLFdqs$8M<;hALh{iU-a+O z$EhRP$cGm3$DCEkS}K!B!R_&_gG7A4v~;hC-+r&l zx-7vhTwnG~dC`8!k*CD|>viuJpC4KuzQ348p7Z&zenIqna3;t2{*e3W(8YoIa1|ZY z`S2Y5|3%M-->4(m$cL46j(G$6H_aIF&Gi{g$2;D%RvVpRo4!lWtg+N{3l;E{Ik3i; zxZ5NYK2|E>u65-SIzN+B`Ot2``Q*dAc{g&r#W(HpPI!C;w*#e4i1_ZkV0g^usbkky z=JxCs9{+{Qyf5^n53CaVkBsas_6t#M@AiO_RiW9>Al06zOcV9J+SzE@}d4`-8g>V$VkduRAQmH zKR>^Etk@5azu>mwJNd5Tlmd-JJQwXJe{cH5k#kJnc=?%_@&3c)iZRb0-k)?DdCup< zfhE!N!Sslc4;4~la8kgyuYemM%Np1XB1yr!y6ga^o7lbAxq9DAHKZ(&v#e08k)EH zmMh1{jL)T)yq)bipuw$RQ-|{{xV^uurr3Y&r2M`e+`{kwZPyr)U;23X{`)lYoX>}2 z&qvRPxcg${L!IYhgx&J0Ff_z2p1Nyy&~P%Pik3&rb36b4&Wp zt(oWiR%On{TS}B`*81|5&EEe0TzrWd>z%+$wR}^;n!d34F#h@T$%pWG3U1d7-XQMB z{crCp_J{Z0B=+B#)=0#6nchXkzHWc;Wu?UTjiGtvyP8K;Stj!F)dH7fJ0HHFk>`9q zoO>~PKA5gC@*!bajC=^tL7fkW=>IQzJ~VqF+xc+TKeT>E#bfoiFS(-Ot_L4&@af`4 zudgeYl-722=d@FO3pRKs?WTG&AM~ZwT;aWbU;iWZo(gOF!sf&57yq4n(DD2K)Bg7~ z@|@3yLd&D)gA>?gqSmo%_fm{}=tBo}KAfcgzv%hU=H+bX!^6)_X|Oc)o`yw!Jeaob zx@z@58(W~>9r*$cnhecqaC7&44fcNVW`pjz+ooN8W1)s;hhLxeVp!7`HXoL}^1qr7 zzthNbJ|E7mh@KB#n;4%D4=j(74-e5noe#g#|6jB@*zH~o_4)$O)q63lS(yXjlPb@5 z4)*L7q0avI&(%9hBhA@4*#CSjdaTEtiSb)%o0TzQy(b;ivHmsv|3!=SW~*`}*5`&b zd*Nbz%xnKEvA%~!nzOO~!P@Ato^?-*=bXl?W5jwpI;dlP7ybW5i}k8&awOK>ux2k@ ztoK>_zY^>3&`5JO*0bJ-9_xvjF`i*6zaAsjo6$iX>znESFZwfmg|txbgAa-S$KM1} zzP!`drtYJ@3aJZyuWp*{n|Z@TU#0oeeeJ3&^KCn_#n-dJIbZ73@(I_xe0RcvYo;ZP z3Ts-%efVD+{z_fP;ctXGoxM5OWd=IQzKGa#4?R-dGdZ+KEgSYtZoY2YF;6y*)58ab}FFx7D_xp}nzR}0G`C8olmG9Zb z+kKzRPxX~8FvIs~Sko6aA0Bz@eDWbYp6nL>oBY{_<-1%p->yxHm~)|z|GKJ%i08O@ zWyQYcf81llZ>Vbr9gmrR;rc@~@|@2HvoU%;c>QAJ!>w<}$cOvspw5S{>HjZ!J|wKq zc0QyXTI1y^)Y(fqT*#|Z=K=5Ik;lD$?e5Ab(Qs-;--(km%I)o#vA$`wjBl1y$+)ZI zx88!VrY~$h%-ir+^PynAr3q*2+$rwo;qeySZkr_kraZA#VG+Me#>D=cK+pG|5`Po; zu zs55(6#^h6tGY54YpIPU+8JW2@Ov&uFY*uEWk}qa{*)=_L!$Tis9{T3X%)ITt%Y3%l ziOl6;O<&l2SmvEiKGeLm)~4{!gMRl{7W<3dSS{i`JpSw!uHSWaU%@XL@ru}g;%B+$ zX6v|d;&{o9t;GIyM^0w@^C3^hpO-6Qt#bHdXm-{oYesBwo1*7KR-YK34?U@iaE{#= zI;ivElFZP!E_yyZKpn}(_lI%CD>*;K-w?m6^^fTz6K?VP4?dXQvf9@8p)VATUu)WX zf%WfrQ~E#Yy*&HQ^vpItZdf1I^o7lbw0F)YAA*~W=Z5=V$gKSRBoTji&QBEk;e2pw zvv9h&zw6^83(Gq5b!N*oo0?BbUM$*6er34W*L?VX@lF4!e5k!SdOmnvVthUfr!K;L zc#IC}d?@vgzezp}p^juDAByBZm;Uji6`Jm!_I~`v`{tzI{>jVUw(8er%zfdSjA35& zj52fcWh5Ty=1n+wOZ=9>yW+QpHGN_8;oW!tYCaShJ}cpP-%c^(&)xok$dBBo@`(F+ zczoK;=9}HZ_fzuh7w0Rt>r(OkqR6n=bMS(jZST6x|EYX%wnfhe_ud%!@CbDg=EEX7 zsPp0aEq{}I7*8F^Mn3r0JeX1V@mjvF754jXf8z_^_~~PPi&|dgi=TAT>Cn2M@5gG6 zZ~fN(zB^lW^}ST>GT(u)rY~$h?B4oU^I=!~ndX(>jcZ}!E4bZR<6#l6;qe#TwkJ;& z`S9}IUqrrWeq_FhqP^>0c~k6b{$R!DVzw8~*WLD?%7?plM9+uB5iy>Z=1~`6KCGaF zIv*Oo_czIh+0>D2iX!_A-;IdF@(OOrPDNmQ(8ZGtSm!zc{Jig0Fc@s82RXXABDO-IN+9f%?I^U6z>&NjK zBk%dyTQjt|lYXn`6#D4TYj0ucUs=_0?YTQPzkY7%1Mvl}S=nI7Pi4~*r?;wq zdvd)7M?NanU`$0XZT{RtX=OjjseCB8`>*DM-NL^=gvXoRVjb_&rp{Zjj-PLP=fe-B zk>`9q%-k0}A5vqjS=&urg!yos4(fauvFC4+4f z1{Ipsia+KXm*zd0?~MhY-tb0)t>317Y#yyQX!K)gPp|pDLG`^kl@FEoUCewqpZK-g zh031^H1eF!hfxQj=Y!ib#`Dr)>LSdC-{_#uhq3!ZSM;LI)!R)SxWIF;tA83ASFl<6 zjP&o!!7h0q)arl#9PEKK(wv>E*ZYg;v2K!M#QI0nJs9iX(m@^TeLuU%v7SjCxWKVq z`tzKK_1s_luf+PjG}4@n^$rK4$9m%a80!r-Quko2@1}z~);ncgVN-z{kDUD`WNv%H~gbf&yo1Q_M>m*7Y2hfv=wYVaKM11?!%G?4j7tB2~j5a zQ&#i#xs5Sx+qO0x(hsf~YfNh^*$lC`d)FQN1FM}m(wIbJjzi3OuAy-gjbZ-EL!m5` zCv|spl-SJ>GfP!#-67_dQLRmbm{(q5uR+YzQYYODF?Wh;?RSWI^AuM3pU4n1Lt%G9 z%&DWWVGwhRDC`-CIo~TR6JuElJA$$O3M=vz9O)Q^HN;qHh20M^ca*{=LCn)9T@Eqx zuxf1=#x^MI7l?Vfwep9NA?|~!wU!uLrm%q!a|S4E9>&rd$OPN~F*8uL_D_thRjn2L z8jaL@yp%j?9f-MQ)k*yjGeZ>SLd+SguoocaR#Dhah#BAm(jV*u4;QH!EyB#Efq3C5Sm&RBInV%)G0xGZ1q( zDXiT0$Pnir3cCej##2~7h^cT{Va{s2dssjylQbKh22 zM~Hd)q@y8b4yhq5g_!%L!rp_JH$h=1AZ8v_SeYM@A@0`-b0FsF)_OtA9j<(P3}W6i zg}n|j=ZL}%K+K)4uskQwNWGB?tBSG73cDL(&IE;xgqT^S>|G2oceQHmU5Ggg8yeFs z?0fxp5Ob#~ti(xZ$QvDHYfT_#H1+_*oEtmI)}}(ttD~@05OZrQY!Ae|S_=COVn$612qn9;4>2QjCL zI_X4+x#bo13dXvru>1&O!5>-G`FDdLT zP}p>cd5aXb8e(oAHPU?$^IlM`nP1UJy%Nfh>oGP&owNSY3!Y z6%-bLn5nF=aS(GWD(ppw1;0CsKzbiyP9@daX^0tp8@%RsXhxI4lfS9L8dJJMtD|OQ1 zd7vS;E37fZJdJgOnA=jdHU(nlHifN(n5VJb5OZ%)t^EsPPIHA-%nJ?iDeMl2c^Vr6 zF~?D@&Bs_%g?SKj;}!Nb#EkxuQaB$Psi$+HKE#556Bl9G1!BR!unTMg#ullEpO+zK z^v&Tzhy{Ot6DK_jvEYw>0=qUpG-R#9ZpGNm3hNIsx4FXRLd;80*xL{@brp6HV!??Y zVx*T{f=24BQdn(>nFb2$1Tm++!p1<%=#f4TF{g!UZ9Bx=#tJ)$F-KunU5X5GzfzbF zVrGTHdPB?^r1JD}h`9q5mIg61SYe+*%+pxj%iu_dsMe}M%o(b%b`Uee6!sv*JdLG7 z%uP|PZHAaLTw%u{W=1IN$^ys`Ph&Sj%z03?)*WK*NQF&V z?0$%u2@0D8F;8R5A?8k0t?h!CGf82;K+HU%u=1B9Lp+VOgqS;7wKfo9&J=~sgP57B zuniFNH1qrh6t)v$=5d9cf|#eV zvR5EO+?lGi<`DBf?DWT*VR%w;ABZ_!hD&S~#Jm)Ry$LZ>NMT<<%+px@B50(}2Wkj4 zFm|arX$Oq`s9Jj%V%`G^TLLlXE_Kqa7%QrZENU7W_sjS{n{A zue)k(A;f}TV?}G5Am;Q?*ind?a_XeTiX%hZxe99pG4q=`X(GnvsMaP!%(+ToDl_7ucSMRn42hz6#B*HDsG5i*_&z_J3yn2yOqlOIU#mptsj<)o@6RJ>M+Rl&NpJ(K(Oe_-Iq z4i64WYSrQx3F`jYTDwr2`Rs~D7 z;rEbE8qSk8Sf>nsjAYVqUh&4V2*Y=hP#Vr_;8+;V@KaYI!)xasMz=MxwCvRgGd;d0 z!wGASv3`do)(2x*HNFYm{iwC)uZHs~EUB1sqD)WWsr?6CCz7l0ar<9kEr@ydH zUA{X>CNCc^F=Sc9<(ol5dHHzJAq%5jzO`l1b&0#m<_le*Pa$R=+d|icu#(px!+E-n z#gO4`NU99Cb8}d#4WC3hX*f^)VVyF3Bgv%Uyb6kLDp2VKU*qiotN3DHQ4qZN;_+&A3`7XaUB$bzsXF0J{U%nQk zlb4StQL#>4zR@I;myegdvMl2AZ6Kk#e0Cu$3!`1We_o3&-=M2Dh$4DogbnVPA)+r}9lT13vOMY1v;p8RdLqk+f+C{)DjOJv`^2kZ^ zFMH>q7*3|s$d6#2qGmDV&}pQTJnzn8$jPH5l}_>`JCm)sg9KqPC&2-l#tYSrY6R>j*Erty5 zLQ-Y8onXjPZTKA0NyB;mBI}gl+es!3=hg5mi!i)UC3F+u_3W_aC7$ndz$_S29o z4M5CHEd4|1d5^Fss5dIY?R-m%A;Wi&R2t5cD_N=y&s!NqIM3Z=oie;B$)w?2Fo0zd zhL0wpjBqX?z`|&TFRhFWPi<)pCrjUhn3-!0CoKQ~_=GOdv?y3biM@ug(+b;*F_*t>g-%-h1{4fD2iOh)1;ZUARl#5<{IXOB!+6rk zVBq=0tW&}8Hpyf#a7hf7MFhjIB$UCxMKV|zEf^|PMZr+Ja0)&A)8%djF;m25A7K-# zBExz9v&E3%>q#mN=gH12)rOxWoiv>1QnOAOUcFkVr_yk)8p5&&!~2m?8qRe@SQyRl zIn|KiKipumk1Tx)VrH1lKEh5>Z`9|Do%?MuWO$A0AqSM{XoC~Y4EW+^5NGJ{GQY$QsW_aEj$ndP9)^M`48pKRoDZ1Se*0TnRaGv*WF=Y56 zl1jsQk~>Sa;h&IB8qRa*S*HvyToXk&*PdZngyC&SC=KUoG%So}cuGxVc#%fdaI$n2 zokoT(Pv*B6a`GFJN+)^dK1;Qe*VYPIBAw)g0jyI_b|#q&U9P*svIr-iBB6AWEAg-} znv?0ZkdtvU@A;$LS!??tdl+J7)oA)`rO)E>wPEPmg$x!$hWkk>4d+!0EY*h3B%L&z z7e%m68NQWd(r_+K#Igv(FS{``L}j=wR>Z<+hS$Ck8UDl7)^M`46U0p8iQm)1f5`dS z)EgP$yokfLh78|EQfWA^*kGwPJXakQ;k+P(b;|I@BvXdl>P;+*F#I7BO2fIH6APmm z{%jp&xcjg*oGje}G4nxlig3d6)I|}_3s@|M3~xeGX*jP|VW~EJ2(n(%|!&2?! zdeTWJd4&(_l#{1OCY|KsUM!1nvVMJZui$cDER5!4m-@&_x7{#%a}IrHnE)|UAi>_8 zL)b>@jl5UziX=lAGW;}2rQy8%h^5-_8V%sR!me&&oie;9$)w?2|BPi3hA$(b3|+2> z#=>ZZ?`VJwPkqXUE?N2$#LV25WH@298lnj2)mRoohW8?=G@O@Su~Zwrh;%B#?aD6J zDZ}@WOd8H5;#d}8c&VFEgmck27Dh9?*-gmsRo$)OWN8w_%-+k$aKcilH!{L`C7Q*M z;d@Cc4d-QNEY*fz(FjF2uWDnRGQ1_pq~Tmek7W^tPa&a zWa+nb8tEi2ud^6(vO?pKBhpD;OUF{}q@Q#$ba|;B>y(qTNhY1-3W6+)aPlJ(N+-Fd zAPb{8`Ez6B89C`LwxLUw&Vrb!U=1g1JM~6I zxLsChF=Tk&rZB?o8cLRG!<&#!86Kfih7Ti|GThdo{U!b~6gODSPZo1qBjC8ZWahNqBJMmVn@WvMoNIq9U~ygZe4%J8p9rVQUp zHN#jIVR)tZ&=95JT)dNo(F|`LkDQES!^zUgbQ&4DyiV3)$jNk)N+)?yEK9YMr%9)r zv}uNlLJX8o#eu$ER5#lJO?>BXrT>V`Wb%%#LOZax`dsj-bllF zjj+X#;kAA6USSsmvs4@2i*(X(UT@4gWq2ydWax4wSC&N>o<%}wIM;S%VKl=pNkE2s z3#{Q}X-$ZkwbpRLk`v&)!mf|D7&81>lFA6@#n3F(hVLbvG@RF3vrZXav^k1!E_TbZ z2*d9tp){P!-?A{8;Uk+P!;7%tWa(mvnQhi^!uC^dWQ6nDaEl?si?x6eZWn^HR2$xk zbkcBMXU;lh_;`}Z2HalZ4W6t`N+^XoeqYfec^uw2g4GwBju&ba}zM#gLO- zNGd~@SGTiNJ2{7R(n(%C&pPGgc9KaaxfU|ZBAhIAYiNklNv@8}!e~y`y%jl`_>6Uu zX0!$%W=`ACCF}|6jSO90>~Aq-_zsdv!+B*tOSR#7Zwnb94d=oEtW$HQ(n-U)UZvrGi;lA_!tkynl!kK| zau!B2{E^#{;YAKx!^zTDA!Z)ycqC*vVIO1c(LIz1g#C)K0tX~k;SSU{lR8+Qv=zn{ z9F(n1yaNRT7vHdbL&30~q%s(|(gsU)Fq|Zv3Df zG{aNcLc{9}Bg4tk1rRfjJ#svByCZB5^+rZG7o4#eGW?3WP=s@J8J23pZzY{HoQv16 zP8mLyWHQ3}4ividA=?RJ zW|RHUBy2YIMusj|5V9CDd>cun;ao0=rP}aZ?L!7g!?_v}>y+V*NhS^FcXzNX!tjSk zC=KVgd9W~=;m@{5hP#dI&6zCS0x@&oFnt;lmZt-XaIT(YF=TiXl1jt5R1!nsg=Erje!U6HA`CxDLTNa^@PviY3@>&MGCc7IYdBfj2x8{MA~Kw?VfP@z zxpI`nkm1WoDh=l{Q7qMlA0nMJoU2f=P8nXdW2mRfaQg-rmPHueg@n>@ekTkIqZvM- zBQiXTA3w;_jdU7yudwB?EQXvsO;YJ3*S=z@cCtn%bg$sjTC7t}_9U5flHY#AvIr-a zkx)9x@55nXG$(g-f=-sPPtNq!<|l}mE%s{^VYND=(B)EK7DI;jBB?Z->wd9R8@`Bi zGIY7D80(bbdq^e?=NAOAEW+?o{?HJm;rz-V7Dh9?nI9QmWc6J7G$c!tAZ9j>q_2>K zrBZKXgmW1*iy_1Jl2jVbHP2Y84Zk9QBAiRAu}&G@l4R0weuomvA`G8GLTNa^Rf&bs z3||>QhW~J%jc~GbH^j_L`}jdv(R)#Za|t+$A;VjdR2t6p-dL&)cS$EBoXg3vP8q(A zWYTbc%@oTb4F8#g%5eMQDHcXEJgy5ed{9YiI9Ymc7vv<@wX+yZx+lzG91I5l%KIp>&d8y2Zk1PWHJEIr)L#I!Qk$&4QSz;-`BB zVcV%U@?OC;{VawI&wGE!0ckiF@MEbqyb0-~;ane(b;|HzB$I~oTgX@zVfb1SO2hfR zWGswk_^0} zb1g@UA;S-oR2t5O8(FFizouKr0BJbag=C#Fyd%k^;ruQ;mPHsokA%{2e!CqDqZz)T z8#3HIV+|)u&(dk6lU$(EV#vwb51@MmSEFR9cCr`gq?26il6A_-RFX+2`8|Fti*PcF zgvv?#WFWMv2Q)xKA>X2m-hTly>X*j?9kcH6y+Uq zNhS^F7iO|7!tlDiLPJ!B+jnEKFq+|kUeL)C)^M`)2|A4oU9L21G34Y9l1eAJtSn2l zlX-iGMy8y!Rcl$NoNP)m=_J1_lw}c4jwYdWl3yRn!e~w|?Tws_>tUaqset5r5Hn@} zN%soE@+YIvatWDo6Xth94%KG@Ppy zvrZX)ZQoE&rQ!U_S(Zf@-j#&XaDE9b3!@qSNMB^Q7iS}!EPWMXrk^#Ouy3e0>Xu@w zD_aa19@j7AfHK^cQf8?(yc6l9;asVib;|IWB$I~oTX|U)VfY6ml!o(rdRZ9F@H73O z;rwYxmX_;}FjM(+ig3dG{b7XL%F-4?hR-6YG@Q#wvs4?tg>=erTcw(H%J5tRV1(Nj z7qcwFa32Y!;ruFN7Dh9?_W{dm z3SBN0ZZTwdYm!RCxh^TU}(54lWs9&_y&?n!?`9pOSR!Ykxm-UCD&P}46iW+-7EOj z-zW#dmaEW+}A;W794LKkU z=X&rg)rKdLP8!bT=2@oVX*ieKXPq+qiWC^(_671Ri!i(` z36B@^Ur#7^@%R;XSY9D;9ZjVu&2V?yEx^x@x-6WNQL{~ zg1B1?o4#Z7wYF**}ZW#X=w;q@Y@vIVSLVdS2 z)oo|eAs%hmkXJoe8%U!fj_-L)>eHasR^*PaJ{qz{?Q# z-X+`?*|we82l2#l824W~3Juk}Z#BitqR^|Bsr5iZh^P8&>r|)OcBUu9z56ike*)r( zy)hox1o5m*gxgZjwlgOoZVqGIUtu(I#+1Q$;BJV=m9;pPLAIT7A)Ztnnr^a4y2MW$aLyaqMuj&|qcWw-E zlZZ6pT_tB>Wsnzm1ZGlOgZ~aeuC^w2Z$$b z$GCq2#GQ52ch!1eHN?&Jgxj@#HfFxW_&YRIh5L)lMnlc2L%u6K5D#&utG&-mmd*@- zxH)QrK;r&|5YK9j@xXS7yJPKDohm!?E5uXn54#HYSDk~LNt}*b5BMP-H;r(+%*tM- zDG)c4G46i@;_e`f2M%LAg>btB$#&+-xoD`VFWDdI9ur3TTR`05UkoK47>e=P)ET=x z$9853#8XF6XB6)L5aMPt#slUFG}O5Eb#l4gjNuh7wllRLZUPwh-w$yo5#xaw5Ko<6 zJk%Mx`onf+J;a@+)Vjj`MXTv{Avf0k;#&`>i`|~}ChMMZbc%UA{odm+|#CzMB?hyBEXB6(A4RPbt4F!S1 z0~r`EPBCLArQ6Q@0P(D?)OUsZuYC$SGZVKSXan)AbZXsB*tVS+4e`_+822xSxO+9m z1D`_NnQXI(CNta46qt{O>JGB4OWfZG;-&}21HB<0cOT()qOa}DlMpvuG46jC;!a=} z6)OC#Q+Rp=PC?vjL#^AnvbHl7pGMBaogpI??r#tAtb3?43J;8jcw(kCvefYKzIT2N z<9>|$vmoyE!+79|XV6fcfK9%NvNO#fp4Eq1SGd1F#Jw)K^}qs%yY~`q=O5b3^d7_$ zM^I-J?*9eiseasg;D!arnbhm3bvrlDcBV7Ljr}N7xPLOlQ*AaWJdg%)x2L_Taq=>K z1#y#%aes-0XsC(%?TtgW9x&ls{zdtCH(!TteUpZESeVb`GR6d_yj{{fWyp}E0mIr~ zYK#dUjjLVz#-GE0yutqzN1vjjMU63+-kRJmsZF>3Nqo`l(KZojO5`LYk+ s+nty;_+OJs|59sYgzTHL5&!ic+s+0a?eqDmePfOtDRe01@f!;MKi$Lw3IG5A diff --git a/interface/resources/avatar/animations/jump_takeoff.fbx b/interface/resources/avatar/animations/jump_takeoff.fbx index 2d72a1371f307a31425f6f471f7165878d46fbbc..4119b2417f72852c4c7860dcd9842e5ceb007c42 100644 GIT binary patch literal 690272 zcmcGX2UrtJ*T)AH5EVtS*Qj7G*s)+1SQQjB3U*Wi1c*X{As{GfY}c;XyK?Q(@)}~ zRLM2T5(%gbCRPQYOHq)wmFeu$&rczbP-r?OX%(t?rCJpN0I&l9*aHAu@_yY%p@F~j ze_0rSB(uLL*4d|@>37iiVzb|s?4%0UB*$r$YE^J@oWdWDIu5!!6GFB0! zmB+?iHnVm#xy>pJz!S4y6zQ*!YyAIp1mL^bUi(0`N*nDDb1QB4D}%zcJlVe_0HChf z&LV#51dV?w0D#nNuS1|xm7rDlHvjd43@kzq-hXoSAZ2(bBrG~pr-Qls@&h#$5E`Rl;=t4Q$IT^_3-c!Ti=%9G_e$di=uRE6LP9S>NXL41Pk znKT0#XAl}wVNz(hRsoMSL%IY88*?%3FiWul0H9gRrXHS6n|n0#lr(SNu4VIfEt@rI z>(Sx>a*c#7ZSK0WGDg9YD);VrWzPuf&cyEo00@xBYlGy8iU@+TUJgvkzQ5|1XCg@2 zDafH04CW455;kdj1G%CYS5--_xQW}`k;$|>Fv%2WGV8n;|q# zj2Ljfx03}qz*!*9J4qRDoDh^Mt+Dw2DyV&*|@%HwI+gnTv)RemgD}iEO6X= z$Qo*GCo_s?I!1G;bdBOn(ybWtGG??O^8zDKAQzsD3wSEnTcy+nh09|Uo#o;1Tn*|T zp-u>kQ5b*ho)8;`t4@I5I&z!Mvb}OrzGS!__f3 zGuYbSkr`@EC{d6~9@j(FStBP82&>`c%%t4nSIWjKj5xU}NGsQ9jm7I`+y$p8;}6g- zfYB9z(a1sbUrPLPppGNj`z2pF2M+}&kiA)V`6k3`)v?CVd&#v-OR;yC{oL*kUBvVuIRyBTfp{G|GLaDy7eu^l%PFR0#}HUv30#w%Ge;SJbW0-X^XL+<--&b z4-a^7`n9P_F=@hMbJ1REwYIxDLXp=nt zz!+rRlo{4|9?n>^8~{uS7?3f-5DJi|J-6ZQ z%2*u3%sg-if#de1EC-bXs`1$&<@$dW0B<% z3Z9E~4om8u5EIiFUTLB&U)%02C#vG_$}#GpYY2FD5~Li-;cTHlB`I$KLV#M$wCZms zWoApT1#K_tGlhc(PxA`HLc$)38H-BzTV?MfJNC+|BNQ3%tPpMaDr;AP$;YezH|D@#JOm>UdPGLXE40SBzK;T)>$jkdM;Ycn zjGf4GLcJ3??#eiZy=HPOQzohf`z=67%_LO zRP`g}dZHl9{v^vE1z8RtS+=Rn8m&Bu813+aEQ!%>F36G??IUD~rhapvdT1gP8e^6i z!s}LHvV_Y=b~58Cr3u8KIC;3Dvsx3Nj8$rTsA7^ym5M_?(8U6rNb-DWm64GuMSOfe zG2 zkyjgdoh0Dc`5JjOI~K{*@mg!$)~UwS3|%6aAM4o-M$Sv}^!0ejnD6|K-H znagFu3B@#IWp85~5RX9hNQ4TgA-#VW@;Dr&(Qo zMM`Y2G&U=ubT`Jv>#TojF}k6t8cy+nN);`S zjq4>>MJYJVu;pvB`rtIV4q*4eX@Ls^r;NT8=!R2LKJH9m%#&gY!z-z&1-jvsR7QbrI3-oC4wFP%j$=+kS?xO1Wr}CENEj5Y9)Y_kfNLY;(VwH+H}H9s?usbmy?2>H6Aqs@ zik8PIf)e85)EX_x%h~*1;4&Jz@s_tN<*$lRB(dL)$w6LfGrGeY9-TD`#Yp0{b%T0L zu4V=SJ!l_w5-v`-Q1VB;d{YWst_ui{k(f_bxP8zx-&!p0=Hor&+w7Jm^qn_T!R4WR zJ2-jusLw>)nW+gpe}hg;bJXwuZQ&C)L&eo+%7&Jk;8|K9WxO&>8KcxD|NfsCk0+3Q zCnhGiUiiyif=(~xs&EC{uZ(`-|L|F&yU>7HqJwvz&6nulwQSyf8s7v>q5`hBHAR1I zwqAGK4gfvc4ZGBGB@}{&?$zWu+6f^;K#?9?;<$x3Vlsq_+2$F- z`&_>2m>76xTCM7o6dschuS`^wX=t(73xnu`yued3%M@XYcMeOUmGN;g@?_(^3V*ZP za``gc+>q%%k(T-IG~{)3D&EKfaP}iY2TKF?BLc^52P!D;c4Pj=TZ{hEtm)dCJ#T8c z&31=3JDAOpxsw?`uQ78+8Qu+X&bPWBkl{IHf$_F7R1w(a`=#-)2iNeU;G#O(^2s@% zu|@q!E9WNryzP(1Os~Oko#4&Z&N1?+_`EUm{tj-@O_&5AIKL-Qsm&Z>0&uM*Kp7hb z)AEGNt!T@aM8hUb5;5cSO4}N)WA&==&gyWd2 zc#bJ)M$9v#JjXFzoAJwYK6y;pf33N}IGN{|MVk}zbot3~%x^r$Oko+*nPY9$&H2?f zn>=P}1&(7r;yGqS3vz8ga;$CN7X0$O#WE(>1NwQMV+OP&=9$5jPiaejc^X*8tie&w zT|CEZ*@~PeS61q_;+N+}@|Z3z9P?bpbIhu($$4@evs7z-d8V?A$(5A}JjX23hM1>| z9mm>!<~e2(dCb&L9FGaZ+VHFGEAp7KiX8KNz;nz{PhxFTD{~w($dg~5*T`dLxN;ox z49_wB+7k1WedefV`?mb@JWL+br7FifxAPp+qa8WVDjdhG-HuO=M3_g8C;J;V|k7VKOzS&cQF@!skt0$`+?_}TJo5(!W?TG)q!7a zv&dtnmgYF-U7llxcoA#s;>dB#9$x(N%pi}MYR_@Z6FkSHI}-EE;F@Qfj{Nf6PaZS1 zD91cE^Bl90l$>WRj$_u4^2;-gJf=%Mj$_W_Ic8akn5Roaj$=Af{PLVa9y7HO$1z9o z9P>MQ%nYuzeamyqIB#NYWsN!J8Sc%mwvWkUrq<^;=1rbs_VXd<*?{Ai-F*1vd4W7; zY8{Sa>UoYy`4aPVX~J>L7QX!Q+)W-cgDamKc#c_*Cgz#JHP0$EzdVP<_~ki~WlT4YV~*fC<`?pqE?nobmpsRe=}fF`YHf~r4(ZIVwwdHH zUEDd2d6nmw!Ci=X%Ib0)vr89#d7dSYnZY&BLp;aq;782Ud0eyJmr{Y zd{=(8{f|6mHrFwq@ftcPaZS#J;yO8^Bl8i4`QBaIUL9Q#&gUR@|c+)IgY9B!LPR2civ>+YWu;JKr1Ym|K0 znxZQK5Za+d6N)YkK&ZoXH)UnzbY~&0lpN6dEW=~@T5=Pt2^M0Gw+7IEf0KO`rx|1 zN?(56KY9Su{qS9@sQa@wqV9)<3W`&zum`=7XdsLBZ!>_<{?Gd|x(NA5?b1!3zK1`q zXh5IU13LQ5|K7^GJZLGc)lVQ){g5HQL+RFjHh}5_3dK68Wc``Ytb!_TerbIzJ?-K( z<}a7&ZWOJn+c7cT=Z)FKBK@3!|wGdRY2M8+oV6hRZX1G&{3^bAjRWk<>sFDs8PSt-E z7U)Jibu!Rjj)hmgw{H0%y4(0SI~slTk|qrlNYyrE$d9VRAvU1OKZMD_I;z&A(5#}0 znnTZC(~(Y7%+zdkc7szDl`?<($D==xb3s(iI<|R}Hmx*8H#;(XOXC}k26}8l_YI>J zZ}c4pG369Ym2QxzRIM3=qv|x;%bBWB3#!1*->GVjqpHqeW_(MkLJ3qQ4Hnw}$dG6M z53xo64-uvRQE1lmzpnbvy|sg-v~GLEfhQYlQZ&##Oeyy;MEV0c7u5gjLc%)VZ~8$> zFZt$BJ}%4AK%c%$)o9r(OW!fn+Wrp}xBo-2{g3u??f<$F78fe%6U{DEx+}tm;kw^V zPU!xJ;e_s=Cuiy>wg=N13E{IV8BKMW|kaMtLuQaYeYQM*%8fBglyK&A?XF^2q@ z>KkSQrdFU(teaE5qR^~i3Jls2LRFH|8&~dH5n8P&MT3dSFC7-?r9H!$yah2;WR1t( zXMdM7(3f7_Km9!WgOvU_qeb=T%ir~Lkz2uY%C&G&nfe@#W2$llvzIGV?ok#@d0aGO z3Vt^Tj;SOxj>U}fz-(P9-^KsA2!Y)X6hgYcx55V9zYK+9UH5-Mp;^`aIk(45_tVp7 z-~E%G(!4fB>!!=E9^3a$+9T46?w`CSSKoH4r-4o?=V&PnPx&ykC@sP;-GZ^5lPS}AI) zrYUi)dIIg`dY~Q|!?Y@V`xk1}jD@IGjc?CTM3OqyZ736fMW?nj`+Rb~V|C(Cft|Vq z8S*<*u^VQCPVF*`$-%l#U5i4qqEq3~VCX&lLDSn1gqnQbW&l@EdD{mS#;~aW*Q-j08x--etEy110JEBo1dR!fRbx0fy1JS1CB z@7gmg&S6Je>Me3Bn5||SQP~RC;Mf|0_Ht$GZi2-*OXjNI&RM)Qu{f-5p`k3WT981% zs!P0ZtWuq(E%iQVN73Jn_W@Em(9wQ<>fSn7i_*ml#A+`xm=0OM1g3P9>Ey$Lo0HG4ba+x zLa`1lrz9pcE6~zqja$5L>18SXWtFztuCNXi4We6@sO~pM8j73?LTiokRZ^#$tqgQV zjq1BT6)R8CnJ&XFhqmlX{fXQPM(a_MsAxGPWw@=GyM_1p7MUz3G%HLgX?gnOAaD0*rsbx%d>3wnC(qHD!&3@B-6F;XB` zN@U27tD_@rz*Uh^Ob*s@B_CxqR{+GeX}7q)ly;pttN2>~ws6YIxLrFTOurvF7sS=F zo+T&M`_`JGAGUb9E_kVwqKmuKRDJd7Mdcv3g1Ks+A}UvdQ*d03LwmV$^=d4`75pv& zbd^QjKwQCxU`<>FM<>LFVIK^6jz+YgYyDUPUHwK2r>l=o$Agoex1;D!#uLJO9q4C) zOR64K`%rU63#97|GUP{B)iE}pD{PDybm>uOR?J$NwVXFM^aTdmP|tnbwV7X~w4tTz zGP=lnY5B2C-h${F;9ADsC$$Vk>yKYcz1FUSfgUFtSOTBFypUpTY9S#NSFIK z#*iOfBgff*E&~e1y1A>`cqTL}=+ey&dYTY*QBSAkHu6g?TO7_^6N|rZ)j_XC&IQpG zdSrKs-=FRj4eqK#_T8>Z(SzHz^&auU)367*6-?LX@uJdIc><0uFSM5{T^s+fpeybj ziLMs7#f)`mM2opA><LPw5%FuUS14H7s}AYBuXAwRlqp(I581Wolx zOb*u3HENR8bp7XE$^T2hJt>{8@cF|v*&foBv`XXIO8O6SE{Lu%H%=x;2Q)FzJ432Y zyZv7Yik^ALag#c|J>~ib6SH8tdjBCRT}gl7=vs#Ma;0ngR13OvUrBVe#L-oFGGXpg zPBk_DZ`WO#$-?O}x%~Bm;nJnsgTiUU=E(x-dWa19(bakilZ2>rrA!fnuG=UyE9Nd( zX1(gO@ivt1!eapG0+PU6OOI5ZY9$6CQ$@vK>{J{EtI=Mr7&y{|t>8)K;0#gex<3O)7nsTHFSIO`O&p(mJR57h(fXM6t3fJ zCNwMP0-=o}V+POF)0vlEX5`Fvfb|Y>y7?nUzW_NGMAzc#)Tvf=0w_A=^#O(3!y*(t zC}dBA)x*jeZXmaU>8hA2DqZbUadZtvd%4o}_dE-_(n^r%!d@VqMk89#HDR8q@qe4U zTFnto*S#H$hWh1}#VkLl<_M%~Ei&XsR}M--)KfU=TqXzW=vqG4db$!HdS6Z5rl&Kn zpGy9`tPn*5YNY-0ilO=s$hjc8_ODbwefY_jqT`0^rJJ{Xl+qKMdv=X=E@o&skBM0@ zU5WEVrEAGN99@UeUaoXyEVQ62y&Q=y>;;nBd?pSHx~458&^2$qaJoz`fBkUyidx~Y z!ohH1zCgMPEnp1!(babWlZ2>rtw5nzN7q*rnl*D50BI46?YxX-em>C7c&-q7rDm*G z`-N6wFnXb=7^E%4VQ>QN<%&Vmr3?nme%(PbR7V)VFIzL+K?+nxM8x1fTij?7frIBu zOilaSEVFEpa1P#7f3m}=##br*)p!djZ}R5cue0PITtj`?2tFSH(*N-ieA$_ z^26N!xftl<&6i1(I|B_%kz2vD%)KR|;+4AuhgYqo%wDc|&01lBS4LeTUf4^d>1arc zX~u5_0k5x1h2z!vs;p5cRRVK~)MA-Hyp+h0-!yY{nGNtNvYg4mI=tk|t;P$ad`wdu z+n}dqrN8?)oC5==Gm2>Um+@9gID128i)3B#p`vN1zwpRM7*%qNH5Tk7IdCWaos!a2K#{da=N6`D5cjpnxjy~$ z71Efz1>tqyvED)N?L8>^T?0eO;o&&q~Y+&M0>g7 z^>K{_UM>Mdys#HZ{(mxoSm3pN4FRwHe+tLTfY)&pigmMBjWtYYR^SBypeA}sj=&$%{E8cAFJSOL6nZ2SITy^q>NTQraB2;X zgDkX{D+j$cFdQ`fb&-_a7I6Syw`RIX>ZMdgVQ(V!SWBRw#0FE#{&tCUbggg-igoC> z^Lx$@J-S4SIZV418??KIp@QM_T7eWaUMGZt@#}0r!C4fFbriU-x0(Xo-wQOaO1+ZO zvLbJ_2Uk?1XwYbHzqQ?t=~Iw%L6gklId8k}yb(mv=TEN7*>Rz;fmWvsE&l-W`4%gvy_F?ZI zsr0vzb3t%9Rh_(O`}v*}o#^cDzTsLaigr77Ea>O6iiXOYn3x5_6|zZGxW;b6!L=Ig zj+0EK4F`CDP9;SVbBkkV;$mG}A$g13-#x(2PILiDY-GI{xfKl8fvuv#b#E&Uu3WU2D_muESiqGrlK>a?;wW+( z0j>+_1h}qm6AqWjrRfJ9eXr7uf7e~BPLC3C$X~ z0HBtS&oMq?Cs`g0*B$_nJT5-SYcz5$m;!yes1*F0j-$YS2eX$e1#!C>3d}x(l)Va3 zVE%}mOsN`{jJ-OFMI&0Az%|%y>e=6}j_&Ocj=@0xQ>p)@{ipwhLHnQAZq9h#$WU&l zKn%JeLw*>f?X&>~Pf;k=Vc@&VY7BG^0e_Zf{?diXx$2uuZO&OHdAd|Nsy5?wV9ss+7D0)eQ+nJN^m7(aZrApl2{LR&{ z2)PwZ*X@I%()Ilyj;I9(=}rXO_l z`Ua6G1yC_ zR5Ydq2!Y2;UHjW5QtnaVAVeJ--uGbH7gD;(^&0yYhHTT*e?Kwk8?KX3o_c{GsE{E) z5KicA0D_Z23=kp>B7mUF-Lmt}QFOd9zPAgUJbmlz(Id#YplQa*x%ruitv^ZWX0FHA zpO&~$bnEQgQ0IYj^goeX!F*B2n9_;FSNJg;Uw@#zT>1KN(t@wdg=D_47fIP@ObfoY zo+R)!_Cq?D!#7P`qPtjhke0@2?@MZSm zVel8?3%+<5KKgr)>a%>a-XToJXNq z2LarS&*}gGnwV}}OT22Gu>yj@n_k$jTD?b4AD?sk^ZJ2x47B|=7tNjhGo<0jtzZb& zoe>p+vuAJ+yhMAsLeTp>1A*BiKrTBG1n?0c<5wE6z0kPVSpo*7&J!>=c9zjc$P0~o zcxw)CxYnDZ2acJMd*`N_qJRG5Q)Xb|6Y1Bp0^#!bn=#}E*QCE~0M~gGiglMV_0BP& zSpiq)pSfeZJuhLPi+;}?d$|Yv2DYVzbbpO~qaTZ$3z}gDIK3NFWOgHpUe&~}efc8C z^>qKEvp04cP|sjMZUw_t=)9 zu#PL;MXR}DlxyTgXXWNTIMR{1G6Dd!JaQ8`7sOTa=z~pVSD3s7vDM?|Gs(hnttfiYw24hi z*L5?{or_gF`e;d@L5ti9W^3;iQQ5kE1;^HRw3jPeVb>Y98va_4XYRH`ut1{il*HH7ghM@Zz=s$c4*ZiAS;}!ZXt45bag$;DpGf8No3w)r+smu71o6k#MBIknedgtsQm)ZqV z^wb5Ll^+WgrRdJ3Hs$`X^D#8K#l$QaFV!tk@tSiBhu3bjmn&Y9dlq|%Mf+l1Nc&RqgtGj0pV>$Oit^K&zBPZXWHEfBA7$dDgivO7!?qT;piju?1-K%rSP zdqrQ8gn!tmr$ZZb37vS&_(Tx^y3wv3yWVB;7KE4N@VSk_j-Cmy$HE}Phsi>aw{0GMh`^AtH%Qz zUK+HQD_)cTwZO|zjyQW^pDDVFhP1%T`(FZHT{DH_WperJhkHne;EihR1#MHCDt?l)EBpidr?$`R2 zoApzVzW62PFn#sij$6;qR-mRm5{Q8T8S=xR++!PHFz~S$80<%(S%bmjb*oysztYo- z_tyZom%)#uzjqze`mRA=>Isv#pjk%Y8ZaU~t}8_+%xeBKdWtJWHw*}L^BV1CkRi8% z>6-gQRJ!&&!O?XK?d3|B?{f>fvOUl&V?h`85{Y`s#9=|#k1PUR+n)-j>%iq^(Wxrj zXVSBt3Z$$0Gsch~UGdLsK-U2jiglAr`79{`%k?>quGVNTSGxXu zWkFY3VG>=~E2NcZL<_nGz9P`&{6aWgV2(!R8wu8|GF#tP|r`q`+ap|b{<79hTS_BxN83! zB#CA)^Qm;OsbN^ZuV$Z|{gnIhRHBoEVQaQP5FQ~zejs?hvjGTW z-iZOiT@;!%AUu$snEdLQls>!t^u__B;ERpJ{G`Qd9@95}&*Uv=nwfSWY0Ap-T_{=_ zzG?0r`_dFW@%p+w2gkKG#3Q$Yr5MSrs3OA7n%)24M&YVge=OX8V_-cJ~owA9=*wYVK0+Tp)oB^i3OFLR7w1 zqEM{k>l+Hqn)wR=-Gg{1;}^gf29{>#FG=$M`W@-;-AWL~d>0jjKfmKZIEnUh1;GQ@ zfx-ZoKW>*^0)de4c_8vvWH$am;NV5BscC<^PFns$I0t2iE*YkIZvGXScTPDiypmAW zaQ%ls4vPO2!a?Xy8*s25g<>5Cg>#wEtl&UWp={>O%|$5MvxIBrHRD%gj@<|vPWoFq z067;l&(v@^aXrw!4Mj&?=ya~^KcA%Z>I*H;x&7>5SccpRo@egoii#KPX*&QwApn45 zP_#TksfzOLUmNY^ir4JI7I?*F;hqOF;)T6Tnt_J2IF0i!4C7?LF@qheb zghP=bziCEq$0Q-@G-F?g$-z3j!U{2=S%DYu@csJu`V=XxdlC#1nD590;A^2;9SYO>E2BDb_QGBzy+lJ=;I+|#fY*Q`!ttumr*UUxv2vIulz+2V?GeQ}oGQqup1g zywuYTs$SiGAgmD8AGsBb*D?oD@jB{&!|Ng1%N4IrP8N8jh0ek}EN+~=uop>P9hpEZ z@Je?g;B~-JI9{gd(DZ|jF4n2+<=)W_)O$yPc-1S)81lnwbWt1NbpnNA-RvcCVnVY9 zF91MP@#n@@cu8IlW}bvd^OzCkB}dK$bFjuqR1Qu%;W&7X_HyN*x3dKYvXeLtf};~+ z!?17g>Q#)uK`Cbf2giyD=V0}=YGqWf+EMfup&mxv-bIUVx&(&8*x9Grvmd%<+yo*|&GnvB)68R(gs2xu4N5UNScli-QdZ;T zSuF*GIbM{~)E1YtgtEoayNk5Xj{FZf7lc>wvm3^YJkWxo#}8cDDWSh3Mc3)7-ZSn| z2dah(6SH8vHbmLnaUN5?*r$P90sjC*< zs&%>l#mmc;$y*R!5#z^YKgwxkpt~JBG}C2tBLjUVrIW|hdkqX@kXylc8C*rh>yaxC zFNda&y(5^I9p`1Xx z0+As79>+n+3j8=osmyQyv$79VM<`+fz#_y!Mbjg91-tL(Pf;N$86V`H`@tq76uRk3zAIgdUZ|Ai=ny z15V!SyZ~5_oC}(0Cf(J|`;mT9N{=qt;?9|j_jvw_eb&1q7qK+Engw9G2P}YLFO=?}K`j7VRE+?be-+_?T`~cd@;hMTs|W;aKQiP8m~&Md z02Wl0$-z3n(otyEO*Q6#F>g9HZjRNMyafUFmWU}o>jOIISm^4^!n@~Y$3)tM+P*jgbWur;N+ zKnji`Lw*#v*02Eu{cDIp!Cn-a)f5!9eAV$0XNeUQG`#BBa@e>;iq4r*?OCLuu7RH7 z-M7Psv58c7&aBur= zTa$o5H#Y(T8*4J!2zkx%_hH9gPjxSAp#NPMpx?LXfSxYieZ!;O10$)2H3foIp%!Dv z53HD4HUMiU3dOqVrBrPuG%LVrUAlMBgyGdGI&E?9v}tE68R+y`i?9B4$$XDkpzXyW0a~Wi~oi*T@ZXu z$GahYAq+m4k}ajd5cI=k_;?%0mVQT01=HHmO;lRtZa7*eqP<*c&8o}LTL0Hom*g)* zD=Yw{mB8u~G?c}3wYn~W)nIqwtp43O-h1)X;s$g}D&R2PdS=V9Z9*SPXSxey^#n5H z$7;noHehvd9VQ3sSUrS7vw~HiTeVUe<}an6z3skRk=BW#L5Tf|Sv|)|%hYA^7R2iK zz2IYG@2&>gZ+-=B&9`ZKx^`84&fpV;s37E4Fjfodii*|#x;U)vqP<+Pq8c(-nZ1(S zB@JN(eL$=_tJPZE9OzY#Fb95ZK;UXiJ>gt^tI+XMpP~K~J?#6|U|;_)Qrc&7)0*my z%MU-PCy=Wu^%+BcT*cM50atrbDAsZ1+JFho8m_1v>UzD#O6ga-_qJ>m4!O#@Vi>f2 zt#l}IE{Lmyn%~#-Nc5rTa(`TCxAkm!iax7pvv9Y6ZNp~dRxnqu8;Hu4OG6x2Ezn-B zTrF$DaAo#oG6oIe%JgM2!D@w%JYIoAYcU$mV#?~>gn*V~BjIS(T61X4s78Ji?RB_p zRr}2Qdb+lEm7$f_ryQml2}COq8S+ExJW4{;`%tADGdWm?ma4J!Xn{2E-rsK>rRP8B z`0`+x`o@Lw7X#~EK6(l{7lc-Ft4dpk*`Mxcy3Ju)egwdw2@|tmwEUWgiq^0uIJ9P= zypG_@Y7|fWlaU5bp;vnL#vJllZ2>f#dwH;*54>JYi2Fumd$+E=3xhBpaCr?2yIEA zpj&f+{W=aA^6S?d&27-HRa=PBugNW#(5&v)yhC+MI29z_STHA}V+L|AXe!tj9$9{O zN1&&h`uJ{a6`x4al}jzHwsV7>zI00~F$iiYDh7!yaTqK?d%0rJ+mped(XT~;j8O=K z(xzLn#5KGhXfz83N_i3(h-t-CQOFwJAn!3NRFbj=df@AFCx2c$Af+o`h~5%BFw(HD zl|ZuYB13*;d9=0xS&6O1AnPg$%?h$q>$0o=8&};xhbCN=hr%_y?BkEC?Q+i1H)zA; zEr_hQgAcY|I>;&~6g>gX#oR+pPF?xDU{TY<1nL5BQby^oR*^|Y;aJ27C@wPWgQ1*`y2)9&xO z-jRM){?^Nw)o`%^faUA#?sdG0oC|`r+KQ}FZTtrr=zGVf?BD*r2}Rd>cy?60+kg6s z?U|Sbb2Xs7s9cS1kK-y0?d8gqgOuUQ>`qqZM8s9zcQlb!@d|ffLa+eK(2)R^rh{;> zj^ws0W?aQvKj7t?L(jkH^R8MqcMu5HLuAMgtkzyk5~6~Y;w1)Hw^3-;fEAgvKyoxy zPj@UDKKEdM2v)^jO8y#_A<)FP>!3y(Xz%qMKB6}tREAK%GmSwylQUY65eF$t_k_u-F47{c_ zF5}T-&mDg9;J%cm!WJvxGM*zPkS#A{$d9eLlnvOrg+j4z8LyQ$6Ph(_fy|YyBd;B$ zQ;SVxBc6x#Qh!V;$90|R==n_N;fGrk*=y6*YP59E10cP zKBBVK+y}>2Z?u;yTctZ$vQ>RH^O;+7wn*!EhtPNy^VWHqz?QSGKni@3AwLQh``Unl z|DjN`2@*#D3rzlQbiY=efK-I>Y3x`us)LbK|y z4T{e_cg1v@#JJ=(eP-upmpb}(Ve%Gq*p9pT;dA5IogL}jxw)$XX1h`Ji+iCzQswC# zCnC3kIXK-#R1ThZ!EsQ+j~@rq{TUAG002sQtCX?E7vltJ<>A8u(m#xy^TiGTO_JzH$z-=i#5P(r_`Rmm*T3QK-Td z+=d^HJeY}X=7B>9vYQzr!{$0_`2#%dKT9 zF3sA>jLSO_Df!$PRSAk#YqUmBLC}$R|9r9jwhigVgx;DEx=RTZdTjtej5>UnzbY~& z0gBjHp^S>w#^W;R)s2urr*8S00=^_JA_eEw_&z9~y!SyF^MJ<^maTFCy9kCCh$K!T zI0q0?00H8Q@ECccLA#5A4pIUIc?Wsrq3umuT6P$s17 z1nMy#64Lt+L|D^Dd=V!1Wfy^ZCxC>JeF+)F_O%@eKK}DyNsxTvBZ71wA%*4x#TTJ`2)hXBK)@BVQwSl0?6=DMF@Q*K!m+Yf(U;pZ70H* zq3k52{*Mm{!-o=5h!`q934X)aMF0;4h|qo*A%iBvY$rnD7n>uOV1&CmXA&9Um#&#l1iDef-c9#zcqhbjusA9z@p_huCgt#99MChalMtz8Cn5c*022CZ2^j=vZ6`v_1a=W5&-u&= zWfKS~I3|cM!b=o7iG*xZ=zQjc|B*7df->^N7K8K?vi3Aa5B#JLW+z56O zLK~yd`G^ocf{;O<5w;_tVG_FtX&e?B78)llSq)Y79c_vDTBKx16z=g zp3E+SM|(aZtVt$_urOJC5k`(=CjrzFAVTa&LI$BDZAU_@QS2fZJo$)FZxkVgDx<^~ zAs2;CA|a!L01@7kGI)YAumuSRQwk8FAs-R8rVvC(OA%j$iKE#`(A5(l!idp?42F)j z9SPDg>>|W<Q+dWT4gAjs*X4>?CBi;3GnZafB3_juT%5*YWHk=voO7!C^ch zgB+BBEl9Y8G9i(W-GGk>$4Dvc9xuKKizl#)KvfhV!t@CQ5ynig9SK1b*-1!m$w!3F z6A3A_ohZHt)h4lvkSXNTn=X?G85EvmI}vW7Oo${@<0Ha(QVK^=3Sutct^R{u1W6kK zBrN!YAi|VCY$w8?$?PP=x$zM}Hkpuu?_}{ws5^ySgw#d?M5sK4kb(0Q+llZm%7jEh zW*t5vTqmV)7NsC230tPJi;yjZgjG`sBFvp?I}xI$v6CRH%149&(+DX9P7|MmX4Bb4 z@Te((1h?sg3@S{wod~Z`CL|IlcRnIKAf@mRN?EXl@DU+mCLx8sGsP#N{VaA7GRg@cp~);l1~q5dPJ~Y=6A}q7yyk@Gq!jL<6vQOK zFq>V3)V2ad*fpCV!ur{^6Jb@PzJWR zsk3G|y9lXe_>i!0IYES}%f%NVb_F{L>6HbD5W0epLC^}@kx*|Xy9lAKd_<_Sl8{2l zmEw!=9)(UK!K17I5uT7TxP>yX1qoYMv5SydiH`_rs|X^@TP3~-BhuJO$Pn`I*U&UV z27}UUM?&*I*+l@RRWMF-g8QF@6e|8Hz6jq@=p+&h)di67mXyK2C<9xNux~ZH2rj%x z*tD7;!iv@6i!g2tI|&lgomoDosMMz@oAlt-FLPj4xBKU41q|kbk_##x^%q~J`umBO9Hxn`d zn{6k;b(9H-1euJF2xmzt96~9Gxq!E73%dxZK>|dWyM-Xaq%F1+VZc^)5?liLh!D7y zkOH+;d=lKYv5Syt3Z2g>YK3it42o^Dod^$5CL|K5NIoL`LrUQkN%3XD0!K^AVwMIw6Iw>Ee^nWCyzlh9Lq(sJVlXLD?O)6X7|^ghT@H z=Oe;BQVN$*3SyG5YbU!1p>hEttlvoxVaZP0iJ;oWPC};XJde}bk$e{+h2UM{li<0V zU4(2QhogqO2^m!1Z95S@piD?4q($%{;VCJF+b9JwNjS2HU4+ms0!T>TLl9xj9@~kK zw3nR(k1#$W4BJacVenq@N$}anE`n^R01;a4BV~~Jgn)wuh~O>{Lf@`X2i!_Bb3BuK6 z%TYp2S{>zC6R6%$oDr!eObXf3RX8KIny~lDMLl~>Tn3ocq!+Fx20fuB{(2rYIW#=C zM#+b*sWCVsQcWrW5Za+d6KWUEh*Xo(0E9YBccaLCGSk4$KGmNy`#J-mIPnJCvH#~W zc1?9YW}0HzpMQ)HcC}+XGzB1TerbIzeJ`95NmCcEF@L#CccUibj7Xa5>UK5#7pJ1;EFx?-4 z$3raphffe{(&+?`nmpX@Hz4$MD{3Uph*XpNS@X+%?(S~bjx!?FWZKo#frqoo8*tTN zwy7uC*_RCC%znyALUGh5ZO8tKQ|y}RdYJV&mi^RIgs>}};-M)3)Es*DnvUIZMkGzs z6f-qjo!zJjI3tp#08lCOw|_iJ)^x{d_L_vM%xZ#Vf9+{PO&Xu(RTH>nOT%zRq?#~k zWlI;~jL0=%Mq-R{+o?0`HAxLMs|l9<{bvX@@jk<&CbN!h-lR<{O(o)tNHuA8WcZfG zHyjO{aYm$?j7{jiVYK3n9#;)!n{<|){kVRd*-tu4D9*66wqt+E-|U*|Ld-P9vOnW* zLfGa0=AkLjRsXrScCfT7&WNO`ZhORmCmU;0V{t|#O@Z!VO1Xz2EPcN99D7Y9v{_BC z?5CY0)TI77J~c6J+0wx{BT`LFM)Pn+#G0Uyn16BGvGeRT$t>`kfA@JpO}x(YsL8sJ zu+I0JevoQ#Mx>f7`Q}hQF3Zud0cS+2$?3~fjh4N#SlAzTft~#f(|y5$ulKbV2*ruI zU_15~Tx8cYPGQ#PSoWt}B!pf1A`eXg7_=jVswC}TzYl?^z-Zw(lt0EQcXV2Xi+`- z@^=>YM`y6JpUwT8KQ4n%obU|Wu|MYuyQZnJW}0HzpLm53w(}JpngWn>d(3n{y${Za zr0Lmr|D>lhuT71>8Id&AO_yIiw(lKFpRfCey(Sr^M+}H-eT)Ag)THJ=ylMisY-t~y z5ve8&xY^R_I3rR`Oh(MVxb47I_L_L8&H4n({+6qRnzXvgqb8HrPl2R&WNNb9O=l#S}dCWd4s(s*#(~S z&%Z&aNwph1YQiv>E$xLfVyg)}U`)mtk!oTZ2}eD%?YYTb6GNwh*xz`QP?KgidDjGh zNSqOSO#oPiGh(j^+m>*Po&C6OoY{}QMJUd|Tef3=>TPySsQ@!gvGab)Z9>@gw|Qzh zc1=mD9nOeF)64*MB}$DmV$-zDB{ud~++nXtwibOw3US^~y+f!;r8_)p0x|B6Gh(TU z(P#qBh^;1YByy+kxLZI?W|`FlJMXW(OQ=cXyS!@xKp4)5y(R!G!WpsGgl!vskDdM0 z38*Fo&-)Sg2*v4p&vxujy3eku$0RdNvFs<^CxmTxpSPx2hg;)}*fec(qZu^}XT+{) z_5rf0Oa8}Rlk~-AHNjr*&-fpqCguLeyC&Jvt~evMnm~-l;*1KY2@CsMAF$UX?N76s zVA)T5K&VOm2fS+nz+jvadrbhChcjZY3ELK%$<97Cl{5RHnS|m5W!jGYaSz!wm6#vT zvFvLf62kt0!shgBkHg0sPP<=}pqf9#Gy+M}q$_R41DP8Yg)<^)s+&;q(%bQD_ih&a z%U%t&1`q?-mQcZU485ZZTqbe1ANmejtzcQdo%58vCb}hNHNmn!@hPDu&QCGv6~w*|&WKbK6UHNO zM&z2njcwjv_pE@Lj5VtXmi@)g2sNqsj7Lpo*zfvkXxf45gEJ!4qg?p6r$A0p2c1;bl%{0ZbAM>0L_D2*pC-!w& z;}-8*dRf}wIi?XvntoZOt+p$y12qI^MA8&Qw=PlLZw|{GH}eI1O+d0)O|b0iUJz>H z^a7J!LF{+L8IfvYVm}^dM65}H?Em?ay(S(>W;MaGKmR46Ce>c@sL2}TtE5ghTN!%c zj7T-fs8M~_r()%)$v7iYO)_1ET@G#8mxX=#D|Ytdrf_CI_!XfzU0&Ia{e;)-nx>|j zX^Le(`ZXc!cPMO5>;n+nrrqNH(mJm(jX=`Wb>^(%YyI0&193(qO##TbT{|I+W!|6q zhP@`)%gkzmWk2N&p(gfkFzFS#!Cd+!3oK){yYpOfWh*Xn@EuO9mUMi&~;EYH$Deh8J_0^{r3;Tnz+1Z!PIv{2jZd)Ecus$Fd*sju7^16gDUJGiy0-Zs-dPwccSGfuyOSp8L3KGrvmv z;*3a|8d|z8ql>&}nfE8XXRnFNTCe-x_B`s)>pHVK^gFO-x42 zzqoD52lkqTjxehUmi-wY2sJ79fk#aSxR$Z^Ni9Ql#Tk)mqCb8u^;)|QhOsy!QcZ?V z&KXzkOjnlY`TPIJ&VJ||&g=*LM<|Z;Kijb%`;lE!*-|r2vFwL_B!vAOh0Te5-Rz*J z2~ijI)jwhyfuw0#ZX>_cvc;(&oDoS=045fH->L)4yg%*}drc$-&imR=gqr-oMOqO1 z%|G#|i3#H}I{K1)hzb}O1wEto|_LVv8nx+po(-g~o zNDd+FCn#)A?EmLp$^T2hJ!$0}Oe2sqO;`B*;hJnu^}rdCGzB1OmBzCb%N#fQD|<~c zpYx5qCOVB-O|a}w{6?sW^EXU- zq?(MmaWXkNpozf;XGH3gogr1H-TtoxH3DZus>#edj+@l!?OD$GdwyqU-(wYL_UZ40 z;b~95lET>0CX1~1F9R&h@>e1 zKoWRs=@AAJ+?M>4y(Yl4eoK6|C*~)iCLeK;lKPy9EnC{)C%>8)r|=;-BbJ&JFz?UI zWv_`L!K_cP?CWv~HF3(tq{m(p06OA~So*}c#p8_FYr?h#f{Fmn z-bf{68g-mPqg5*6+jz79g@8Q(rk4kc`tA<79TPw4-5nlt%{0ZbA8bbm`#uVr6Z;;X zKd&|2Uc^wwj>$Ej5lPdu^*MKDmaR&4!5I-XjkXJY(C%-Rc|V~LdreYTnAHS3??)FR z)Z`s5QuaR2mewi6qb5uXWlIO*jEFTcZfxiLQwy`#B*XmP2bTSm!i1XG7sjMVs>z)N z)RCHN0;zU5BT}F2@?IX^^R~pG#u<@nQnk}N??KPHvz+t$7hz{VHIXy>9f}Z&)3k`~ z*dJuiu4&pxvp&bNFS93veH(?H4`Y(1Ahc0r%;35DlJ=NJAZeO;>19UFYzN90XGGEz zfDosfKVn#}_lG+aP?O1KHNmnU;XtU#Yh0wnnjj);Iq;~7$*3>Rh+Gr6v9UkNk-a8y zqs(f8Wk1Q0P!l^xOnRi6EUr$SYE>tIYK=1@^-0R>0}8i?MW|soBT`KUh3sjtdRRFY z_B$12XP;WgnSIZqgyJ+TYCHD(JF#mj)0t_CWk0})5cYKxHaGSYA9`O+-KH<*glPnl zrkU4IC4XL4h?3%rNSXp_r2X=Wp)Bmj7GtkTW`WQ3g%%^!2*Qq7C=s7c&#y>#=okJ2cd z5ve8YF~v{(9G{;vA?zzCY;NoW zkQTAn&db4>ho-P&pAWR7+T)B!ngRequhfk7V)`AoDNC}~Bz=ropJV6!kdlO&Ji$du z>T@QxY-#0^JZl1{@E$lLmYNhW?~g9UUXyIowJq!uEc1f1!qLA3EbGu`4cLz*CfOIc!*^`x&oml?{JZl zYVu>q-nP5G$f!CMc+_P5rwcp6;+?61I3rR`60Zb$c8{pQ!hWlY?Cht_=gfY+iiF}+ zsc1X){VTC+8n?i#&#~A09V!vRK83>O#J(1r%9(bZ%j)X(db} zkTeBxUyGdU(~o7|A5@vWCNj7RV5UsLRH(b->DkArrD8Znqt}atVRg?7z&#c z`v3rHqNn7@C!7&WpQrg1x2GCb!!!a(QviU$|4=B){r&#c*=v%TU{(_>`vKJnHMx$9 zl+@=YB8yeWq{mVd;}kB%8L`x)fO$W*2767ib!IievL9N5P?P7lNZD%wK=m5DYXU$J z&WODxY+GXqJNsY~XZ9r$LUCLrwqxJBCcCC-F=m=#*>72s5cUxiHYfIVe=pFyD)mbG z9%n@Ab6JtM+Jh^qQFUu#8iA;3qrLssc4NEV?_G<%CWcvNHNmp)SBp@SE4WD6`#f9f zPz#eDsU}RbW=q@SjEFTcZfx_uvNn57GSy}^!LlDxn^2P{xJXGgSv==$*PSioDr$dUAC-muynW`RoflY2qaB`&OYq@BNfXzf6qGX zHIbN~3t`!(>kw*k5f>?`&rL)Ys)I?7R1=d?8=MibCIzyusLNiHv}ChB!Lr}4E}hrX@%6t6=6{2d? z$20;-Q=ro|XdM;8!hT=__L`*qVb+kL1XRi+^(JyK1kzr7w@3;?PN&WO|}B~EN@ zyKqt|7WQj4VrQS4!kPWDjR?hYY-Bt3TQz3aRMNHE{JeE7?7UyEF(K?7C~R)*13)bw zpL6&r&WNNbd^CS~aAB%SV@xBEG=)Zwiw|PCzwh6Ky(R{QSxvC)cW6SW$thfb74Lo5V8}%My*>w5MmV;vudnx8w7^ZJse~!D_ZzifEPD?u+oIkNd#lIhcURcT zztD*v{dS)+GRdH=6n~x< zYyP1Xu`)3{UyC&^pYmk#YQtXdf7KGt#MPI`gxdQ(S~4=ZNLwi@lTW{#G~k1KU&@tR zQXU>Flil&zD?=ipG#XkF>v{6$>`Cn(&gp_&_v^I6d%t+1#l2sp6(gLNu;f;Tf6%s6 z|1MAS*0^cGIKq_L`_8Qy%ih_F!oxC^PW0}+|BG17&$J?zsl0G-OxJ7WWXIN&GqFr1 zDfDiw4cPDN)3(7garlPFgxdS9+AuOXMq4Q}6X?i?v?5j}#-b)|xH8diSnv03i)WJX zC6NiW_q(-aWOA0aQdTB&9tZY+an476t1ac>u`+pk>4@VUyE)2kv?A8?B(C+GlI$zk z&-ZJ#!+Sq(jK#fQu^l6v5?J!<==~4dkwnVhDrl$FWyN%fCN2YuvnZj^_|%4FR~ zR|^(ge@AXhD`GuQ_Ef8KBfi8Dx$al*i1&U_Z;N}sd`Ct&PhiQfqxTzkQZe-*OsTzJ zyAxyCn>$i?Sf=`)x+nMlx#J~T5zAEnB;C69g~q-Uztf7aO!SxV_uct2QMvB-)Zm%q4I-W=)ZUjhj7)x{trX8h zlK!O?;h9KMOAU7>*j81>dtWkUqW*eck{RJVf+fF>-mkC4O@jgnQ)=(m&@z_&y-Z1( zZ5sA+_kn9^=V(Q^X#=@}mU1Sf_mBT@ zcW6a;CdC>j557!PdcV6To{2o2$b{Pa9X%PDWYbp4%A``&PjkL)^N;4gv?5j}EgUnq z|KQw6{=}0blQzXK{i^ydM}8husWaaDVG|$|_1=HZ3g<6a^6TjRdS1Ax&YLi$_P&D` z!*pF|O42M-Ns?am{ux>k%hd2q`BT}}i*hFGd%r~&JQKMOkqNc;8+Bo1vWK=(B$HPi z`3tQG&!kvWs|#NyD!uR370;wsKOz%q@3-s9$RvZdQdTC9*Y64%cJwXzA6gMBlNX=g z3astiN_OeWkx7_m?~d(bP9d*@?7HE-uNz=-?>}XQQv^$X9lc+tJ8oJ)zOO~?{VLrV zrcvD}NwZ81Z7dx-Nh@NR8j3!aU($+@-q+vuBlrBxKf^O|?oT|=slD&~86%UO-MKR< z*8EH>!ZRt>IDSSs6P^k7UZ2(*&qOl5uf_a3ysf+$nH;07l=VCrvg>!dOwZ7FB-`uv?Av7e0{mFowKojlk3CJ z@l2eJKfBRC&#Ap%_j5)ju{}95F??$))?A|%u`)3hRr{QBCT1qDHmvtMeSv49n?yWM zsMmehFBq8|p{1nhgmZ`1O?C8Vc9py)~BIfhFqI;Kg$IZyST>XA{Ch|}s6Ke0*=*P(9`@TGx zz}5I1t%#M0v8X~n%9)s%z?lsHrERVI7)e$)PpO!m`O%6guBarv(E>hRX` z9a<49lQrLc`$_dCjdV`^IWp-|xn5e~I_!6MJcdkIy&u-i;@-c<3MU_y{5pES(pR`? zaE0SoD%s#LVQ?hTZcw zABtzvYdDb!wfCKeGBVjYgd>v&Rp0X0*z}ZtrWLU=SripN=5e`-vg1(7nOK>0skZCo z3mXsQ_5L4_39I)bdsy821*~w+!;)V|@7ws}rqUOLDfPPlw;#iFrXM9~R_{xr&o~c% z6qkO0R>U&(`>3nmw-?IEcWFf|Q~lFCY^I~T;rUM6Y7WCQ$s;qN_I|}-j7*mM^JMaB zE7tr#D`I70EGnTDVVU69{SSxZnG}7A0U zc9t*EiddNpt2F9*NxO&X_5qYLu`=1wbcBC;qAk+Z&@4Jq+xc9HH!pVgtzmDF2 zHX1jL=|w!xsn`AcqZy{bqbW%sg zr1z_Sjc4K)Kx9Jged%jPCX2@KW%8;c(`ZFVCa;Pf(Tb2vRC>RDAfAZ>`Ppx3@7D-q zWb*yj+?hzyIa(2(i6m7Bq?`%Q1lw*wCam6fF#bI__0RQPV1<(dOMV@_|9C8J>Kscv z&#AqCZ!E)f;#f-3%-;Vsc2ldy_tUr2idd#=li!!FM%R<`X+=!ahmNz_aH%&Sdy6ZL2o{&%|K?kqNc;9VRd` zSvQ^|laY=+rX>V@CZC}du`&r>`su%;7dpze6DVh5W#ZDKXQNItJ0Y+4Z$Kuj-p`AK zOw@Znj}=Z9Ectcx{(~UgG;B3tO6~n$gBYgcf+$Hddp|7P?jw&S>04+;EK|SVeZpFg zvym^;idd%lXLiOUXXN$1-6TAdV)FYTwfCP+WMmRPktdT^Te0Rat%#M0v8aevgk^%? z^VgY-XCluho+s4auQHjDNz^2cOl&56(`4KB-ttLW5i65kANVb~kx^0il2*jZoLME)A-UGd0G+6RNow7PoE(7{5DhZOcJ7qOsKv8cQ7N9nZZ1nK<^)* z6|pig7Tu*4VVU5)UvrvDCJTv7sJ<8Y7eCQ#mrJGUdE3T{xf(?Z)&m@+`p??O)FxVp4j(8 z+`!QH_t&$KiM;kzqt8)ZV`r&d6k9I8P?8 zwqnh8S`jM~V^Kb>2+IV&?pK8<4I$UXm$kO`~zBX?Nb`>Aso;q06H`g;Gyx45Z;@%;+s z@956^ma*)wzU45L20O~mzaCFtN-JWSI=HqjsJhHn&Y~5uO!dv7dx`yg|G_*wle}HT z^PJlIzs_T1GHxDECeZs^Xhp0{j767eMOY?y@7pcFGwHR4$b{PaPv;2`;*$>l;Xhp0{I(M1^c}5;WvcID-|BX9I<1Ihs(+JU`p*N9 zzsL8-B0Q6zO++Tt-Y;0h$YjhSzD!hchQPinGAZT@LYaRd)-Z15i65gUySR&$shaq{*je< z?>o=3xc7IjWQ4P6lc{{C$mC5mgGaGN5QIwufD`I8xQQhuOW#8# z>b<`$ni0;L=-1c#*&A?EIfgK$UiXtXFqW;|z+vhCyJF3Ev?69EueM@MI<1J6 ziLuD=U)uJ^Mm!V0r9>vw-Y?k5$Yjh$j!fb@h8GmQ+fuiYR>aDr{_XNN7S#Ggcb-U6uS-I);^?pVSZtA*>Fs1hXp%})pJH>FAwyQTo z8dl-7W)iK4Wh(D>hzYJ?FYllgu}oh*v!~oe-siu#8P6nWH<1ao_fKqQWYT*xM<#|I zEY{4Y6|pig7NyXN&`i+x`hMG@lF14p6Kd~Y-@?db`&~N_05r%@_JekE0a0C z8@+PAClef}}Sf={saJcalx#vF{i)RwHmB@tJ z`^RG$ne>e1$i&#jb7)1(OkQopnj~5gmI>bbx3}Y&^x8&bLhb!4+ZmboZ|BHl#GLTr z!e^~@t7%27OuUb#EOD6GOqWY5Vr7yu!^?Hi^^cJ2e(Vms_x<7^6ZL!kwL2K$MC^Ed zy?-PQHo^Wm{okth`sAHXU(3ewnMl$KS`nU!B<0YG@Jz66b3ESrG2dF;`>Wy^;Vg)MeZ8N!8#k4Y5~kGNkK4^y zc8lFsn2w?q;ie7bXj+l=ra$h%GwGE^WJ2xz)IE$$y!LQsQmpxgR)l9#tl38^!ZX2s zzJFsco{4ihkqNc;^Y$__`D!mmCiC*cR=!9$u31VeVr4S(?UvVa@*bvV(TZ4^eEi3Q zo^D}Nk$e8{_o?)Lw#B_4xsMUf+P zB@vlWd;in{Mkaj^aAfl0#F%?0_gv6?M=N4wGPTL~r?2Ie)1}jjSeabS)!C0hdw<H1;eem`5{+>g)X|Mf+DYf@w4l$Pf;UNxFLnl@|HiTBhGBp%^EU%;$ zu}lp`&d5D~Mk1a`&=DdNYVRLPWMtAQkvo%O%_Le8o=LH02dxOt1nd2ahw)4bE)tng zd;i2?Mkc)vb7bOjzFn)p@Tu~AS`jOgXAA4y9i?lcOQ999GFhnY-|^9G?7AP7g!g_- zvcFo_XPXwvKJ{hi6Usozh8DYf@EBr}%XIGMw=P4&J$b0@wh52O{bOk>vejrs0G z9o;fo5z91T>Du%EIy^&uXVdFL?+bUKYo;vNzbDknZ#b%6!Tf4R-QvEVrA0#SlpNI zeRWThL@Q!ta?yXh-Hefsk>|vR_PeposqoXP30ulG0W zaMPG`#Pgim`>S+}W!KYjm`d{g2~GRV*7Ttju}tqDI^3%Da4&fQt%zkR-8-uryl)%w zJWtHPGtnI+GNJZ#R{m`YNNw(s*RX`j=ISf;CPx)&XN z=R>uz<{4$A5sJ);310xgd4;-0HZ(V0kpxqDdQ)xwbCXy6SE5b9uws|>t z?-v-qAFBU+KO~0{PGHXK>-}{naMNB_2vchBFFwIocFhwUrtp;Q_!+GTGfjP~fjpa5 zgqViiySgcrt-9w&Jd>~lA`@!w$Nb31LMmjU-uqK>8Q}!vzP{d%I)$5t z9V1Msy}$4jW7*YDSz+3RR)m|jl0#`lY}1fSQOom?zr(xpG@eNw`8=og{)W?xOd6l& z&ZJl~kXD3eQmk1aEW z6A#b0)|02oTWLkCO#V}CeZ2!aY>@Z*X8wftet|I)_1+Kqi4o55pI%?@FVDkG&vv?*6 zKN6Wxdw=a&Mke*oa%A!oywBf{R>aEW_7KDS{1LPwRwhNN@ADryhiBq^fXIZ}`@7FE zGHHE|Ba^>|9Bs4Sw~zcat%#M$lr@L{8a=axyopxC$|U{XE|LVJ9II z_1+(Qo)ON_^RKV>mt4S2d*u?Q)ZP!fz*u&r3mm4BB!73~;6-U%OuQ+_g(XYJZ3A7@XsZ_hq$MiVj@1%a+ACb=pr+@zI>-~AxanoY*UXFU* z54p}*w#{`8Q%Ne=*k;s)v^KONmT6?=d2i|8=MSV6u}mc?ta;-T*~oQ&-3>gGgiC6@ zzxW0tlbScUGSP3vn$KuOtV|4zTdbK)D`ID2IFq56w(a>D&&2OKkqNc;V}532^5M@M znba&v-X~8@b{|43Vr63AX?3gjPKTwfq!qC;`MBD*o$H2$BhQm@H}T$=FF+>hz3+FE z5suHz*Vp^u1-NPPRl=0o`%?-S%YFgNZX!vNO)nq)OUe=Ke~<6gR*?E>0p*FnGS&Y( zyuoSCasaJ}WokH681g=U)Ga)d$U{UX)a(AjTZ~Mq-=aJ`=D$(+YAe=sp%o#Sy!wU_ zN-M%M!9Lfw^A|jmu**ay)ZX9l3nP=pzi?+FNdsv`cqWpxj8=qaf^B08@!oef_Kte* z4=QAY^F`t7>;0L(;-;=Q2vchB2mQ)e_A^+vrM+*z;^bNPkAI~+5pdI1@^D%a+caWH zm%C?>@8`>JMR+E~nrXBmb|!{z80=?y>>WH4hj)nQ z3AOjv-eF`?{|-kcL6;vrX+G|Ndp}wcE0aSnUvBELtdSf+D`I8x`*pt;k^TwD^Caq? zA(QtcNqSp5e#|)kDPty#_Y9sibz~2@cVg628%gr9*Jo;@AAL38qu(vLQ%i@sji}^|V|2_@<%u#=qnEzItp#N{T>1R*s|GNezsxL`WRs9M4#|Dq= zGIoOhlu^d%YP^~>Od~12&Jv|nyhl#gPMI>P%UJ(_$z#aMivH67eRkO*RvyeBMrf@_3V8#*O7^ z+*soS;LPIQeT&)N(m| zi+g)LWP6A8LA>K(2~_Va7*u*!c~DB;U11_Cyu*o7KJOB!Ec^Slb`K4?iF>#Ho9!+2 zLcF742~_WcFsSr?4#$PB-)$e60^qzqpGcJQc^5%t*?Twr+w^e7?#~}NeX8Zb${PW~~?SD)USIoQSQ?_?lPsBSEmO%Ah34==S>u_AyyVes^ z0ElTGD?Bp=fOxkY&`7(hQ4L-Ef4gUV zbZ=gUL*9@KEm6wnodT7Xd0#mkZ|0daaDdr9=4;%H-cN}&i+MjTVS9&pBK__AUqj+l z@5wNz^iGB2!ro6{A}j9KT9lZcDtzAYP-&SrkEd}n`WiRm{yk#NV&1o3u)Upq5O442 zi1%<9RC@1*|Z_0ZioLSs^UM047 zfd}GU1WTZL+g7A|_kiQV-V0zN6>r;?d$l9iMQMAspQi2n-9T+Wzfsx@qLk0utrCxS z{=s8r@3)k=tufo@`FZQlWjJ+epE1{4o3WoL<@0v2=kZp0?&%VlL0+$)KVEOPul)1m{%AO}xcA7a zY;U+1@ht`a2$$K=saoL8W&Uhf?zH3KOY# zH~yw>>)tQJT8&$_qxFKA8EyJR9BmU$l=69(K&5rB$5ti}Hh(?-{Q~oSapCKC+jmR>AU`h|JI+}3tuL3wt-IVG028-Zo=T(vU%#q|ee^4sZMFK%4IyG->Nkl=6A!L#1WjOTS&( zp8Wp&Mc7=k{VESSo9$!XwQHIlu9$btT5NCE1BiDhEP?915(bst*WtLZcdd6#0U+MA z>vwOWl+QaCDlPNI{EQpI=Kc6TZ0}y_h_`PYL*i8L$uOw&PKD#b-cMj6mHRbXf4BIL z>8Zl!9S@ad?|qM0vzYhox@_;_M8wGkVc>m3c1mU$1W>|*wQ+WE&{o9#z6sART}eLufKtXa(a;(KiGgd>Qz zTRp_v2L_ei8{oLG_hpz!#e3X-cioJMH`14FuAlz?s-5nSn+IyDzh|v?1XNn)J^j}z zX78`m{pyL9yk2*)Icm0#d7mKGEasi<%=QjCh-Phql<)c-*ue0RE%V0wj2qeS*q@JsGwUC& zn0IU=#QO)t+rA;<-4F(q-ecjou=fs_NX0w3-sTQHPoF#**XTxy*F}%iJ8zFoy+xGr zdHXiv@$Ogi@iEf#m%{hb$o=Kri_FF`@3nAdaqp-P*xm_Qh<6DrfqH-L*w~Pa(t8jb z7xrEQ6RCKA+_kEMZD7x?;=}%YN&yNr|v$%KgM{I8g?EX9-mO%A>1cS={?)af0 zF=6j%Fp-Kk?fSi+DCP5Z`AEcD>Cdx28qO^4J+c|wyZ9*5-?^{^s&^p_D!rSzl#+J< zOk{=kW}=kO+o{=`@*V(Z7WeM^G26Qr=A8mdpn9K&L8W)y=B4D_A11QGdpS|c=WYM- zoAT}kXBPMNa%Fqxr6Tk@q&4d54#nzrS|=>3Xwq%)53g)58_>uGxm| zov;h>4uvI9y;s7Z()&6b7xu2z+7tldO}jtuO_cI^=R&1r-k6_pGk(7|Zmj)^ZA=eW z%-gOV+uId;e=QJ}K=qyrgG%pHa9r5CLR(V+i1(dxC)@3JIoa-l)2Vim5A)ic`Q~Cf zEm6wnodT7Xc{_h|yAA1A?{jan&#T2mE;ifOZ{(jR_n#7L7W01Gp6wm97wK=`c80{M z-jiWa>75G4g}tA^L@M`dc10oD3PmB>dPO1H4n-l_o<$+r7VS+>6+Z8HsI<&GXGDP6 z=aq)e=+uGq{NL@M5G zlb?6;PJZ6$=;7y`!VW#})c?TqPW9ca^^S&0%e<9-2LcPt{yymRm9|HWp2m%}e}!1H znD@m_Z0}z2h__ou#M=i3mEIfRxUlzSm`KH&cKxp2$y)CSsI<;o>Cdx&f>^VdceaM@ zoris1(ZwC{_Jl#D_hL9M?41J>S>bJ?vDP~jD$CwGnOL)!ccPZ)yiudOWHfbtP+n`w!(ooaZ ztx;O_Ej`oE5~Y0J-d;T3_SY-v$osGF{){zyKXrEbJ!bou_d+yU@LRz;R*k1u&6{w>I=ny2Az=-FDY$x)xvms2jGjM3+I7@_D;;9gJKH<*G2)#MOQ3o`f4zTep6=$cP8M>i9teBMspBHnqq-OS#9X0RaXQu6K!6RCJtOIxD3 zqg$eRD{F}+_xKXc?(8L+aH5pYy96pNdp#CoQ`5||`Sst--aj36cB9!o=H2cKLvG^U zt$VY*T}zPuj)o;ry$`~m()&3a7ruVC?PUr8`FRQL`aO{-?ec0X(FA(ntSOV31D-0^V@56Cn?f!Nb``GvMr^K4YydMu>d-tk}yuaq#-;g-fdom0vy;I@1u=f*~ zNacRb{q0@p**?|ft2H*s*S+t`OIO-yTMRHgRrtK)q0&O{e^brgFTIwu%51;Wme0)g zG4FfCn#H_t4`h2szKwW$e}#Athe4(HZa6ONeHSKD@y=;^VdclHprchGa>`t33p@%Ds4rT1bu zF6^BH6RCLDUo}Oi{IVHht#>F?mc4f}v1T#vL_fB7LKURHorWUbEn!gUJp+yldnds} zD&Be1b7ke%1EQ4g`W@(Jc*te%9S3LDKU^{I*kNq%UjHKfZSRkGH-tf@_gFYC?7agf zQt|$6+`C%k*Da!y&)av{oAO=@XBPL48o~C?tBmw_2`qtnf9^Qkkc`rM5F8iwUIP=U zc+;-mXNgiiZ|@Or%6lQ4S=@WxD7JUdTZnfNEP?86JCg3*1C9%OFMx@x@XjDg`Mljm zy(#YyIJ3BS@MyMoiq}?mHpi@z>t`*_cWNu3h(_yDWA8?=r`p(8qO^4 zJ@RX|caROz-?^{^s&^p_D!rSHDJAa!m`K(8|F_LVDWA8~*Kf*u0GwIeyYE=Gx2uGB zr@#`Z-sfRZ>0LLll)U@HL{@k&CrbIe?Z>_;?{08rac{5jZ103e$oKPjSOV2M3kH?m zRmPQ)cUPFm3h!{Dl+U{aD$D-$SiA9t+{C?G2eG|-*&_WN4NIVUAA~`r_j5QdeEn`a z!4v@U^Ag(q`9z|W&$|dJ%ig_YFc}2~_WuFsSst4#$PP zYfUx#Z}x7}2>x6T>y4umC8z30N9()$z~7xu0Y zYzhGJjtqKNtNhXurF`BgP+9igPl+{)c|V@U_V(+5c>7K@Bu@3741-GVR5&i|{RAdb zyauO zy0zZXP+9igSBN!>d0z}=dxy0|yxqP*ynSF$>AeAt3wvLNiLCIh9%`+31XPy2_X%Rn zV&2&^+1|SLh_}lO#M=`FmEMctxUhE)Or+}l|C`NBYrR9Evh2N+i8YIPC(dSj`?W^A zon|54En!gUJp+yldnds}R(L-kO8KtefwK({x$M2;;LQ4mE9M;=&h~b0jCk9JA>IvP zQ0YAujthJ5fQhW|zD1PsdHaUHDetv#W^wPRxoq#S_Yv)vqUMMxA)vP<-HKjEbcvT9@|^~6!9*CB~ZO>zomQkfaAj63t%EuZ+VBL z_{tzk`Mllcy(#YyIJ3BS@B+5C?jyuIAC^G%eguQc{_Z&6keIOdG?+-mJ9+<7t@3L> zQOf7-vfxd5kA^dgdyibi_KsHi&l$EP?8M9tM@(b-ybm@BT26y7&LRzqXtx<@2_WcvIfp;LPIQ zUQ5{C&e;3s@vsD{cNPpPy{jxPCGV~#=rA47rJWw_e8f4r_$; zcQh=4>U|IfmEO+iB|5) z-X}nA;S(U+`2@)I*IMfx4V9L8A36EE*}o_Gaf{Vv`;BVUHQU#3tiK<8g;=wg_r>*W zZyED;TZef2z@XB50~{Cjz6=woc$W`rpq(?jf%e1M4YcKEH_(=!-9TG?y|vyEP-&UB zgGbL4(r@Lz#TNS-H)H(?V$EXS*&Ep2VeOFqc8Ny3Jz-Gky%>%Qd*{GJD&DhdO-b)m zdrErMT2s=m)R>a~spFJ%n+?`_heD-g-pYPGx8K|B_pMFt6JWNl{PW~~GO=bc@5GI4 z?*i=l?esn3-4X_s-ZS91uy+znq~c9`{r!L_<-2|dZZte(>%2)n<7Vt%)y72aowQa*3*EpN(uA)Hy2!l%RW;;sBI{+q9@ou&7w06S!pR}VJ=(Izs?$o+8U#Hznl=68y#qoG6{mLzm zH2ZyPTWhT_+gJX1a(@7vS=_ttF1EKr65^c#OQ3q6he4%x-JPZ6-5(}W@uuCMFDFX* zyzO_1c$0p{&C>7Z-QdjP-d?-e-a*-jcRVbC>YW9HO7AN1rR3ceCQ|XP`0lC>m$sbl z@R`kT9X^b@)8R%+Mu%{sl+U{aDlL0G_Mi6ecf>q5+FrBx{g-yT4Y`SXx893*ry$e_`5s%sZKsIHAEuCAR(l=68OL8WEh zMP0&B?!J3BRkUpv9{ zaK*f99$vwOWl+QaCDlPLSy^I_7{T=)B z6%Uvmu9&yoA+~qqF2p+!mO%BM3xi7UQ*d0^yTUr^0bz? zM7+HZBi_SdQ0cuJjthI=g^5(WE6%#5J@oHY?Ye$>+F`5DYx7z>)Yeb3);k(1E%VOU zA)&9Y`?h(<;@@|Fg;=wg_r)V@@4N)W+btRK_JKjA_XapF?0p#~Qt^K3xk`6Bhz5wsk7j+N47<_~er+1L?{TDq*}i@w$I1P*aAtAu zs0_Au-ciK61eQR(KX=p_l2LjOg5$#8YhWT3?{zf`wYCn0+ErBxwL_~EYQ60XwP%S^ zK5y>~9`AQH1sx;(vSwXNCHI^Cv(Rjx^fhkAKfe&pEbcuoi|y@t9PuuKB~ZO>GwI$v z;JC2&0+>j}yIB6Nmy?>eL~FDCP5Z%i{4?`Z?6EVAjuDnikx|5yL__Rd^x9ry*!a9JewC~KX!ia}Ij=SrdpA98 zdbnb*zZ?I=_D;Y)&lCYmpn7kGL8bS7I47s-jn9sHS;|7_qS&Il>%MO_La{Ma=-RZriUx$UGps4+ch5P?@(9* z)q5ojD!s46abfRTd8PmmZ`%ENZ=#gXI~OV~^CrEF8|(YEabt~FJZpNmV%~P=+1^3+ z$iHV12uq-P&xJvy_bE6o>|NoUDFDR#T*j#m$188?@Vw@_4k^DK?66vMv4fT<5AJ0HPzhb-L7W(-Y{buxkN~~GT`|$;~xATp}mCS$N!1uf%ajN%Z7*u+v!f|2m zCoqx9{hD*nliI;OPHIy+DXbxe_0 ze3VkYpJPh>OV)ZvL#1Wji#)5NzTRtUqn~HgZ$|Gc#G1vtFJ57Lhn+_H+wC&q?E`~K z?+tKV*!wa}q~cAxepkO@t#<@eTINl988@S!akF&)1hHl@@9b-AZ|5tBx64(;+Y<(r z-izV5uy+njWQDiQHEX>?p|b3~lZiEpc_&_Hd;9&2csu1I-YsEJ={*CE3wtNQL{@k| zAWHeJ-+|W+54r5Uc`L%Fnaqp-Cws*`$ z#JdERK)pYAylF^A={*RJ3wy7DiB!B}&pp@vHoB@uY)%c273noST5hiCah53M^Y$*_ z@eat^VfJ~%m3{W2e_s^yUI=Fv_n!9)+uQFd;#~wwpnBWhqI>s%SkZo-$B5#o9<9=6d1Mf!eBN%q@OYoK%P@QY>|A39i~HHQ8T&m1&MfX7{43kr z`A5V%AC^G%eguQc{_a?4NKDv!8cd|(O}jtePn7a`yZp-IjrFT>GkO{~*8XTXv$*%j zJ8W;ipAhd{SOV3%5C)ar&2E>HcK}SJ;yt)dIi2!rGf~Rt?R1CB+b?`S%g?x3Iz9l- zEbiU+9^!ox@lJsyP`%H?pwheU-BR-I4-=_)ZySF>qx@Pumq}i77QxAtNd0<-d$lL74OCkA~njdaH5pYy96q&dp#Cb)u9FH ztK7$)AMJiOFrOH@_FY& zW!Zbz{?qhu#k^}4v%RG}q`yO92~_WuFsSst4#$PPYyD*kfYZA-QOf6?3zcQ>U9s5o zaK*gs9 z5~Y0JDNt#hcZJ|tN67Ejv5{eB`{eg;<7WKxPl+{)c|U%{_ST(2ynP=U5~q4khC!uw zDjXN~egYGz+^@x6x|Ol*(yfdgmu_XmT)LI9^U|%17LQC%6+Z8HsI<--^E7ToU*l%n zzelWD%=`9Zws%23;_dx6;yoM&mEODIxUlzKm`KHYce{R>`maOn`ei1x>zBE&UBArw zkFE8NhDyu44{Pnw{&h<9Yj5;4Zmj()#G1vtFFs*=>#*RDY(>27pCjH4VNmHk7LE&h?|_L^yd(GBIx>6Tts_zUZXNkx=ba;K zzp*`fizwyu_I)AZ9kVjT%zOOLLoNROifiG_;@(j&+1_DUh<6DrfqH-L_^%-urS~8> zF6_MqCQ|XHUBAx~rF`DrFGak`el>1nzhmPI;mqRR^K8mXwvr^hCrQ%V+VNw?`A-=$ zVZ3MXq^To&=(Z!?MX&^_x2;rOveDnKNRsrH_sHq}Mo#L!vzjFJfaAj63t%EEyfcVW zK5sW0!=tk9`#I)m+*tl0aAtAuU|Y7gb3Edm4@;nWKY~G}cgJ#u#Du-4!9-Sg?q zUA4FFb<=JpO8LB<>_og<++A$;?-3pEveV+vHw}O@i+lI2!1fM1f_SIE5~$wiVNmH^ z_pMU$?hg~Gc&D7cBkw(ZM@~C^N4C9xM|Nv>M_x{p@_E}=5b;hbZf)kx^ZmUWoLSu4 zs}kb95AlwNB~ZPyU{L8@rD7?0cZG>mypQ_iWE}L#$v!AArT~zim(cFd zClaN6-bGMZI^LMCaWme(HMKWAT(Q^RjjOV~bw?5J2v`Eudn*hoz3;haEz^LtzP2@0Bp9^u7+qg}rONZ3+PK-f+T2ek;R8E;;NXk2vHa4>{x__a;jD zymO(_GViHvzBTjBpWn%BzvRFZi~oItiq%XHSIpb4I@>#D58@pNOQ3qsg+ZnFDL5|d zUBST=0OB2$wX^+_ZP(f#8d|Z#Uq>o-C@P=VUQ3knd8a_7W!}wdYt7yt56OPf&iMV= zxLLaYlvuNv_v3fi-p&b#w{LYr;#BX+FsSrSh2z5BPhcXI`!(A2yTv=ErwX5UJXBid z&GY@*xUu~25o;FnzFmXu?YA58_I5av3hPB?&P+9igSBN!> zd0%{&?JZ>>-flG!Zyy*`dT)T^!rqr*A}hSBziX{`1XPy2_X%RnV&2)c+1>@m5O0@S zh_@#UD!muOabfQqm`KID^YBR-YbSF?TIW4|>ru1! z(_+UwHrvO(|0fe`7V}R058GRZy?^dh2k~wRgG%oia9r3s2_{nUKD9kSc4+>$drX@s zom06)-HWC_ravG``L5rA|1msd%e+TTe`@yk1tYHF*;k2epS?eloAg)@tLN4>}P_Dey$OJE7q`*X*7hGdl9gW$NZ_ZpZ;#hZ42 zewHZZ^Y(s^$D7B~xEcG|xEc2s!kNXr=Q*>zrBuYb2$n$gwtb)O-2;vbdoO^ARJ@-C zr|eLEWe}x&-fqrs$~y$kEbblLfbA`3BHsD11giHV7*zIm$NGlEguSQ1L@M4RKByoo zzxETQeBLe%-jw%fIJ3C-$VQ0wUc@^WmO%9`gh8cuvxcSQ9RL%lc#p}vv|IVLnJDG+ zc53vdya&LU#l8D}!1k7s5bqRN0@eFG3@W|rHZCRa{xFe>H|_epoG9h| zi+g)DWqUhsM!e%;2~_Va7*u*!X;MnwU11_Cyu*o7KJOB!wCwemely;088>V9+ch=h zChpz(Ber)z4&ogROQ3omgh8eEb2u)1{ciiADFEc>CA9vYNR;w<7eQs&dpG^a^l-&q ze>ZN%_700dydz)h z3s^03wu}i#1sIhx0Wd7^G<=vviE*UtXa(aaSOJ$Gxm8VUsproRPV_!sPs;S zT-Fg@O{pKqkrl9}+fqpaI&tDBPh(t86O7xumk6RCJl*j6A{*j^w<#TCefI|}5j+Y03B zt*!NrfJ)1}w_kZ`_IW1KPrsd8eBWa46U3UuytCV~z5OB)Z-S8NWXp zH%s^9;LQ4mE9M>R#`gA$K)mfcAl?mOQ0YAujthJ5fQhW|zD1PsdHcG(Detv#W^wPR zPHgY6ZxHViSOWF_+_9q}8Kw6iI4rVIX0mp^C7r;a+-f!0#=TZLG`5u#ReD4wa_F9h%Z6|nS5T$(HZWf5~$vfU{KlL9c4pe!rs$hA{Fn5lW%2wx9_oT z=$5m(?2U!G4{kYS>?cb3yj`?B-XEVSKtGRoJyc`y=h;TXnZ>pE>}}EbiUci|y?^74c4iB~ZQ3!=Tc;Zs$_+?hg~Gc+>9BmlLIY-u7NR z-b!DQ{cdn(ac{4#Y;RpC;vEl5pn7M)pwhcams0ZX3KOY#4}Y{Gb;P3;srvuUezYR> z!CxyF3q->^jx|621PenRsgzJK!|zG?Fyep$01{;6g`e4A!Le4#HP zi$tG$-FfczgC!!5<$Lb-OVec_Ms}Gx5$L&-;dkd}%O?SQ-eEtWnL49^7H;A(UNGMW z#2~W`-++G7#skE_MJsj#{e5mU(1S;q0L?H81;@MD;t|kuy%&J;zL#Twwz;te=;F`~ zKo4lV3h2E9%z=J6Q~>Iuw%_Ah3dj*dj+_87ebS>xKtCw^3UtV=XFz+-%>&x9*Gr(= z*?t4M(&ZG;Y5Wf0^CJ7L1zOzy=%s&IsFow@#B;Y3gQe$;M7n!QSKy{g%o*CtMEyd| z8IH?E|5uFzYB&y13l?>tjGR%Mu8k4;(ha*Ud){+4E~)su=T@;gsthp zAS||}tdh<^kt@wu_zSL5A_ICWBr`B(rHTQizBg&Cs*?=#!fwlEU@k7HI0HMy>QIb< z17Xq`Ae?C!sKQlBWT0t?WCkXLsu)m;0qw9l$$%wxTQ&p!xTN9?>=LU(F$T7U(=gyq z!@zG`r9=h{R!e4J(i#;5N-?0fRxSqGS0tg+k;%y7dKqr=_qMql{(7y76$nfg{Wd1!B9ej|2PW(zj z{VI}BE_Sb?=^DC^NcQ|o^l`vE*>-Ql4*@b|63J7Cuxco1mn z@veneg7#4DK7(J;?w_A99@J0WKO+e~*Pd!WoPQJ?|J}haK(~1R9_a7$>hZ-tKk?(O z4;P&&a{c}#K7Xm~-}26r3hSx%`&U7f^jKd+7wgM#b0x-lwGE=appJWoY!v-pHSVq9 zxVH*-pp0=ZcSSs}!rPCR{K$w`ui@=e1BRQO+B`aV&`iVB!Ge)L20Lkf8tnG@;$Yvc z!GnMKvAO*N8}L>)pT?WqV-@et)NmfdFom~j_+8$Cn0ox3>IVEi%X;#!ZyLh4{V zs=|qXz3UkMS}g~D0hh&pv#TGUz0HJQKB+DL^8J?lJKuHq#iGQ_qsDzsu3mfSiZT^*IZo?hdTzqxHf&(BM|3~4|@*u;9jqRCN-`AEgoltTTsMN zo_HJzf949}($g1ZK$BAjfH*d3t`*R$JH7zrT`~~Rlb(hH&HK>|?0?s~571_BjDU9N z*bV45oUTAmuY3fKzi)9E(A4&G5~qXySvlVt=-)nzfS!Hm8_=aIP61u=Ne$?L)eC_x zUvUiRjGnuIK69xU=#we!!1*BVKL?*#LKdohFRv4Gr9Zbx&l%(zn(={Av}XTLPlDP z(EHq_sI^B3>Xfw(=@&(!ZvD3+w$^s!S#JkQ-xP<;`zE4CnyKi_&J6Ukem1HsIgX}z zoe4Y;MkI&`+{m$zk z&@SH7fIiW^9?;bGYqXpVa?W=nIF^ZY)d&3-GPW1co8H5*njiNiINq+uEkJo6%GhY~ z@Or==v1c;S4)qTME$)9gKC|j>&S1n!&l!ny*I16|f zyY75%n!iWnv3z4~7h8L-_}F)w{&1j2FCGN+7Ate0i`wV{U26D|BYwO!hXg=39kmQ- z@w_bD>UAox5#K-S1{x%;8@3Ms+VcHMpude>3bbCU89@7mj09Tq%>^wI7NESNQ|J#85?oaC62X|u#uWGp6wO& zmuh~thV!%WiJ}gaG1j&&*GJmr`smSneU$TF9~HdUN6pLikxRKg3Mtn|Tk-p(a((1n zu8%y+_0hs|eUwwKj~af^M*}|SqfsC9k;eypH2Z@-n(#p%d414F>p$qD%^&np!Uuii z{y`r-EZ0YZa((ozTpxWZ*GEsw_0jWkeRQ*2AH|pJBj0j;B=m*YCvv-X{}wklS>&aB zzs0q>eS|F@he_ikpvB{aaErfp%v!vBuV76O2b&DO0PGPxrU6YoUU=4YP~VMugTQ)F zYI*(l@4))XqQ^1d_?13dz#r!0FTg(KRT$6{CUgS2f#)=Ed}{q~ZR|jKtCuZ-{`owO zcK<;h{XyKe@oEY*wZ2dHPto>AuMUx*|6UJ14RqAZ9H7OI-z??t`CKjTQ?Er+_V>4c ze^--soPV!h`1k$aa@Br|b4r&U>xoS?v3?P5uEba$g^ko$e|5j8k5sX~hOvHKs;C2H z#QIsWsT|L>8Jy6&S)6>s9M0n($2c?M^Ej_O*__LP9L}nj9M09#91dy4;l#$WIj6qo zaem)9#>w4xloP)75a)1AD(BR^y_|^MJ2)AEn>ZaE!Z_=L=5d@K&*b!Xoy_S!cOs|N za#zkG-YiZG5y)9y7|dxMy_#dPEu8ZxCz7+>bt~sZRUGF+P9jI>%fK{|+jVDr5OYxE zv3%o$Q`s9q+_CI53+OAXfk3;DHUoN;Ujv{KN(XU<$Fl@lJnjfL;#m@ill%)hAdWs@ ztOvTo5j_ys^~OyF`o*V>K)V(12l`1>KG5}j%YoJj*8|`G)xui>t$wc=&>a(hg8gTl z%>}xEXALy9{rvKwp#PcyBx z6$47mtMYIM)-eWNVYg*7V0xsc42;2hD$am=4h;jVX&A`HRZ3)_5F4os=wz!HP>O-m zxC84L18=e0vKcVXsVM^!@Sch@5PVWP1B}Hq3>?Q*N@U;)Hc}aAd`!iFQVg8K9Vml= zj^AdnmhSUrMRoIMy=y*?_1HUz73{f$mC>+pzBT?#L=U)1CF*q#w(!%^+6Ws3EK`evT|pbtjf5XDUnROVDxA=f1t#ygt(tTZoo(eZ(JQvlsX)Bia9BB0Vr(jLZxSnv$&cncmz}__$ zt{Hkz{^vJo{KzC`e66oxwYCu!lTebK(=%0t4<3Ru4-4G76PSce@ zw>O>ywAMryp#AT80&V|m5zwdeLxJvT6#%rjzX2!zp7+(hpNpUR|ITmHy;J8x>B`o` zR&8$e2w!?`2w6*bocOUw;r*htL{793S>P%1Z+;0~0L8|*xr5dnT134Y{tBG^X< zt(a|r`WFmE83ogkKyM-PeH@BhQr4n<`$ADPdp7bY9*idSYJzr!oF>Npm_#%lvC;NW z=6rtT9dP|N$WKchqJh3|*aT?v9y39HqqcWO47|Q?;So>J zKaCv?fQ}y63Fx+8R|1{(DhBAHRVRVoG`lhQJXWM2pw}_ofEM>Rng5rCYWbp0%&nH5 zm7X&g>u7R@1}SQ~#GGM-jntemT_D;?HMd&BxmAPHq7IaiGgdVoVEfIu4|#C$Rr2vH zW5EyI@q#wR%LFUdMhS+_ND$269u(x9IwFV)%@CmO+XO>?%ofbQ*Ir=fl1?tL^W@j> zhlulg?-9?`x+8m*FLE{5f-<_MqoQwIbm!K2bm#I#v?k{ax;*bFa$2+x^$OpPN*}IA zKW@%LX$_sxQS)BNLi0O8l!Oxt8V|57z;4%_oMC!a)Y0Yi_a_}3w1MRyeg2M zGZI5-a)$Lenw&8n8>u;C>v>hqP->hpqCl>kQQLlx3&L*8o`=oEB^CcY?x9#6)mRjc z$%UK5-{TrwmCk^16b%E+3p5N~IuI}23|D8<0&i*hmWF|E1HVRsXoLq9s&c=u>y zGotM~>*?LrTCd#M(z>X?$~re;r}fq7x7KC5n%nfzR=05&b>12U?y(lM4zhl8WUF;_ zvcAndmq?oxIZtfvoG~2qr>*^B+ z3>~!0d-$O58}tV4x6QU;%yG4eoYKz5YLkx5kF@4CKe5|&V;m{oA#A6VNYoVu5aTB@yU^EvZ0juY%>dj?004MT{fR3#M5DeQe1S z;2+ihIeX5W68E=J!)HLVci#iL^~M`Oe=1G^I^?J|(9IuhmD+z_@tGt%@2kaSMdx8_ zGPkP8KGp8cJ@B^l+>jVdlN(&G(&UEu*htL{IoDLVL8;s@_qtrU;n%`a)Ne^C%34;6 zj;<_4#cN8@s!gS6)s|9pbaN>h8Ci-nHMzr-3N5(>h(ZV+pqI*7*jky^dQ#( zy~tq)(0(Wr=s~ad1N}34I?xljx4`H3YD-6;#r?6k{g;Jm`JztD!#X{bo->N*u8WAm zO_w+in~IIpobmFGsJ~S6ur-{AO}H!SKpFF}6DEz|biLEUM#nzc=Fy1DHjQ>|wz)Q= z9=lm`7$>Gx3r_3E`ZgB#p4j+bZ8xZ@Ol?s1vUxVni=LeuLLk8#GVtRC?}1ZfMtZ?({!9xhs#va$nfQ zao1Yx;o6(Ta=G;bxlMbv=I)+2f-~}-$nCl@p4jmKpQUn*C(>Vk1@U3~f*_#9oQ=c?(dUx9Ms2}4eJYQ(;C!oAg~3x_d-Lk zKlS(%0}4QXS)gYG$`7@l0JLrJQJ{;=wLyKqnn8d5oLT|OQ|mw7sw?P^`!9dd_D6^5 z&A~Mbi?#ECrXIh~<(c4kM7A!dukX&@K#T7`^x@z0zFL0xUyUdJSL;2ht?O`kB0V=4 zFQXYxq~m5w%nhfpk(wLoKgQJ6V=y!wJ(oIp`+55Qdmnw{e<#Hl490<#XQl|>$NpM( zLiqk|`G{shb7EDa0O9+vrPm9E?;pNyGZ(fgc9Bb%@SUh^^F;XmYQa-0;n-<&MbdWR z`@ng#KMLQA%Dapb&bfvAzdIm&@Ad9T1EG`me1hDC?;&LaxWf0&-aiaA7!1a>m%IhS z_a(E#t_t6cpW9n#G8l}4Sz7Cb?|~(kN`&t%XKZFRWH1=y(+Tmy_c?BcUxe>X9zPf- z?4i6V35SL6?hlk3Pq1+Z%E%41ZN9Y>yDfXZ)#!=n3{iZ()e-Nh_`27r#nKr_oKM3* zCazK<1Lv@j%7A8(iUFm@)I8jQb&P>m*lpPim=@QRfiZYb#TjrfmCis&3=IR>xJro( z6k;Ql0i9OKZx&1iYu>3~Vct&HzdGUFbNjQX&Iau#w6@ z;}tCKeTCyA!~27#^lpLX@+H!ODMKfUM8Z`{tEzs@cMPdH&cleqo1G{E(Zw_+!l$ z@Rz(4eeQKS`idhr0^R@DS)iBqc}% z&FaN-Wl%b zc&rWd!R$sr+ZSj6ZR`F^>gTb(>@N$|#?EzOZuQ6q={bX-yC-ECZo0&rq4q}9FVvhd z$r7M*Q4; zjcq5OkG3IKyAp2;8xfr@t+s8HfALiLnTPzgrtajSU{A7a%V=^)=0x(#_Sxj>>{aB8 z9ns`6jiY2<;A1ksR|CODT}{DOPBA&S=V6k#7*Cq)*-d_4nLxJfm_iEPV~9gjk(o|a(tG`HMbgQ)c~x|wA~O2;s-UJ@pFnnygRs2N9uY=;TH7v zEI8i$-UgsP`;R|?om$`FWBH)I;1}AUzKs_<0)46zTu*EN2Hq!|di*IyxgbB3Uu^~I zbGL%eG}`W+2kdjd>4W-D7Ipyo!oCnto?8FB{e3;np(cT(G&1&3D4^@fdAC; z$(NX*e|cncaC~<6!9a`epZwwPd0#C*)cU#A(l63;Lr6Hym|EkbsM!*8gAq1TbHnrs zOr2_O)yz|y!C)M@`-&94yG%UPR!4YN-40kHd_ORu?=9i`*c)GZ>I&zU$Fd`Y?*^Yl z9VjC=EdLb3+jF}qzs3Ez{CXWY{AFvJl99K^lh^JBliTMkB8w-xk{#LoN#dIk>Dr_d z>3H6fEKIN@A3W|xzVR&Q58knydG+mh;%(|#qQfdJ#M|5xJ#ih2hWXD$TYQ2M$7n5@ z+hP-%&_5alZC#CsoT;erYai6YwjK)Fv7I=7w#xSGZ9Q9u%MDM3eG1_fV7Keec*69H zsH5c@Ps}{o9>lNdR?a{tC&BN>JD)xTb~N=o(Bkn$xFHw#+ynC~@S5wknQkC{QTM;H zIhi*8h1{40emgQ)*aIA|@NzE@|4OUkf!^`%DA4ZZ2|&Lri3a-5!!G42B-^_x76+t<@;IqaN0}th18%S)lx5_BvTB(ux);lto)}s=9tOe7lieC0P%;vQhV3$wBk+;erwPO33=4ad}T)kGaAV@!S6t`HruD@3R53X#CB5OM7aQJ!5PI%HRf z4%!u>9J@kv%B~O{vnxag>hf z+^!qBVWv7hOXbQ9g*O?rvFLT24t%bF*#@ASa6SQ@7gz$ca1;N1uf!2P2Z3t0UE3DK zHmW`Q<5Q4Jb~iHs{wL0#3^eaXKcH`InhEr$)(jB)Uz8%CQzF{~P4%Z`(NLAq7w`YI)hIU%(xu4_>uGYsBHV0=xgiQ0skz~*CZ~EzU_V>o;*>=O8uunIrU<KYNh{ZTs2=e5H;3T5k10e+_Q218Dc0-asF`KN#r6&f!3F zxH&*C4@)0KBK-3=C^Q!$1fP1M#>@i40_8 zBb9+q%~T91#Xx%VI?2E-?6zzM^jg%E0UNxh;tT}0p-Ip~g`EfMW`0++2lm82Gl1S=HW}z!hn<1`F&^IQTYPLL z+`ihb0qbIZn8S6U8fzlKI-9Yx;IkOU8C3v#WNgzj;`6w(LOTQP*t;Ik?lmC=BZ}-gtXzP?&KzmM`1T?ijc5hw)`tMkC_$-Kptr`M*c##g! zbNg-tTHtdM=#`t!0`2=b3TSbE0^0m#q1xEFPF&~E&_sI9FkVQLGj8CfOUxN%*htM8 z7VSj+r5ZoiaQs|?J5a{>IhJjSc%z1(oNg{CSz{LRz2}FP=!YQlIcw0I)vHlEw-D55 zT@V`I*aKaAGZnQPJ_|X0xXeWL%@Nww5ux+bha%nnBT&ypR_KbOB^t4ag|@LR(Gnxk z=f0xavw_GS~TmP6n(fP|ZP9PaO00oI#lpWO)JU_q0+KwoKa6lm)4R)#&7dVQ8~>)`^Q2{hs+ zydHvjymKRU!10Ffp90!PEgu;HpOHkhHxPuu>kX_Mg1kX3zgN8>ZGE?)-<~XTkg=^%%*0GcHSX{J%CKkJyh)!gQv3LifHY2T=#gh{exubU+I@hKL>A935MvhbE8KK&{Kmh;5dqh<(4}h$_ZfqScB} z!fo72VxMUk@hav3Q99uTp?9nq+Fa5D`L|#pBmQ{Ai}ONNyF<|V2J4Vf@(T2L(Iv8Vd&n3Rp{^#e^fZs6{&eL(bF3pP}`0o zx9i3lh}q`&ES2kbu8?vM5EDCT>HwW~3tnT8zLW#(*3X)Q7<4Az5a>|B4NyM1hY`^C zTzY~SF5JXpd|J6Wh+)$n*?{ksVK@DN9`|`V(6e862fD>__*|eKM|6Qbbz~2qM<&DX z##H|-H3gtORC|g?FW~>yvFm8B0pA!_0LoL3XEDYd)K{W69Oy`mgFyFv2d~kqx4JL* zd=~VF*XW7YXV~fQd0#CD)cQ3Lox4iU4erZna>Fv*Y>Bxc1{d zR17Hf{V1xNTnr3&QID)muRQFwYz8WDNyQmx-@T>`^y(#@0fI-92f}cb5*dicMk)gj zd#D&tih-D(axw7r@-5ri^x|T-Wi#*vmsFgA4!vs1fMs9l420~aVPGAuQX&Hh*hppI zX>Sz+N-?mbk6aAgvD<80n_eV#TQ&n#xTN9?bn07E1_t(*&Ol-w4Fel-l@b|9!bU0s z&n#68D8)d0Ke-sFZR3G_?6zzMe&dpgGhorbrVI?SlFmR#1`PvIxJro(q+%nLftLeR z3@F7w!oWJoKmm4JHUnx_q6R9?Ku^4Aw(=ZT&tCYw}D#v}~`J8+c}8OX#&Dg)(%RSYP_ zKq^x%2I6-87SyKKHSD%*2DEK!%D@1;r{W9@V@YR#$f98&9#<)mfoyD~GVqB|F`yI! z=}0aHg6DV;wdr*WyDggmJyuN_u)%vO&cH}}=?oOn&5b7DDkU;-92=<&d>x`+WB;7T^sklms4Dhg#%D|uDDh8Bd;P?o+ z7`Wcfhj**J4{xxk53je04{vlkA70;SKD-<4e0cSZe0cqhe0UGr`S9Mg@!_er^5F#- z`tVvA`tUxq_TfD<^5GpY@!`Gh;KMs<;=}7>5YUE4Y=HL3xD56mlG*@h@wHpR&DL@# zxaY$B%;i9*EFBJX>DivN{_BqK0P1_u;`KoBwR)@L4T0TtyB*L}|B`=jPKfVMwI5vb z8yx?j`4(`zriJT(?zQg)C{I0JY}RYgzG2HQ0L?hI18T}z#JK?(puQHHoq(p6-@c-W z)b=eN{g;Jm^OtqvI+#2s={Y0h7|r*l2e|1Hb4CR=QgcS%v7-J`t*@)$`nodQfil+D zwMup&w31zjuSqV%pClK;DA|P=o9sfwCA$!pl3j==$u7jRWEUbQ*@bXRb|IQ1yAZFF zT!`mMF2u(q7vg)83&BWsA#{^nh?dDNgkiD^(IVM}5FWQ%vI{XR*@bXPb|I!DyAb{G z{y&mjh_WP?f8K>&}og{XCZnV?9{*mL)L9$gHalG7N(m__w= z0)403E1;jpy#{)In^2%#>mQ(vk%Pm(ff#shU@MR#7S4SH?5^KZX#G#S8Vl;1xXcyk zb&I2b=Jh%WG&QE4oIOBlOcieJy2XI~w?69w+P^&89Oz$@qd|FUeLbo+f%c~4PX_wv zrA9zg_kTRh1k~3*uaLIBwbQeXNjo1hoc^*~YSLrz;F`g!8=sAm;E-`1AV(w2XE4m)n;P`AtMH8=S|ME{&!^Af%=+x=>V<2%@XLN9-`na^1 zD%>JF4h8$Kc=`qC4{oJEJN6p_G_}6fy9m(UgJn&C?!P0Kw*7mmE`j44>|Y7={NVYP z;_cbg+P$y17Wd~VKC|lXb3i^!qsbYWG&zGcg(hdXVk0$Y?3${|8A{Dfjh`l0&Zuqk zv%%PH+4HlhU&r zTm{-2nmZimw8ff0Q}-{s*b3BlGEM;M^Nuv)iI11Hz3ZP6*W$-(Kld*S)nc{yPY{ys zY4l(GuiE|IhXqQ{8AZEjaz+?#y2Lrxcx##hvVGUi+#mQ)g> z%PR@XdS8gE-ztf$yh>tLx6i~Y?Fz#A&&LW z;3ptnTmI@n>;JIR1Aza}wtVJ_w>My0BVZpfvJ=p$2IoL|s=t-(>_B_&yzd9vo4U^& zXv?T5P@a0c^!2Mj`)_KF1?_#idjoBICk@>L{GZz+gVz6wyVIl|&nEEid0#C*{9oN8 zNz%RYAAu95KV4Kz|EGJ8;)ZmH8*^nhpAKDBdKQhNXncq>OdK}p?FP} z;K!yTf`J!~3e1V4f{BB&1YGY!f{Vir2zo5qFVJ3`B6wnzBIxxbRnTu&hM-;hBZ7$Y z*#Za69D!-CY(d4oqk^hlM+I6Fj|hUovIIZkvIJvuvjj`mWC;@HX9~vMIV8CJIYaOu zKSQwe`C-8o-6MkIi;oKW@68sB3OOoBIGQCm;gKcKUX>;ATAL+sUm$Y3?u;kc3q>Bw zH=Y>PARK((q{gc^4dOt24l>pU-|uZVjs-e}qTRZ{`;u<4Zw`(}t*_&pY2Y4krI+D- zQBC^5`$c;V?n~>x{;E!(zPNtnApWk~-Uiq&T!G)$ss7#i#-@$$dp2r-N9p-Tz`%7C8QqVJ<*Z%aa`!N^M`jqQ5Lu%NKQG zJQ1=~dd?t^)8vdBxakseMj19zbB4tdQGcm&Mh$aD3GP4{Iit4y9;drh^tsEPhjqXu z6`zM)AXY~;7HNNvTeC_!1Bs_-7`TJ0l*qt)Y@{;KbGeEErRHH@;0~0*ziCr~Klzq0U;CCYD|}0sZT(7^Fa1iG_WmWz zX8tA2?|voB2Yw~Y-F_uZU%wJ&tX~Q9f?o+U->-x@-LHgs!ncHJ99Y8iU0A|A7Er=u z_?0jh`js#b`IRsa`js%l{Ysdt{7RUM{YscZUp5DeKKCuv?g4M|PtIX5*8B(efTyl^ z73#v3B2U{%98U|q6mCuM_~Z26O#{TomlrbfffjD!_AjxaV7<(fyM2J}e=`@X3H8Yf z1)6%iOASa+U$hatZoo-f2dsah?*BDS57eLPcnj25Jfu6YUpg`dXzKB1UF-qY_fYLA z`hMW}hxC8a9xr2W7`SePdc59g!C*}<)oyh^k=7p#UoGH|#<4v>b1yT1?r@eTb^X=i zRe#U%YB5;+H2-&eOT8!jzyJTgey=umPFyQJ2N)lv$pH^=lO^VW3T&k2fWD!s9H3MV zD8n5nBL^HjZ$T_9uprDYSP+v6EQoXGEC|!H7R0YJ7DU<^3nJu<1@Z8V1(ABzg1CR) zg7COtLA)ujATFP`Ao%Akh~4Kch?Mgd#F_IJ#Od=EL|ftU&RGx>&sh*l&RY=73l@a` zMGNBWB@1HQMGGSPf(7Ax!GhRv!GcIBupm6oTM%>3TM%*QEeN46v085_maM^N zsl+@G{!gS~FpM!vhSRfYOlUfV&nkE-Vd;^?Z;yFqD+Q{(eB0_9#f~9SFPE+sz$h| z17+j}H|=<X*qbt5UU{R!tjiu}b4_vKo0S!YY13gw+5d(rWsPZC3B| zc3HJeNwCU#xXUWbH`b~f8EciF5^JTe7H4((Wt`Pw*LW+^G}fwh(srxZq1&z2<-}PT zjNfbZ>~@@0=b@2SEzibVZJ4vyYFF+atIWhbR(+oCvdU-^XJv04XZ7<&oYgq(c&kI$ z?Z|551LJ?#V=zK$!T8|LMp5TBl9&^O9{#gohGrkg1u;UniH`+BE&G9(dev$t(2*bF zfHq5O3-pZCuC(8sx5Pz*n8axcpL4Hc6a(y3|8iGagZij;1203+KFvTApsD36;^FmW z>mpau`afgO4qE@djnkx!;jUGhASY7&Ni=l_Imva0CT)A~JV+Nz#S+f z*4MWA(6&*c&t3L>=tNvn@%hlzVs%vGPso^X6F!5jBmVpU+qP|y&Op&g8U|kADkU=T z8yl$%*lt!apcDgNa0k{g28_4J%YZ8`sW=1c#p+Ouf!J-*83@@&!$29XQX&ItF`_n8 z85puv#eh-_RN)S+V+?fMCNBeSxTN9?M2OX)7z4TS(it$O`)&9=u2Lcc8rvl^Fm#8C z0i_uDjXO{V12ZD}3sx>*3U>9f7bLYAEC}@KE^wXOR-L$KK^{BI; zD%DbOJkLR(6EjJWnLSOgvg;(lL2XAtBZHxWeeS~r(X9wU^DBJ?P9OUUv`YF3JR}Hu25W_qdshN|di_11Z*e_={;(CU zouMB8+9~+_$h@bm!SfW#7k^22;)^1FlYEHCfbE2*H zh&oV4Zty<*&Nk+SHqlSZ@|23iyV)U6mSJ3R)=ZUcjra#xLI zlBCYkh|y$?d91;D;G)h7bW~@}H&$nHx~sD+r>e8gxT~|qtZKlj++CkFDyTjyMMIs{ zDp-?cC(vfSs?=pguheF3{-ep#S)$2GyP(NRS*OAJQ`CTU_+~;HU@C)bb~9 z!o0wJk^$Od>yQF8b$|OO+d%tnuImRhwfxHUJ!tLS#~6V2Rtb`U7C+v3d?pFc`)YZi z){hVF9h9CMNDfVI=$b~88(7##%?+y$sB(i+b4S((<;o4U?RPW}?6&OR(GqY;#ph2i ziq%n#L*aKN;U@8Sv`2@eGhm!V!$6O88U`G&k;=gO3>5=PF~B?|7X#lGa?)zkYYui> zHUmkxq~Z)*6{|xr28yzzGr*v`wx-Wv8U{vSBb9-OOceu4F~G`_ivbUFO`*0;LRI3Vk4D-WKzX|QVh5X>Ldeeu-md3;Ng;rGw?>N4#gPwnJ=9I_op-rj6O}n zz-(-!GLUvg#eh-_xaHSL2Ewu1vKcsyODfJlxmX>FF~BIGVSr1+!1%K?49vwwDgznk zR17G^z|8YGo#-ywdRT_Sl<|!S-gPv%MKP$li?XW^YEi*_)9u_GaX6 zdo%K=y&0KrZ${?Xn~?``{X$=wUlh4rH`dX)UB+jrT(VJf)q!oqeO+WR`}DXS0av(RD!O+9|^#Y1WBVJ4oSe(L^iWeq@k zsrDm*OZtnqr=3qD;Qy=6t$;rME)D1fq3b~XXlng^3i+Ua54{=(v|V&5 zXn&_XLtv*KZ|K4cz`v=p;B%%kv|)S1_iudV?|EM>F4troouswV($-Nc`u}QctOwqb zo*RtM(&UEuxY-idSZ~BeYHqlA6;r3W&ZcJ9*@Rsab)bwj)|+#Si1ged;%sgaaXPn% z*q&QNEXge*B65p}-MK}?hTI~;E4PTS&n+U_<`xkha*K#=xkW^q+#;e+ZV};-TSN@W zEh232%Qm-&7@k{1jLR(|{Bw(lExAR+f!rb@FSm$@&MhK5a*K!=xkbd(+#*cvc z#Jb!fLg-7c>wg*1b@W_{f`aBd_|EWJ005D6uh9SEf6H#-vsA9!&?WagZLDgop$B3Q zxkH=wo;q3^*3%yEbnu729EE`+z52H9v^_dvJKu-sp|#$dn!#WY6sfi|JE1a@1c|aK%1Fo z0xjIcbK9`~QNW*RKFfj5w`m8o`2H5R{+{>$licucy#K$*5!G^o(*xOdK}VfG|P=B#0k%%1}tnZJ8EGR=EAGT-%bWP0>< zWL6GzWcIdmWFjX=<_IrGW?-lzle@-|$z0~h)D3lH`mT3mzKL*TzKn8YZrSX}e74z< z*)Q6WIXuFV`DdLY({#Nf(=yzVxhmX|`E8vevtgJcbM{I{X6jN$=68Qb=0Gn;riX_k zbEcak)7;gO={(7iIr8pbMrwa<*l{19rE=wlZbqdb78x$g0(#86PJLVy|+HNXXo^QEkDLt~+jDfTosD4|M>!WX_0WpsD4fUkJeQm=O%n{{xr&0QQM)Ucm0u zet z6rTISP2%-8Q;TRAD57B?6IUsbfpgeMWkB<(iUFnORe87r>lg#Cu-md3FfFPn17q-> ziZkH;OgaN0H)$Bi##KsWpb#6W4Cs`o7*L9V)3^ic7z1yy+p-xje^yflCg434XCU~s zbOzi@X&5+;tCYyV6>Ow3(D=EE0i_r?hdWRP13hN#MSa*A=-r?^Z z-N|Ff^XySHWZ?nywec?Wa7qMfw0$mmJZlc})eT1Zb^++8CZm~-Gf==FZ}h&O4?3;wizYewp{e%s(8!cUDB5o+N=pqv^A>DCuTrDY(E~B) z_2F1FY2R+N!ecKAdLjDU>&EYEyjS=vm1~^bY=$+se@^H&HK2u?_?+Cw%x>U52v=G+ z1iHzD&OkT%c@Aj)*skC{Zjnq^pgmF|fxbAx9_&x`=j_#FU=MQe1lsUrcc5>^*9ZD} z&T!iMOf)>y9^6Ob=*QVWTk)C$?LMV5xbMds;y1VtM9U|;fqh1n4%q+avCqKX`6aBs zq7fgIFQ{J(^cka#KpS|k2Rd`hcA!Uj!u#@x_iw=Kzvq3m@8>o7eeK`z?7wrV|I7Dk zYHqN2qsk3R<%Sa6fiiN#EA=F@FFHVuU}Tc>{v0B6 z1S#YgjlJaZxCHX%#y#W;UMyL;c?-G4a0A&~dod~S4kmZ3Uq{{_9ZLF-T}6%?wSo*Z zUrLs}UP!hJSxB<2mXPsVR+6*A!pO<**O1!d){{-UZ6F`TMv!s!Bgib94P;by1ldDl z3#sk4jXdhGlf1QkH@RldUUK*AedM;cqR+kVJTv+Idy&WTJu~@iMhp)j&$AS1asiZg183%iU@(ogQfPQ!$-bcxOvKfe9;_*tj^*7&7>#uqD(ct)wK6`;b zT)6TH``<{!w12b>&<8dw0(yV9Wk4^z-X3Uif0E1pvQRBw)QM*%uc?%tGZ=Jps~R6f zO_!K6jIfcKGp2tO?WCGpt>N6NL4~LTW#o+b)AdMme{HhzQgiZRe0y@{%pT;ks(xg! zx&`T-*_K?}&Z8G}q2Qjfei;5mO+#by5d{TKOP%dhZH zZo0w`xqq2&vG)@H$Mws6$A`!H!3A;rsOjtZXI3re>)EIBcO1ULXI9+jKhF8YH&1Us zKCo9K=UilvO*PcWn}_R>dD!g@)t(2{{vVzgjNt$Hc~Cb#i#qmKJqDu*UNvGjBWQ+$ zZ@`S8*+TDx+Xp;Hd-!q`hzU2ZBmjM+h!6CMYiU3WH}NsX_EV=oj9xJ_1?cz0c%Zjy zjs^O=w>Quab(ug@>+=oM05O+pU)TLC*x%FmDs9Y-TCWD~b+^XV3ux-`cXv1gj<4P% z8|ZbE_EPuHIb{gOG(i^xpnoIoZUp*d)={9T$KzVgk=nk+m7+64cw&WPqZxkqyg+@iT1 z-J-emr$uvHxkhtYlcKqkoT9nS#zk}eMn!Yyj)>;=9v02LWgpEI`jYrV^tsoav2fKd zk;n3lg=Z|h4A#69F1i6U|K2^I#Z4LlZF3-j_F9A|R;$6a2=34C0KN9ZK=2I7^YgX? zy>3x9(8pqr1KrMJHPA8sPC(}eR{-5BsT=6e=sc+V_*((H?UFG-_q#b1=r!8yfR2mP z0@_8p9?;^SU)JxxEL4ltbz&_1p@GIf592gqFc^Bs-`j6SkavLpkcIOXd5&b9rpXyi z|A?9{F=uqeMrzLRVQBpG)lM}QuHjhNw4SH~W#o+8ftJL8d6vZPd6tCQd`rS_z9m7< zwi|n)Y=9-i@E6uv-64H|E}q^J=vUegz&cOym?PZ6 zVxV(79s;`k?9M>z>ZF03GOFh;pxb|22y|P|#z0g3OY?dMV)m8Q zED%HI42IXM8Ea{R_7*#s0PWny4Csy<-htyK7w~~@+ItEpZ`^7(&`%AZ&e@&@?1v*b zK+j3d1$t{`B+#DZ5}LREl<_tG%q~?sh z8mgS3)Hq|Jrd&Bg;ChyQJna&@J$aMO-1mU(9#G6S-SUF%H}DPH#p50O=jfO0CDR|U zhcvywUg&z9eXMah``Nbr>MD~SWNo?N7WcJ0l{p_6lgY1y3!)#*nQTC0~$JmdCvf0g_onpH!C)p2roME4K zJ*q$r&@XMIOtSGnRk23u5rsgzG@xvbzklw_Xm=XV1p~Z9|R(T0G_mw=F4p z;M#=1^D}|I*mOP6UAN8wdiB;HAV*z4y$2cA#I6cn|cV^*W&aS9U|)Ip-`WAGmop(5Lfyfc=>f^MPLWz#HhDL!jN_bQrK- zKED9y0b15Tw=9O&I%IWk3hd(jW2p0&g}RwDI_pW#8ALHn&RB+DUpGAY@{;q zu&Igxr5K25Rwo(YVz*^8@CBDtoPiF_Ys!E}Yv~LWouSDC>u{A48A!lJDg#ejs2EU+ zfgJ{NF;Fu4JTeNuhK#q}N00qWkm=AjsDWAqn%nssB6NPBEGJ9h=#JYZmqohynWLzI327 zyfy=^=F}SK>pCf5Jx>Uc0Q8Y;_>AZ-=52tT>Yv40HL#v2OyC6c%Zgl}%U;80SeISy z4Ep1f4SdG*ykF&D|I`^gpijPc0{drMYy;ZJD;nsA=I|N?(?`b#x_uHpE(5O<)A z@pIY74djSs(d1*jZRE6)SkgFc51G&0M;eBtkk1;Vl9^}rku7s$$tzKt$k62%7S{Bp&W+?Y6#4CgtKWnG=gx#|r7A9*kLU zAGQJPx$-19-g7iurKN_r=EnFT8 z*qC<}a9wA}H-y0;zQTSIU?1^kzz=(M0{yw}Pg}sX(>ek^=hz3Z^C@`z*6+1I{C!L= zs4x5I9l+G~w?Bf-|7k%rJ>#~8e9th?VepJn-1G`PL#vgvU+A7OsI~O(YR{;mXZ*$; zsK7H;9?{^mTtCb{Xy?m|J3C~HcK+mvrT@#ZBY>;Oy=Hfzzc|G3Q`L zE@$@7EY5K2#~hucWX{Yhr#Rfj2RPSSZRVH|3pwBN9XZZVdvHG4H{}dGqQN=Q=ATYf z!!znyNHeb7Jmc^^V=#uC3U2~fFSt2iqLV&gvfDc_#w7U01C||gs4euN8yLeEa*qI> zqIDKZ%u?O89rrpgcm~tjj@Ip^qN>m{Y;ch787nN+o>8r9Q&t_M z9jL%FYTD;?6LH##ug`|zlFDD7O_r&n-1|4i`zRiDm9K%w+YIlUEIKl1fP;f{4Xo*; z)a>bq8JB(sC1A()NFu2G?^z5Wg>xSH%}lMFi0R;IZPlL zF;XC!>MRf?jTVUX#t1~*F#-{Pj6gJRj6k$tj6mdxe<+?ZMj#qIMj&cGMj(1IS|Hjo zS|G9=Ef9Tm7KnB@3q;pP2}BQ^1fsH$0+G*1fvD+7fr!c_52qbk?*7nL|9J|FweJ7; z{?Na3Zj-Lk4sKE5vu3C)sm%~Sn`3{2ZeT5@g^oMmHS@Ot<_5(<@|t&6 zHa~j4f=*L<$;Lg3biw@;9Pg2UlV+X;JbZ`&SX=Av)e3OGfcMwx0$8@aDfrBx&hc_%u=f_M$VbWXi}yz^2#(u!I{SBK&CM|mT8R6XBs0am*+U`n(qNEdnw}q zw;RG|`VTvu2-d@o|NREA%?vHDHZ09-60SC1CF<93O!mpZ6k=ww)Yl3 zb5zd*p4!Uy014A~_jIvl@PL6hNcRB$-f9o1mIrY9RLTSTjTtMPes7x4M0=rdcqf2|LGr`FR- zl6@a+@J$==$_`&Z`Rwi|0LzXs)b>127u>IKpq3Zlsb28@hGPRZfq0!lAHbud3<1}> zz7cp-%P~s9HMgp-S@*L49j3gplZW2X9(uiy8 zo)HI^zb0(Yydy5fzaqA>o)E9*T_V~Jk0;*SY$cAb_8~mN1Vo;2HZijip9tRTLfrW} zo+v#rfoR8{K&*;#A)asM6Vn#XBpx^LB*ytJC#H_{BaXKUAbzb0Chls55U(2UBJPZe zBs%295D^-2M6viNG2})9@fD|C^XJ1>4$=;;+~;B!{`>%IJ2gHw1otOI_wNXpeX%p( zEpJ)^evwiDUQc$uMQuxyyMy};3J-?^Hk}j?*z(T_z+VC*0e@TH1Mq3D1aLn>bjohP zKdwy!tm*O?tc#-OJAHXNShplFL!f=Fj!pn<`DZiWPsdLHPHG7M ztR9jhd@D&oYIh0ePk4;E&U;`mZoX(SMtJ_b)8ZAw`=6it6SZSt}n+GJKBVde} zJ@_8rr>Q>xKQGh*V~$5(4ZtI2lAyfo7(;Dia*V*3%pJG@@We*mfCu!R1USdQ3a~)` zG#JALlgt3KM`;0ecOk+31TRl_1x(M^tDrkr@6Q@`0PvpQNq`5=V1f28{oWRE@YIkJrDRgLB0p% zF|Cht7$+4=h4%?e!$GDGjtsu4h@3egbgXiZyT~(13K;u8mP^n0hj3v8t}qFx&}_qP-~!C8kpl&DGk)Lb#mKr+KM%B7nf9C z13zTyP)-B&X3N)r`4@)yz+`s@4J^h%x(3eARBNDG8kj#zB{i@Mr>$564{%B4HSkBK z4&^jp=pkPNc?JwVFl{!22A1O>T?0vT)EcOk2E68~qz3llv=wV04VP421DYPvuA%Bs zP6NiC@-@IOX7B-bTxEsVMpxq?T?6DiwFauCfyMJHsezicMdGv-YakPsR9*wxo>kRA zQ!n`%FlYLVcn+?zLJj!hAYB8=3)C8@mIjtDR7njS#Az$mKn^acyax2Vs;U9gMe;Sk z(r0)tG#^)4p$0bKAYB6~-f9h0O9QI~Dye~3oVH>ON2$ks-=Mq%PXaU)o~nB5XT`W#&O8j@f>pKF%J1XfkU23;E=J$ zIAp(L9P-Ig4tX!0LuSQsNY6M9+3hHYte?Ok4Uciiw0I7AIi5pW9p#WVM>*t>qZ~5i zD2FUM${~NpamanK9CBL>hx~SsLzW%pklo`sWdC>$*)g6&4vOcHmhl`iH;zNb#c@a~ zmu@ShY1jPqa&uNmGp*cuxwMx*z_V~<*H2N~SHo`LIRx&0@Hq+e@=whoz;hYwUdICt znZyHZFFFReh4ov&1&a;=rq^e=bpptb9_LT51D+dU{iIm#vvjDRYqP(QtVQgJe9-@L@v zfa|)Q11x)dlhyyu@p9K>{->WOSJ89gkdbujDmkaWdcA$dwavykQsE}8Nig;zHC9e#%MP=wHrwkQv%Frcl8ET2j zP#7vhCx|k1msf^H^eaP2L>cOg%1{%c4E5rbAtzoLDkI8JDN%-=5@l!`QHJb@GW42P zhAan^A?rb9=+)pdba_A-+RrOPzPvIN#4AI5UKv`zD?^8P($}VP>9$sy_NH>r6gB@( zM=X|44S1%gi=Q-0{R*EaN@e)pCZi$Q@urXOA@H1_?)&ZnrZ(B~g(3~`9G}Dc8v-tj zOabHD){b@{&emHGI3S$|m|maerzFOFwq5iF$A9Y*4(g}B-m%jY!1!%lzcb*`%{qbc zH%>PUF#YvfEPV~?|Cz|LlkKm2PR~G`etgj_yNj~r?~0=t>zgOi0PU-n*%femv-*H# z>(gBK?|d#d-piiS)VU&WZ2q7AyTY;FI7t3j?^D7s*1O~8RyfwL#zFd6Pp+5tk$SAJ z;#j|UgR}z`jP)y)>WlT(>Wjw(=!=~q^~DB{^u@P+>WdqE*B93*(igAK)E5s-))!~( z)EAr0(HFDF>WiCu>x-8L>WjJF`r>u2`r`r`SM^u=@C^~Jjv=!=^< z>x)A=>Wj(R`r>5IQE(-*H>rY~N;R9|fF zFHO7Vzu$KWlxAAF_xoF3&I9Agxy|soajQOW0^{qN8OH$2jvv%k;=TxsFAsN@0M2u) z1;)Fjzm@@xwmb}Y`0&Mm>Gkz;MU44Oa%O|$|D>jYeCV(D*}FUF4{`Dq!1VHuM)<+k zFWw6HWHEfs9sPLsMXx~qJI(X~*Nbce>Tl)KAMls6@VRxaqHe&i==J6NJ|*}4zIM>R zbG+R6|3CSDpFXB5>lrfdRis3q-&m3E(dYiim!X-;F8K;_te`Y?Ha0%avG#`LT$CFFG$6||NqT2M7{>{-Z8un znvbijPy-uqkgkE0&1wx)>$>OaEh?#jSe&+E4dmmJ%4?v0NL4jpzD>RcBuvi_^}O6ycJ}YoOt_s%pS)H-iSW7}h~7##L6Rfz3Eb z*FfrawFauCfekwSbu6?$~???h_z_ri6->I~E7d}fXyaq~TN^RzNUeT_f4tVxk{?{z;1#)t0N-e~2k_%xdjU_`;Sachc@My{?d`hz-+5o|{kiO^ zuk4&wnevsr{#tIHY`I^)H=yqfbA+w9*%f+291haG;dMAxod%1gJ=AU4=-Kn!JpRsG z%>PbiSS(iT>SR}HKl0ax^VI(93WtVNIP$x`C$%5E?EXz^|L)6n3#v`K7LWI(_UKdd z9BTj4JGU=&O(En%)DCLjXQBIhYM=MEgCjLp3Yh;UmfClFbD}ntWX4V1*g zTh9K`M2p2@-FqSSruIwSeN(BudEtOg+AJ39`gC1?YQG@=c0RRlIyHo&!(y={xz6F# ze%9nBpQ*iJcBT{6Ll;~lj!}DlX7#)wYLB!770i>J@5hnpbx)J^S6(EwU!Nsw?b$_o zt@R@XM*_%Q%Xg8QnS04G+Iz?h{k`P(?>opJjkl6lwyYq<%N)tOD_zMa!#&CQA3VsR zB~!^kv-srTu2V^~-F$LY^klNbBtLSh$3AkZr`3SPfBGzu{Nct`Os@Z21F-1*vI@cQnvbpX@L z3zC~MK3C{j!BNmY!=8zN=ZFpgroaBBr|%it!=5l3wC8-=#eiRR@B;iW>l)xVbNFmJ zN3-o9E}KvD{r}GAa$~;i=VH_tCGV#)*F34QOy2Q|{!SeWw*H6YkM;aAhOvG%Zf=ER zeHaeX$NKb0X&HdAtEv*yCj(shm{6B_$ z!I&oT7yx+xbqT1?r+sS>-*Isy;Gl(HKzVw7d!3l`Y5jULc)fOxA&g`C`t!}f7#Xn1 z5^{9E!NMSIwk2@^w zKn31V(>^bIgws}hozx&!+Cb&6liK1VmH)hKWrBPS_QSUQ$qM*}uq8F#^M6RdoM6XWTiK0&0iTLr- zw0D=gPTJ-_KVh*p)}ZU8)b&!~F=@v36~0HJvZc0e*i#JCn}hWvq1-lriwjx<-u0sq z;KOya0cWqT4VcV0v zet=!;b^~nH>;d@h;fzZM7{8DH(CMq-yNHt~7=imOLIj-vhpyuS{vGE8_}-?efCqf^ z0o>p{{QmiU2HQd0vSAotSM$AquUqc}{O7?Yz|9Tj0Z!7A|NZoy3IDWE?md&N9OS*P z`QQF~x%W*c&dB!+{x1g4cz~N;p=Z3pLAqyjIw9>Z_4}qOzHiFM9jM@a(=ZMj-Q=** za4s8VaM{Qeu~8~wBO8K^;s`c6K(Nte#76VDY^29wqxW`f8+qBX(MDS~O0Z?4hqlsk9qri2)sBs}+p*DoJ2o<~XCp^@Hris(MtAMm zs5Xae>$l*)NL6d5O@vrm^#xNh~)2QTQy-{dRm%sugS-1Ej3P6KRwbsgZeG1~!WkBpVAXE)%-d7*%1#~f-~|0@8DNt$;CfOY*xGuHv`c69^bC7T=ppP2X&c+{G8 zdjaq5?g6+Ur4``S*ONg$9R}=WoF~OzyZ}6g+d><(*Zy>Kz?0)U0yeko4fsSh0vzN& z4sgtO_d=2m$Flc}yX3)S`9HeVtC#lvzwKOnXqLLa|iqlrCfm67o@*2pMsY5vp zyi1m^fyhn_8sJ@G(0~gL(lxN>vRVVx(!i)>mDIpWoVH>OoWmuR*Fb?x9m;8-?M?X_ zU^QXTz`!dE8kmfObPepks@6cYG%)sBr8JOdoI}{pEF$VZDJ6IwUx*BsKZIrrO`c76 zEnYldmp7)BE^l;VE#9R^?}%E5pAe-%SBcdl6Nx3eP7yB#A0w8893g(UJWM=IKSbDF zI7Br55<}$L93dK;9wi*u$BC;4PZ14zog?lw6cQshiiuyXl8O1E6hhGdHsM_FK5=r% zW1?xVCqzz{rv#OYPKq>b)tNs}yCKcEa`R{L>K|ZjjlEc1u(zwa=`l@!Fv#d zd4V9VQ3tZu{$LQ_#0mn;N)Z5dSTY3g;=V?JW!vB6=07cz8>>~~bGq^Oc) zO|S5p>sB13d&a|C(*9CkbFJbv*Pz?d4peZ>b=R_K)G%95mQL~^TN^DT%f3w}&GLtn1K;&08+dTZr*&<~9-n)Y{%?Ab8?$?o zcF8@-CU<&~3CDVq9gS?rjNNwRxg}h3_Y59MgbW~4dJZAWN{5lDu_MVRj-$yZt;UmQ z54(~P`zMou-}t1_vT3B0H9jL%FmM6NAPwZxspI&*8qf-`;4;KkYf8i1`TDY9d zf4+*mdT15d+jR-q?9M#0!^`R9_J$KlpCjYQpb2Bho!gzsxsykcN5Y)QXM@%9J$VedV8>XJcsq`?UO$11?&?aK7EL7kMocE#Eaj6(2ZR1@Sjdp!!vF^R>m{DZ>NJXV!lm2;5q{#*Ydds;(Wu4fD=An04zJ^ zP#fRxBp8#8Pt5~kd~r@FV7IsH02jvh2Hf(_4KU`%6bS&2&9VY~R{Jfezh}M8fX6nP z1)ldv&;M!edXenB=}FEtz&A{a0som-3-rgW3Pvd@og=ZZA8@_e~ ze5dbNz^N_91HQTnzMjXpHXz=$=q<=!w*70;{%JuqJtO3ae9y@H#IUv_A2+>1&-jjm zbkDHOPW=4K4< zn~HFi6>8u&4$?Ki%~ETiS{nF_J5YfJ8bz}??V{P7-qCE%plCK{Of;J_Et<_)5Y6VS ziDq+FW9}Qx=G2d7bIPLFoT4Z;Cp(JGNsnT49!9Y__oCRGyZDFt{XrC)^Eisld4kJ5 z!^f0Hu{mF&*qmQcYz`}$&C!fzb9AEF9Q|lE$0(Z3p>m0R`p+v=<9$=DTxrIYn`<{4 z@eSM;aQoR0z;&({0ghjM8*qBN6u{2}nSf<|m)eG&*97-1?6g`7?rYe+VGrP_JYT?T zvP}RF)r`YZ8iYT2=PBH`@V>t|4PScy=3D{QX>Hm^1cl? z|HfCqgX^+D{}k4*1$fpH4&aQK)qtOlo(Y&0WdL}fa4)ED^MrK3g2ZN^{1J^NfFFH= z`O3EcJU+8jcYV{>LiwJN*Mh+_O!B0ns?am6aFFg9-p|yYQ7zBtm@n->1)kAA)D+DM zHAP!OP0^W9Q}ig*6ul2MMfJCuBCD;YsOwf!^e)sCZ4Wg?)}f~8ONc3Y5@L$3g_xoX zA*Sefh$)H_yHbSA_UUBu;5LrhU-h$$)wF-0uuwL?u&qfk?15^9Rt zg_@%7p{B?#)D%&kO@Mzzt_5uK!wqnUs40L8`Yr-2 zJLXWExnn4}Pr<8q1F$C3J)sNW&9xc=Zls+6)@&{evjHsW{0Y3i*vh@+$F0q zV5==Qz+>q7Pdokx+^g2tsV7)N+i%?rz^uc;fEx`B0bI{?D`0=!G{6Oo>x2G0@m&LO z+^k4Yepo_Rz(p;*0KfQn6mY=JBY+z&gZav~|200dRM#`uCGtH((u2V>h8Hn-hC2?@ zJ>&2TwP#ezGbX>RlxHM-))zi=ZzOE>*i<-pbsJ%;x*ddh_c{xk9n=F-z|NS_`>pO;`^9~iF%%Q5>49ONWATJE%B|(wZ!24*Aurp z-cEdd=U(E)F=>gnBc3LvndB#GG<=!Zrs;>oubw{>w>{Ai?(^0Z4mwy{IH0$-aBs1$ zP`_V2q1P9Ep<%H!ZPoFNIj^M|S8kq>q%#GK$(mD~0kfLJb^j%4CLq3iq#@u;-KKzL z$DIFuckc|wytQ@`a9>08e)xS5Ct`nr_`deX0NWYDd;fmN7=w6G!BbG*zCHT@FWTY+ z*vEDQ@C17P3p}lWM-A~@1bE!?FuIrUL$876EwS)SZBPbG!k|w%@GepBBnp6HPZ)>Tv0~O3OYTEVLi#Tn?*JnTDlFDD7ZSz*zHB=qtw8(gU zw&w@=8i+Jv(7*;jHSimkR9*v}K2=o%9zW!309i9= zU^A|=LJh>=AYB75KC3lQEe%9`sgwph?i3T-Z+{?8rT-udShaWu=G5Vxoub3@o36*R zIc>mu;%2~`Hc*G>Z1jt0U#E!pHtZ2`WARObyqZkhlZc5aO_GSH&F_-=@mwKM>)%gO`m>bLbG0Wa=&8u0EL zBEYiqFKW9J^#t6v(64kJxDO&^<$l11&vyY%jvo$qLCF_zpTwRc#{q}+_60n`zb{}9 zkIx{V87VCo*Yq0Pybspsew|?m+MCs{JK!sZPJq#-k$|TR8wz+^&&_}b1fK;wY5G3E z_XBJJ_nUhijik+LizVmO!@Io3+3LktHk^{QbYT{2bZ^Gu~-Jg z{MqxS3TAuG@3+WnvD+|yM+VQhjGJDeXJq0a-7}j1lJ=K+{#?cR^8?(03g*wZ`+Ae- z(^ipny00Tsp9hfJZf+!7w+kzdoS|3Sue7|Eo<`4>b~Ti+jiv0EJU{S9za?y8bY>7btGpu z9z&koHj(`JU@B?ZY6jUd!;N&>IGembcOH4^#scz;jW@aO_dlJe#@db?miB)SBdnZf zJUA8s#>nJ-I{=UW1D}&E>^cF&d3%NcUbN2{Fx@kvau$F!s)qZuz!+!vp%37)jlBTt z_xcXj#D*H~0{pr)d@j0idMgm$Jog2t-^?H!@R%mo7}vzQTlj%Br1g8A0le)Yyl>&W zSOfIOVNJ-}u73x|-}CAM%8y+)39w~7F5uE_aLw}RUJJqNUELf7xZa2Yz}7vU01oCP z0G9p#ZPSpRS*rUv-6L)Jo?+gB!81PNrdQ}02DPO9Lidajn$o|kJ)?@Ap;cSjfeJih zDyJUTq>~o+{Fy(T5ueI9FneT$oM ziOXi(Ibm(Mlc!p6`BN;p&WRnkEkiqUKke$sJzvm)YnWuoO&r#qJHfXd_ljp5ZeCa` zZo5rp+yQk>xpzl2Gl%ynGXkQ)}*ZPl4)7->s8 zx^kW|>1kgu1_{%w0sqKq1NhJ-1Hc(;bO5(o+Yqqqm_uz*`klZSZDn*CjOqUQ&j36B zz5)2H>l(njTQvYY=g>9q`cF4*0X${jV8Cl)H34Uh@5bmEQ#JFzeFkCEn}ad-yaNGv z&5fCW7k8Tr*sjHFz~;+i0JrQ0$8?iJ5)ePI4IZD{`zOfHe>NMGpV?Xf`1=zNz%dgL zVA=m)3O=(`*E2He%J+=CrVO6ZQAaAO3O$2|gLKdE(^Y#$wXRLs)UA|f)U?m(X5h3H zU!RS@C6&KEdrPK{a&vyh&*`4(%h!NU8wL$@(PPlSKpdoNz`vea1J%+1N54`UXnjTF zNKN{gh0|87fhb&3c@3n>)S;XP@(kr`Ag?=v2CNJiG~j@PbPWX8S8JeJ8sHgLN&^F2 zEKyDR@xW;-)<6s{sk{ar%G9Bp2A(&RuK{xt1`Sv@V9>yD9HeVti;-Fb)zZMghLzI5 z@XR{#HR)#oPFt}C;&4gjHIOb-hjJQt*_c5CZ5cFRW6Yp|Q8-A~z_vze4OB}54vj0N zftofS5a6^GYajuaR9*vFGIc1Yfs$qn8eqELfzyOR17mTJu7RCR)f%Xl28K6NNewK; zX)D&iDO^%{4dlwyp_~TZnabBdqy>Wy@Jtvq;DUp64eV*I)avJz(CSL50U<7_ zyary#)S;XPzP4u2KyL;OIG8hNU>Xk6H4xoOt$}K3z@@cHYG55sTd@YjxTNwLcr8ybXf}+;Nbufx~Up8mN{ACbv^b4Fuq{6>H!!E~&f*-pbUWoCa7O zC-iJX0V>>WtU_K7gHE_J6S_9S6fP1G(Y2Z*CpZg+?&;1_9=Q_plxnZ$vn|C-F$@4?R13CT^q;e-iYUOXT-22%VT`5#xXv( zOc?A zsRDN@!~Zt?J?`EkBf#@=s7>}6a$|Y*!Lt@V4}#x`NiTmb9e&Sp;am9pq&oJk!LufM zjL2oIKcZ_3@GJ{@eFxrN0k4;OwkKeE`4#DJL48GYCW7+K3R(fy+0F(mPK*Woy!I-< z^!fxQeZjLNh8)WPe9(Ox;9M*CY>P>S!$JSkU;l$=E6^VAK4$!-Bf()LQAgkvkXz=qAH1&B23V2?Ejyx|xR4yzlY1%u=jrFbZ zsTfcL#(JHpQonKNiO=R{;3Do*&>Dr*Z+iZPN$;pP3Ic zLHrqc0`O6j_khW8ZFs!iBM_Hu{||g7QRjU*Pmn$J|BlzPi{cr8dd4ruTgEug( zk(k^|DxeCzVKENUz2UqyR-Fcmr9ISb+34Bx+&tR;*PSeuIn@bxi(Q@UO8q+W*M{@d z{_6^dhEzE6yS^v2AH3}TO=|z{%XSN@O}iG4_oepeQ}Y~Z|I$0RuO^Gd3i%MVgWC65 z=>DGC=e_OVNPSOm!2CC{)V|xB6Sb)%Gv*5T)ZXV+?~Byla`ulVS}Ydp-V3oewO{J) zn@a7?3kP)4X0ce;r|bGt`vv*8^QnE)sUaL47K4_$GRov!#TM^}8G&=m{%>xz3j=!*A^&=v0+ zs4ITZRabmZS66)eosQTpPe;7|j*gg>tRpTF>WF`z))B8dt|Q)gOhkq)iZt#2SsZG`o zx-KpS<5e?_8Md2C8-aGzO`p2JYds6>Fd_QB@7}#z!izfx-RdYd|uH zK?C8q$_h1b3J2*LDC4O$P%RC_^;1a=Ji=)!)__6(s%pR%AE~?sRyxSn0Bbyh1|o2k z6>8ud4$?L7Wq?`()zU!1z)ESrZe~;7Ip-Fl*b<=ORb z?gRM8i+x7w6bXrTm-^Q2$?#$}}c)^18 zfT!pm0sP@>5a2u~2f!!Cc>#9tg0DAV$}kX@ZNI0(KP{Ad&#n^lXP=P_o-u^MGalfk zSLhk9aFFg9orX#KOFe(C;`})ucc6m#^Lm|l)HLQe%6oPOwVs%SLUvw3vm4(+C#-Iu z{MOe|2Z!q@U{NwMoF_!ba}!X+;z;BYvje%!3q{W@g3&7T_2_DtFM5={7OlziLAg`c zp!RoGA?=TAQ1sZ<=ws8=6=2G+mKIqTXgNFgvdJ_(~ z_{t~1QOVFt4mQ~f;@_W@f%@jAJOX^D-V(sF?eQM@?;J1Z1+u3(buFi&AL^W{=xk3*p#X-6UbRDJkfNISfif{)i@Bl&2yOITqZ%PI}OOf2% za#ON*?;{DimnrG+G)+=R|AFMj*SnGhyYEVVvhGWU1!YKP>O7RhExj!v^Y2NnmEM*( z9!`K2l8eQu5|5sDB_YoL^Z}oVDl3k^Ya;$H_czBV;{ooZ zJs3k|#{_C?@LdeX(7bD9fa&GuF7N}dH}sGP;7*M;$Q^^I?YZd=#@F}LISXEoUjOM8 zK_H&DuRmjbneDHExXtW=;P@>&%m8me`EF`(4KI+-+=E-gdQP#{F1T%&haXFfV{C@cAS^}U0!)bf2Upp zYylJGdjP8wg9l{dCRgYIA8?TF0X@d6J)l}1@CtXJ0uQKZpQ9O1kpABlUq2m=ODg|4 z+6tLE%4v|&1+`W9Ioic3@-@Jp$e@87TxEqC_>6;e4fJ+ZYoJ;hD8(J9Km)szvTf~S zvTdV7vu*qPWZV8+oNcQ&C)+l7Lbk2;;B4DpeY0)9I%eDIuFJM9Ny)Zd{3F};f=P~T zhfX=Rf4b+`?zPUbMRqy1d~S~I5pIrcbl)6Xoo+d{x$Sdo+nD9pMm5c`ecC9;cD`Yb zEk`@Y*6eq-tzKESt=8*o+adYcwr3t^+m_zUw#~nkZ5y7HZ5ugJ`hQoQ>!;r*OEa$A z_0uLU`@nY?x16~d@aFtMfc=kI0M>Xk5pY+NHGsWiJpfai>>N5Rtu9#aV;eUE@aNa? zJEE;Q%|W~%_Y_!j-+EUUz;!OigYVa@o3b156oDV$5gsF~W%Hxw`*r23^Rn>*yE>qK zBj^4C@t#|s1Kw^K4EVU&V!-eFWC5NsOc%U<7oR48KNIx7gr&^pN>tJ+yC;9b_D*)fUIS%lUeQm)QO3zo=53Xso@P+pjyiH35 zo_1t*65y$CrUO2Y=&`?MRud4D-lhSdgeAw5?Y%i^=@IBwD4?;qzEmuB(qYpB%RsPv{lD5rp=XR zTshAey*2@ix#pvH1D+8y3h-@lC%~yrQvi?Huo5tDrW;_{F^Jl1PSyqYDa^X+4Y**{ zTEKfR@c_>l1@DvSVYU#AiGwshfHCmO=q$juhQa&Ro|c4yc?vz>%w6!ljk+nepnYrI z;e7)4OPhoE)U8RtqcU_40(Oix1m#bsSpmK!CII(a(G&22eXzc>0Xz`DcghKH$1`03 zUm08nux$IAc>L3XYI=sHr+m*aAImV$SdN=s;XGq24$?j2;XG-7splD0oM!~hmv*3n zc}7jUK6?hIt@!$EDK4q}_1Wg0(ymc{eJk5bz6SCpFlb;kuChW6gyA4v1L+IY8mQL$ zrp*ggQUe!o+KM&s0hd%>17==T)j+pJ@-@KcFlfLRS6QJ3!f}wUfh=#e2CAijtpb(Q zz(t(4Vhw!8C6(7en?+UCz>F30HNbLa(7*;D-Xk{?-bsxXJX4VwPgK;3_x)Z=p0HPAUdxoayj}hpycxt7 zA}akIq1&pIm@}k=Sf%-j$Q8XL?nS;N_BD7-jD1x~ENSzBNZ+-*|Low?q(AS*f)JEVA=T?wRNA;72K!rc1RfDKh5?4 zo`|Lcj*Zj->{J*4*7aTt6I7NF6g%u@FLxjfQ4Rg zZLDnj$F2CM1=XBC?^-M0Ga{KhLx`JRp=Uh6LAqx&TqW%<_58Vt^XHqm0~O4lt;e@R zb@-Mjqh}}dbwg(~NZbW&ncWSUudzZlu|3fc7i(lz+5?T!?u@o?Y=hWQ&C%dxjZsNd zLsUA?2o2xU0G$joL^gE{5&5M)%BtT05h+HfuC+1pz0e3Dd(7BsN3y zdNxN6-%QaB+m`6z4l_iqX@%N1Z-d^%V+pW4R#?zojJm>kND?-Rluwqpxd%YO^^lq7^(=j>Ll;pFXHNxLv^`=vX%);hbm zZ(Hx?TG!gcy^+0_yUc7qH|TOC_efwAw@LFu+^&v?xe0BLaGTAG!&tz5cbYSL#-kK57~{V_%K-dt?p?s) zErfu-T1*4HUw<&*Zo5ta{(UnS@P@3Q`_{>t>wH^0@I9KSJ zf%c~@0_|fg0`0>s0__7V0__)B1lqe<1lm)%yu@j%&h=Sdh_s_C_r57;wProSv_+t@jz(_`Gfp0KZt&`K)Z*++ic& z{k}&5hi0Y%?z5xUIoab)w{8SHXT&bR^)}7}Jbkh?;H6_`0M25=*SmMr55(C9!vM>+ z{|i2|RQG*TpPlkOBM&inhD#`eXL#Wt-7`*aReMIYJY&wbN_obSLL+pk&(g+^!${uwRv z``AJwG#;0kTxf)*6&j&Ag+@qFXoOZ38X>c zp0PMgnsMbkV#JHv zi2^}tVoUa;#2wn1iH(f&6SuZ1Nt~JbG12PE@5IBg+QLKrhC(y9CPL#G&4r`%&4gMb z+6wgtbP(1b)>+u9u&Z!n_&=Sf#`W2_z0!;;=NV34W5F2htr-J&^?i6>h4HmO5ck~R z4Y=cX_=;CCM-RRR_dSFU8wBo?__M(kaAII@z-Jr%2FK@&I0$&~9S^{c ztoDEfbKv*&I9ShQ^tc~dF2JMSMnnK!vGX3_SC_Rx`-@iA1AHP`5AeXd0|D>Z?E_d- z&jaxKSFHj6Ia>(oZ+8WH?wfw_`z=BqYJub3FJA-4%l^Nn?E9w$)%1)7k@7tw&w*i{ zaTzzgLeI#=LAqx&-!JViwP#e(Gald$RNxsk?fUFboVMcYv(}N)Ger68v(xdBR2}8C z$asBL5G`K==EE2?a1B>kp$2krkgfrVdh(Je~}Cd(kpt((yG!+c|y&;TlMcNZLXijNeYQ+_;k% z*)^P4`E4KZe$xSB>*9li&A1pMfJ}Mj{b_%d%$`9^8l}1 zb^|ac>=fXTek9-{35Nm8&c~?DYiMV1AH<10p@84;_5)rta~p_T7rX}dO)Sqi z0=U6aPrz5(nFD@z{t#GCMbCG6fB4L8PqzYaAH>YSMxedVkMjVh`V9fxZR23TThDF= zy!p}vz}L^h?}r$;bPkC3n%@p^?fVFDZsSRScgMo~{KD#kxNQ6PAN{9=a$~hhTyxDk z#o!rC-xpEqn6&8?dWH!O(mlg9L3)sS{#?cRv(a&B2P*K4S3mc0gPR`ahWef0*6NeU zy<~fdd%yK{?)J%dxv?f`T;aw{Zu*>u+|pi`xQBYga>HAOayxfg!_C+-n|sdDg_}?` zocl^Jh`Ve6k9)otarwsmxPFZWafS7Uab2=Uah)f*a`zmc#?3LB%YF2HDficFKW@9j zf!xd=8@X=UA>1Zz+qoat?&7vd-^0!RxsQ7Vr>#1k(ejkEqbuhb`S0t3G4JBbPXqW2?bYMXfcW{5y8t%|8Vz_g zr!HX8#}u$e_UD96fE#<+1O71L3Rojc&-Y!cM6hPmVH3RXAml8(FX7V#H_-k^PV)d; zM9&4>q32=1h2k`uc=7t{ zd&L``Y!Pou_7lghnk9BL87qG1<}H5mV6WKyZHhQX{7QVYfd*N8P@BBXsz)X^uTQRN z(12{@ZbV)a8IjK2jmXy>8j$a^>yx(i49G3#^vLs3x}-@DZSvEfTIA`SKgA>Kd=mSu zc_)6`uS7hlVUhSlvpn%li!AZ2;&ky>oVMzC#;6O*c*e5;IHpAHnE}QO^H&oAXH9Sf z+~|-kU~8QbfV++F3s`mxqP9tggka2`bJ7Tm`K?VL$M?$wF1Np|c57(rhSe&+E4dmmJ%4?uLSyc^~ zUzV={A3FvOc;PB5)Ibmp(lu~PqSipQG~j!wQW|)6&;-?_pQAW!#TqEWC6(7e!^>6G zK-;VGH4w=(e_o8MtWX1+ageTo)MT{=s-=MqS1P4}R#RSftVus7aN3GB@CuhyUIR_8 zR#gL**X3(~@5tZ-%W;(zYG5l4(lzk#npy+Z(m+s3r8H2}<^yMN+KM$$ic2c5f#%n% zssZ*b`5G`E#GrxIxXKDO5Qc+v4W!>tYoJ;h*nCqZHE;o^tylvea7pDgV0Npj8t8VH zK?6<<8t}zcR;Yn+9HeU?>$X}0)zZM$J1VJxi#Tn?8u*M$DzAYycdM#_p7-TzAa53f z1~%X-E7U*)4$?J{o2u48wKNcRuaX+5S(^l>tylx!aY^Mh(Eff^HPGjgd<{qh3>pZ+ zRaU5hC>*3~px}X81J%+%_(PS{z!jXfVh#MpC6(7er$<%QfL*$L4e;F=G_V<0S)m4E zaFDKn7mw8%sFnsI(o|9d*Kyj4HBc*E+Cb$s&=ns^)lqI;EaSawC{w-$%?hq^5z1Ol)AQw388dU#G1xKo)@n>l`_%*;Ja8K3!JJJBCJ z6Jg4{?SQH6@7gPBt8e)hyq@35IKWHJ-T=?#$0x_?gjj0M=@Y}ez#iu0r|z=ECoEdzx?k`9g+9%yf63OME2DGpL`AybzWBG^~yaX zDWpigH~9E6c*7~&>4(L{^I zV%>Wo_NMkr-F;K3y?Nn)PTDLM>-uzEe`>!V|8_pLZ#p%Eqr+mcB)QJv)PC0FCZDOj zVRohy)k7CtBaTsferEN&;WF+(1^4T$49Gy&12Ryfzzk#`n1O}_W}r!d8EAT723i!D zf#L!)(6PV_G=cg(AOqP3WS~O-479>O13CC-pw|8wNYg(9m2AjBIU6!i=7tRPY(oYr z-H?Ga{4-E9{|waAKLd^O&p>?t47Auk1I77gpkn_F)G#0ebqL5n4gncxW}^_WEw2G~>$Muha457BGH!@Ld3(pCtj~*{ezO0YC5F5AeGt)`0tOs{>x|!8{WX zr#4xC8S(uCcxI2OsaBF~To7>q#3x)i4fun8DBu?xECBEL1D_f61rxLueSuytG-;L%z!fGtzk0e&sk1^u~Z^LW6I>;?k9R!{k%XKt|z=j=BZ zj%G_&PVL^V9M7SyoXzg89Jaufb7_q$r*5Dt=ld2{&ZO{oM-J_IkCn6 zbfOy1)vHyajAz^n`UJ+*;9+M0YmYPsOrjJph7QRH18lHqHQWe_sS+sPFo{fCVnzfE#3X0Q_-LtlTk_+LpZ* zgZ7N?WB}Ux=%N+iaMB6zg7o=-2S4WmzGBo8@XVqx!2W3kfH%Fq30SXS7T{eM5n%E1 zae#03vII{+-wU|Lyh%`5G|yW6(g$cMKY^ z#zDFUmb_PMpjsN}@s)%#-ITk9HeVt z#YeRUs-*#|Pb#T_i8yV=8VJKBmDfPBOdZN;p!QGs8j!d#Xu#q#g9bP_NY}ucFKP`` zO9R$l{~vqj0o6nk_5BqT5EM}n5fxFaM2gsfB)h?mT~zGYu>mSpEKv~I?n^HxStGYbbcp&{%M(V+iMFZWorp) zbAJ-;b6yi!B}K%AE{})_nSoRp|^>WLHCFmMvn+n(?Vk1 z=I2DP&r9Nl*K5LT(??>`axFo#Lpp+&$nENL%~kMAI?xrn z<~r|~uHfJK5xw2rb{;%eZQ=6{fFpdbfM+NTJbnVOcr#?%p5CB-(A@Q)JiW=bZ_r^p zcuwAhGb;h_uImVR(l;D%7fB1i8)rzsbMst^qX17^z7()|Tt~pGN)o~IH<@hN`ZoYPcl8dywi{jmF44XW*!Kh+|HPybz+U6! z9Kg>@y91W(?>BU0Rp0aH9$I=BeFMuJ!!Sd_bJo1cKC`?$ofj=wJbj@1a+aJiwv;7j zEI>wP&N%j4lQY!H8MFUXC}-HkmEws>HHgxeHHjnky2J_n+Ju&;KH+#*kBHf&OBhV6 zO&s&CP1H!KMHHs~!YB6rh<}*-7GK_|2v1ykA9uNP1D~)c7atUR8IKx&5g$ihz}tPg zh-W{(j3;)!j3Bd%NYkU&w!Zsv1A|MKQER5?)ZWLEE`kk&G=jg))<>T zZj-`i@X*`C2i72FU+Lil*tE$lu>Vn4^8vR>)B)wGZJ&UBdMEgtzOy6TfIa59Dd5|m z4}oVc(VJ|JTRu(Qi1CLf8Ut?f zZY3x$KJo;#r!(CM*wgBM19sW|aI~aHR`typu6l}dM!^!6oUsXYUG6p41Y~69j2GI{ z@zTugsN}O)qiaeBP{}pdh~g3CGQ+E=q*wQ1N#?jO91iR!7>6obC0?I zE#W6X{ww<067E{{3^Zl?ED z;rR5`D?9GXUH|#tn)gTFUtoLDbKm6I>HmYx%|LN(uvo^D8rP{$jHnM+WJU! znt6Y1rt4!EcI5U;l74p^b*PyE{lVPyT1vksj_90EzYo3s)wT{j?{TOon0^#*hq(j!Fm>##1UCf!Ck9kSk!+gM+*2HYpJXdoFiDOUq$k&&qZ-MShL zsC7=Ag$7WG2J#~~+{7ImZkMeb?%+@k*K8w)yDNahZQ{q_MhZFHWM2;VsV|3n(2v8N zvxURGw41|q-p}DqJ;dR9q;R-fk8-$IQaRjX$2eTmG!FOfF%I{2Du+8FmBXEOl*7H9 z!r@*|=5U7`=5Qkqa=25HINTG79PYY(9B$TL4tL`o4)?-t4);?ehfDk7Z7BWRtIs+0 zMI))l6+5SXcIGTt8!%z<1Hky7kAOc$YJqt>J=>N5d(w?-fE%tW1x#B!E}u#{r)60sdz71-H*&?mwO$3HWZb3*c1?|A6*~b&CYd^mmAT2>1=^ zw8!ayM~piGcuOJtP3zT7&A@nUsbdNl`-1}>9%2i)!8~igwz^?}``g0tfA%{S*x$O& z100w?8u0l^odC=Be|7ybEtHGZ)ne`|*;sMTz}e2J3sKkQ=8T`n$jljz4K+DKt(@@@ z4WN>o;VL%4gT*HJS+NPOFEPP~OHA+>i3$EdVuG7yn&9&?P4L;7Cir!U3I0`Vg0~c# z;HyL?cw3PP{xQP@zno!$pU5!56EjTky%{F>z6=xmc!mj{oneCKW|-i`876pXh6!#W zGQm5GOz?gp6MVYJ1dkG#;15M6xHj4+AvVEjU*e6+bW%O$zWy;$C1+S#!uk4**S-ZY z`9P5tmaVS(2kOhlAbK+~+zVpVwl9al+{frIkMil&ct!OB@UUWD(Ks2dskSO-g zMp3ihn?xhxw}?h=+%B3|Crb3ve2*y2f3GO+XPoHDf_jwg&tu8}9$a$k)Js+5W#k zM^^Q{clX^wan8UNv*e5c<}5j53NkWtMqCR`&QR+-W2{Amaz<6VKI@0vR(^dp3zby; z`s{0&Hp+c}W4%6GYN=QQ_!<@s3~tGyff>li)WH5$8V#tWfr*yYQUig=ZRHv`g-WWf zf%h_PsHOp|onj4;UMw0I-kL=NvyqXhfrD){8c<6EQ`%Nb4QxbiE7!nTR8n;fe3EHH zH4SvJR;+=TxhxtO)s962p2*15KuUX!2Gr8Pj1CpjfRWuz!rJscVK(v!@uuBN;&T7@ zgy)g3#OeN}#HYuYAUUR{K)kf3Kr-bQq38IVuwRo))G?G0+l5DnFU|*uvp3?1V@WZ@ zBbR6*nY)`9`(igSeS8ct`PE+H z!a3qk-OI%Hb-6^a;SIvl_9kI!C3U;{oIfw0qu+=QpPTFCTRcaQxr9 zfIa`70$lgyKEQfwpoP5ri`CWQKAlHrmYgw# zC1(VouFJjV8jFm~obl8~I$oM`!0Qp7(ftwLAtE1N;&2An<)z^h$Lzx`#z*064sXZvCx_u-1)K4{ ze?oB6b|JWPatO|g-HZ==7>eVk!|=$P+we|Tci>o31YU4{H}3m=5B{nk7VnBD;{AIi zvam8}R1hYU8<5C>A03P*V7vM(XXuuaw zZv-4TejecTYa0N|#vppTTF@NKH}#t12x2&PYyn^gQ!l{I=A8kT)PDeC{_>=4fVFE5 z1iUFp8}P2Baljv@zmFbl1@lEm>t+L9A%W{B(o7qH{yX%8^M4DU!u1M4?wvvTq4q5R z|I9N5d}}6LUlDaZ3D}8U@beiO0QY~eDct{U4-C|o?f47IU}m4;+%o|v*e5{ z)OEQz;}$Y9b4KH?((%&dj7sK=t7rg~s=m0`Ie_4PI|02S1S43ICSf z4L`Kq5ntsc!1uQi;HIy;;sbtJ;){ngz`q67#B09%!N32ggpUs^=C_S_&TsSJDZkhL zLjHtVPx-fb&-wcdU-IWXE#@!Z@}56&??=AK>Knfax0FBY(O-V_bS?aesV<(mvkpG> zY(0GWum*Uauf}-r786|9txPA?BWK*}p;FFxRu{yG#Q0`_-`?i{zW%g3;7!xo1Gb82 z0Qg<5EnwLgL~q{@=7Kc|+1x@9!}kQ#0y)9!?rUIwX&nPtcXe05t8TsmIbfGg1mJEN z1mMqa-T;4?{tolM4c17^pJ)Jb#`yk_f4WWvb}xYk;6-1i0>&&t0rw493%HT15U@kx zEx=d*z?>3mr3J>HlW-7}4~dEd{8xK5VA=j}?pdaV>X|cQoD}B_cW;)Q@ey@hZqCqm zkd6y8XSg^@|F6jzmCPAhPSOEXk~6B>eY%dwZROWzXP}a*U!M(^X``GLS?|;33lwW0 zW}OYQT^yUjsvU8V#s*eO8BGAr0Kx&f&J&%Hj6j!r>-v;&4qia=0ruaJa?( z9Bv&y4)>mr!!=yP;U@ZXxV+6AuKo@VH!q6A9UjBs{@Tmon#6IqH{&?maq%4P_IM6A zFrLH3<2l@GaU3o$j>BCZ%i(73<#3lW%&>^hLTy*XgrginVBfcrm<0X%hO8sML|vH^F!QyYxenVww$C%)zY?r)3% zeye>J>@V8?J>b&|>wxn52ekoPKfeab%l7{qIj`M-nsnE-nsk_-nsnY^Kj`nUb%eQmp91m>XS42 z^iw5g%yQ2GF}HL$1-Rz>bif&NAs-Kge6S=Nl&^RBCSchZL~kKIlR(V6JaRdhv-#97 z6tLYwFTg=0oI~n3EFH}0942rEd^Z}d9n5QW477jqiz{HJzk|$AfSjV+zdy)HyC-h~ ze8M#oaHnbcfDI#_0Dju86&SDSbzlyi(PaRzJ0+L^e)Hlb*x$?hb%4ElPq>a_%VYSv z8N0uJ1@&e7{|g;i)i-B24N;skECN~1GbZ+D$r+20k(o124AA5ZwQ`21ONDYqjM++2 zZkV4advlQJXZmJQhoG&Z9Q$3O&^^0F{oP_kYp3oPJ#DyOG_`KDsNJ?rqPOi=h)xtw z6q%It7dfBciLgE0L?5o(iJ~@k6z$t>B}#6<5$Vpd78$*@6GhMKDr()e;cahtnw(QjaTk zp5fa%4aDH}=KBE)4s8Sc5Z?s&Hy#N1l>G+4U3}rX3)vV%Z<2SDKulYg-W{xqczw|e z@FLGyfZL9A0$jZ216U`~rXUpX8TX-pA2w_ZxP&JH{xJPb+}axCAmI-9+j>VglmNS} zTN5xIp10sShWppxZz;_h2G>P690~^YH@i;+%o8J#Rb=bzSaxMiMeIbH?kT((%$epvGqndOZ<+R9peYV#~#Tu}f&GNlzJ8Dv{29lAHse$(+ zG#XIrdsBjIwbVc!a$C6u^hZ`!1Km+g)iu!1jYR`KEE_&JKt6I?xdsf~DysoUR8w^gteB`+12LOfG!TuNl&gVsWMpdK+Zc@o)Y3rm*a~T2 zuID}Cg?=INJmD45!Ks9JIqxg6)w`5PHK`$(YgJQlcd4#mO)p(Rqlq;Hoi4m18s54~ zOjvi0IOi)O25vk?lx|8UUj9xZrcFpBmRj#8To>&pE*2#c@9G{TeqKLJv>0=gXt67e zDC%~SxbiNO7;*YE(Pq?nViV^Q5p@3w;T3m{NbPx(=rQsR;o*6Ya33diyZW3zizY}t zuGsmr&5$B6Z~kFnKH%mDvH|yN0O#d*?{Ng!KTS#n94&+!L&W zxHWMX;0L|K0Xwys1Ng6`5#U#kQ^EYG$D`poiuBx`z~1*>5%7oUZ)39oV7`z}&TYV( zUo`~%r*h#siU~d7x(LqiL7==-t&M<}I~)ew)G!M0vu+asKib;{@YFg2z!JR)fP-%K z1B_cY11#HruZd+^DEFPcTAV*0nWi{rkO?d~<2vfP+?-L2jLe+DahHyl=KQ&m=g);` z0F|6S|Mc5{_l(?(4~^Z1Kf4@(=S1(p^#$?x*?o!lwV{V`VZT(o^UtGrLW2YN`J`QV zg#HG6<{%&3>eNJhK*tey@Wlam(V1TODOVg{GmMAxFY)j%Jqdix;7=}U0HW0!shF~2 zuw^Z{PN83l6&R0Cv*2^Sw~U(w>{AbH2E0B)2)MpaH^3*C=mP%T)Esb_H(W=tYep+z zua)%+_%GZ4xM^itsGd2aXtv^iewt!<+!5A1{hYReoF^Jwa)i(w)deM-l zAcjkR)&@D@S`z#%iu29WLHWVU#sYS6{{i;@?$2Ssbw!H-FaFX9Fw@`Uk?X)Z3Y}GT zL5yA5w-?|^fvW)j>K+WZIk_3I4|X2#nX$hCTYq^4Sgf4{xWgm3&SI_OE?{5y`v~Ab zZXDosez3l5|8JustNPCEcsWmT&M275k~19Uu;h&4$jHnY;d3=PL#=C51LjpIXH>Oy z6h6pp<=1DEQAySB)7_V8qulp5*86mCycKI8CYnV9JWm!4j6z1H26lL9G@zCS276aX z17|ZowyjF9rO0jN8c0PYRoB2{nKo3@K*>VI8o>9kXh1NZMFV4zk*R^m1sV;grGeoK zE2II_GjI4+>9qp6ty}}?sHEx|cq-F|Y8v>wSg{6T4zp;Wj}MCmCL$wK1A7)}G@zCS zMlG(825NLVWL1@3Lgcn`4Tw-l)iv-!rVZ6J@O_zL4PZem8W^yIMFUfik*R^Wr5X*W zrGc@_Dx`s`c0S;T+*YoEEL2i;4ZN0VLp2SQu28H2Jd{NPgO{^tUncoq$eUB{w<1<1(Mz%f6K2Gr8P?Df@B16z^X$~ABm zl~i2=zhv4_O#^i{DAqts9E%1f`m<A_SkwpUwP?K^s5QL0O4P4r;(STYSSi7S_8mLO=0<}kwR#pS8BNb~v7{j80#i&WS8rX!4Obz7j(r7>}4fsb?OAQ=BZY$S7 zF)FFL1{z0JRs-#$6>A`7D~kq}qbB8QAPgCq8n_Xq(STYS2-;mOHIRnfR<40}sHEx| zXc}Ev4V1Su%!#{K?^h9_=h^Yo_$;L+IO?zea*Dyd8b+PVg%o~ zv(x%=huB9sZLXE)_;c5Hr<%Gp+#yXqITf^7&pj7?m;3T(K6hbb9=BzWof$g=44f97 z+vsGMYstO*VhY#jZlY7i3*k-}zL9&Xz8=?Zuhi`g7{*wu-vZsxUV&kl-sJzg*MK;w zhrerJn32o$WrUB-u_0@FF~lgbZ2Q@bUV{A{vOf)Y z<(*c5neDwhqtDvj8~t!ryTR3=;P#?%h*3wsUE&Y@P=>RHOOYey-;XS%ok3W5zkiRK#H~;d_41U0em;9AGe(^u7{J_7y z{yu+%|4BagXE6WFM<0I9@-Y5Dr)a)@?r#2+v%C4P1Kjy|pW{4VsxFT>>%`^6PUijk zvYR(MZal9?)(4(sS8x8tDFpvQ@FAXYOMP)0-b!v?|G!-0l&w6YA6Iy`+$KER7CJm( z{2!hLwuD!6ztruj8`I4XNIk4rOt0CcJ9sA3{Vf)Not@w_sIE<`3+%fLzOt^3U+?D) z*0P7j+ys20O(U>2-tWjuV5c|P82!e~pS8Vfr%p*^%h$d?9PHn?Pba{8ufu1AG20{G z`Lec`zo8Zw4`%()W0@d^7hdTK+Gm!(yXzB(*~jON1MTZ{TMw97e|7!apgmdpX>?>% zpO_w>qBy2o>}5GuZJQ(&f!vtRLq=vyUwu$oNfXm68PmHRk`ADfnBH>DNOAb;58}8k zLnKG0ER{Gc*eu!E?672e`;!uThg8Xw7x9udD>g~Y_D_;<1brly&pjkR$1RgA&si#2 zc5-Yz&{Zy$!|z z;CBZuFSY>RbMGe`0&Z90%i7-cEu&f6>lSpI)jrN)i<4~soBnA8+GqB!?}dDjlZ-Aq zg8h&6atF+;|CpZv+LN{KNRb{H)hDJWrYerHjq` zy^=9qKUF$_N@99dyARa`xvl*AqbDk<`t`?1nKsIuBcp33|DC6{{!g+p+o9u%HGu76 z(ZCs&vBU1x2NsR{7(ttTtEj7>&xvg9SYf(wnHIO3HhH4rha};X;V|$ic zgHtRTutG+r2IidBXh1Cuw92WL8W@P&R;~emR8n;f9Fu87H4SXNs8|CQM_9fKnVezK zfDJM-HQ;qtqXD%v(Dqz~G+=KK$Q2Ja=cTL(;TexS%sap9GOw^1#&@2e&wrR(lb^fv z18)L$iMLEIf@gBkpXX+?mv_J{nYStSFi(HiVct`VCA{E-bKErRX559^L!Az6TFP}P zJa7B z$f!Q|yFBs~$8`2Jr#aq6Q7{%EH!&Rsnp|KL9k=LGb?bAB4&yFPv9kMwPTe{wR%kK{JT z_0BcNV*(5KYx;EMhf*QD0cR!LNwH6O)?b_RBjX0xr~Mej@5|lA6SVN>{fS@2pNM_qMO`V=1^itw*~yy!mvfENbEO_u zET&)W0)GqgTv!a4XLXJ{8t|*>A%Hj6T?|+@hS1v*{}K?xoMT6Vd098xZUfvZ)dMhd zf4NO{SljEIBLMgKTRpfA_II++QNRzbHw4UVuj%_=tTB5?(0W#Xg44L5|5L9XXKnA- zu7#kzTAud-59-D~4=$@d{GBt|{*22jJ9f)`UzR<5<;??D_CG~)iWU7|-u}wXHFmnC zIHtRwVTtKksH<{g`YmK+#`MP5rQ@TC>6MJ>SJ40}iRpXZdy=IK zzDSBA>tu%U8f0d>)XNO3t(Uop^I1~Y>bxYscbWvV$(4kSxi6Xb`kv%S;XTQeKl>#i zwk8rme;=`M)IQONux#;_AKH@e)nf63XU-Cl-VVu+fmoto+cQhcV{I>VARI5IzfHBif_Y*=%Y#At%<{S`hlBP;-#-p`<-VJM z%hiwSqOd=JiiNyNY8vMzO^7Fw|AKF+B+xnKAwKZB0y9>)K1~9q9lniRo4C zKGbF8w({$bI(MZVRQ>v6XH=7Jqn!3w=cV;}pjZR=DHaWEM@`DrKr%8iHSqqPMgwYT zAmM%mHGm~zRp^z6+*Ymu{RfrRKzCGAbq(};tXKo?M_Duwftr-7fmCEZMYQUSos?aMRxvg9ShL0<&0Y_9*bqx$GRIGs*w(mmGs7bjRNJmDd2EG+&G@zCS zlAlyi16T*F3ccKYjTL9qrbF0g1I88sJ>L9PLaB zGwzq(5dZA$CiyVRUDBp-uq5cgILU=I-jcAS0Lk^B2#L|TY)R)uC6bnJ>Sfw>G0yCY z|CD^=XG>=F*)NGR+9!E(AXbvmK3*c`?UsbyS}Yl1WiFYLK3MGWPWriTDfhgzmjB&* zz%al6t-(#lxvg8lAPcqSNIbeE|oo_bbK|Z+e;zMbJ z`((`WPmYMe{>Dv)&qLT&%zmHCr@a`o_h48}&|dfP%>jRI*$Z&u%C4Y3)4!qCrQrDo z&NT^8zUMX};5)Og176&?C1{Ul(FJfw5(&yP+ds|e4eoY zH~s8RFy1904Z!|pf9(cXw*HV0Wyg5AYxA;)xx9G@75$$oa~uBclUWBHyJegEHxnF9x#CIp7o;KqWaK;N0D;$RTmOwKkpjx5rN8XSy}xA8Hnu z`SZR(Ha2rl*24Cic%N5z^BsJX_^l^~@Yi~1@%QmOGev?)*=9SRX7{fDCObrQH`{Q+ zi|nrkzq214GbBaAR^;`y{mBW_SCI9dZ6}wHi6k!!UPsRP(vMtw*OuHBXifI5$svQb zbtF5EYC}G`Q-hr2myk`Jy_+@nbD2KioCjHP{ENf?m+uK*RmlNcO#Q)pth+C?0n5e+ zdh_>i2lKJg?*sv6mbasNgZ-T_*$&vW=1RcK`VFV|2KzT%7Yw-9`F(&-O}z@3pO6LS zlQH+-f4(2Em+rp^*yW85*q>?Oc)-UtCIU{bB>{Y9aX-M!_T5ik0sY^d5)0UF!B8-t z?R{w|>m23f&hR;T^PcSj`)e?l1T5>%h;L=bc=gHw(|)t$01-TKN5<%GMm?q&@9z1mo=s0jOQVdF zIgc{_7&Q|4ivDIi>LJYN)uFU|r_NdqrE|j^l8bjb%=#MSaAVLmhc;~!9O`!_9b&dT zafmTAc6?jV$MMjDm5wi8uXEg;HO$d)Lqo?;rQaP2AANTCJg3BAf!!yEEz6%fjGuMT z;Z>@OL#pZgp58yp^ij2QKvJnHIl#1o9{3)7Ds2u}=OG&-=uPtJhNJ9x!M8Lc*6*b; zH{iOF_ucD*_T1jzU~O;XfIXl+-ugd)6Rf&{_P1 zj{uwA7J&VCpSKtAj>t2B4?dj*nA!fphWEgD^t!VLaLxWb!T7vgB^1eyKXZTmx9NcT zjcw)wE}ZWNShoJ?-(|;m^~wPrSZz!j!!UCU!wd<}S@S0Q%<}SdUbJBG^nn%$EIB}g zx-2&bTt`M`4rusSlLOSs0TmC)l~g@=v-~Z z8i={XqJb>bq+AW$LPn+r8f!^=sG0v?+4=uy0F`K9{D7HcReF6#ZY$S-y>?|aFbUOE zT?5`aiZwv8-4i&4nv|=7d&tPtK+{?p4XBj|uA>1|qJb5wS`k(0Rf^nJu7PekmDRvh zR8w^g_|#Ucffxx(9yp7dl&gV9$jH<{3q6en)Y8B$G=NGpF!1-n?5gy_YD+(N_HDb|2H$)bU)s7bjRc#e!r4YV`VXh1CuJVFCl%`~7_uc8_d zppvRiJIiXg0CMG{?$q6j_9DB=wsNoXyMAeP|~#5LmxViLZK zIP1Ed=s#jB@sYEYIKL^3i2EK&=(&XwzlVhq!ZzW=lH#p|d+=6b>FXUtw%cyPyvJ^0 zj^l1(mu(DTxHgvP?-ol)9AgODmyM02pLb6UXfTkVmx@ z&PSlPEqZ>e_WX^;a86rabJp^2KEY??xxZ8VyMB{Q%Z}%A@mBVb{x?@mopQtPashmKh=|4W9)p@^yT2Xfm73sH* ziVj;(g--FOQUn2%r(*zhJ>8Ewwqq?dIdl!>dwvyl?A3Bg>*rGHO}8bKZ^OkDp6x>| zyyQ&2QO{WO&7nm3ecKBXU(^n*ub9&@6GzZOuP zI}W9m+#N&JD;`gU7*3&HADK%vnYoNQiQKL}Iio>q=|ER3XBY-)fpdj`z{Y^T?$QN3 zyVfBP!J@5Ghz^S@6 zfX(U{1AgZn4LE%veD>$VbMJxO$5;#aTko4Q;Jo>x06$8DbD2kWxB~jW)ouXb&T0KX z%r?3JpEbI-yDiv1b9|TRhJnw=?B_wiPg?K+`}Oz=KEKxqbAjE$GzGA1d*{%RMQfpY z=8QY-SaJs2J!Y#mQc=mx83JTv=8Sc1H914A^9+Y}70MY^?f%*{ep2- z$+S^Ui}ZIjdZVAUTkiK#A6O~Y0M2%P9 zSU>c>^Z?7Kao`FQ~2TPJN-5z$p1G%kS1F@*2>Ke$EX+t#)6j&?PfbcZSxr052MFRtnk*R^; zjv5W9r2&C;g*5Q~OMRj$z2+jfm1`gYl~i2=H)PsSO#?-CiZu}PC8tJY4GbG$K&!k4 zy)xmyMF}Hl>}JEFfx*bg)WBw2jRw@xKp(pbX#m57Nz%AZ$74D!{}+#wP<%!)-vZhG zz5k2f?sU0-@gMz7m!bdnuPnU}`fvJ$Biq0K{cH1IUEu+5(sG zWoN}2DEP{v0f$a38W@g@ObvwFYc!yi1_pGls0R2|>72j^xvg9S$*8328n`dhhH4sk z(@n7ka5fF_y0BOdG0cpf#>o12MN*z6>;3i(c?_{v?)PzkpVWr-I~Qz#eY8)$ z!uv7w#yPVO+#h;9wU$7({nMgufa5az1Ae<7+Aq~=4eTj%EFnK>2l$d*E5IHPFu;FX zL<0Y2KCl8zZ?gW*%^eBuPdSvn0Ng%B0LDXS>?**2KEr1%Sqfr+{n^t+fSK)k%vk{Z z8-K?RFthxkz(nBRzJBmoQ=4O7DQvGXUZ#a|vASB!!Eo!NIA`F6EIDI2>bl(fX<^97 z%o#Tb>3C`8U{rDrMvy={fJ)|I;AiJ#2h6hOhxXCNW4rXnztt1sxfizJ?|($$D{IE# zUk^v)qt0x^kB{@j+lM&gd%GLqV~7O)h|K=n5;2*Lj~_su@row>XWb&T!nLX9c4pMz z3mvJ*Z#}7I3k8(csNU4_>wPJs&>>V}=ord{H;U@$>P)@y?Lb)==~2e-&XK+g1Ie}# z<|OTllXIC);4_S5MIt+I?*IDl?Y*TQS1e~lyV`@88CBX4aCqZl5Q9t?8~{w+a|3MF zqXuAR%u2Z17R02P7Fz+2cH9d%VcikH9+%+nt?uR51UXC8r#)b;gE-)&8G`{&8sZB0 zSXvm^psV%eR5?eRc=t zL~H&;@cFeLvlej4)K!2Bt)~Ds+TIVaZ2K;K%Ct~DbH>JjigSkhHI|%_jJhs2XPiYw zX3o&P>w%TSFc6Ur*)y zT0{BbbE$V0L#av6yHMxmHl{dN-;o)@7_xQ4+GP7*JpST)^YFBSYxtMdMueGFSE8+# zD^Y9h93p+EFA@844RP(sdLlA)6Y;rj1aZf8C*fk^Pnc~QM;MNX9=px~P&f2JAQlV&KYx2*7?r0{}0(I~MTFzAXUv{&9{q=FPxffS8vx zO#ot4@s=Tgx5c>vcHKP#aNmXN0S}4U1Gw3}Lx3k`rvP@CoD6vVd|1EVn@zy}orE#> z$ORW*zkk00;1R>qL4U8VgXaZ%Z)O8~O_#=?zHH24%XxhwXRKl zL<6WKXH=zobhIzchDbkm<=1CNp^~a!pIs-@Mma6A&fD8MQn3bv-&npkJx5K-)xaNQ zWNLsvOrrs{H1HJ-pb`yazAF-Ir+1XxnZ8h>Ya1a;^F+EuqmWJ)y80J{of6_=PLEIV!s<=nf(F$u0`-+9k6!me)dOTkL?%-ST>*1TSJ3q;Q6>m zJvV^0a#I_}06sJH6yQN4ZUFYYk^%Tq@>alFEk*+VdFTshzgGToz}`^{!1I5Z{y#51 z=^%UEHMzzLP`)$S8SKws(q_Pe&aDC5<*XrKW_t~xMMQ*G z5s^8&h$x&?L~IybM9{t@x=BCxZROTUxB5><7#3KC)=7UHi;hycYouvU>CFOZsk8QR z5ckE45+UF616(WD5-`2V#?5PjAs{X{GOP{Oca6Gi1-Q@gsen&B2m&l<7y>x3nIGWJ zac+RKh8qAr{A>^KNAHpu;Jc22AkKSzz6;{^S@%Yue7#a%z!9ROfW5mU10Lr$4lr}P zO#Z;%w{k8uQ5dJ`&CcBo_{-d%W%E&>zRQKzfFA^m0H5E9mtlZq_ZK&=?6@zN7i158 zX8cx_6~%j1_ERo5T%N2rH;|86a)ZuzX}9I(26JR&=7vcVkm_n+n6At8Wv;X4O`rR3 zt&s)Y5ws<)Iz5j5bLemX6ZHFc-vJG2bIi}WUi5pvW!Em!?1wx#>DbFtf6`W^q$ z^b!63a^d5i^ds^^KgMpO-+RoT@qvCXc;8_Vy}l{X^KByiZuj;`P1?yjb9~(CcVTgN z3H{!F*007o7>4D&$XZChFP*VAmwvZ+)|;b?Vc7YpwFBsP@50N4^t~owZ(zXHuOcvzLvK7+iOe>~`oXvH8_oa(H!+ zWZ||H$-7#)l6Be7B=ttVkbK^BNkaRQgWRqjxxsKUI!Y^)8^Vi4Aa*UE2ET)U@8t?& zCB4b!0>OmVAodvNS^y4Q76iVtGVAZSFa_A{3xYs>U#mudnf2>k^aK8f+3W{wIxzw8 zFr7nydFFco4}ZNL@XiJ80W;e_Z+HUu#}^a>4(N0QFth&eTZOFS`y})js4t1C2V!}K zF9tZP`6W=^E_@RhUuOF?SHj#aYtKeU5`ElPIycbqjE;5x+b_Buu;oo-$qnyVazm>r zEV;n}8JW4kcd8~gsCDhgep-ccLsh$eIv%;L{QBv3R8sZpr>AAwD5pbOSM(-#-E;m- z#TvkGuxOy|bQTTpkddi@)iX32P)h^dW>!lLxFfffYajxZR9yq-WZF12%J5 zG|&ebnHmU~tI>d38sN>VkOoW|H6+(0HzE%ns!zH^8<88%Hzp@a+L5=fcOh$9^dvVQ zvM0M3Hz$id8jwdPHzNH?>XT;!bjVG{@3Obd%gf#rk&%7*=f3RlcH!9p>(^#qbeNMp zX@Pt8(WZ;DC+rN#{*f1%Z7?)0yEG&&TQDak`~IDT>}K;av!`^(&%QY6O?G?-ZL-#4 zBXZc(#w6`a5pug~KUdh$TRONEd#*6O$)4|yf4&1eyNYQKoqQO~v+RH6E?~DgYXLLs zZ*{v4p2aop*9XAN@~3hp0{gkF3xMZLdJ0&0AqVg@n>~Q7Z9@T{`sN3C!bbS)u`s9F zz|QpVM*s1kzY9++0=)Z4E5Jv)*?{v|=Kh|Ai~;3;zC8fAj^Po&Et^XKzpr}+aOja8 zfMwe|fsPdVcrF)jRei3oI_JS`b?NJZU@KUxIMx@i#d@>(EV14m8JV$u;Q~#pS1Zo*=Zdp`pIeb^EI(0&m9p>HT2G3o?9Z`d6?{_A_Z-n$a~RN!@-_T>(8yLwy`ZM7I3 zr4_m+I=4?An78QvF#)he4;Vw}O*Th7UETrAlg#k32HdE7HfzibKKK~eGp*oyvZi0) z{+R859~c1q?c;eEaF;ib=R8RR_QvB50M>Qg2zd7xTfofrw`ZIN{dM;L0obyS7%;QG zuis~dF`3>%+GT?Jf9Ak>s2A$%f_bT}-SR>CMc=H!_%hpn5!6WGywV%!NUEOKM2l7^ z&JEZTmh*saOIUKlU}R+GhRsVgxk0Vm&}UhNa)Y7$D{|4B4wUx#Nz|Wn0o3Z;om5K3 zUaID*7^=m`?Nt2tAZoPhQp!LwgsOGBHFX{PK^9o0knfr;BI9m3ki6L@WR`v%^0lo# zX|?iO_KE&CvZn{eWKW%>mpyFJE?$qf%lMHxd-&rwc=78~PkF-v0(pD#Uh&Rx8vgCl_$jHnA z;j1(`K&>1wV0DFZKvlah=7Zc;emyi9l~nzD=zWzEX5es~HNmzaVXrKVtcq7-a=P6~#fPQiF-DVYAw6fA#P z3bu1t3Rc=91v74xf-OCojA8u_V=*giFugA}*h@28?4+G7_F#}L<|DMlUMAXNpYv@o zyDzrbxmtGEBqKX)l(8N5Th9)A^U@Y;ebyGcv&$B9v$Dlv9BeSWS2DJ=K?>GmObWJ6 z>UQ;c?#tNqQjaUPhM#;^a76aJSC}{*@-RKXm_u#IjPqvfNMBp9KmAxcS;l;G0Snt7 z1&moOg#3OARI3YdW^qMWQOb+Ha7$UA6C_4G)%jS;2c~zecggDYp$NvT;c^Zv3}-n}fVi z&MsI6>nGVlK9&R7?RS~-JG1^e0}!V%{tC#MQGkWn zdm+#F2V8FdrX1Mc(3y~JjzLb{37O$9xj74bp7@~%z_R6kp(Evg6vA&YA-gA?50F}gg40E%>ur?hr zoa~6YJ7N~Y ztg+BQYs@0X8WZMNW2CMP_HwWd)@6qccAm1qT%Xxs*J{~fIfk}akDoSJ;%OT!E6N6Q zpJsz$HEpo(Vb+-NPDczoZ-rsrR^O4^^UJL{Yl4o(Y5$+roTY}M0jz;xMlREr5k5W( zdQJA3+-I)WWS<$d7xWfN>jiBfUgN)#0VmdEjUSke4vdFGd63;(KxV`rET_N*w3j~= zmKR?FEaWx=@kuzL17K`L1Hj}`eZbi1>tKK7{PAlK;xF0D7BEJc0>)cg0VccF2aKJ9 z`AT@tU*UN2w@5!Z`WPzry;}A#qSycYi+6MY`uF$$)O)#k2t&6jj-kS*EHN||bxCdv z6(J)thW-lE#89=av80AqFossG-x+v-+*W>_(PXQ%gQ{O=bVfDlHp*%2Urdxc|K4?n zVh!MI-y;%GlX5kXg^WxM{N1L}fLa>P=MT4t^u;4fG- zkc^s?tAVq~$kc#tltu$;X&?&?pb`z}@8?h}&Y4k;^Y&M)d=BQa zFzaVq!+GRP`vLj1E?f7=mla+O;UOrZou(%%;tTmbPJ2j7LroWgv{6k2EXm6xE z9d=V;Yf`8m{UucHv>Ymb-(|`p<{EWu=0z%em55p}?=UrbKqPf=pD(pwU0=%hWHV~w z(kCSC%f>kA=U#p0B0S$G^|)el5f1O~0OG}sX{$iI7%)8_#G|c;;9Ny|lZ~H(mlr{P zaX3&H#KT*L_JDg}=71-ExCr9!eBZTzU7p_qTsy5hsPBAlBH#_%1_Cy;(g&P#ayQst zFV7u-eO~cNa9)JfWHUnHR`3ufQR6DfPH@-1sr`m7IIK9U|D}66UwwuE?-oOxd;yqDb5+>E0&z`2X$R;&M?_8 z9T#TK7@a8nzvli_CGSre9*_>8lAO``P*2O#W>N|D`%w)~a;R(j8c-cRV^n{q_hgUvPslN@`DC+} zS>%(U8^|w7U$b-b4S2sMeB$T6>VwZqYJ-ov@|8E^>eKAJ9`ngQACkyHKKbPJ-+xI- zKz-`s3`>gkg+y*wpPXTKNIK9J%Nbq%27#Dn?Kli@%G`Q@_Zq;M?RNtH?v-qep|_Pk zMuHr)bo3&?aSx|K_B#Xk!k4ZfhH2YP2kdV(3GftGxL)AULVW-yeCQ2$bG{$oPHE6zQGfWGKg{uJwYfPMZ|6Ge0FS7@8gRULB;YGu zIs#t51peN`$0-egJ@^d#4IeU47uaR}%SA^Pt%d5DGYV1^=M0N`EIGseuvAoXbH)H< zWafpVk{QlXqt)#m-rMQ$s<4x4~Vs(wH2hD;mfw8(lt?m?Pj4PdWXG|=q` ziv|WGBU1yLk7_iamInHyR!9RqMa{^n^vXeQPcJvG$K=19f?;m|i}QL0r%4BGp4_=S zbj$SS{!b^2^Ipu|2F^9PGY)d;NXYSBA#be>IrIgb8^mm{OFcNZ=7vAy0XHDm{Rr9p zKICp9$d^MPM;Sq8`tzm(^uN~<$bnvv-MNru_Lqx33HmrF7pG(o1v<0-yFSQ#r-KUf ze-+_!^Kp#Cieq2F8s zMbd$;*f}HSZUf`Thj_rkBVmx+#X;T!nVb;??d{$HCWl&rxrn$Y2XM*E-hi?7QvsLk zoemhQIT$cmSQ{|bZyIP{HlF>r)tmt19fz@|%XuuE%P86dSSb1c`Ak!=W{AAg3bLIE zU~FS>HN^#C`f6tOLW}vRiJ!f2njLe*oEz#r*wQ|Pd%nIcUAGZm-s`P4~Ri=~b@w=sK zNvX#b%o(LkU`!2$IRZPd4%$D3Lq5I@@GBCMwxR)u`4$4Wt`(y`!t!qU zfJyEdz*zha$UcnqSj_&M4F~-f#uY>Erv-9^P_F>mf2{*7yZ`P~nHI{OGgOOvQ~h!j z=ZpfjXDdXYuFK6CsmRF88J|u`$4hf>s*?Anl1@tpP|3Zis&<_*AGxjkI-_BZw1cW& zXLLk0RiE!U@SI`|2+hvah^&EOBMfNW*C0G+&2wI~VDa=3G)AK)L<9ms+XX3GFPy%u(N>wvAw|OskvZDXA{Bb zxVnO|gKG&U*24r(so#Wx1R%cKe`+6{HwJA%bpL?TlZIw!CchEYr?@?OX})tz&dL>fO#xW+bsj^uqG04 zw`K4-m-n}C1om6*YXF~^yAp8tK^MSXu9LtYW`BN~4*>Io;s@*m92B$%aASNe;GAy8 zfMxf;B(F>h)${!MiO}yRDZr9C7f7~BB_Q{>rzuHw5K`6pK3|XY2Th& ze~Cjm_O_wg&9kF6wzsENPwz}!=+T+FXKqjZ>SIS~54Wa-ecMrQwalrhOBzxIiM6Si zus@_{i`OK^c}(7z_JquD@s8{fR!VmD(58wxx)kk8)QvKoRF7+}UvH|CGrkTGfSkdz z&t+Zz6?L>Xh_S~X#R2xVCjkG}JqOlX)g7=8*!MXZff(4PR&&5R^qT?B|6>f;rbj8* z-(vR@fNOpT0{o`WEWnXn>;cQhNP070>kG!C;~_gRUL9+|-+{ru>wp|JB6d39dpoxR zo)WhcaL||?fR9MF1Mak84q&Fg9M|)pKMV3*z<9ZL>iG4qbJk|t+VGH00Il@6eiobj}81h4&@)W}waO|AAClE=wnf&_O`9@Z(~YyX+k|9I!4_xPbUKyUY{Ltu#iL8OrO%r3Zk%k& zTXTFYuQVoyH}b0~UrbKpx6X~>PximgA41f{P4jWQV5bM}a%(=Wdwm9eFl#ct;jA0Z zb#=lyj!p5=v$b&Uuv>io(Cz%oD=henM+NX`U(O@9tNwHSwH`+yJqB=C?=FCc_ZR|ssxRPo)5ijq^)Ce-IsfC&_5Yt$JyWcKf?6!!gG>roG+={_ObvKF(P%&|4YVz+kOrDW_RXwHFA}-Erri8~`dFto zpZ~@A{d$uzg)dQ&bU+`IFMQ?fkG{l_O@<$KO<9TBSIo}fUNju0#dO#LBLMHn_ zb{_~?zbE9@O6KZ6Y69mKon8%C7zLlBiECqEo{vRrW5C#s)_^VSEC34&>H)?g-hlQn z!yS;B*~u)fUxM?aH(^7JuTE@sLemh|_AimV@?%g_O@ zqGc78EBCw;;cLY)8Z%^x(IV7Ex#z0ak&zjr8@|xQXtiSWMKpj)&Q&erP4KGpTKZD@ zxmS;8VVx>QM`@*=g@t+SfN|kZA>^TqID@qTaYfk726CDsWUpS3lNf6btcL?THew89 zLs%a(GlTYchQBs&O@(Zn`)`ZNgmHGfC5X$yMuCto=K;n_biw>l?70PCe2xuZOy3Hy zaHA<;?8{HM|MM}BW#Tlw2~$Ye|F=!Rd{J^>JHVK`5#)Y}u)fUx7rrh#?#tx?WzWJ= z6vLVS|DQUSEteajJ}Ax&IL4A2ZlP|=%?`?gwk*sGVenD%8`EHBMA zwQamDwk^sQUlVVOJ;|`eoo?9*=YF;odg$0;+6H!8-<4?u-usdj2;2AU|DU!;CFwjty}a6KAU%NE+3cLgl$)CVwj8s-Xef-|)Dg5QDN zJH7_~AL(!yux#w3H{qP~AUEK{JAham(+TDVcl&eDJ_oKf&~vQ?V!hG+nvie40rklV zrvY0o-v(GH>I#_YKPDOn#sgm|0&Ed{A8_7b_#0vPUteIC-T&qfWygK>%MCG~73YSS zhAg?^BkH!?+@SyQ|FL%-P)%&>+aDECX%|GHHV#A7vim`W8 zjujO-R_qFPB^etk_Kpp)_lmvXw{o)2N1h>>5c0qGlC?5xW#P|vX7)Qf^XwBcM6$$19Ph6Br~mI6`&ECxjrXxvg=2r% zhN%8}kGq_#`uqOtVrP}#Ef4K7MfI2YTsBqp_xY&%9;$1yg&)^0QT=T=V!#L0-?Vo% zTB-WDW(|82r}`^>v&%x2WXj;t0jj_9mko}n{#FnE>7aVI!=-16QL4XT1EyS5{dIcM z%(VbTQK$PBnyLCb{NcHWs=uZBER+?bD2l(|K3es6U@wO+s=r0<-EOO@p#$AxwyXXI z+&0b|@_tfRpdN3?ZT*`yN!t4RHwTb44e#IlOQVb|I{f$4(%Qed{;O^cP(MV*27Mt# zYSn<{V!^8*V@9fj&~EA8rW ziwoD~^1iOkW$bn1#@4FAEniiU`>?7MH*KRG=dZNjTu**wo9=ne_V03!y_h$JJ+tf{ z%M^LdHm;VzUa3-$b1hexQ{}>wv`y!^f-;oVKb=+Cd#+&Bni4QB#;#sI7`IYwHqN=< z;|9(ty(1Op{D0MMC2a4>YzFROmjGOR)jr^<2`7M~>s|nM+IR%GeZ~@C*TubnL)KOT z4qb6ira8y8YSH9#Wj$eB%-Tt@z%^a+!8rf)2~C0Tj|~G}Rxc8`e#T zAbwTS9QbbkACQkQ|7-n9!gB#v-A4o0y*U%uM~?HCHh5zPan1c-BxjcC&gD|1fNsx- z6}xt*lSeHot)AgSg2L;Of*UzpmKJlwRTJ-3m`9+TIZd&+ghQIAmpGSp`)+v_%XjaHAGYfN8tdz>{RXfafe82t2yiAmDZGs6{sxb|-~B7_={yy|qw0>50Z(&l4SZ^k zH*oov?!XyO?167~OoZ};`A_@w5bERE(0br8AKNqot~|>Hxb*KN*j{u0`3k7dEYtOj zrnb60!|At3f71YR=vqA^f&_)0p)6?j45O|yLJMWfGw#Iq;A_0-!TZea#eW#khqvn1 zpFiDl5dUXG2>*M)82O6g?A%Eh0OMdc{&b%s@>QOMKK23DY|~ZC)}CXae)dLP0#0!L0zCBT6X2Tr<^yZ)zkzM07P9mS zY05!$pCC7UvW(&IE~48rCv-2X+0Jf7j&jcg>Vi>I&5JyXL~Ei*#=KNK32JHjU>N!poAgG<(l2gw4eE z!0IXTqO#bYjKJp*D2tvL-#!-EF9Nx481k;s$n0Qb*AB=fypX3=M|QD7PFjn{qtV{| zv-C~D_V$Gjw#O~^0W+31FxE6yApw^6@CN3S+5t1~+5qzd8Ua%d!g;edLHWY`WU6Y^M$3oOp@0{b%up4wZC!Lt1H( zYu-q5*jl||BMAz<;aPd2I`i1ns{O4firRJaIj{QLrPH=@|IDg(lIO(P}Lt_2b*CQ3qiER!%6t0hx@u9KvNZIj5a?Ul$o zDJ5yQPD|t)uSlqmcO;bmV+mF2wS>x-E}^=7lTZOaBvhpz5{mmOp+etFsL&@8YR+W| z6?Q;E$yQ6Kno}gy`t}m)L=6d5#acq8y>p}1-*=+|(%q2O@aLT+xvh0cWFg(mS)cz=HGr0ZcU#yoYS!T_~U8_ai?QTx$ruq4*;7hVcB@7QYMvmLG@% zrpli{UVRq%Iu9(rz8RQ$KNI;*Bjji2Fh4>5@{cx9Kk}~Vy=i|%LtNTmIdH&m!83&# z`)^Sx^Sn1bZ|G80w>R)*MZ94PIc%-oaEt_n-jJ^fQJvWva^?+(NCoQi21-unrq5%N zw*LN5$ExZx#PI%5U$Uj){h>Xo>(+q0gop-qkRr8eAc+Kp8YocBtO29?LlvX~O-uvN zN!t1~P^o%OHPD7^X}AUgTy<+8t+0p&;z^NOHIPh#LJe5Em^EOO29A*mG%*dlCTZ)} zfU|2(HPD`HX}AU!*Ve58s=SB>_K_mBYTyD13N=u;mRSQvX&{MIpdJkb=dAzzsu$v}RJP_4CfagOy^C;%eC)Y(-X*#BbxLzS&gHnH4;{G(|BBp#>6N)Q;Z?b9 z>CW8JDb=`IqpEQ=;+(l^$EtAC)>Y&Zu9xNJRW8nTY;4U**5u(Tw0_SvZI#Ma*>{nh zn|P9a)8;6fXIB!N=jb(dR)?o-%|74QR$lqIe)%l9FgNx0ZaQO7uSnGyXKy^wZO?-+ zK85zf`8zxO=mg_kt`(XB?B69CSYe6pHz@uMpHt{le>beZ==)CKiP2kttA51&I~Jb{ zafkEcfNx!B1zh1lIvk(owV-N=?ztVt8wE_P2xBsX<}L-kayvgfM=)WM5AYOgd_O{B z+8l@%EVTf*!Yqv695ou^Z|XG!_WFGj@(~_S@k>SFxr5JbhXSu#J{@>!`4PZ&-Ryxi z_dm0CrWUeXJDbFKqSFmTJfo0^XS^hbuGKU0)=}4s&@-C2tN-78{hY(==O3g3^?1gs zNeTRhW_$QSclPp$Ef4UHEf4eKFDdvX-;VK@2b|(BA3w`)`*xB4<#vsqZIi+~kG#YC zOitxj)_%aBsr!hp{Phv<`tT9|=lnyyRJ}BQ{gXTVk|S67R_jmm5kn61O%8A4E8bkq z?{2?a@LWdn7_+4GF!Bj!V!8T0u)aEUvWU`%3{WPDEH{el4yx4MYW8O(Y<72+v#G5$Mp z0>tmP9|?RfwJY%A9r(V5q>!2re^k&G__psxI3CS5P__JiFc_YFo3ijOj6vPns110| zuEK|b3lAy+9<{AYecE~8St^!k70Yw{U0W0mgzpX@XJfLXT<&yd2Ye6p;}a0J)=Ge3O!@2huJfX zy3TNGq^>|cp0U7pm9k3r7-itqaP9#$Toa!TpD{hG31o`=da?(dXC zl71;;k}UX>)2;X}7Yg$(vy1Z+E0*HpCza+ix|Zgn20HMwl8fu0hlrg@m zlxZYw)49%Q#?*os2TM*im-_NQ5`#$#swn%6Wyz@Qs@YCK9 ze;R|&HFUGYXW4FzstW7BtWy%WhvGZrOFdqUtZ~mtwOlIE0X*aE#S`EeUh(yyjouy+ z13atz3t+`Md~Tt_`J&L~Z;7-8j`5>_ZL3~`{Use)3T)B62C(pW*0l|S`WX4^B=Ckj zZ-GC3c?4Xe;UZwo{l6n;mg#y1#fo@_*mXubnTTf$B0-^NY^BYfVU%YCGTHKs+;opl zmCHPmw*LOvB(kRA_vz9#%E)rfuNu#!TC~nDX328lX;z_`onyq*e{gAVHx9PIWMA zz$gtwbj+p(a@P_^($=qmhh$B|HBh)yPBq}vRksHC=OP*yNs82}fjJ~7)WF%!W(^pn zfhk=~QUlva+WIx{l&opE2JE}$R0CCd=+;0$$)r3h^H5a#f1be2qnefep9$&Ps~VY5 zQlwT5M3SIT0~foQHDHtmW^^}64eTOm>({_bvZmo0aOja!4OH)~TLXLn5g!OAMQYVR z6bTA7aJ8pd14e0JPOofgAa^~_xrd~!UjuK+nucqjZ10?EU~ymF8jznC@qq|Zq*e_q zB|)JEZU&e&V3Ys^V=067cR{I!TffPebtp?t96b#RSEwr z|Bjmwj1i*>+TlD8E)9?m^hAC=8u@t`veg)5eiU*)!JK!(eEc|^r{VMz^f$LPMzu9RNCoN{%X>QYHIth@aRb!fyJ^h3_HiINOS3n(&hpCGh79=&SU%$j za>t9vOK$=5-L3=6tN#LKwwH#vU3mKvz{76}=8f!+^RgYtGX>VC;zt5AT^V4i_YWvP zOPd(SV;gsO0M2bQnY4X zpk`i}M=jv5+Q7`HdceE7Spx?Y$GLYj_rESU^SsZ}rfW_srEMOx?Easwwm!St|GQom zZ#WmC+Z*^PBHmy*NPXB^y}^+Lh2GG6Fj1X(Zk?RXty6M{x&rlh!=77~irn-$LDDu2 zZ?Fp?XKD7lK{)SP>!H||mR*4yy9Id(zE7T__Fy~^y@UE-iEXzG=V_yM3+C8-H2~Ky zJPEl}DDn?)2HD zy!3Bis>317Pmn)by5SZ@4s4w>LOV6!C^i z!$rKI5eW*tVf+ZQHyCx@P-kSeydk%Z-|9iq*56OJl&ooZKizSSGP3AU_1j3bXnmgJ z+8Es$pu$Bo;5aZ$seu5JwtfvnlQj+3z$uL~4AVeLm~IX5 zOGPx`5-Oqrjs%4om^#+10i!g~D9j`^(2u08Ujs2@O~W;CR-+8VG+;AHw+5URiD3A?Ooe|`}m+Sx7UN=!mOHdR%e=X#d~;j@#DO?zxK4?=2dFOow-7D zdlU`1*D@*hwYf8QzH&KkOtE5IzSx4?y@U+5%iNFb3~`HSXEoaQft=l#j*6($>8*rev3$QRh>5@1&z6Fo0 zp#I7vzJWNqF&5ZGHV9aA|L)T>wUFiaz2-E8|K1P#U;oW=KW_9~-JTIE*59<09JEzaS{ApW|CVxQ~AJFhW{&$2^$~BjBc@+*}r@X zvnVZ&nKN+%v*_+dX7tCc%eeGADoUU{2WXWGc4V%_NT5%T%Eb zGgW(^U|L+f#N0ib%G@gVijnz$U>Yv@z|5%nkvVbVBeSF4E9M?`pBWr_gNZ$PiP`bw z6jQ!JBIB`VA2W&H%XFEQsT0$ft22I%I^*nlM(2r3p^d0obRlr!@>%dcdCfLNwQSux z5!z5;+(%Lx_9u)FNnoLkjoTFn+;=g~gH}F-5iRf4-~`cp=f87@dDf&7!TaEQFM{%m zZodyaYf)urv%5=hZq1X;@H*>ZniZ@+DuadmqtZ`7KC@feL0nj0j8}2+fY9i&z^lX1 zgG-IS4fTH|z#i6r`;h`}+shT!*DOD1Zl)GY(=);%b$bRgN2G5mnH;)S&v;0JLeHo? zUtM2j&&Z)?+$I&M$1`%Isu#?yC`zMO2E|H`p8WM?>eWNKwQ4Z2YIawm#xJmirhQL%E88Bb89~W<*Uj`snZpu@~6&HzN@P=ZIqjIR)$0x+gu`b3au&S*H@Fu zEh|Xnmy1fN>UpKqz(*2h>2ZlXVWZ^J???$#a=e5u93-JC_Loqu<0bO2MG~s>774ZV zgoFyeC854OkVsc$>clkq_2$H=GtQo8$cqF)n|jC!J%p!{ATEFY0GK)?Xrp}!LYpHW zAn=?OWial5&tb{e;XBW%KP6#(suOO{%n`g3ebGb6Z+M%zz?yBKY6 zIW3{h4%oF0n4gvc%-qB0v=$!t2=TP_I99)WE~9!@^`f$KjHCE=YpWV zVy|yP-k}6$j_(4ddiOxq@FaRorWUgFC78s$Apf&x7)m&1O&_`lT^sOLH(xAp7YC28yL*DJYJeSn7d>op-;s>;ZsMbUn}4jXi9AWf`q zdm|}Qs|FM#DAd58b!H71^}A``dXv;Z8cAEf2Fh&6sRmk*Ee+Q|mpI)Tkk1wIfi0v+ ztr|E+f&Q5rZzD$vAy;4w*CzXlxRa;gDevZdh~n72bj14~3Su!9t-RRc*R zDAYiKEoKcEr2z%0Ks_3WI(3ibJKtxE1wLd4xjkhsRd~r(e)g8#pXU?nTJAf$_+SRR z$u%!GB;JAxXi|XN@u3j+W`Pa2^@1JOse2Lbn`2S#&xs=3)*gkqIqz*a_ZbB_yU0A; z*_$8Oed|)$cDIw+hx7Kc*CuUb$COyXe)zeNoiAC)R(4v!x_6Fa*V`Rp??jws=d4O$ z7oWPv4&SQ&-c6^^HF>)_C+#c88J*gjT@6xj(aD{z^As^xK^sZ44 z#?PPo6$+eiZ7Q%=_5Q%aDi#9P-2bEHk7ty!D8c)mD#2f89r$9UOY>X*l;PX^l;>L>tiZ3UT!|lZstWHq zpc?O)aCo^sLR(L>drrBYV)I+T71-1XFg)L zBR?*r1Ru5Cns56h5AQJcqq55U$I6by?>`NQ-MkF=(b?6& z-skWgvmJiVgSforLf}{9kgcMoLfr48FYx)lzlx5nEL;8vjAg75*9yimmX;j^E;g_n zjI9g}=mvab*c9M_t0RHqY@&eOI?n-a=rwa<%-W+mD_?hDuaHkRyx=( zR_-}7TN#m8u1r}nOu1x!fU;UhCuQIPf2CtBf91o_PRigsfl7~K!#7tvkO6+-gKw~@c0S%OxK-#RuG@`=N0VFrP5yD&l86NpSsxu@)aIWtpn5H_>0y% z37oL~32=cjH-V!@<1=-d`+r8xEYls^@hwreX9O%0@eH5CBAyXQfDB-fBcg%U3K0zqAVHx9HYv>- zFiHcx_-tu_Nt7q%rq2nI_J4N#{oAAJ%E{sQd(VX>OIDVcv-38**)t&D%)Z-b{1q%t)&{9HKTS=&AMI?-M841&}szm;G9SK#5 zlThQ@NvK|d5~{-}$#c)i5-Mn>MD9FaLOod|p&l)kP(PPQsJn|LREI?p%5AQMN{NtA zHa#R%Q+Ek<#7aWNo^zwZ7PwKnnz~U3Y}}|K4{K3QskNwCc5ak+8#n6Q5;v+_vK!^` z)s0HFkZe1v{@zVvp8GH7$yu7cdG0mZgMStlpX=oF4TttNEp0jSfCIp>T~mOWOK*Xx zV;_L!gT4SSzw{oMvcCZQd}ll`6)1Qf%R%rvxcXW)tTRc zlCyW9l)0>~Kz-hj%ic%Xl%%b{|1N^8X?XwLR*f>U=uou(?#MOW8eqikd)i$Q(SS1v z3N;XP)vN)duBjcbnWP51N!t1~FompXxCVA=lwp_#_?x;l;1nz31I4b3XuyR8g&G)q z!>j?LG*IcLNov54q^(~AGsv2TYal_R48t_=>7H&4$QO!epk#`O2HZ$csDY4MW(^pn z0q5J<(!kT;UF-y>-Ry4L1omjqKGxyXAy)ZC$xeH5l9ew!$9`yam0c5ai#_U;#!f!^ zgx%ZtC2QIGExTmp2lm$1bk=dmCpLIpI$NRf2e#L<*X+!}kJ;B1Z?b7_N$jxx32gto zvFzMe^VmqcFt&c_-t4_M{_N35ZP*J`0DCQX0$agh0ed=N4f}E1R`zl5E;favZ909c zb?&JvI(vPq^ZUlYc#O*39ATWg^`Ri(>j4XZds}P*ZkIO^xc9@;z&EZQ2d>?X2X17& z54i2(Wxz>|<-iR*djen2a01So76-?tc`c|~7K~TI7{TU+d|({=#~M6vig#rg_x>a) z5cpQ!8Ng?q7XTM;xe$2w8(hDDcMpg!KU56ZuH0eBS9m;UzBt1;`2jA$z<*AM0q^SD z33$`2KTv;~`+r8xEYltTQvI=R&v2S4;u$`vBAyXQf?Z{#sYrpmR&*(Sk#j)o09AcA39M87bePJl=4R(&m9WZPW3L0gu!f zXU{WyMk`=Up=})*jG>#FI0yL5=Pkf4E=jLlwscaf~e`UA4L z*p81QGe{AP4;{*1+M+M74V3_KET&J0)ZQC?gu>S zYENLrZBO9BcPatTinRmwwY&oPa_@QqYqo)^WzwSY&<0xs=L3&;e0nr+ztq#fE}j<9 zra$m43Tz#33*4SMy>jljtb(Bp0X zJ`C}$?}h?v?!V2;Of8tEX9T>_?HSAh5zmMxhpyE#l1WhL8P>1W^=0;q9D2qvQh|Cr zBe&hBdri{T-#_d8Mtz1D-ap%(Y^f?Eixx%i)Af6=TLXNUhz9nNBDHGZ0tpH=Q23o$ z14dnICy@%&qXF6XA#u6s^PZ%wUjr`hbE<*PWJ|*}Ffd)W2B=9Q8aPCX)T)6iBq-E? z{YSF~jM6|dsX#p%Sd?Cd$xWY6ByIf~a7)jr2D+0i4cEYsFCrS?MKquwMQYW+4H6V; zz~Qr514e1!0;xbf8u0Jg)+IN6zLB)`YoN}ToNAyq+0t+g4F9fM18MOh8aPIZ)T)8o zBq-EC*>7eI7^Q(LqyqJ5Ah%r){32=V*FgR6In_X4vZdh~82wAP24eS#XdsCcsZ|51 zBq-ECg`Z{(7^Q(5qykM$1Jp0|_pV<9jmVmYUlRvulwsI4ao8W-8c0hL(LgdOQmY0Y zlAurnl{3s5FiHcrNd=mi1}y$$R|7J#rr{bGtWkzx8kmsRn#xB}lp{q^cI>d=p?yXN zhYV{nW@Ko8KluR>4O}2aYSq9~5)^8nS|00v9>*~Ejpnd#G?i4KUJc}~rC?t5_paXu zII^bU8VJ!S!!QlV^Xt|?>`4&~Tp>kj)xb*<6l%cL!mI(KeBdFeKoiq|P5$g^pgCF7 za1D&oD8n!fOe?5c1N;^d4cs6_YSqA75)^7cQoyVMqcrf8RG^7zph&^&YQTrAX}AW) zYLsD^24-37)_{}P*rMB{NUa+9NPIXIBHQ$(n|1 zV7x{dhG}4ZQ4tNC7V&{pQlwT5d?7)h1{&C!HDHtm-jWK`qXFxcGCD3=M)!-B(PN@z z^oD2|{U=&RcU~!@$E=jmdsfP5$toG0xJpL*ua?ozSIg*#7#aOLMn<<ZjON$K=+rebddV6Y-8V)?&t4^?omR@|ywNiH<#HL_V!4c7v|L69 zt&q`wR>JoT1KmKS!JjG-c4h^i>F1%S(>f+E-s%bcTjAsR$jl+4K?HWTjcT3 zd4>T8Gc$pYxy=Li95Np`X7*&@KdU+5M{)O|{Je{Y z0QY;^8aUtFI>4G^%T$Y{do|Jc!^69wK8tU!e@L^wER&i5Ux{)6ju~eG99Z}g>`$1_ zS{CbT#Hy`u{BF-C0jK->08fi31gyFLWkoa3`z-D9e`3B1U2|DvXHJYP^IfDk==O%# zZ6eX~JW_zSFJv`ExcMa2-&kfRVl+M)mOlQ6{PG?dY zr8CVNr8BQQ(wSI~bVilS5t6p)Jg-yIL0!?=8+(0cV9&;y?UzGj)kd1E*yMcL+^}5)@CNG=zz=>|1KUjM1uU$u?XnS2zxJd40OwD!_SCGuP4(|VJf+)t zVBd~=fQ98H1XO|JueR9-n16E*@?EJ|3B2#Z7+}r)pCxCO=|1E6sl0B_NZT&r8E&Q2 zqSERa%}G$`88b_pJ;SK$30awJc}Cn0YsKJbYek>w)(V;2S`j+cTCshBwPN#1YsH|| z)`}wQtQGb5S}VGow^k_9tQB6LtQFTSZ4^&R+9<|WvQZ?t*eDj)u~FE2*(l1iuu*(% zWuw^J*+$Vcz(#SPtBoSj(?(IJxQ$}u4{OEz=hlj%*Q^yAk69}M6xIsaF>A$%Bi4#D zd#n{rc3LY`xx6N6o5uA-{qp22&DQlqyEA2={c-d^A?g=PCkH`$GJW!1;MjNNfg2Y} zh2O*b!{Q;nAkq=qvlP$D!2RzO1x~+d2mI$#9^iLg7h(T9i!T8_w`Tj7{*Ze2DcIZ#G==tC5$~6?Hbjiyx`7O9(*Yk6uUs!VQ?-k_ede+?E zWo)^^6N+$$1{dcp94W<(U0;s#NOI&}opIz+*O%w^M3v=gM>}v$+7#us6)nWQc=4NE zcIgG{v*`vK(B?SXyWDQ}(4uv0C>O&rv;Jm_o!`#-W$^4P=NoL5+0WVA8$PqDTuQm9 z(>9&H)sZ#T8E3C=^_cx_m^Wd-0ymgHZQHLVz?-l50;d%30qilM7VxT<^?=Vm!1?bs zc|<_GwI9BR{Z( zrz^0}hS|Vf>n{Vo;qe#n+TVP@Sl`Fh}EzaX|yx(Id(Zr6X2Nr=MJ(L!S$pLraECqu*AUNdIg;k(|lx`!>7t@f9#fZzw|&BRUh85suw*{+Ovn8Y5Api~Jg0a?D1W`OE^r_3 z4!{$=M*@4sJ7D&7*{F!~g%ubF=n_ssJ?i$CZat0CO z4GIzzdc&W(M0Mt|8aW%Qv9F%G0`+*qz%(a>%6})Co2DBN*>N& zng-`#hYq)3i@O$Nch$0DS1l^YZuPcce@)E8&Z(BcbjkS4ymffXY@PLr`L*U1Q>yH9 zrd;R8%$+w67*#I)>St;M=gHF)NWnf-uK4U|NY2u1dBf&88=*iie7ur8RCUzV_O$_8NIB<34FJEjXr0f z{QZeHf%87aws>?f3+25I9{@b9{&L`eeaXPrYHb4+*5B$IyP$qg`&|QmS!We+@4Lr< ztG;{>TgE>hE2@1{#qy4c9=BMj3`_U|}=e8gSYp;seQ~NUa)pNPzlq%vZr7E~eDRL-d!3H)?F%h?J8~lyMt6Ztd&&$f{`+3q|zZ%9i{#Q zt);yECkdZ%Lqb(iNaVY=OXPjyB=QG4Bve~oBA<3kA|LTtB9|7FQd5dasWl~~>%B8I zlKcCQ-uf8g4QbumL0igGbAaU;XMy=?w$PTyZ#Gxs0mtpBK3Yjp+c@rK;if8BSE8tB{JtO27mP^N=PYM?1eTfYV( z$eM<0V5>$MhH2n+H{BXwj)-W$uA_(soJmlqfuK%i4H%^X$IjW(K&JpJu1Z;JuBy8Y zXA@z|Np}_I0=+`B}L&{g>0^3yJdM|O{#*eY%_yHDNi(#MH6^$OV6XUM2H?N#wJsTyl z`(l#WB6c^}y2qZdfepT}JvZj%M%OFIQ2|!mC6c!3jN5kUrmpDhjoW^C={AfFXysNI z#sP=7ZVOy#AHMh8uk#9sf3d>%px@f8fOzB9M}SKfN&tS?XbbR&;3dF+9i0K}wRr@v z`>y)Hd#v5y_*9E#ziQz{ZZJ;UM!p94#0o1IH+|xp5Af_06M#3~!}pvovR@4GC(by| zJLv2fh}X1g4t#D*3gjcqKQQTH<;eR4yEfw4yUK5jH18tp>*NK|wG4RH(nY{&eTM>vmA3-cEYH4ArWUgNzBh?yeL4i`_6%O^ zS)WjH=vw<+BS}!`85aZ9^=0mJ&0(MGjK1m$)YIp>G%bWq4jWES>NSEs7&4OH({wbQ z5*kWZ{5y;e@flC&YZO6$A1$YQUY|@~FFuvt;4+QAQ*S!GrPK`i;*J^gio_Xo2iXjI zcE{@-jCB1Np2a>t;!>6fI8#sc}8gd1ZY#c@vndf zeX0d*aD%NQfX`f;0=(`lw&}xOZi0CD?l|D__0|E88@n3#&#$?_`@$yyzg`psoDeSs z_U!Hq$D`Q>s+MM_s)I)~TfZDQf2F+8W*2tz06x+T-_vecG6dpFP6h#AE7}tH#lq^q zuj*02Yg*5Me1!Qs9Q+H8w`V^KX!GAWI|EldUlX`fa!FwJ;C|SjW_c}wGPPiup3!BH zZqIO1h+JoEA&0KjGmep<&@=J{tLw|`89DTfL!<)rct&o!PxqLlt-pWPagh29F}#1) zmu#sjBa0SA@6+`ds#^opCJ_znAVq4`KoSWGHBexPSp!C0Yb!_v>d`>niq#ak>GPbV ztzQF`hUQcQZOE2}Yhd9h-5QW@7STXFDN?Hjl1Wgg0qYR628`0cF;angG!PP2j>%1* z(Zki>yXo{dT^Om(ID6L))Rm<$UQb@WFplr?;(+B%Lx7o<(~*}g0_HDA1Iv4@Kz<-M?Hb#p}hMc3+_=+*HdA9ALZHs#?vtyl)&;Y4lrI%zOorG zqXP5p?Z|W)sEVpq&UUmrf zr&-?AQJGrE@_R+|Qd)Bi_W$zVS;v2m)9o3I*gMjnl0(<(85txf^bC58*)xptj4z}D z^>_v)z3fKKyXr=H-*uyoJa;Sj`nwy|p@4+?QcOaPaFS5(-6YglFA0_5E8!!$N~k&g zCDgqT33Y9(grX)&s5uc5>QcCbT0cTU73?dahI>n>$5kcNJW4`Qhux@olijEyE^d_5 zty&ZxRg01ju0_R8uSNNvtVIm>cDG*=>EO`g=ExXM^5`k+U>= z&jtzG&*zTNzIwLq1x(e81b+BuJ+Qp%5n$@-W#sL*kY6A>HM)ZFXeDsku}#2~>uhA( zX2`+lm3p_Z)`xHOpHwF7v$4;t#q0*`Qq$bbEvRhRF3q-f;C{YxRaQ zBq;QT?&FE-%;UdvIR1Nrx&rliLoRzyq9;jP|21_eS<~=-rF9x*WYM8$ztWybx-~#0 zi)g@dqKF0@Nl>VP-VtUE81?(8Gi$&o4LDBEmIfN!Kgd>`d5Cq*m&lHuz_V2xPqJGglG*lcF0u2IZm^z%@39xJ zJz`sBykJW#c*l0{`H5ZJ?;HD9=udX%fZuHIZ@*c3=`Xfz>F@0FuIa4B)aPsqpIfYB zo>Q!qUjn<_C6?_%N3x~vO=S1_4rCj=>B)BA)t9BKhp{^&=dk_jtYixh+rr+OvzHxq z{~&vbq-{EVt1h$D6`j4lReILHWY`4<5q$P440W3Faer63(8S*K_+kC% z-t!02U3LtnUvC&n_xBh^cepZyF4;Voe&4q*UDvG(y4B8$9vR2K67p=^Fj3;MWU3Hs?@73pT}ooS1C>a(BgDAL7n? zoq+RyE&zOG{4K~=p0*sg{<2cQ!Zx@0)WtAXE@iAOjHRpWE(cy(@Eq{Lfc!Au=}Vh3 zz_&X%0hbCY3*5G`EpVMb_hJ9@U(5y;=I;|92=%dh;x*unYb?Mst$JDlM{mCa>#ylE z30Si{`vsX=$nyK%B<{y`SfblA2v+jw6^l=C#ka6DT$IP=ceSZyDBSHtf91Va#q%kucXXVtb(%fxU$OmZyc1pHx*ZQ zI$2cN*}t&z>>yj^lzKMGq}$fYUi)p78EJM(hrnV=_vBK_xg9GiZ$EZX&TL&*X&LFM zyb{(#8OeJq9V+-K^Q>*9oORAm*<))twMsiVN%H(Uiivn>U9R8_(rA3&ML>98h=2cFAJ|fn2YBY-D9A^c|MCk%;dnb%c@LaF5yyW&XzvN0;9sH! zaNm-bVSCN;S}e)bf@yk2m*u)WBS0zQ8C%GqYxRs{Bq;QZe9P4JW%i65dd4AAfqFb6 zx80|EOw!ihKkK+$eTEp`KkG}jRF#oMi=y}GdaTr~0qT~B26m7lwQ3-V1ce$X5N+0g zQPGPVTtzQGqF*(&hd$OhB8tC`8ZVm7^MKrLF6sc7M7f4X3 zfx@w74H%_?BvOHTG;k(8X-jVUyeDbv*MQ64In_XCvZdh~7`R@y1_JJhXy6bjQmY28 zkf2Zl_Up_VFiHc-qyqJ5Ah%r)d?IP<*MQsloNAyu+0t+g4B4bx15S5EG@u|wYSq9E z5)^8{VWU|CMrq&zsX!Ccz&Db%eht*wlv54#CR-Y=f#F+pYk)s5qJd+iNUa*UO@cxV zl-+FBfKeK_LMqV2H1Lb0tzQH6x8zg`Xe0~NNJ zHDHtmZjcJptASj$Y*&Bp`ZdsqtZ8`PXplx3hV_kx?b59Q{*{OZl1Y(THSmxGg&L^5 z)2sobG;o_#powX~Vpn!GAR}uUu7SZCWf-P`2?@G2;B-Yq0~bhDB-*<^wlKkyA8WpUYFlG* zI;y*^3AiBgo0B`N{$jPZHQh*1*w(B(Vs2}UYHQjhsw+@WTeE3n8k3to77BIRCiMIV zug*AoV|l4g);e{qT9_de%*Vnj%Omfr2b{Lc1DJQhcX2by;TX?{{QhYVh*Nvo0S}+o z99Z6_I&%7E@pdpD563HavVr+-m_1RzRQ^}MOaQ)5mEY$K^Qp-{dI0lXkf~%(h%=uW zBX=qQtXeeRaYH?ujK}+~FwCbGP^AHK_gcvPN+37<4f~6=jRe*#-~MQ(7P7SSCh`1- z=PBKuL0uE^j8JmuT0J9@1cjb)@tC^4%wu_TIF@(Dadidi@r*7t{A9W5Q}={AZPS>K zEh34Wr8%3Ajq}6ycDNjQ@p5Et4RYjmWzTCM`3(Md*mYS$Q9BsA7TDAo8s}c{SGYO z+yLA6*Da9$2;P4_UGTp1f`aH3ZT-<-QM%UqpIvTkpP74wED za@bnEVG9Wgz2VhqqB`?@Y&n~cZS5I#1?usJ+|+lc%H?1*;2EJZ4YrrTC?6{IG4Sd-&kS*410J|opA3OU*Fgs|_2sY#1IQDbJ zDeU|OGuX$C=CL;%|6<$bS;2OH9K-&Ru4n0Go7s>4+u88)JJ{m(+gL|xGy7Gso;|dG z1$%$`Ja){saMp5vC$`zR+U)9gw(PAD7Z|tBvzhik{1^*a59ZqS<;*PmJIsK|#n|*d zQg-N6KQ^*lAiH$?KsMm2I&IVGTRn7LopJX1R(+%+VQv@AYbDk4>^%+hn_ZfS@4xoy zJs;xT2QCEuJ!Tm&*B|F!3yf7l{8-d3VA)xm@2$u9ScrSvp9TE>Pyt}!@gxl|4fC7z zxzimu&Fcwp-EH+?zO;sSIsorZ3*zSZS-b$f>ViOBD! zE9B6%dd5o<6nchhin_kc*Uve;ett+QP|x-AzT`ekxBP)jvyXk4OSStmYflVhF3%jy zu*Zfmhf9uR_)cRP?>FO_twkc3%RMJE@0v|z+%8XJrgfgd^e;J+8C-M*bFs}d=IG$b z%-)k@7}ubFOpnfOm|d~;n2ig{Gw(XvFx9ta(D!cLr(2|*qwPYk)811*&`eD`rj(O2 z6FspJb2G9v^CoXk=DxHKv+$PsdpC`~)!TQ-S(>B1)h8Rz!8|J!iYtKE97q5jt~>!8 zl0d;+Umj8Rz|m*U!1_Dfu)Mb+84!;xbP+hs@c^);$1GsY_D!|;&D{v|)I1Ni27hc9 zI3IYH&tu@T<%@uS_zjc*zueXc_{OC&z_Sw`!v3aKx(|e9|xpy-$zcABuRxI}vYqOAcGBH(1_N z*NM;@TBH)ynfq3A*0-8LDo~F%OmOyPsJcy<%^f_MR-HVUjjJ0mu3Z@BhhPA1t zS=NRLp4*k#RJ1?i(SH#0z4>rv#Pu*{-uVfPJSm*9iyFtA?;XatUJqvmb`NLD)tSUJ zxjmJ!-!+@@2%X2sAI@Y#ijHNzFj`B-~%6v76BHnuW0BE`)k^D8Su!;lYxcXN5A|H^;hn(HTc4`;1npo=&i}H zz9oxuc0Q@&4(of3ECTCmmM?jjc}`|&w>3{oWAA;b#>eT}hvxmZfB$XqPnsGF^F((W z&72i!qoc?nYHg#pkf5-Qe)Y)QMjO@Vv-YvN0`;`fx$gcKNn3xvr{xp%0p{TT7unMA zeoxPrA{uxsqJgEPNUa*!L4raJynAZafKeLQ_$*r*sJ^qgXKwmbd9F^of0o}VB}seS zt7EzU~S^~!IskTZ1}%cL%UAUL_BVH^~w|{lg8nZ-?tQ?1UUI z7(Z0~CdL&9k+)1i?jqR!v>-0b-*OF(ugTbmym&V9z*)%Ox*`|J&{6>0mT$YE%0`?ipvu&phw5wDp?(ciP%&UH>Q6zU$g1JO8iRFD#?K==O#*v1`K}N`?Oy&Wf&7nV!qb0U zK`O6jC8hReNT}BLCDf-Y66)nu3DxbXgnDHmrK%N`%1>30QgdoasS6FH)Ro54A?cYK z!RIUczp|?Vj;v|820}E-FiZpTKe{y#`%9$%=n5%Ps|H?@ zpil#@8DTDT=aFwe)>P2Zs#% zr@8k_OBd0=4N|054ZI~mp#~&*Z2q~NH(w{`@H+V^sX#p%Xy99ni`!d_i+fNI$oO=Z~uB}4K`N^f+&K|Y7p8M)@4nt~l zZA>KGMwUCboj`A>+qC~uW^k{*>jE^3^WSy; zYkjB(taW>X(;pFU_(%?0t2bC#s_R7P4Za14>dbwpIqO5sTToqrdc5K4!UU%G=LAMp zWG}P-_&(!ZM7^`4jr5<2x{dO>^o@*KBJJHOm-HVyM(z#5##xt1; zb7wMd`_5-tJXywEij85SJT@{v(zh`&iMtt#MG4F@3J+4N4KaAOuI;d`Fu5iuV1JFytY>fU}4*N<_C_`6vpEv z?O_~Yq2o1xGfMaZw|*Z7EaSHVH<|MhI9OQ&yhU-LC~%!BXJP%gUoV0Gd^`gz%zxJK z0I2@}@6S*lE4^{Nd(itE5D&kP9Cu_J#2W-P2i7cqtF`(}Ql0mv=MC42>h=b?tw{fo zosIgiwR(dy2@1U-$d;(i>w8Wrg+mA8nvejoe!YD=Nmz{ z+Blg`o<5gWoLfR4tQ1R6t+s_8zF-eMxoRS<>~e%&B2m(|9h2#$fl9jJ%^h@2un^!GY++!{@=uX7$uzworMPZypq`6dH@FlN@dl0rh2AjL-s}xV z^{X~2o-J?4ZTHvuk+k*SUyC7Y8s4vZR-=q8ztKeRuPrX4TLb)O5e>MN5Ya$$5)^7+ zW=XRKjM9M2AzKS7XL;5yVjo`n1qQ?BqGIn&AlkT?L$xh_j_9Zz?t=G2ykG=DN zYTA4Me?UY*h9aVZ0-_?yR1t*&OTcJms@|F1i@ zE$6vyG3r1S=eGRomUu<_>}e?f-7CkrZIKa z(Ae`WKofg+K%JZn^}CHg^G)NR4jKuynJ?76>j8~<(_%R#w*4-)R$#6s{s2CcAD;y0 z`3Jvu19L+$L0_PW!{dPFUmFW`_#mKhHtiYxCWgR{`Ah@;mGS>RoC}I^?ZABm#92?M z`?LhQ@ODk0G1r~2|0w;n-Tv=>UnUon_nH_r{%ev$sLLt(yG(Al=%_q5;MEv%gJC22 zW-H7Mw#Z1&4M7fwI^DQu`Nlo18p}ISMQ*?zw{^#wwsXg3`?+H-9o_i`0q$5{usbH4 z>W(#xbjQ9gcgM!Ab;m-I-T97t-0{>@_msNF-1$RKy7ME_+_B^|cWl~mcg*y#JJxr* zJ9ciBJGOMHJBD%HvFcXtSiQ$?*tUIcSY)Id79Hq@5q;b+!%#PD_BuDr=8PNG=Z71X zUf&%HaCgU^wsk*^+^!tC!ORiurRB>Fcwa^e z%Lmvo)RZ6d!ObI2ahkYGIY~LGoFH)$ClQb3c$LiK zEFU(R^Hgslr^I6>$G7!T&i7Uc9LI;-IDTsmbDne3IG4U>aNZ-gE6?-zyS9;cw0h6u zAGKjWm}67rj60ZX-hVWlyMNCo8Q2%^Jr4A*V0iDqv&%AIFOZ!BdPoqw_aIJi0@xSD ztO2@C!E~U<@j3$C=T|nE^Kuva1m@U~g?ewo_Juq!-!r{KCeY^FY{A@rr{zO|PBvW( z^pS@vfp!=f3$)dIcyGebMnS-STMxF^f7D9g58eN+(~80NCv@}$_YT~#8wm8A^ZkH! z?dcBm%v$jGp|)3w_AHWxGO@Z+JdYpuRh~0~KQQEs5nc>AV-7OXbH=H*x}2d^&Y0Ft zt(+0C?i~Bw>2vIj4KA^3&B{nC!{EP`x9&scalu)OZr~PAq_N`+K^!xYC{HQsC8Ux9q*FFk7>)0X60L=4rj|l?0SNIm7 zYm6X(HnPnFx~K-!)8o$qdmr0_K*xH|0{Ypl`@lcC|M{cZgZ>IVwg%`3n_WPEjNS(H z#-)pa-gLzZXli?{egCphIdeuBS9#9BneJasM4PVgdR!_p(sRZ;KY4%YuE&-0dfYC5 zc?YVv9_Ko>18yJD0XO&Vh!3CK314H;1y8c+hM%0+1LwZ)h4VKC;ME-ZZGuwdCLhtoC@HsU7e^?7y7A=X_BQ ztW?d1`!DTBIdYHH%Nchb1%a44+wdjGA!)@eKn%{?Fa+qW(_oI7^ePV610yy8ZF(JQ zyRAvU-lSF{(9NDt13LL=51^B}HV68Y!vWy`rj+ZxOgZD}fMy^jdm5$y{h*N^h~e8G zI0Kz=k_~jG~Pnk^q8x9@{w*Thmqd@z3!h1(# zOWp(foIZI#pEQE^qEK_jSngjIDre4^-9dTIAPg9;QJhDct}tgjL`HhfXxv`jU%H%8 z&YW=vb)brzQPHl`8FY~U?yARU{ZUQL$7d%}+9<;!<8`{Zos}~XT$O)tvBv|`{T>Od6?RJ8K}%RcHd&=b|voPh{R8=5f?+fO+Ih58KV0|lr_ zg$#T_Mmhs+`|23bih;MN11lK=_4=vHKme+#IRgtQZD__o+(6|F1e-H3@DMerkbz=k zq%*)8pkqKQ28vJzRx$<}3{;na0jQ?t3@oCwp&0`Sfyx=+S7Bh_DQZ$710~2vXMp{W zjsdM0_<}l6B?A?!*axc1Kp?8AIRjCYHZ)^k{ZQo$*fU)pc!`=+$bkM}`8LxT;0@6+ zpcMnfr~@k*15Jji%fK*HQ*#EEQ`*psfuvyN3}8&}MtF;wRLFqgFvSdX3eqv46$2%x z11lK=PQmIjFap)ooPkx8HZ)@(Wu$Tj3coRo3l*Uz6*6EvTrmUPM(7yOiUIwR@(xtV z0H0lv#)e#w+p4bGmiuCb7ZmVWs0;;Jw1M4VlXvV;v@yZ#%y%-oMMolVYz+$Xo2KtWEF`yL# z#^cpu01M4ySEP?8a$7Y6lTl608Q4T=Lo)^rgeYeqr40iEC8$Y-3|LK2%)r2jItH|2 zz$`>91{U90#jZ%7cF1kj3`|2cHD@51(uQUX91c~^0OrBKfc_-;uc43uYh{pdy_MkiPgLw^cJR6V=q5f$fwwG-E(Gje!A21_lhLFfd??jC2Nqrs^2b ziUF%>m68D*xviRkIjE-Q4D6<~p&0`wW+-O>ug$=K@pJ|T9FURDz{oHi16na)J)=@G z&>p$1nt}PKrsfRnr?jCN1L?DsGZ5T@fdR9b3=B9TBb|YwHP?E;HRV_ zeL5qzRWlHYYHH5FAxayXF+fBpXP~ed0|OS}3=FhDMmhr%=ja&FiUEfRwHWxvJCWAD` z&A>`jQ*#DRQQFXqfqqfS8Axfwz<_Ne0|Q>jNM~TyA{_%-G2pgXEe2|`#`#yI&uQfL zyfW`QYk(%WPWd0a?<{qxyfZH;e9sx_5m|^o_v6!^afmrRbz^$A2R7veeD9c9G<;q^ ze>2n@7eQUG3)Geqp*DOd9q)(&nTgjb|b@G|-D zzM@PlwwH5@@&5<0nEs5dWwCNEO%=YIhV<}v`NPmC8^At4WG~Q|g#*--*zmj5dzMExfAk;DssNd)VjpbPaJ*9a|psRbrcg+-feh2NpH@gNjKYJ9=Sdtyk z{JqzK##UmWy)xUo)Dgb><=a1?|A>OIz)l<(3N${bG0^<^AHee1Br(+4KcJ4Z0n1~v z-$Og)_i4T2@BUmSwo`ZMKXI4*{u7`7d{b?j{;keB{_Lm!R~D~fi1jrYVtpdoT!pbd z6&dNV{@qGltk){m?^-4AKozkb`(~7i#nni~Zq-f2aJy8@zELVRsBtRBw@<~ehH^cn zVJe0jr(!Ah4r2UW2eB0XK@4Xf#IR!rFs!#TMjUd%>>XXPlp(HI;bd0~k9NiI3|EZE zcg0fnxnfw5D~9)T#rQ{DFnr|!40k?=5%UjXaqA9Z!AB2bS8g4|a4Z#jZLn-To_c{yO{#+UHup<>%%@ zJH?+_YyR#-W#TJ!HzFgFIcAtmkTKVs;di<^@s;T+-T?D0#GK~F+i3;D_6}w(J1DFK^ z0|lr_g$#T_Mmhs+*XbD0>Nw&p>cC3IK)vpn{m8f3ck73 z7Cd57JZ?RGG5$O*1b^53AAEdPXS~3~1MeDG7r%0_D&BqPGxoYOGWPN4vuxS2+faa{R0m}z{dkgGTf1|ek-N(zsVCrs8KR^AW z)j5~_Q&-uyI_oHNKD>Un@*KdoWXJ(8(IzX*0s7nI{Xx$GyzTP;>&}PEc|Kf>I#5Lp za6fA+iNfq8gX=evG#=+52@yL;HZ^Z7X~B1p9FR4V3_j){=~UN2vLf47GB4O#a%_^7 zWZTW!61!8iB*PAxN$O|Ukhokhl4RvqlLV|bl)P>WS!xMo0=SmLHIX0Jp|DWSw&fAyR7SvCB zJ`iYne{jaeg8p>>kq-LriNif$Z&VBFqc!&dJG;d~pcmcj1N4VUD}Y|POXc|N6L2J3PIC5p zrE&BY5S-$BIUGAr8OJX(m$Q1zbZ5{+~f4`^nlZ0*(1)LeSi6=*g4?g0lBB@ z;4=p&>hg?$bIQ=q2wq z0v*0=JkX1Cmja#tVjIvl;jutJc@qNkFDc3=Nrexj)c3ILTpx?Jj0J_VIfk2D1oPZvfFc|11yOscbWn*`sss64y_;(+# zTsa`+u<{&W&oqy$2yL>$9AKO(?+wtG+Ha z2G!JjJT#utMi~alb7!(Bye@X}m~sY!n=%{+eL+nsWWel*Vg`C0)iI#ec&MR3Ee0lw zA7@siPb_kKQkn74dT8ei{tw1Oy^hH{Z-v6SHKYe*;s4=Mlba7o^Wcr;iPi37M=<|37sd)W6G&8%{Z;JXYc^46)Kd zB;WBA#!4S#q#rLt9M{E4tzu=X6KciEjVaD}MfzMsZZ9koE6L*mvV{Eyu~N}EWN zhvqAcpQKl0xk55TmW64MZ`b!g^JV!!6E`_PlZAS%i0J+_or{NZzAfG$%%uQ!$U4V@A7;TrKi_u!e=o_d5Rh`rM%2Kfg7gMqHbE#Onv#D6S ztW+#Vnu>X3rDE^JsTfA2VjhxItS~(l;|o)kQiVZrSiuIPIdSuFfcTSlYZTg!V4D&2!jBX)DF_ygQ-feU^CoF7Q`{7~3JBEY~ z4;w?Sl`L1#H(97L62smzA5&shknzsOP}A4vfiv5qxiDr<84q>4(NORCsdWBy6xwpK zPs_v@YPTArI6{plB(=)o1PLuD>!l1a!i^zDyhJW4j1l^T{FkH02%bb2BeaSU#i#>S z#fSoYF=aW=I~$qHx?N>5Os)Qkb6g`W7{*kO9Li#SC;h ztz$qd21-x|Rx$>h&Zx`42vk#Z23Aqp(2RkU9OVq)OwY9`LQN`U!1%0U2D)YI7|@CV z{Tz7*Rx$=$klU*Bz!+3ha|YrmZD_{84w-TWf~^>ig}$IB6*6FUPB8<$&g&S^iUGq5 zYB4ZpnJc@XepU9(M$7%j_3h_#(i+EJVc=5TA?c$YhPKrHk#Nq=5XT=w*oe-y`Z4l@6UMBukeVo{A zdwp^37k$Kiqc4k#I(!s&UV26RdgCT>GP}086F1t|`&JGAgCnN;C$IeOUt^go`!I65 z;?IYEc}d>E>dk|A{ZR_8^Z#)94)mymTR^Wn-y3MMP|x3p;SCt=(xoKUJB--(bspe<6h97!{q%y2Tfc7_2#EQ)7O`2 zH3Rq)a9|8bf6&i|`(Ba%UpEh; zobw=zugW`6#c|N$g-Jxb$xfoq+atv7p9B%O@B*=Y#wFr(@_C|W<2=HHbDcP~?=rFP z*A3$C*Jnh6%@<;9>_?*Cq<6&luO&p1Uk%AVvs#kg%Pl0mS6NB~S8Gbr5{xAMLkuOc zTl6Hc`R|F(r+*Niw$zc-&u%EmFtU_v;~Prs+dn1tS3N_x&dng)$BBq_U3U|+7bX!x z__x#H1R_`T#xu3LEIx4-SEtN{u08>FCyl(LR;y1=bf74Cl7d z*SCCPXQeTVEY@Lb!1`Lt76EN;19Qw*#{fqE&ZWW_%zqE>DVcX0-cvI2ZXXbX>HZIT zaR>ObtJOQ8>GkKtrGoXXJ@*A@$yY-zHRoP^vmV%eeeQz(dNAD)*oSkuKvUb_lqcVF zDqjwmdq;T=z#1^*fD*LH3Uh$fb$Nf#bHKnG^8f2{Ksj@O*-d!|s>lK3TUbhO{j!vL z#9K-0xYUz2Zc$&_WMX~kPP6(_*6I4v{HoT{q;d778Kde;6RKEC$3$35Z)en(`V`fZ z_B&Bu`qIxvI_80mw2Q<>di|!2bi28Ybo83~(t``?Nvrm*D@``Ek_NA?D}DUbM%wUI zL+J{mhSC%MHqyYM^`sw9)sc3eSx2fjy^b^`+fqvUau~TCQ|7vf-EWRCY;HNPn<%oahR1-eOJIFI_`<1DcKiM|P-KdV)P_Xbhh3%w)XGsu0n%<(;S z_oW}NYjRvqy{1K9k23vd{bi2p`T5FYdJ5CM?zw1F6~^?J$ViXr&i8aNU8}L3ho}Qp z#Po`Goy#O&{=2Il-|UELYCgU>ozg}b_E0QVe4T4?p>hVWE)2&tH&Bxb8F-6~bOzk- z>lo0Afv2bgRWRT_XSGvB`qV5`mw~RRrsfRHqO_qI1JRF^Gf?;s0|R$ZlL{FqLPk0R ztsm+b(29YVr~_3nu;`Q_UXebQkJM$LC#tDA0}+%qG-Dw4sd5JJ4h#$wpe7YE@C6y^ z477csV?Zkg-l7gv!9b@4^$%2}Praw=G7x}jYRM{_BYHH3v6r~N#7+C*KIRo~=40+%sYEmHs`fudhOlN@i zR>y!=3>2ddtYi!{d8aM|!%$7l8CXtfLo)`FiWnH^!@$5>)TBZN4BsnepwkB(16naq zf;vzo0~M<{6{*X>2vk#Z23Aqp(2RkU&&nAn?8m@B5o%H)1I8Z}GtljmjsdM0(Elv& zz)Hq|3vyfa^?@;{rsfR9Q`*psfgRtJGr;f0z`z&Oq(TPFz9?p(*H;|_S}|bwtx_`J zf!tQjzywrNa|YH?+R%)FJwKE)kTR5kfnwC8LIy0pD`uc?v5o<)7%={!CIftSMH(CO zL~g5QU^1$yIRl$0ZD_{8ffD5m@cT0`P=cCN$bi*P#S9GmrDH%V2Fyy-WT0Y|cF1kj z3`|2cHD@51(uQUX9M&`aJpsuU!!R?>nn1gC+&cJR;8=5h2qMC9B?7K5C zU|f}f0S9EHGceLn$ADH0SXWb%fr@oL&>p$1nt}PKrsfRnr?jCN1L?-f83+zwV8E<8 z0|So8NM~TIk&Xea7_c?2lniu6ZmVV>64lh4fkTuwG-H4;RnCAtmw^F`8Vn4yKt?(P z6HRmsXvKhoX{BVKJ91k!14~g&%^46-+R%)FPqi5s7{I`Ql^FvAZpcVyAkMmuZp)Tte)iAEELwj%^GLhLnfk}yX1=!inVIsWI5TnIlT6DM;>_zFn=+4YnVZ?> z!Qjk^CtGF)*j>pG;?GVEx-0U_?cn4eAnffw`PT%0gS%1w&yMf)e=xYv-zw6K-K|+u zwqBDq?1IGh?Cz3s_>Wx5_dl*r_;op3E2C;_mrT-^TgdGV zW$ue?j80r)|Ht=5((k2gRa@TCehS|kNqS2b4|Gr3>{B-2zQl!2Yru0v<_xz5I$ggt z&}5~#Cu44$$({5w|z2!qwus(hFK|ovX?g(_t`!#{ScKQ`q-|2sL z0!{DFXW@3>_c5Qt0yd<@VwQzL-Rs}~3~)t_f*&mvhU6RW8> zD7zo>zy5!j`!1cVmFJA$9t=67t0hCu7=euRoUy%*E@xRtE0rumnVqn&G!@Y z{nrpr21F1WrgtQcU4J32x7D;$+q@FQyw_G+fu_b3vdlG=fSlqzt24N_+~HjW&`XVn0{vxfeW2%cD*-WV;pO(g zo@2Ng=w@?w0X-yXEznkVpg+6Q;k$+-ZEu41)(vO{mM>_$4(OoAr-ANyUkLQk>+m@* zi^mTD_VGQefTs6nLe8OeeiQI|e-rR>z6yAGUj@AV-vm6HVgYaT4*_rJPXX`oPXTZFF99#PM8JDd zD&W=D6Y?JD33=`Hg}jCOLSC-EkTyyNDv?LegIDmcg?c^%g;Yy1#}zB zv!K2~T`!j9WEZqn6?gJS> z5Bqf$XwP*rpsD`!vj59M<;)qujg{vNtS`fP#!UQ(&dbD<_sz7 zKovQoBF)(&eR+-CRy{uJ*jT=YXg)sM7cEJ)QHDjv=evw@RL(%KKLZ0I)TBZNWXMQo zz`UuB0j`ik;uFFyPI=fDARMkb$SjNN1o~TO9*hF;IXyPz3`s zc)~2xAYs}vxQl2<_og|<_oi;rVF#$1q-w8^$=$DVGFaY+6uGyF2XD$ zYhl)9Jz-X}7lN$M*9BR3F9@=_o)Kg#8`V!Yp{<~M6IT$Z}eQ-H49`#8{(Lw5k}b8;}yDMrO$-kh7~YoPNV^FV!J+g(8aV|g2B znNtqXxv^`2t{WN(G&|4=``%6UD_!e8EjDso2p$LaX7k^AxKMW^v2i)8rp&o}XEzaHUHFCXGZu3W~e%|3`9 z*%6DktT`I57upGr<21s?W&${=%69^t2wMfUa?%1JG1|uAx1v^3L_P?5sRz*!wZ$3@`$E)wv%zZ3A>-U@goZv?#6uLL~7D*?~&t$_FPgMiodvw)ZWRluwE zUBFvkEZ|xE67a5;3V4S4LSEl0Lf*Y9LY|X>khj1<$a`)e|@*E9>ym$ISoHsmg`SYtwu+F~&p^m4H4yTG41~M}`a+&*sepH(SimEF`G(xCJUJtvlO{PM z;pRAS-eA7r4VbqT@@y&4ju(@Frp6Sq6ulw9JhkSteZX=1if8`-{V>`MXorA8aGo+c zZ9mW+!Xlt~!UkY@|HYO-SM8Jw>JQmI1n4yV9N_Qw)9Zoe*$aWzKiL#$`u5A7O$YrG zy?qtXj)fb5UXnKxX!`m-mq7okq-OyCLyz_cn!fyo<_E$0rzG2f-+O7)V4$5Zg#t}o zU$xHiJ*)EOjC$Ra=M20TL(Z6hHeKO)MkF%Qb4FGdUCz)dXUyrUR?ets^Y(Tiw^d)G zC_pteAD^w!P2M${U#Dx(Q#k{~1O^5sqb3zHuoM~T3}koLF`yL#^Lwbt0KNySK%YIx zZPg4sL^U;Mz`SR98L;o8oB@0;0|V1glL{GFhKzIuF7(ndpcMm=z13u33J0q|p99Ek z)eJmEH8p3TcAxSx(4?Po27;$BFfbD}sgQw{$Vg}4a)6Ejtr%F^S4{@6y;ueM97b-d zX5b~NsW}66`<0ggr-8~DD4fQ?z#P=1LI&0#Bb|Y3{dEjz#lW%wYBEr<=IsfQ+o~CO zi)w1lfX%@2GT;)ZoPm@j3=GUiO)6v{5gF+W+#IB1Kr04T{!=L#IDy<&%|H>VsW}67 zf#qeuW2kZl?3vDqBT@ws(7z&rU?!J;heqaH@EnF&DJ_7eFHiKW z2cG-T?NmMRTnKvoyt#|O`g$(74xS4^uRrw23gCa>+=D=up4kWVr45IGUeSLC(44i= zK;J^cv{ovf3T z?GE`W~bKc&0 z)PXAI?OhbP;H~;u;t#HTWfFk7=GKdxJR+Pp)17bCoYP5&%tKzTvfG5W}Vf!@1=2`r+S< zz&ycvH-i}0Z-2lN_`kM=7tq({v<7cgD@x zK;Jy!12lbm7tfr`xc-5LY_R+<(R!fQ)`|hTa77@{=F?^{_8<3bEZAPEf6K@JWubEB zjEkYlb4KtChMe&hZMwpoVK_nFFZ7(zX`=l9x|~tYoKb>0P({wjDi!c#=-eQ`RPg&l zo+sp@|C6@sr2^iKQUUK$sepI3RKPn!&a*@RyI(5cJu4OP-j@n^UrGhMAEg4`uTlZ; zTd9Eewp74-R4U-zD;4kxN(H=!rGnq}$#s(FBL~Un?3D_5$>?v=m%|}{IjI~u!#Gs# zv3lnjCxdOkac!GNFId#`2x`nA%k%3E(x`S{lVot-G21W>X!`O~=PYhVUEY1}d~n`D zuW#S!30Pl{8xLrD{aq)%0{^*dYJ%;vXj&EM4N;|_eo1Zt(D(8V0Nr_ePoU}TUvBXd z_;=OO0UXy`U8w>zefis&9*pb1@uW6be(jIBK>w398|aC~{eeDLeF5-~-u{{8QJ{aQ zb}`zsD(}3#E7O$cjKWzAIm2O!94du5!v`7ZIU{1KE@x;pHq~mHyaQF_jEXjIZwPW* z_4sTes;T+-Y!;=BGRJR>&)YKySI$827zPF$r!z3%kBoE%7KG^-(24=C8EP@mBK9Nh z82b@#j6O|}ZWQ|ww~PIV*N^>(*F~RtNR$6}iv5VY$9}}UVn5>Eu^(}t*pE0X_9N~W z`w_=uKjJ*}A+=}hN8CB~BVH@^BmQIgNBsNpkGNj!N8Bv-BW{h_Z65m(Cw+O2+^#(1 zv)tM8j#lruDKKW>3F>hSHBXYIWukpss(sz=tw7W3Kd#ahtnbpM89;ZijsTjze9Pf> zVErR4f`Q)Dx&=7ysxzwq=<2J#0KN0#RiGV((Li5m_!X?*LbeTPcB2zO|FcjJEFZty z4`}-KY@TE!qW0gi&(Fa22X;0-NwtSfUIcW>kIwu~@j%ZNs78L^Em zBUNpHa6Sgkm4!V`f!;Z{A<);0ZGqljrzX%@LPMYrxBCFvqx$2#;4cehj_)hQvp7PQ zD9;&0I77}@i8fu~x%Mt(r00xhe0hKA#%IepKD%Y1yaQF7Yv)Dhh%ZFvh@+x&#BR|! z;>S@r;v-QxVrf*4_*PVoxNdZgct&)N_-J&F_*rz0xJFElcvMV|SQL{Zei4%+{uYxX zeixG?z88}tJ`!NeS?lC#y zZ816GM=?3#!k8TK{+Jwb*O(kJ=}Yg(znoN#@!5Tghz;WgDJMkdK_Zhqa z=uMZ#0Zol5WQj`&yj>hW2xX!#zZ`S|Qev?SR^ z85SAGXTz2&XTUy;fq}EANrem)AS0atyJb2Cv^v+mfjY2~F@PlTFE)WAI>@V*^^`4 z)q?ZL;1&M7{~0`twGQucq89!mAu{tA$D8o%o<=O$QeEOQ0+YN;T1*7n9r8apYcC#f z&7Je+EuZ7s}?#a`@vkcu%3xfX6z zo-?qS4989SYvr4+FlSgJBRyvfPLwaCJAW?c`Lo43c?YV<87U_t#PLOEG6Q6%eaD(k z$QVA-C9~hbeVIeYiTp~9w)kf~ieNwQkjZZL^Co-Kg(fvhKXYpH-r^{m_2M8xD8Aq?7LRlmhg5az=~OV!o*E;_ z^3TC`U|#>5gvCJ9>%XgG4c6EF%mAQU4O{^<@0t_P!`e&0JpW+MA)x8)?OOd1JbSTo z)ixkUiO)0w+PhZ?@PAm7OF&=Tw+!e3JL&>GJ31Y-cXzTmuphGu2HN@lG@vg&@C2H^ zJ+{-ADZTfNEVXXk2FuTA)f{Z!jzMtyZ%g2PAo%GuV0n7`!`(kC?eA-7&#Jt0JAS4p z&lxFG8FGf#CWf5R6B+3_V^xwaXJ~bv(Q&g{Ipe{`8Tix@pV|7u9oXCLgW2p(iEP1& zi)^E*#r`D)7c>0tmWbmF#u2CUR}j(X_7a{>62kD^b)r#|b3}ljh^RGsFEQ_*wZ#0w zg+x#BbfV#;sl=U|bBK9;=MZUa2M`}`>l06oR1v#5wqghNn2sNB_!$4x+=QbaYr(mH zz?8FgnjU9s=0p6^`V;u0jWh61$nCWlM!Qbu@S8Ubi^edl`tXYJ@lQc}sXm6)>^3@_ z6BahD{qV5i9YaEghm9dUC5t_J|HRBzIO8$y_GNk?p4Xk|3N%@$$G9)fTmbRQ;EE+! zf99gTK-Y+|0NQQ;Ik3FLKmzCuu{(jLw|`nV3HbN9-ejPq2WA58*fIfVNv)nh&wtWe zY5XM1E(iE*=f_nF!SXAaMTs9Vg}aKe=C)cR5&gD9;UrBN=i-z*dIb zFaa6qxnch{U2f1SH;mY>R&J8>jMw8D>{HHwJ<~j< z0XrBNn2d~c1`h4iF`yL#V|J;r4C+R`0+Ati0prS)12fXH`weWUc;^ z#JZWXl-0F)49l%bA}dH5$J&28f)!BGn#Hkd#4?}VhUNCDJ1fg?I*TJ(#o9G1krf<~ zz;fEVnsxQ#8dk`aI9AcP<*fX-Ls&+Aa(qmjulk(-RfDzRX-$?xm?>*T2V>Tdho-DU z`Sn;sPB&#Oa&l!^?v}e_ z2-xZC6SRW!M(K9;yo2wOkK0b$RoBfA{+`aryt(@evvC%X5rW=6!R$4=c|N#C(S1qiJZf6^^$q zLq>XTxUe5lr-xzHx{VI+H-6ISkS4#o6T|HPBry!b_RP;3On&$Nwe&do{Vk$vZPJ`l zY#K^_cMiXLp8S6QHNlbGru8!iMUvn6XPW}@`}y?y-c>LRi!0i;mi%rvb<79yyYOA} z9tPy8+T=HT$ZwA~hYd(4Z%qsfCcpVF+oqG>PUC;nHN-IN%CpSrf{4A?)TkDevcbg_Y3)5GyhH>vWHF#-g1!q4!)yZ zZiqY}??4sft#(yhxq*hR+)qZX+yN%8+&8AK+!1E3+>lzXTv07o?x9+)+@CdFxueZp zxns>-xl>GBxgTq|a%-Bna+6J5xt&a1x#LV-xjv?@++QZH+;|gLZd(&qZbK7S?t>bx z+^scSxgj-NxxO`Axf_jKxqA&=xpoGw+z@?N?i@W=ZW}#U?kGK1?oT~eF6oQsLAl$N zXS{XgA-Tuujkn%>bP>e=_cH`Qj~+1@=r)HwgZSyWsR^*ZxgrC0vQW>j>t|I1@pH}) zBM>i}zO)8<-LjTIdp4;JG=2Sosf|GU^)Idlx-{GiX!`P>qqBj3b^0{`@qK8vH_&bG z<%9ai#%%?9&DiHae|~!x=ylVNZR8H1V>{0W`cAwp(1&-g2J545 zZ$z#)=#PlZ%Rp1>^A7)Ip-jH06xZlNM9Omp&U8+_3vIf>oRNu)^qf(8MBZPzoKene zbfTm34pfmdX00*toouq5HRMNAR^4Vdd=50*?~_FA^l7=(z^B2uSnr@`ExbS5E%pwq zUf*Zj)!sg55)b=$d!6!WfAFY}amx)pyS~oznNKYA8L6M>BXr;Cvp)Q|PjyMQ&ymtg zJ}n(@`&h4iX(q0c@^zE9BlrmX4P6IqM)y<**Uw)LGn-`96x!%n`DuDGw?D`($% zYfOAc3FK~9j`NAsV`wi`>wMzkY4|M1pZ$7(cw-|?1o7rv!|5Qt4f+-a?AfdL06R6_ zkj3+w3>?1>ybhlsb8j09#H&+P-huk`_1$-b&tMsMTf*3W^(NoJeQc%=7K8Phl*|G8 zd&)wfPrY#h`p`^$pjQ<(0-Ek`t!y{O|-@S9cxSNpYQ@zuc zli#_h16AaPiZH8p2oETxSyxrod)WFf~<{*2-LUzsgd&Ok~80|Vz# zlL{Glh>Uaw8fWSl(29XOr~@k*0|sJs8SqCnHD_QVr47v(IB-Te1BK%l7?7bR6*BM? z8R-l(ljs=Gih%;ufhrhSXwhH#t%{BGbybFzDD@K_;fXtP1GxmuzmwMn#O>-kX0G=pFQAQF6M-fR zb!@f$iF;r!WxuNz!Q2?zyYPO6dz|W7)a8qgJp}dX>yMsN0_N`c>x}^ZEI4NXG=2HL zk}Y5^&yHT7fp#vf1=^1aI|S@q=fSx_^ADZ?_D1IWfOd9T40Pr)7qI?$>!yJ1UvM@B zXu~`4K%a~l33T%t#bAB(?cLI6C+LsldhmV;YW?Ihe_1FKt1HFW>fsB@a|SkoA!q2H zm2bMjoMDZO^qes`TfUI){JEUx&lWlI4pfmdIxlJ@8EfS&d0wrzBw*koN%tM=C3DB8 zNX%X>mlQ4;Em4t-FQLdUo9bS zZ!?t~pJ*$=emG0gYq&`|E@~u6M{ch!Gnci|Z=Nu0aRr&n`sKX5gBvOwS0%k9OHDKm z6*$!m#FDUitAXzFj|ga1^+2G>LXG9CJobawGoWP>(DeF^Ziqmvq}yGBi$LuE=C}c@ zZ|j@IK+~7skpCU5|9b}y;Q!Emp+GM`+X86%`nsIi!#J;XZMr@1H_W;<@Q<5T735y} z`YqFj0)HbGIRSsxNm7A*!SItn7bZLh+HUP5pc_8)2AbOb_6zbohup`@4ttYoVH6Hb9&m*6hGtcLA;^A{cUyLaEg-)Ww+|JWx({GWeY?Z0f&SO3s& zL)fA|E7?h}PO|ah$LzUFO4-HN4e(oYtKds}l(3Tqd}Kc*eL-$lj_Ye?SJ7UoFb~l0 zXApK<4dTM!%ke<>sXh(p9)}Ep_Bit%oCA=BnimEeJf~8owU+SEJi1pby*{0Q9IC~ME zK)0C42lai^je-C7@~wc*pJEDhmAMW;FXrn5Jz`@FSU-LHCr0lB_v=}7stvZc)lVMK z)aAu!Px?>qXCQNorg`DdGN4?^Q=S_zrt51CxeU3%2N~(PA>x`YH)u7c)GAM{+)&Z3 zuMI(NtG>37h-zv+u9`(@qs;Lc_Jdxq}$niP@1OCWJXJEk%9Rpf1;B`|i z20W?~KI_|6_i>upIj!cQo@utbZPHTetUPgR-ezyC??9h$v!yiV!A z`0-dvBER{#)vn2U923H_X?>6tvb2it?)GfoUR_4|rrS>U?PV~-clPNyzB^kj@ZC8! z(szMV4`0WxnXJ>6ReVj(dHUAw^Mv*G4941aRqE{*|JLV4^)0Nt;CR+QRSBQR$nDB; zeB^cq?WJlRAK8~w2gd`WF%!o5JBbZjg84!F#DjtUXK^6Vj?u9Gk68)8j%^eJP1J|= zb6aLH&gXH9&IQZgJm?4J-;6&M1@smCG|=N$!1=kCrgDM(#<3P){dGc~0Dnx6!*>h! z-DU;qQ;#>uGPlGUygQd}=Y2>9?bGe`de#8Tr>(vKem{DBi%j@#VYIb~9uD#cLV83VP2i6x-7v6U^_01JfKfO+Spy}<`fAI|D0rU5r!1m*IKY<>x;~eO( zg+z03p53j;3h47^`Jg_%{euO0p#S!_cnCDT{`!9Tz#ppp5!!S9)$!B+A9WINPeix0b1pN0gu$m0e7DJ`p&%{!>a9Wn|^9b!}K-NAD&v# zx9zEj2}j!XZyU?HVEL1^_MoG$`zqYG|CWBfLqfXy4)3*-HRj7>mSq#Z@7|YZeU}Ss z`JG$K^P4Xm>KFQLtl!l7X5MmhsGU+Eao>bPm;>q^PM3FNkF28vKk%^9$JQ(gu<-YaK- z*w4T~Bx+J20~?W%&cNNbItH|2V9mQq$v`@CTQvh;P)*GlX#Bps40wKFV1Q{1bt!66 zAp={Gk|_4Vj!_dEe6h-J|V7OH$XOeYq{$gUgbWi=m8<48X*%fA4hEm~3>!8nJq_WfI6eO1jD zf%)^dU#iU~~`MZyoITqJs4k|gusm}GV^42MH9nJoy@*Ke5$8a393~jQ)9Iy=; z={exhS9w3_&WFo+KD_apyaQF751$DSV!Mv1Dc;$q7GW+a5YJzZ5nfBW5q;bE5#xW< zCytsq;Jk4He9L?(zO=OrujY6ecQ)OQr`qnrTkpDopSyr@KG@oF`hNE3OyA#&^TKuj z$7_0j&bJZ$Im;dUaT@Lm;509G=a^jBh)aXtvB%%N=)W_vA(7N;4$<(#4YA&=x_Il3 z%W&Iqws_|`L2UfH-0c-*?nAKu%@Kz2E5LmS6Mo3OG*vjag7olrK@3@L_66~ZEY!K9 z*{2VHxu$fx^t3ma3n^{d5yY4ELGOW%JO$%*_@~Zb`O%Rr80S(N87G7K`}N_osmIUt z2kqt9j0gI;48H&7>G6YLee~_?d_n}q5vTTT4CZR4WmX0Kv#Ibq=uh`I@P0df!zIA} z>fJ)1T?Rl+_ose;7{B}K3&Hl$>wkUN6Re+Vul@7y{#+*BQ+H`HPOEb*OjR9Kc18IB z?Wcc>ufEyu1Y?U~m>FmC_({XV#)nSk&6qZGbayvxR3K6yVol$FJ{yU+~pZEk|ICU~$HTpc3bl-!P}b7~tApL?Dd!^$U$G9MFe13nQ; zf9gq0Q;j7mSQ0YIk?od>jMn_fV-9DWz9?@S06=v$p# zfzE0@0qC&@W&us#o(F}-jQw$OGkm8Uefcd*F>uVk?!*Vi_01eP2yEYI&uKtY*YB<` z-;@5;`E-in`oHnNGIJ^Ls>*W%HkKhbtU;TtFgNT&MtW{|RRvL}d!ASMp69jAK;D5W zazjPCE_M;Qt$IAvu&R6mH6IUcjh58>x>$&@at8RD7#K)IO)6v{6&dLayff4>pw)Qj zu4-yAaJe8`v@$ze^y+T5=o(SI?X>wbyo|DYh{j&i^GZs{7Wp1)-8MKq&1*(}w&+2A zwrF@kwrJ}8Y|)GR*`nL`vqhr&*`m?+vqdEZ*&>7c*`oGH_rISlnsGl{bm@M!D6KGC zRQ+MLsLO+FQQpvOQPI6@k)SYJ)QOud;-_Sb9L%#t0r#^-q%SVj2`g^R~0DtK9Q!IxA`?CJ^fPd|7 zEdiRo{ID^Gt*GtM?H(_k1j_6|#((#hGUuAqUFOf&A^Ds(O^*>&onMmAz6a%-IYX>G z#1Jc`Xmb?C$~(wNkCiqi@;=ZVFO>6mAs2O^isOZwYddrPe8S>8bJz);Il*f?bCxG` z=GcGzjgl9E$=tE&OghNfB*UX`QJEG`R_ijOss5x_QomyL9C>|>o34u z?$srQv6J+OEQFHSc^5Z7u8eiEw0-py%(o;9wf?Iq&S1VF-Hv-1F~*`oyQVFv>%&`( z1v=&OY@qqF5TLQ=-GB~0;01NHW?qZLf0sT$ z)BB^i{@!EM`gD72mu*Uye}#T&a{nt6d#Sr1J>Hfl2GiH^NB@bpER zG_KdPrhG?M7>masBRv-HuBD5`S{=6zvygY7idbwO94=bVjS$W29WI(Da`Ev!{L*_s zM`Q1NPr`fnvDu<}J-hqtxMkwAEFfGY2nZK#>lH4F?h!89+AUmkxNEp*P}gu#XqRwN zhtA<5PN#5DlaApcuMXj&y!PRu>FvWsecFeMI32=8o4SOHtow$G8Z4M6vQG#X1%-r* zzCVr@?LHnUN;W;?p{psVMC-I&Wjr`mBnx%@{92np{3AT30FAF`0W`K~BZ!au1&L6v z{RT9aqYszQDuwn@pMmDvZU!1#;sy1^FW^|4zCGBfEg(L72z!BenzFvX(qmh)v`Y%| zr1lSe{Zpb`TPw4#sw4m9$$hd+e5US}e`4vM??18hkN&s5|E#x6j83ViJVq0{7><{V z&?YL3(Z*Kt?Wf1+Zgu7V*BvjH^LSamp1cE9#OR7PzUG45Rz1Ep2G!Jjd@Y{RMj7Tv zj>tlCQ07_TJ8YCQP?*fXz!%h{LI%w0D`udVwT=O;j#CV6DkTFR$ZgdOOh7d?XJ8$r z4b2$XW2>A2`y>Viicyma8L()en1Q|xbqr|5fU#|*WWW=-t(t+!sHWx&Y@)QG83P7Q zlrxaBg@J(*)TBZNtn3ssFwkDdfL08cHByU#_#$_&Q*m`qt}k-;;wKNoZw<7<4~Dj4 zCqMtp_Bz?4-Fenxymh`Oe*d1k*R*@?UR7SYdu{mO?ltkdyH}%9cdwEv9$vO49$ri9 zczB((_3-N8WP|^M$+D_^jkaqHmyBFz85^{S+ne$a^bdWOcKRjO@(^%e#2Nj;9lAe$y<zgT$t%b`OVryU%`3|ZuwoXGv`gx|H zsV=r^6#l$5{9kc#R?Z}UH3O4xkt+(BG<1^pAf3rh&2>y_#bgQUK$T-Z z7}jyE-ft#%#jMJ3IK$4U^E@bB>e?Wq?ck@!kFMFDfn|;T*O(mO+3SQ&yzDoV;{*dw zJU7^R{KV~z|HmZWLjJp#pUFFD4dr9<$nHUm%thZ$VYG*`Ybj;Y&iU`Q{X0y0xG856 z+rYqNByvR|lN*td&g5Me9g|uyxyDuAfhw8gvn$e=Y&vpV^?CRgR8#Zw@WyWPt|8kf z!^WTU35Daao-LI#K&)Y4U@2-+Ap={Gk3=AZqCKWP}ii~sy-m!EHXvM%T-%82A738*R28{j6%RpPS zq~;8C#+5UGF)^?aHK~w+qsT~S;G@5e0j(I=$F7tNnulLI!e?ksyS}||~b)X6c{vUha0o}yW zg)4do(>sV>48|0RjwPEGszYx9nO==UhY&EGP)!XjpkM-~n1}Q6r|jit;P^G-~|TxG3-3 zW21HytrHb`WJ}b8^mn7aZu2^-x%yR9;Jb%Wiv~T33M%;~>YH>AqY@0)qo%)n5cTh~ z=TQ%R-bS^V{Wxk$p}SG_1Mfw>ANL@t#;^CH>Q8tO)xqaU)Zm40qW&Hk9(Ab1=BPZ) zuSB&U@Gz=)uNP5A-oJ?|miA55iBT`2HvRJ=%F>t6Agk_Q{{C^%y}*O){y|gWcAFQh z4VK$?S}ogmP}hH$kKTW~alQk32YuLsS}3-BZ24jRL!)iujQ2C4nBs?3>WJE>3B`3&UtRV?^&j;)hho~lkt?>K z{`G0`7{%>s)I;&?X(tuy8#+3JVm`m#ykEBe{d#{s$L-$Xa%TTHXV(#s))uw!Z2v7+ zkKMXg$;UT$%%}Kmr}i0k3lH2*`9jGN9&~$biOY zwg(ibygi^_(zbvMtF{N^?i(4<;9+FIAD1EnVkSohWLXgzF#BC(K<|q?0-omH8F2FA zjsWwb9Rb^l?Fcx!C^F#tzL5ciheQS}9UB>NaA;(}#<7tBt(HXwENHzW;KiAp0Zk9= z3^-V3XTYcPI|Ay@-w`lLzat>yo5+9}M*Y|~=k95?WvIO5H(OBu z*G`_M*xzirZ%}{2_gq17siJdH+_KRu6esSOhvKQj`=glVr+t=C)L+~3DFZ*B_jdC_ z$^W1#mF0wz8?RIP|H+vPXx(;m!Fxj}tNVQ#n!ht19npP-P=4Ss_?bAwBB!)r*O zgSjE^#%wXa7c3YvY)9D`Uqjm%z4z#tHnk_mgoai#b!fP|S#{m16!m zT`s0Ux!f^jXC94SK4eSujjl(dXYIKaeM+4rW|4P=m~`o}0&#^l+U zEoR-2&mPGNE6Fq-9Mo?(GKlTkGyltfKN;%G++aEo6JvV~o;0HiT5G!Z?WHKLo~{;( zZO0PJ=ldng&^ltdp3gur#jjOGp!R9G*J(4_tDwX5P*mUMX}wWQ`NvwGM)jLd5zaECmX?+h`j#2!MROUb$)ZgrF^P>Ls9(NbTwEk3k4xs+#oLvv~Z``04sQjlb zPf&bo@@o`#eRUkgw7tOP*JO`pO~~i--u>o=$l>yHgK@9I+>rWP>tVBVLm?=nb3@l* z;5yHKb1BOr?1-#;A%Nax=<4!3E;&WDz3B+G|ge`WV5c@HE; zDR?0L2n7!mgF@77HAm@x1<}c|-2iSe8};*FX^W#blb59CIixW4{Wz-!#NL_ zCn|X0qJjrAVMa*|g!D2M$k` z_kiJqf(P=2DtN#L3aJN%PV#ubB@dLH?3Nw~gl;?bz$`#I?}5EGZ8+zF~Chx z_0;i!mm9=CJ5f1)?V~4VbKS9Vlhr$PX_M;c2Tz-=ui3(+U!b|HzuWtT{z{Wn{(rVg z>whdEo&Tq}^!|&Q=J202J)eJ@vpM}2P0sG0lqS7@o~o(+i)4GPUsC3pzDC3WeXF*s z^s%`{=w~eP)$fZ-rB8%zFSpynp%mP-oa0~*hyUMyN2@d2n&|rMa}}1}T0Xw;cj)39 zJD}$#eAdiB@%HMSP;6KegyQLqx1hMfz{x0%y89K1d!GFT#g>ol-|p62#?KwZx6Kn^ zTV5f30E(ymSs%qu7gj^@?YU)9+-Ory6fY=p2R-MICf7<77p_k3M9)d&nwks6 zWvWG>xX#+ah)<9A#i%6o+{3Z_RZ)JvJz5LJF_%iCxO}NaDDE458pX5nyg>1pjjBVo z`Ao0=7nQH?dI80@{e22&mc4QV5DMv> zv3ag%&TwhY=s7Q0b4I_03+lhmmZw31to0hy{>i&R-SS^GC^sWtgF$J3sQ+^L#@K0b z-UoU$dU9aY@IMahy_V%bfjlP~jH=bcuh^g!eq)yI_q*}vPrtmGU;5?jkV&_CUJ+g4 zsTFmNYG`#`b~Vv`nDVvm&C7u_nhU*ELd{94UE+Thr%H?fH$C+}ah zBJcirJ?F-5Ql+g|vf+aIS?623{pCN4-FShur^$O3dv4!LihGkZ+xs@oc5E?!Qk!k# z)%Al=oZ;I)P;5KaSU%@h%|_3B%{u0f#@0JohoN}zXtCVz=2iU-#eOY+ zNAZIxsZjkbF26>sdsCyKSXm!o*g2Oku-KU^He zJEpcr@$+js#r&S_Pz~vstExAO-?iC@V%z?|fHTK(j@!)z^jU0a52*jeJYY8mq+KRI z2dMWb%mE!1TFt^fQxOb>bPm|Q$V&1&Q<1`FD*7(ACeXn%6|3h=iPJB+6K9AnWDftX znpu^XJTo6Uk}H1w)PnJT=}X5qzg056QP$G&`BD{%fBIAA`0Q;| z@z--cHy?|Yo zdETwf@jfr(w5{{Uohg0kK#$9h4+OM+b0E*!>~Y`CnG)x<#M4=Y?)>kU#4w@r2H!q65AIy5& z0mc3$>Y+HK&Uw_|TV1c9xZ%NK2%la%9F^1ld~;v`nzM%|>_GAKN%C{_8#r^4H3v8v z`~P3g5q5Jx#+CAOfbKViIiSljt6A7NU>FqAIbhdvE6Fnlq;L)xxWbx12Xnxe_II?! z&~3-}Pfr2T`TM6IY}&B94zqa9@?oE=duf#SfH7Xd1Kn3DcwiJ1QV;B1}h9BqMrW^1EWv$dU@|DbJ}b%xe|?G$aXnv=9c&ra2Dc{5u(<-`JQ z>vdt;pr6NUr)8R~{psuJ+MDx6Y1d5}uKj(_Xzl8z!?giD2W!)R3etZ1s;##7p=R1V ztF7IhYj^)t4JT*l7jXZyfzjH_Z0tQ#OAr71z&vs`e!a%_d#&YT8^4WPv&S~3IDNsTm(pe=ar;M=@aa{DBNPp6GV zacs?PD7NkIJvdV==d;~-YkNvt#@_#z*EG`gl;xb1{w@8syWV-bUVf}Mm=wnPW&ZcU(r>z#hNEBpVy`01a!viNbP{D9^A6D-MdM+n8L=p?Ubqk%FNzjjkKzmE;}O0_gNrC0Ic*4vZO0qS=ec)!v?fBo z0tHcgYTYH&Ua3>1QS4iG#V*_No7R74%yl$h)W1CzjkkrWe?alec|Fkl^q^-bikpS6 zKrwA^QK5sVf8B=sjpDD*+(GeMpFdGNRP_PHehZhNn6~$au@gGJyDB|8p3<4;p?Fc* z?kKjc?{7GBT>8$-joH%JE%)15j@=wkX{-DkppH|R16pmcnuVPM`a&U{12#lhNuD_% zg>yjnjn)J@m;*|#9T1iKbxaQq72U^EFSVP1T(J zc7&$T>fsu%P1bI^&+`%kw^)0eyyqoOUg?F#lHnEiqxjEmOVHSHd*)UY_Zp-|@%A5& zBfjleWBE+`7@;_}^|@OHjkV#==cD*!rKKqDI=2>z`~Or{v3;MeSAVNZ?MkT! zYMJCc5V}vn1GAt>)&pyykb2YFhsR!D{%6mY! zU%>!FZ(;ND)32VC;N>ZoLSU`Rr@_)=f>jW2NeyZ9W>C&j<{v^{=z+35H! z{o~`8r#}_HWYg*R!pFD8m)v$SzJVd@!RVM;2Mg5hc<_4qJ_oCg?Q?L<^3DghCIuWk zbEC|`Ef?R$ce=hHe((Og@soCZqq|zZyY5-%$oGYoxMjYcdyma|J&VL)g4rr8vW}Tovvs| zpo8zpsUH8Kf3W+k-mlm1`p(A7`uFoL=qshVuRn0=iT>*OME!W5czp?*(A1 zdg;AS=g>b}l1o3YdjWluQpNPY`Kk4Lrk2#-I$uWr*Mh40hB_bp^((3MA4<;F6&!ln zZ-!q6-N~xe^qJ}v)K7TzSobcmr@r=^3Ho$7hU$AKP1aX0yik9t+y?!}+=umr9{-^? z#94L!vY(}x6>sfj@}8yWnJZLrOx0c2$c`D7&$|Mf(HOO6Ol~xWRn1iw#Z?|;MseFU z85N(AKi*e`#<(m^E2Fq^pGW9<&cuOJQ2g_l4JZyf5`_4+W1!_Tq|*|VkKIiUqvLBI zi(|^a)vc;xzJjxNNA*#D?{}XR`J3A|LFK1U&p>ff?-&#(_S%kO+xDs-{Cqy!jrm{f zvlMxb%a8Tu!wTO6mcrq(WBoQLq+|WlL!PnTrSAdj4_g!HV5~n7(bdm1vxQ%~@jiZS z{Qdm4EI-}g#Hl9@Uax=J;D9cyL7VZK`m;ke?Em&^jkrlCSH>-Vm=Jd>?or%F{j0cN z3%-ck_51y}GDXhE70j_S?%swWaicGNgX`l`3>caqScNQGB$?OjI9jzkWhAy7q1n+#ikW zhnnGc$G*AegT{H^=AkIwSiKvHv;5|VV%mP`?msK`uSefv=y)jqkJeKa`HlDBdFy(r zo9OtSpDKy^S9x#_6x+6!;rQos*KS<5J$)_b#{cbS=5;_ie}6N}No#7H z|2tROGx8oVoL2BaFf_?}U^*014;=f=;{lhhYbN}jEDtm(-7Hphy;JOlMM1H<4bx*+ zJP3`Qcq2S^#n!{Ioktvw?YVSyY)I{fvHM$%jj6V?O!T+Kv)9oZzp3-p?3#7z6)#jL z=y97m=WhI1r&OkSb%sP0s&mDsLUiuy!(uW%`xx`uCxA5huUMSAE?zWsaEu-NvUI=m6{bZZe8Zsu+q(9OP{iK`^)|g+bY4@%jA8B zZF^`tTHpTIV*mZN*DkhmX6wz#q;tO;~*y)z=c zxBtjg-u_wMSM(qJrlS9a*A@MzzpdzBUghmyE1S1}^&;N>xhs16f2`*1f2xMJ|G-M# z{!yj8{pYK_{o{*z`!`X0`=2iD?SHd^xBvJm-u~V-y#0^V^Y+gg;O$?cg}48Yt-Sry z*Y);)me1S&n3uQz`|RHS&$4>^x6SJ9-z~GZzbT`)f9DL|{-P&DcX#LTLhYR|g4>_lv=Az<)}jfDbJp&G z;;KP?Q9QM1Wfarn*|H@Ny{kUwKxPz2zj}g>XHJ2>C=MxH9K}066hd*Q3!PB>ZBz{u z_Z*rU#fPgtMf0F-f4wh$_JZAaVz+n}^yO9gIV16U|S2A*Lbl=@YV$&6?8au3D#@Ifk?#2YYD;c|T-?G?`i=M@%nU{Zm(do_i z7auuu|NV3u_g9)4z2A6s^ZuRhbM4PwFzbGG!dLrqRX7)WET~)TAamo`mx}^pc2s*< zHzs6#y@*A{>Yqu|u715M)^1O;yYE@-KR>BdWB!NtJsV%O_UJEfh!A`H14+s2CK}{@uM=8r^ffd>+py6klUbCWLR< z@dY}*+}RhSe5e0zH;UhfJV)`E@1LRAw!M3BCRol>yYbxiRJA-)oZM@8XK>Q@2~fhBbi>#_lie{^nHZw&VMo z2LS2({ms8^+OYGTH14v`@qfH2?*a2Y1rM}JRPaDwD5M_P@R!E}F5Tbke$y>IFcZ4% z*aHUv>AVM?+O*-E2UK_EJz#vI;DI)`6g)5x3aJM+-}ZRGB@gtx-^LW4|5A?mCEDw}9Hcyvb_gL33psIeJ zPY=EN(vSK^aX;#tyzH%iTH>N^)ac>nN5Qk=zZ-Ep{{HPZ@!BD$<5QiQAOBbL=JB~z z>EhSik23d4`oUZz>T9#HRl{)kM!_PQEe&#m}nmMsf8%YfxOfeoGY7 z_MTV7`v$CS-xxhx@B7>f#Z}LrM*W{#Gz7&9^5;V_ZSU5ucj$SX9N%S7eD3ncyWXh2 z`3-uZ=Wl5J(;DD){=*99RXqL&qgJ8%jcM?^3e)|rqw-?Ke?jr9WBpLv`gJxG&ot&l zv2B0LJ^JhgyRq6W?tQAC%g-6c8wzvAEI4%b`}0~Tq;tlt$JTs#zCWk%`}2w?)&x5E z{@kEht@yltP2#s)=@6gjH6(s{qki$DYlg;;iJ2OIpnq`us3Ds8`f-1lC;inhF8o<> z-N{^4qX>iQm1Vb^T71yyiFY?kHW>J12F|x2M-%>Ex>~_i?CxUh&2HHrco8 z=e^yaFY&mPe)5R!x=Kf9>guTq`_-1|NHoFMg841VO zbknPgSJ$_@Yu4ROc<(o0d!e|Hc7Mfn%yua5$dcH&@omj<*=yuA=bhifterR6>@#4W zx$%-W=5`&5#{d4Zc>KDLY2pLs<}`m*>7lvPDXO5C&{QgOVDX&i~hw+Ay3rHV2w|ChnqCj!%c&$hMRUx3^z@w5^hRgHQZFbYPhL? z)o@cn)o|0QYT>5M+HlkImEoqX+HliiZMbQ3@m70-XLQ>8r20D30ByMGn;PM!M%BYj zCu@Y8>S)4E#WmrkpEcp8=Gt)6(8}Sa?<<9yEPe6)WbO8s`<`PnRgV9@FeL9i$Fv9b z_}V_hGr02&6h{nNfZ~k(=cCy85yzH~ZGGKhM(wbTD^?n@(>A8~?T_LjZOeNv*P=K= z{S%67F7!h6AD)l{)jxleR&g!qP_Mb@cxZpt7aJI5+aFqsLvgYAawr~OHbQai@=ooC_7MoJvJc^tjyFJY`45v&Z0_3+#dPefI4vU@ zn`wDi^LuFQO#5rkI=078@nc_(Msq=E^|EMgiCkA39lvqsW!yjC?kK;s{?K}rkUqNg zgHUX2xCq7O%v(_$>c1Yvw)MY&GsmTU0Hk?<8@r`4%Wp^JwpjXayZf7SvdYf^hQ|tX zK!*%gv#@hOFci``V0%U@$ukF}a1Q93$(le1bHFo$k9I+jkG5S;A8k-iAMM~iKHAHD ze6%zBT7NF|^wD08u@4|HS*D}Y~rJ>*1|_yt&NYi=ig|YZ193DFs zzlTCP7T3-08H-)I$MPH!=wK}V((W@B$zj#q@qNY)fOP&o;}n}V?ED7fDSw}FaV`Z9 zJXP?(U1*Z^z$YlA9`MWQ@qkMncnt}3GY_b9CD#L80O`C3X4tghoClWWmG^-9gMtSh zLX)fqQs=fFGxb0~9*+lH^1ypYpqqK1bl&88pgSO)_rPqMHk|W7yTbAw2u)J(z!PYa z^+5W3ya$@(_jtf14}5|II`F_xe@B=W-it6*zZGFRb3MXT<5Gla!^H^G%c~KltoI^J zu`eS`7t(ArEls`Abo73u=#PifM*ZYQeyDNU-4rf)t*m>#}~Fg_seX4P<<$GMkjPHw1PODdc3>)-WC|MPFXg14TF z|4Ys%Z859vRqgI6T23F!r*ty!DIS2DED5k*yVz2TfvK*kB43I@Prc91Um5Wf|xryu6bYFvG31ccT604Y==egtqYIun0D~w zj^Gu?|BIzh;^xOSGndJ^M~ZDfvwH?<7XMyc_;o;qGxhHI->TOk%O&xDa-U*3AM7~q z`}Yo0%$*&Vpxb&o4`%~J1o(Y9JD$rsCS6p#HoDs% zKL=y^*p3Nrvt*Z@%PpU;#;#Tz6MkFWTyaczdoRE2m{6sZ^~fzbwHp&`(`p$9q&eJn zZqJj64{pQ1W2oqsO2VFh^)s2?eKU@0`odSDwAQV%>W=kb6`9#~&KnI2GWP<=r^3D9lF z9>`oFr5>mWOy@n&w356B5|y4KTmen89@q(m)B`UndOYBg2R3;p%LAKUq|<*%Kj)#_ zjy;gQQc68g7nsg_phXpV4;bGn%mb^TN!9}9(%SHwph?yP2ceL9AWcn=2VC+%G$hc$z2h%w4zTp)Hgwyu2a457 zsRx<^(|Hect1IsT^Cg9OU>h{adf+G&QV(RPvJEv?brh)>!#EL zt%2#h2Mi75Jz%`8;DMdcBH}gOeblb59D(X|}fsVj*-UIy`%6q{0 zLSY_=h9+4LoP|Q_f!qNe54hxk6OcfM9{95E3%!JHJNAH2!<2fUD=?k+z~CnG9xz{1 z@W26RlJ&p^D5M_9-`L{;mppI^66nAKVeRH=zoehH&~3*asMaK<9_Rr~=RFY8Ox^>A zTM8aH2u-pcxB`XL1BJixc)%qOoP`8B@PJBX)PG4oAEDchJ)mutQV;Y3rt=;c(Nf+6 z#+<#!u1uv;wY5C0t4d{AW$(WQ_S#y)qtGPlf$LC6Jy5)b#{({T-~uGjfd^{T8Lj=2 zepD^3x;yqj9Y8w&%v?X4Hte1=wLDL1`LJtAjtP|aKxlRa51fD|Sr6QVLh6B1tvw!a z$pcp)fet+IrTu*%ZD4Xe&;XFmdti`F8_s!Pd|P=Bs4^&c;1o2;df+Y;QV*1Chva&oF(94yz;K&3oby1tZt@<`z!NB>9;njE;{lgEa2FEjzytY4 z9@Dh1a#(Zw#sSS?^GQwHO}}dPPPnS+nB%raH}DTliGjN{p}$Yn?7drFvokck#y2vn z#@r^4=Fc=mG*w!c&{Xj%p()j+xTf>0qM9@#N^10l3TcwE-mJ0trvWwcT-#FJr_8PD z>SBd7#}}s6tUCRBjYG@7(lia}qv=$+qsD*5V9l>nrfPm)woH?~{2q<($YYwBovpfm zx$A1L?P~2|^48V9Bt-0`5jTF}=owIqT$@YD{`(^U)wT*W@sgB}yXI>$^>QB!)q`TVeC*86->vTc!TXd~k z?a}4*j?oRg6Rk^7?bB8L*QARKj@5ng*`>RYYMSn=jahWh?gaYHdq2~!dG%eo@Zu|V z;SYxCc2>HlTQx4VzTu%)x~7fN>x0YX)fb4XpugnZSYPFOM}4;*pM8N0?V8)hO`6h! zO&ZPQ-I}D6M>N$A{-!C|EKXB0*Uy^N5rZ_QQ+YJu-#n@@q3fF(1;?b;wE8-erc|cv zns*7=G*g;o)o8b7)@ZZm&}e?jp!urF(Heu_Hm|WZcxm;cCyrE)FlNwvmvp~IHpA{3 z_p(*f_-1Ra`KN9J&Dx1yYtED$q#0tKp_v@_v!>Jdof_XhpMCLVj_J+%S$mkg>-5vR zmDhxRctaz5-DmmKJ0Fg&Lx=3ogs#hLgqB2cy@0C-Z#(8#KHBEB&~YrenQvjzX#P*yiO0_UK;i1>rJase9yQP#k9RL)zc~Fr(55Oiu|ps&nfa(1(iqT zOQ-fn@wF|#pg2-zM6qpupTe1uto?7ki0gFKAo(#p^k0SN5!&>(nt&bC2SOno(>D*W zl017Kq;T(po&&83bTFoWX`eaJZO4C$Jqk$Y??1kR(4q?WYq|t0cwiV5 zQV;AJ>hXX}9vB$nmL6CP-FEDOQ-E~d10QVKaLxl+N634?kV?S=-M>}vz$hrB9@sm~ z;{lgE5Io#1J+KVA?brin0qML4{AVNhjJBr6(uQ*$$TLpf11hzG2l_&j?04b`P)I!xH^$=umpm|P ztXq1(2;FwVYDm@*YsG#XdSDh5QV*P*=Ykj{G`|MZl4p!^T= z9xy6>Cmsh)vL09nh13J*W_Uc{k_TqZbW0EHhHg9dz!N|^?}5TUq|^hI=E!?MmquY8 zm;g<(9#{&6)B_i1c|72f2jhuc@LPCz7tP|CRq=x zfI{klKj(Tp;F1Rx&T~r-#6q_nd*C%7o%cYg`6=~4jfL_aFcwyr2c|=ltOr&@A@#tG zFpmdZ^1#vsZs`Ftblb59-UHHk50qP&QV-NxBJTl1Aq5Z2f+krHtc61Afm@3_9&pJ6 zD;B$@2M$BG9edytAf5Ms_mY%)px!ch4;b?)cwinh$$DTt6jBe|Tk7$EOCDJLqg#65 zICR^w2U0Jy9-#9cr~*t&8_qo|p<5~M0d+wI4=jWxSr2T2Lh6CPmwPVfLOblw9ESIc|AsI-pNQfQL(z&0qP9(cOS;{lgEu>L2v^gsf1+pz~S zuTH54Y68=F4>bK*-UBM7=Y>{4ldK1JLLv3Qi!~k(xa5INMz{39dFZxd4`ly2r5>mY zOy@n&Vx58q$|zh5t%fF95162kdf?4kj|W`xz_xI=^uQ(Pwqp;flgcHJ)kb7;DJrhBBx1l@M*fwH?&>VdYvblwBKP4XTvXIAil37TX*a0&{k2XgH3c)%qO9EAkB znFo@f+m1a@(UejTbOfgJ9_YVM-UF(N3Lc1tCRq=hg+l6q+)*A6xa5HokU%%{z)R@1 zV-NW3OQ{FC0@Ha9433reK%$?52M$1!tOqVYA@xB17>@^B^1vxbpqqK%Ep*$l2dc%U z)B`<$>AVL*;^aM`s;%IGgU}@Ffh$l*Jy7_7#{({T;4CE2%{=fCy6xBl+PIW@pcgQm z_rQpQ@*YrEQ}Do1Xp;57btt4BC?4e1V2S(ep;hYC%o{{%Js7}EHSD;DO1J9w5 zdZ2oO#{({T;2|W?%{-9jOmaQY3Xslw;5(Z(ob$k(^YR{0RZ;N3b!d|Hz-uU^9;kWF z;{lgE@B|X*W*#VTKDi!f14!pRFwv$B=R6R0QQiXve+3WRgeF-JyoW;Sfw~tw9&pJ6 z&mn!0)oh11@>sH6+l@ zJfOakTn}^sr1Kt_Vbg|l9$0ow-UF%{3Lba}O|l+H{ipSqsRshCdOYBg2i`*h-OK}} zuO-(5-2v&m2WH!};hYCnCCYoisPxR-6KIn4K>F*v2b$dQc)%qOe1ZhJnFq=zCf5T! z0qML4=G(O4oCl1zL*Dz`DEg9!OOBKJXfvWId4mHt&JfcRU_&$ph){x}^tvq1%ow#SNcn`F_@9}_39?1N_Ej>^Jy6xBl!GLt$11oLXaLxl; z9?5$kw6%f493%ZO0xM21w^Uu*Rkh=R6SkRNezd zrTapuA6wO6Jx~Y=>3yNDPdpxQ$pg8bx}^u|LAM=yU=$#o_dvK!8_s!P_j7p<7%C{t z1L>0#JWvb@sRw#I^LW4|59E99mLAYSw;g+693Y+dzy_N(oby1`D|rtjdMkJ!^9uzJ zl!QX+fnF~?9&pJ6gA@#tZ_Z|

m>Vm2Am2v?5BNYK^}x`7JRWe#17$zC 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 z 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 diff --git a/interface/resources/avatar/animations/walk_fwd_fast.fbx b/interface/resources/avatar/animations/walk_fwd_fast.fbx index 4f1d0e60aa6eb583fbb8d46ab32b0e5851ce180b..1ba94a798f327383e23d6f86e11f866a2aa0a27a 100644 GIT binary patch literal 1008352 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

F#8D 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

9e7~#~VqBNeS!Hy& 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 K^SHEMlN$i5Zv&tJ diff --git a/scripts/developer/spin2App.js b/scripts/developer/spin2App.js new file mode 100644 index 0000000000..b4f63356de --- /dev/null +++ b/scripts/developer/spin2App.js @@ -0,0 +1,175 @@ +/* global Script, Vec3, MyAvatar, Tablet, Messages, Quat, +DebugDraw, Mat4, Entities, Xform, Controller, Camera, console, document*/ + +Script.registerValue("ROTATEAPP", true); + +var TABLET_BUTTON_NAME = "ROTATE"; +var CHANGE_OF_BASIS_ROTATION = { x: 0, y: 1, z: 0, w: 0 }; +var HEAD_TURN_THRESHOLD = 25.0; +var LOADING_DELAY = 500; +var AVERAGING_RATE = 0.03; +var headPoseAverageOrientation = { x: 0, y: 0, z: 0, w: 1 }; + +// define state readings constructor +function StateReading(headPose, rhandPose, lhandPose, diffFromAverageEulers) { + this.headPose = headPose; + this.rhandPose = rhandPose; + this.lhandPose = lhandPose; + this.diffFromAverageEulers = diffFromAverageEulers; +} + +//define current state readings object for holding tracker readings and current differences from averages +var currentStateReadings = new StateReading(Controller.getPoseValue(Controller.Standard.Head), + Controller.getPoseValue(Controller.Standard.RightHand), Controller.getPoseValue(Controller.Standard.LeftHand), + { x: 0, y: 0, z: 0 }); + +//declare the checkbox constructor +function AppCheckbox(type,id,eventType,isChecked) { + this.type = type; + this.id = id; + this.eventType = eventType; + this.data = {value: isChecked}; +} + +// declare the html slider constructor +function AppProperty(name, type, eventType, signalType, setFunction, initValue, convertToThreshold, convertToSlider, signalOn) { + this.name = name; + this.type = type; + this.eventType = eventType; + this.signalType = signalType; + this.setValue = setFunction; + this.value = initValue; + this.get = function () { + return this.value; + }; + this.convertToThreshold = convertToThreshold; + this.convertToSlider = convertToSlider; +} + +var HTML_URL = Script.resolvePath("file:///c:/dev/high fidelity/hifi/scripts/developer/stepAppExtra.html"); +var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + +function manageClick() { + if (activated) { + tablet.gotoHomeScreen(); + } else { + tablet.gotoWebScreen(HTML_URL); + } +} + +var tabletButton = tablet.addButton({ + text: TABLET_BUTTON_NAME, + icon: Script.resolvePath("http://hifi-content.s3.amazonaws.com/angus/stepApp/foot.svg"), + activeIcon: Script.resolvePath("http://hifi-content.s3.amazonaws.com/angus/stepApp/foot.svg") +}); + +function onKeyPress(event) { + if (event.text === "'") { + // when the sensors are reset, then reset the mode. + RESET_MODE = false; + } +} + +function onWebEventReceived(msg) { + var message = JSON.parse(msg); + print(" we have a message from html dialog " + message.type); + propArray.forEach(function (prop) { + if (prop.eventType === message.type) { + prop.setValue(prop.convertToThreshold(message.data.value)); + print("message from " + prop.name); + // break; + } + }); + checkBoxArray.forEach(function(cbox) { + if (cbox.eventType === message.type) { + cbox.data.value = message.data.value; + // break; + } + }); +} + +function initAppForm() { + print("step app is loaded: " + documentLoaded); + propArray.forEach(function (prop) { + print(prop.name); + tablet.emitScriptEvent(JSON.stringify({ + "type": "trigger", + "id": prop.signalType, + "data": { "value": "green" } + })); + tablet.emitScriptEvent(JSON.stringify({ + "type": "slider", + "id": prop.name, + "data": { "value": prop.convertToSlider(prop.value) } + })); + }); + checkBoxArray.forEach(function(cbox) { + tablet.emitScriptEvent(JSON.stringify({ + "type": "checkboxtick", + "id": cbox.id, + "data": { value: cbox.data.value } + })); + }); + +} + +function onScreenChanged(type, url) { + print("Screen changed"); + if (type === "Web" && url === HTML_URL) { + if (!activated) { + // hook up to event bridge + tablet.webEventReceived.connect(onWebEventReceived); + print("after connect web event"); + MyAvatar.hmdLeanRecenterEnabled = false; + Script.setTimeout(initAppForm, LOADING_DELAY); + } + activated = true; + } else { + if (activated) { + // disconnect from event bridge + tablet.webEventReceived.disconnect(onWebEventReceived); + } + activated = false; + } +} + +function update(dt) { + + // Update head information + currentStateReadings.headPose = Controller.getPoseValue(Controller.Standard.Head); + currentStateReadings.rhandPose = Controller.getPoseValue(Controller.Standard.RightHand); + currentStateReadings.lhandPose = Controller.getPoseValue(Controller.Standard.LeftHand); + + var headPoseRigSpace = Quat.multiply(CHANGE_OF_BASIS_ROTATION, currentStateReadings.headPose.rotation); + headPoseAverageOrientation = Quat.slerp(headPoseAverageOrientation, headPoseRigSpace, AVERAGING_RATE); + var headPoseAverageEulers = Quat.safeEulerAngles(headPoseAverageOrientation); + + if (Math.abs(headPoseAverageEulers.y) > HEAD_TURN_THRESHOLD) { + // Turn feet + MyAvatar.triggerRotationRecenter(); + headPoseAverageOrientation = { x: 0, y: 0, z: 0, w: 1 }; + } + +} + +function shutdownTabletApp() { + tablet.removeButton(tabletButton); + if (activated) { + tablet.webEventReceived.disconnect(onWebEventReceived); + tablet.gotoHomeScreen(); + } + tablet.screenChanged.disconnect(onScreenChanged); +} + +tabletButton.clicked.connect(manageClick); +tablet.screenChanged.connect(onScreenChanged); + +Script.update.connect(update); + +Controller.keyPressEvent.connect(onKeyPress); + +Script.scriptEnding.connect(function () { + MyAvatar.hmdLeanRecenterEnabled = true; + Script.update.disconnect(update); + shutdownTabletApp(); +}); \ No newline at end of file From 8b5f96a023a74cdc38a66f4873215dbb0e871917 Mon Sep 17 00:00:00 2001 From: amantley Date: Wed, 15 Aug 2018 10:23:23 -0700 Subject: [PATCH 128/144] removed spin2App.js --- scripts/developer/spin2App.js | 175 ---------------------------------- 1 file changed, 175 deletions(-) delete mode 100644 scripts/developer/spin2App.js diff --git a/scripts/developer/spin2App.js b/scripts/developer/spin2App.js deleted file mode 100644 index b4f63356de..0000000000 --- a/scripts/developer/spin2App.js +++ /dev/null @@ -1,175 +0,0 @@ -/* global Script, Vec3, MyAvatar, Tablet, Messages, Quat, -DebugDraw, Mat4, Entities, Xform, Controller, Camera, console, document*/ - -Script.registerValue("ROTATEAPP", true); - -var TABLET_BUTTON_NAME = "ROTATE"; -var CHANGE_OF_BASIS_ROTATION = { x: 0, y: 1, z: 0, w: 0 }; -var HEAD_TURN_THRESHOLD = 25.0; -var LOADING_DELAY = 500; -var AVERAGING_RATE = 0.03; -var headPoseAverageOrientation = { x: 0, y: 0, z: 0, w: 1 }; - -// define state readings constructor -function StateReading(headPose, rhandPose, lhandPose, diffFromAverageEulers) { - this.headPose = headPose; - this.rhandPose = rhandPose; - this.lhandPose = lhandPose; - this.diffFromAverageEulers = diffFromAverageEulers; -} - -//define current state readings object for holding tracker readings and current differences from averages -var currentStateReadings = new StateReading(Controller.getPoseValue(Controller.Standard.Head), - Controller.getPoseValue(Controller.Standard.RightHand), Controller.getPoseValue(Controller.Standard.LeftHand), - { x: 0, y: 0, z: 0 }); - -//declare the checkbox constructor -function AppCheckbox(type,id,eventType,isChecked) { - this.type = type; - this.id = id; - this.eventType = eventType; - this.data = {value: isChecked}; -} - -// declare the html slider constructor -function AppProperty(name, type, eventType, signalType, setFunction, initValue, convertToThreshold, convertToSlider, signalOn) { - this.name = name; - this.type = type; - this.eventType = eventType; - this.signalType = signalType; - this.setValue = setFunction; - this.value = initValue; - this.get = function () { - return this.value; - }; - this.convertToThreshold = convertToThreshold; - this.convertToSlider = convertToSlider; -} - -var HTML_URL = Script.resolvePath("file:///c:/dev/high fidelity/hifi/scripts/developer/stepAppExtra.html"); -var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); - -function manageClick() { - if (activated) { - tablet.gotoHomeScreen(); - } else { - tablet.gotoWebScreen(HTML_URL); - } -} - -var tabletButton = tablet.addButton({ - text: TABLET_BUTTON_NAME, - icon: Script.resolvePath("http://hifi-content.s3.amazonaws.com/angus/stepApp/foot.svg"), - activeIcon: Script.resolvePath("http://hifi-content.s3.amazonaws.com/angus/stepApp/foot.svg") -}); - -function onKeyPress(event) { - if (event.text === "'") { - // when the sensors are reset, then reset the mode. - RESET_MODE = false; - } -} - -function onWebEventReceived(msg) { - var message = JSON.parse(msg); - print(" we have a message from html dialog " + message.type); - propArray.forEach(function (prop) { - if (prop.eventType === message.type) { - prop.setValue(prop.convertToThreshold(message.data.value)); - print("message from " + prop.name); - // break; - } - }); - checkBoxArray.forEach(function(cbox) { - if (cbox.eventType === message.type) { - cbox.data.value = message.data.value; - // break; - } - }); -} - -function initAppForm() { - print("step app is loaded: " + documentLoaded); - propArray.forEach(function (prop) { - print(prop.name); - tablet.emitScriptEvent(JSON.stringify({ - "type": "trigger", - "id": prop.signalType, - "data": { "value": "green" } - })); - tablet.emitScriptEvent(JSON.stringify({ - "type": "slider", - "id": prop.name, - "data": { "value": prop.convertToSlider(prop.value) } - })); - }); - checkBoxArray.forEach(function(cbox) { - tablet.emitScriptEvent(JSON.stringify({ - "type": "checkboxtick", - "id": cbox.id, - "data": { value: cbox.data.value } - })); - }); - -} - -function onScreenChanged(type, url) { - print("Screen changed"); - if (type === "Web" && url === HTML_URL) { - if (!activated) { - // hook up to event bridge - tablet.webEventReceived.connect(onWebEventReceived); - print("after connect web event"); - MyAvatar.hmdLeanRecenterEnabled = false; - Script.setTimeout(initAppForm, LOADING_DELAY); - } - activated = true; - } else { - if (activated) { - // disconnect from event bridge - tablet.webEventReceived.disconnect(onWebEventReceived); - } - activated = false; - } -} - -function update(dt) { - - // Update head information - currentStateReadings.headPose = Controller.getPoseValue(Controller.Standard.Head); - currentStateReadings.rhandPose = Controller.getPoseValue(Controller.Standard.RightHand); - currentStateReadings.lhandPose = Controller.getPoseValue(Controller.Standard.LeftHand); - - var headPoseRigSpace = Quat.multiply(CHANGE_OF_BASIS_ROTATION, currentStateReadings.headPose.rotation); - headPoseAverageOrientation = Quat.slerp(headPoseAverageOrientation, headPoseRigSpace, AVERAGING_RATE); - var headPoseAverageEulers = Quat.safeEulerAngles(headPoseAverageOrientation); - - if (Math.abs(headPoseAverageEulers.y) > HEAD_TURN_THRESHOLD) { - // Turn feet - MyAvatar.triggerRotationRecenter(); - headPoseAverageOrientation = { x: 0, y: 0, z: 0, w: 1 }; - } - -} - -function shutdownTabletApp() { - tablet.removeButton(tabletButton); - if (activated) { - tablet.webEventReceived.disconnect(onWebEventReceived); - tablet.gotoHomeScreen(); - } - tablet.screenChanged.disconnect(onScreenChanged); -} - -tabletButton.clicked.connect(manageClick); -tablet.screenChanged.connect(onScreenChanged); - -Script.update.connect(update); - -Controller.keyPressEvent.connect(onKeyPress); - -Script.scriptEnding.connect(function () { - MyAvatar.hmdLeanRecenterEnabled = true; - Script.update.disconnect(update); - shutdownTabletApp(); -}); \ No newline at end of file From 6b2de050651c74fd3755e50e06c0eb67c4b03f49 Mon Sep 17 00:00:00 2001 From: Cristian Duarte Date: Wed, 15 Aug 2018 14:50:53 -0300 Subject: [PATCH 129/144] Android - People - Bigger touch area for friend star --- .../hifiinterface/view/UserListAdapter.java | 21 ++++++++----------- android/app/src/main/res/layout/user_item.xml | 21 ++++++++++++------- 2 files changed, 23 insertions(+), 19 deletions(-) diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/view/UserListAdapter.java b/android/app/src/main/java/io/highfidelity/hifiinterface/view/UserListAdapter.java index 7e6ccc10d6..b92c0dd97a 100644 --- a/android/app/src/main/java/io/highfidelity/hifiinterface/view/UserListAdapter.java +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/view/UserListAdapter.java @@ -105,19 +105,16 @@ public class UserListAdapter extends RecyclerView.Adapter toggle()); } private void refreshUI() { @@ -136,7 +133,7 @@ public class UserListAdapter extends RecyclerView.Adapter - + android:layout_marginEnd="5.5dp"> + + + \ No newline at end of file From dd5bd0229af41014b5f3b95c0da1d0c18501561c Mon Sep 17 00:00:00 2001 From: Cristian Duarte Date: Wed, 15 Aug 2018 14:51:37 -0300 Subject: [PATCH 130/144] Android - People - Hide visit option if user is offline --- .../io/highfidelity/hifiinterface/fragment/FriendsFragment.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/FriendsFragment.java b/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/FriendsFragment.java index 2cd80bcca9..37576a0e2f 100644 --- a/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/FriendsFragment.java +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/FriendsFragment.java @@ -13,6 +13,7 @@ import android.view.ViewGroup; import com.sothree.slidinguppanel.SlidingUpPanelLayout; import io.highfidelity.hifiinterface.R; +import io.highfidelity.hifiinterface.provider.UsersProvider; import io.highfidelity.hifiinterface.view.UserListAdapter; public class FriendsFragment extends Fragment { @@ -61,6 +62,7 @@ public class FriendsFragment extends Fragment { // .. // 2. adapt options // .. + rootView.findViewById(R.id.userActionVisit).setVisibility(user.online?View.VISIBLE:View.GONE); // 3. show mSlidingUpPanelLayout.setPanelState(SlidingUpPanelLayout.PanelState.EXPANDED); } From 81e24bb28ed02c3bb74d9ac0db2549eb44db7426 Mon Sep 17 00:00:00 2001 From: Cristian Duarte Date: Wed, 15 Aug 2018 17:29:28 -0300 Subject: [PATCH 131/144] Android - People - Remove connection (from People) working with API --- .../fragment/FriendsFragment.java | 46 ++++++++++++++++++- .../hifiinterface/view/UserListAdapter.java | 6 +-- 2 files changed, 48 insertions(+), 4 deletions(-) diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/FriendsFragment.java b/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/FriendsFragment.java index 37576a0e2f..306b965501 100644 --- a/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/FriendsFragment.java +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/FriendsFragment.java @@ -2,7 +2,9 @@ package io.highfidelity.hifiinterface.fragment; import android.app.Fragment; +import android.content.DialogInterface; import android.os.Bundle; +import android.support.v7.app.AlertDialog; import android.support.v7.widget.GridLayoutManager; import android.support.v7.widget.RecyclerView; import android.util.Log; @@ -13,6 +15,7 @@ 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; @@ -26,6 +29,8 @@ public class FriendsFragment extends Fragment { private View mUserActions; private UserListAdapter mUsersAdapter; private SlidingUpPanelLayout mSlidingUpPanelLayout; + private EndpointUsersProvider mUsersProvider; + private String mSelectedUsername; public FriendsFragment() { // Required empty public constructor @@ -42,6 +47,7 @@ public class FriendsFragment extends Fragment { View rootView = inflater.inflate(R.layout.fragment_friends, container, false); String accessToken = nativeGetAccessToken(); + mUsersProvider = new EndpointUsersProvider(accessToken); Log.d("[USERS]", "token : [" + accessToken + "]"); @@ -49,16 +55,21 @@ public class FriendsFragment extends Fragment { int numberOfColumns = 1; GridLayoutManager gridLayoutMgr = new GridLayoutManager(getContext(), numberOfColumns); mUsersView.setLayoutManager(gridLayoutMgr); - mUsersAdapter = new UserListAdapter(getContext(), accessToken); + + mUsersAdapter = new UserListAdapter(getContext(), mUsersProvider); mUserActions = rootView.findViewById(R.id.userActionsLayout); mSlidingUpPanelLayout = rootView.findViewById(R.id.sliding_layout); mSlidingUpPanelLayout.setPanelHeight(0); + + rootView.findViewById(R.id.userActionDelete).setOnClickListener(view -> onRemoveConnectionClick()); + 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 // .. @@ -73,12 +84,44 @@ public class FriendsFragment extends Fragment { @Override public void onClick(View view) { mSlidingUpPanelLayout.setPanelState(SlidingUpPanelLayout.PanelState.COLLAPSED); + mSelectedUsername = null; } }); return rootView; } + private void onRemoveConnectionClick() { + if (mSelectedUsername == null) return; + + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + builder.setMessage("Remove this user 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() { + // CLD: Show success message + 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 @@ -86,6 +129,7 @@ public class FriendsFragment extends Fragment { public boolean onBackPressed() { if (mSlidingUpPanelLayout.getPanelState().equals(SlidingUpPanelLayout.PanelState.EXPANDED)) { mSlidingUpPanelLayout.setPanelState(SlidingUpPanelLayout.PanelState.COLLAPSED); + mSelectedUsername = null; return true; } else { return false; diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/view/UserListAdapter.java b/android/app/src/main/java/io/highfidelity/hifiinterface/view/UserListAdapter.java index b92c0dd97a..c1ea698b08 100644 --- a/android/app/src/main/java/io/highfidelity/hifiinterface/view/UserListAdapter.java +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/view/UserListAdapter.java @@ -38,14 +38,14 @@ public class UserListAdapter extends RecyclerView.Adapter mUsers = new ArrayList<>(); private ItemClickListener mClickListener; - public UserListAdapter(Context c, String accessToken) { + public UserListAdapter(Context c, UsersProvider usersProvider) { mContext = c; mInflater = LayoutInflater.from(mContext); - mProvider = new EndpointUsersProvider(accessToken); + mProvider = usersProvider; loadUsers(); } - private void loadUsers() { + public void loadUsers() { mProvider.retrieve(new UsersProvider.UsersCallback() { @Override public void retrieveOk(List users) { From 5d44877f5655cd421c763ae17c8bfbe69505d26c Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Wed, 15 Aug 2018 13:32:58 -0700 Subject: [PATCH 132/144] Fix functionality sometimes getting stuck if window loses focus --- interface/src/Application.cpp | 20 ++++++++++++++------ interface/src/Application.h | 2 +- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index b5c2800ff1..45a0be98ef 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -3705,7 +3705,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 @@ -3916,7 +3919,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) { @@ -3952,11 +3957,14 @@ void Application::focusOutEvent(QFocusEvent* event) { #endif // synthesize events for keys currently pressed, since we may not get their release events - foreach (int key, _keysPressed) { - QKeyEvent keyEvent(QEvent::KeyRelease, key, Qt::NoModifier); - keyReleaseEvent(&keyEvent); + // Because our key event handlers may manipulate _keysPressed, lets swap the keys pressed into a local copy, + // clearing the existing list. + QHash keysPressed; + std::swap(keysPressed, _keysPressed); + for (auto& ev : keysPressed) { + QKeyEvent synthesizedEvent { QKeyEvent::KeyRelease, ev.key(), Qt::NoModifier, ev.text() }; + keyReleaseEvent(&synthesizedEvent); } - _keysPressed.clear(); } void Application::maybeToggleMenuVisible(QMouseEvent* event) const { diff --git a/interface/src/Application.h b/interface/src/Application.h index 94e561e550..66388afbde 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -621,7 +621,7 @@ private: float _mirrorYawOffset; float _raiseMirror; - QSet _keysPressed; + QHash _keysPressed; bool _enableProcessOctreeThread; From 24af22a52f37b66296507cc2cae3bb23e61ca6b4 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Wed, 15 Aug 2018 13:56:44 -0700 Subject: [PATCH 133/144] Fix edit orbit camera getting stuck zooming --- scripts/system/libraries/entityCameraTool.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/scripts/system/libraries/entityCameraTool.js b/scripts/system/libraries/entityCameraTool.js index ebaeb1acb5..73e73d67a6 100644 --- a/scripts/system/libraries/entityCameraTool.js +++ b/scripts/system/libraries/entityCameraTool.js @@ -145,6 +145,10 @@ CameraManager = function() { }); } + for (var action in actions) { + actions[action] = 0; + } + that.enabled = true; that.mode = MODE_INACTIVE; From 2f833f16d8c1795638354b723d611af05fa4caf6 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Wed, 15 Aug 2018 14:19:20 -0700 Subject: [PATCH 134/144] Force entity list tool to refresh when opened --- scripts/system/edit.js | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/system/edit.js b/scripts/system/edit.js index c4c5561236..2499a244b8 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -871,6 +871,7 @@ var toolBar = (function () { } UserActivityLogger.enabledEdit(); entityListTool.setVisible(true); + entityListTool.sendUpdate(); gridTool.setVisible(true); grid.setEnabled(true); propertiesTool.setVisible(true); From 3217f61a53bf749e93bbe5d299b1abe375552ffd Mon Sep 17 00:00:00 2001 From: Cristian Luis Duarte Date: Wed, 15 Aug 2018 20:21:34 -0300 Subject: [PATCH 135/144] Android - People - Visit Online connections working --- android/app/src/main/cpp/native.cpp | 5 ++++ .../hifiinterface/InterfaceActivity.java | 5 ++++ .../hifiinterface/MainActivity.java | 16 ++++++++++- .../fragment/FriendsFragment.java | 27 +++++++++++++++++++ .../provider/EndpointUsersProvider.java | 14 ++++++++++ .../hifiinterface/view/UserListAdapter.java | 6 ++++- android/app/src/main/res/layout/user_item.xml | 1 + 7 files changed, 72 insertions(+), 2 deletions(-) diff --git a/android/app/src/main/cpp/native.cpp b/android/app/src/main/cpp/native.cpp index 9f0e088157..ce5af01f29 100644 --- a/android/app/src/main/cpp/native.cpp +++ b/android/app/src/main/cpp/native.cpp @@ -209,6 +209,11 @@ JNIEXPORT void Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeGotoUr DependencyManager::get()->loadSettings(jniUrl.toString()); } +JNIEXPORT void Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeGoToUser(JNIEnv* env, jobject obj, jstring username) { + QAndroidJniObject jniUsername("java/lang/String", "(Ljava/lang/String;)V", username); + DependencyManager::get()->goToUser(jniUsername.toString(), false); +} + JNIEXPORT void Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeOnPause(JNIEnv* env, jobject obj) { } diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/InterfaceActivity.java b/android/app/src/main/java/io/highfidelity/hifiinterface/InterfaceActivity.java index 8fd8b9d0e6..f161783d6a 100644 --- a/android/app/src/main/java/io/highfidelity/hifiinterface/InterfaceActivity.java +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/InterfaceActivity.java @@ -48,6 +48,7 @@ import com.google.vr.ndk.base.GvrApi;*/ public class InterfaceActivity extends QtActivity implements WebViewFragment.OnWebViewInteractionListener { public static final String DOMAIN_URL = "url"; + public static final String EXTRA_GOTO_USERNAME = "gotousername"; private static final String TAG = "Interface"; private static final int WEB_DRAWER_RIGHT_MARGIN = 262; private static final int WEB_DRAWER_BOTTOM_MARGIN = 150; @@ -59,6 +60,7 @@ public class InterfaceActivity extends QtActivity implements WebViewFragment.OnW private native long nativeOnCreate(InterfaceActivity instance, AssetManager assetManager); private native void nativeOnDestroy(); private native void nativeGotoUrl(String url); + private native void nativeGoToUser(String username); private native void nativeBeforeEnterBackground(); private native void nativeEnterBackground(); private native void nativeEnterForeground(); @@ -280,6 +282,9 @@ public class InterfaceActivity extends QtActivity implements WebViewFragment.OnW if (intent.hasExtra(DOMAIN_URL)) { webSlidingDrawer.setVisibility(View.GONE); nativeGotoUrl(intent.getStringExtra(DOMAIN_URL)); + } else if (intent.hasExtra(EXTRA_GOTO_USERNAME)) { + webSlidingDrawer.setVisibility(View.GONE); + nativeGoToUser(intent.getStringExtra(EXTRA_GOTO_USERNAME)); } } diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/MainActivity.java b/android/app/src/main/java/io/highfidelity/hifiinterface/MainActivity.java index d259e18ee7..1916962756 100644 --- a/android/app/src/main/java/io/highfidelity/hifiinterface/MainActivity.java +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/MainActivity.java @@ -37,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"; @@ -251,6 +252,14 @@ 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(); @@ -280,6 +289,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() { diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/FriendsFragment.java b/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/FriendsFragment.java index 306b965501..4d48743883 100644 --- a/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/FriendsFragment.java +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/FriendsFragment.java @@ -2,6 +2,7 @@ package io.highfidelity.hifiinterface.fragment; import android.app.Fragment; +import android.content.Context; import android.content.DialogInterface; import android.os.Bundle; import android.support.v7.app.AlertDialog; @@ -32,6 +33,8 @@ public class FriendsFragment extends Fragment { private EndpointUsersProvider mUsersProvider; private String mSelectedUsername; + private OnHomeInteractionListener mListener; + public FriendsFragment() { // Required empty public constructor } @@ -65,6 +68,15 @@ public class FriendsFragment extends Fragment { 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) { @@ -135,4 +147,19 @@ public class FriendsFragment extends Fragment { return false; } } + + @Override + public void onAttach(Context context) { + super.onAttach(context); + if (context instanceof OnHomeInteractionListener) { + mListener = (OnHomeInteractionListener) context; + } else { + throw new RuntimeException(context.toString() + + " must implement OnHomeInteractionListener"); + } + } + + public interface OnHomeInteractionListener { + void onVisitUserSelected(String username); + } } diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/provider/EndpointUsersProvider.java b/android/app/src/main/java/io/highfidelity/hifiinterface/provider/EndpointUsersProvider.java index 102d0995ee..af5a441737 100644 --- a/android/app/src/main/java/io/highfidelity/hifiinterface/provider/EndpointUsersProvider.java +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/provider/EndpointUsersProvider.java @@ -85,6 +85,10 @@ public class EndpointUsersProvider implements UsersProvider { 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); @@ -199,6 +203,7 @@ public class EndpointUsersProvider implements UsersProvider { boolean online; String connection; Images images; + LocationData location; } class Images { @@ -208,4 +213,13 @@ public class EndpointUsersProvider implements UsersProvider { String tiny; } + class LocationData { + public LocationData() {} + NameContainer root; + NameContainer domain; + } + class NameContainer { + String name; + } + } diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/view/UserListAdapter.java b/android/app/src/main/java/io/highfidelity/hifiinterface/view/UserListAdapter.java index c1ea698b08..c1a69639c0 100644 --- a/android/app/src/main/java/io/highfidelity/hifiinterface/view/UserListAdapter.java +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/view/UserListAdapter.java @@ -70,10 +70,12 @@ public class UserListAdapter extends RecyclerView.Adapter From 1a343c1c3313732321c97f364a9ac32ff935c518 Mon Sep 17 00:00:00 2001 From: Cristian Luis Duarte Date: Wed, 15 Aug 2018 20:57:04 -0300 Subject: [PATCH 136/144] Android - People - Hide People menu if the user is not logged in --- .../java/io/highfidelity/hifiinterface/MainActivity.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/MainActivity.java b/android/app/src/main/java/io/highfidelity/hifiinterface/MainActivity.java index 1916962756..1d34470146 100644 --- a/android/app/src/main/java/io/highfidelity/hifiinterface/MainActivity.java +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/MainActivity.java @@ -58,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; @@ -77,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); @@ -167,11 +170,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(""); } } From 6f83c7bff0dd157d641502503a07a25fedcde260 Mon Sep 17 00:00:00 2001 From: Cristian Luis Duarte Date: Wed, 15 Aug 2018 21:01:42 -0300 Subject: [PATCH 137/144] Android - People - Show name of user to delete in confirmation --- .../io/highfidelity/hifiinterface/fragment/FriendsFragment.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/FriendsFragment.java b/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/FriendsFragment.java index 4d48743883..35e64aae2f 100644 --- a/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/FriendsFragment.java +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/FriendsFragment.java @@ -107,7 +107,7 @@ public class FriendsFragment extends Fragment { if (mSelectedUsername == null) return; AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); - builder.setMessage("Remove this user from People?"); + builder.setMessage("Remove '" + mSelectedUsername + "' from People?"); builder.setPositiveButton("Remove", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i) { From 43b2a862242dc2b2539197fcf7746844d6edb6cf Mon Sep 17 00:00:00 2001 From: Cristian Luis Duarte Date: Wed, 15 Aug 2018 21:36:11 -0300 Subject: [PATCH 138/144] Android - People - Make list refreshable --- .../fragment/FriendsFragment.java | 25 +++++++++++++++++++ .../hifiinterface/view/UserListAdapter.java | 21 ++++++++++++++-- .../src/main/res/layout/fragment_friends.xml | 17 ++++++++----- 3 files changed, 55 insertions(+), 8 deletions(-) diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/FriendsFragment.java b/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/FriendsFragment.java index 35e64aae2f..b30b387f4b 100644 --- a/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/FriendsFragment.java +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/FriendsFragment.java @@ -5,6 +5,7 @@ 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; @@ -34,6 +35,7 @@ public class FriendsFragment extends Fragment { private String mSelectedUsername; private OnHomeInteractionListener mListener; + private SwipeRefreshLayout mSwipeRefreshLayout; public FriendsFragment() { // Required empty public constructor @@ -54,12 +56,15 @@ public class FriendsFragment extends Fragment { 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); @@ -90,6 +95,24 @@ public class FriendsFragment extends Fragment { 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() { @@ -100,6 +123,8 @@ public class FriendsFragment extends Fragment { } }); + mSwipeRefreshLayout.setOnRefreshListener(() -> mUsersAdapter.loadUsers()); + return rootView; } diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/view/UserListAdapter.java b/android/app/src/main/java/io/highfidelity/hifiinterface/view/UserListAdapter.java index c1a69639c0..2f1f1c8a82 100644 --- a/android/app/src/main/java/io/highfidelity/hifiinterface/view/UserListAdapter.java +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/view/UserListAdapter.java @@ -2,7 +2,6 @@ package io.highfidelity.hifiinterface.view; import android.content.Context; import android.graphics.Bitmap; -import android.graphics.PorterDuff; import android.graphics.drawable.BitmapDrawable; import android.net.Uri; import android.support.v4.content.ContextCompat; @@ -23,7 +22,6 @@ import java.util.ArrayList; import java.util.List; import io.highfidelity.hifiinterface.R; -import io.highfidelity.hifiinterface.provider.EndpointUsersProvider; import io.highfidelity.hifiinterface.provider.UsersProvider; /** @@ -37,6 +35,7 @@ public class UserListAdapter extends RecyclerView.Adapter mUsers = new ArrayList<>(); private ItemClickListener mClickListener; + private AdapterListener mAdapterListener; public UserListAdapter(Context c, UsersProvider usersProvider) { mContext = c; @@ -45,17 +44,29 @@ public class UserListAdapter extends RecyclerView.Adapter 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); } }); } @@ -219,4 +230,10 @@ public class UserListAdapter extends RecyclerView.Adapter - + android:layout_height="match_parent"> + + Date: Thu, 16 Aug 2018 12:36:25 -0300 Subject: [PATCH 139/144] delete access token log --- .../hifiinterface/provider/EndpointUsersProvider.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/provider/EndpointUsersProvider.java b/android/app/src/main/java/io/highfidelity/hifiinterface/provider/EndpointUsersProvider.java index af5a441737..7c32a8e8fb 100644 --- a/android/app/src/main/java/io/highfidelity/hifiinterface/provider/EndpointUsersProvider.java +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/provider/EndpointUsersProvider.java @@ -53,7 +53,7 @@ public class EndpointUsersProvider implements UsersProvider { return chain.proceed(request); } }); - Log.d("[USERZ]", "Authorization: Bearer " + accessToken);// CLD DELETE THIS LINE! + OkHttpClient client = httpClient.build(); mRetrofit = new Retrofit.Builder() From 99760e2b401454cc2da9e37a2a6abe77a36caa85 Mon Sep 17 00:00:00 2001 From: Cristian Luis Duarte Date: Thu, 16 Aug 2018 17:29:25 -0300 Subject: [PATCH 140/144] Android - People - Better navigation after logged in prevents log in screen to appear after pressing back --- .../hifiinterface/MainActivity.java | 74 +++++++++++++++---- android/app/src/main/res/values/strings.xml | 3 + 2 files changed, 64 insertions(+), 13 deletions(-) diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/MainActivity.java b/android/app/src/main/java/io/highfidelity/hifiinterface/MainActivity.java index 1d34470146..28af228541 100644 --- a/android/app/src/main/java/io/highfidelity/hifiinterface/MainActivity.java +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/MainActivity.java @@ -7,6 +7,7 @@ import android.content.Intent; import android.graphics.Bitmap; import android.graphics.drawable.BitmapDrawable; import android.os.Bundle; +import android.os.Handler; import android.support.annotation.NonNull; import android.support.design.widget.NavigationView; import android.support.v4.content.ContextCompat; @@ -114,7 +115,7 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On loadLoginFragment(); break; case "Home": - loadHomeFragment(); + loadHomeFragment(true); break; case "Privacy Policy": loadPrivacyPolicyFragment(); @@ -128,33 +129,57 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On } - private void loadHomeFragment() { + private void loadHomeFragment(boolean addToBackStack) { Fragment fragment = HomeFragment.newInstance(); - loadFragment(fragment, getString(R.string.home), true); + 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 loadPeopleFragment() { Fragment fragment = FriendsFragment.newInstance(); - loadFragment(fragment, getString(R.string.people), true); + loadFragment(fragment, getString(R.string.people), getString(R.string.tagFragmentPeople), true); } - private void loadFragment(Fragment fragment, String title, boolean addToBackStack) { + 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() + : ""; + + Log.d("[BACK]", "Before it's " + currentFragmentName + ", now adding " + title + " (before) backstackCount " + fragmentManager.getBackStackEntryCount()); + + // check if it's the same fragment than the one being shown + 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, getString(R.string.tagFragmentPeople)); + ft.replace(R.id.content_frame, fragment, tag); if (addToBackStack) { ft.addToBackStack(title); @@ -162,6 +187,14 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On ft.commit(); setTitle(title); mDrawerLayout.closeDrawer(mNavigationView); + final Handler handler = new Handler(); + handler.postDelayed(new Runnable() { + @Override + public void run() { + //Do something after 100ms + Log.d("[BACK]", "added " + title + " backstackCount " + fragmentManager.getBackStackEntryCount()); + } + }, 100); } @@ -217,7 +250,7 @@ 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(); @@ -239,6 +272,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) { @@ -267,7 +313,7 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On @Override public void onLoginCompleted() { - loadHomeFragment(); + loadHomeFragment(false); updateLoginMenu(); if (backToScene) { backToScene = false; @@ -319,6 +365,7 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On public void onBackPressed() { // 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()) { @@ -326,19 +373,20 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On } } - int index = getFragmentManager().getBackStackEntryCount() - 1; + int index = fm.getBackStackEntryCount() - 1; + if (index > 0) { super.onBackPressed(); index--; if (index > -1) { - setTitle(getFragmentManager().getBackStackEntryAt(index).getName()); + setTitle(fm.getBackStackEntryAt(index).getName()); } if (backToScene) { backToScene = false; goToLastLocation(); } } else { - finishAffinity(); + finishAffinity(); } } diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml index ea4e59a35a..dae4fe26f9 100644 --- a/android/app/src/main/res/values/strings.xml +++ b/android/app/src/main/res/values/strings.xml @@ -26,5 +26,8 @@ Online + tagFragmentHome + tagFragmentLogin + tagFragmentPolicy tagFragmentPeople From a3927f4d4c0a83151d1bc37b623440cca71ef6b2 Mon Sep 17 00:00:00 2001 From: Cristian Luis Duarte Date: Thu, 16 Aug 2018 17:31:31 -0300 Subject: [PATCH 141/144] Android - People - Remove fragments and stack related logs --- .../highfidelity/hifiinterface/MainActivity.java | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/MainActivity.java b/android/app/src/main/java/io/highfidelity/hifiinterface/MainActivity.java index 28af228541..db6f0fca24 100644 --- a/android/app/src/main/java/io/highfidelity/hifiinterface/MainActivity.java +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/MainActivity.java @@ -7,7 +7,6 @@ import android.content.Intent; import android.graphics.Bitmap; import android.graphics.drawable.BitmapDrawable; import android.os.Bundle; -import android.os.Handler; import android.support.annotation.NonNull; import android.support.design.widget.NavigationView; import android.support.v4.content.ContextCompat; @@ -155,18 +154,13 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On 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() : ""; - - Log.d("[BACK]", "Before it's " + currentFragmentName + ", now adding " + title + " (before) backstackCount " + fragmentManager.getBackStackEntryCount()); - - // check if it's the same fragment than the one being shown if (currentFragmentName.equals(title)) { mDrawerLayout.closeDrawer(mNavigationView); - return;// cancel as we are already in that fragment + return; // cancel as we are already in that fragment } // go back until first transaction @@ -187,14 +181,6 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On ft.commit(); setTitle(title); mDrawerLayout.closeDrawer(mNavigationView); - final Handler handler = new Handler(); - handler.postDelayed(new Runnable() { - @Override - public void run() { - //Do something after 100ms - Log.d("[BACK]", "added " + title + " backstackCount " + fragmentManager.getBackStackEntryCount()); - } - }, 100); } From 90429f07cb59a37d1bec3c2c8e0fda7196baf929 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Thu, 16 Aug 2018 14:38:32 -0700 Subject: [PATCH 142/144] don't forget to use enqueue the Transaction --- interface/src/avatar/AvatarManager.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index bb517f4d60..09fa6dc573 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -277,6 +277,9 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { sortedAvatars.pop(); } + if (_shouldRender) { + qApp->getMain3DScene()->enqueueTransaction(transaction); + } _numAvatarsUpdated = numAvatarsUpdated; _numAvatarsNotUpdated = numAVatarsNotUpdated; From 4851b44de1720ff2dcb1ea76b7a6f9c07ee18500 Mon Sep 17 00:00:00 2001 From: Cristian Luis Duarte Date: Thu, 16 Aug 2018 19:39:44 -0300 Subject: [PATCH 143/144] Android - People - Hide actions panel after remove --- .../highfidelity/hifiinterface/fragment/FriendsFragment.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/FriendsFragment.java b/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/FriendsFragment.java index b30b387f4b..a313b3591e 100644 --- a/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/FriendsFragment.java +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/FriendsFragment.java @@ -139,7 +139,8 @@ public class FriendsFragment extends Fragment { mUsersProvider.removeConnection(mSelectedUsername, new UsersProvider.UserActionCallback() { @Override public void requestOk() { - // CLD: Show success message + mSlidingUpPanelLayout.setPanelState(SlidingUpPanelLayout.PanelState.COLLAPSED); + mSelectedUsername = null; mUsersAdapter.loadUsers(); } From ccb5bae4b6c942acb304176a2feabb0ab440bfad Mon Sep 17 00:00:00 2001 From: Cristian Luis Duarte Date: Fri, 17 Aug 2018 14:24:04 -0300 Subject: [PATCH 144/144] Android - People - Code review --- .../hifiinterface/fragment/FriendsFragment.java | 6 ++++-- .../hifiinterface/view/UserListAdapter.java | 16 ++++++++++++---- android/app/src/main/res/values/strings.xml | 1 - 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/FriendsFragment.java b/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/FriendsFragment.java index a313b3591e..2a008d7950 100644 --- a/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/FriendsFragment.java +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/FriendsFragment.java @@ -90,7 +90,7 @@ public class FriendsFragment extends Fragment { // .. // 2. adapt options // .. - rootView.findViewById(R.id.userActionVisit).setVisibility(user.online?View.VISIBLE:View.GONE); + rootView.findViewById(R.id.userActionVisit).setVisibility(user.online ? View.VISIBLE : View.GONE); // 3. show mSlidingUpPanelLayout.setPanelState(SlidingUpPanelLayout.PanelState.EXPANDED); } @@ -129,7 +129,9 @@ public class FriendsFragment extends Fragment { } private void onRemoveConnectionClick() { - if (mSelectedUsername == null) return; + if (mSelectedUsername == null) { + return; + } AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); builder.setMessage("Remove '" + mSelectedUsername + "' from People?"); diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/view/UserListAdapter.java b/android/app/src/main/java/io/highfidelity/hifiinterface/view/UserListAdapter.java index 2f1f1c8a82..9f62b21250 100644 --- a/android/app/src/main/java/io/highfidelity/hifiinterface/view/UserListAdapter.java +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/view/UserListAdapter.java @@ -66,7 +66,9 @@ public class UserListAdapter extends RecyclerView.Adapter Interface Home - Friends People Open in browser Share link