diff --git a/android/app/build.gradle b/android/app/build.gradle index 97267258e2..7a6f34b58b 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -98,6 +98,13 @@ android { dependencies { implementation 'com.google.vr:sdk-audio:1.80.0' implementation 'com.google.vr:sdk-base:1.80.0' + + + implementation 'com.android.support.constraint:constraint-layout:1.0.2' + implementation 'com.android.support:design:26.1.0' + implementation 'com.android.support:appcompat-v7:26.1.0' + compile 'com.android.support:recyclerview-v7:26.1.0' + implementation fileTree(include: ['*.jar'], dir: 'libs') } diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index b3a8c87649..ed9caee58b 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -38,6 +38,17 @@ --> + + + + + + diff --git a/android/app/src/main/assets/hifi_domains.json b/android/app/src/main/assets/hifi_domains.json new file mode 100644 index 0000000000..a63ef7b6aa --- /dev/null +++ b/android/app/src/main/assets/hifi_domains.json @@ -0,0 +1,24 @@ +{ + "hifi_domains" : [ + { + "name": "Your last location", + "url": "", + "thumbnail": "" + }, + { + "name": "dev-mobile-master", + "url": "hifi://dev-mobile-master", + "thumbnail": "" + }, + { + "name": "dev-mobile", + "url": "hifi://dev-mobile", + "thumbnail": "" + }, + { + "name": "dev-welcome", + "url": "hifi://dev-welcome", + "thumbnail": "" + } + ] +} \ No newline at end of file diff --git a/android/app/src/main/cpp/native.cpp b/android/app/src/main/cpp/native.cpp index 13daf4c471..facf6bd4bd 100644 --- a/android/app/src/main/cpp/native.cpp +++ b/android/app/src/main/cpp/native.cpp @@ -17,6 +17,12 @@ #include #include +#include + +#include +#include "AndroidHelper.h" + +QAndroidJniObject __activity; void tempMessageHandler(QtMsgType type, const QMessageLogContext& context, const QString& message) { if (!message.isEmpty()) { @@ -138,9 +144,26 @@ extern "C" { JNIEXPORT void Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeOnCreate(JNIEnv* env, jobject obj, jobject instance, jobject asset_mgr) { qDebug() << "nativeOnCreate On thread " << QThread::currentThreadId(); g_assetManager = AAssetManager_fromJava(env, asset_mgr); + __activity = QAndroidJniObject(instance); auto oldMessageHandler = qInstallMessageHandler(tempMessageHandler); unpackAndroidAssets(); qInstallMessageHandler(oldMessageHandler); + + QObject::connect(&AndroidHelper::instance(), &AndroidHelper::androidActivityRequested, [](const QString& a) { + QAndroidJniObject string = QAndroidJniObject::fromString(a); + __activity.callMethod("openAndroidActivity", "(Ljava/lang/String;)V", string.object()); + }); +} + +JNIEXPORT void Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeOnDestroy(JNIEnv* env, jobject obj) { + QObject::disconnect(&AndroidHelper::instance(), &AndroidHelper::androidActivityRequested, + nullptr, nullptr); + +} + +JNIEXPORT void Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeGotoUrl(JNIEnv* env, jobject obj, jstring url) { + QAndroidJniObject jniUrl("java/lang/String", "(Ljava/lang/String;)V", url); + DependencyManager::get()->handleLookupString(jniUrl.toString()); } JNIEXPORT void Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeOnPause(JNIEnv* env, jobject obj) { @@ -155,6 +178,30 @@ JNIEXPORT void Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeOnExit qDebug() << "nativeOnCreate On thread " << QThread::currentThreadId(); } +JNIEXPORT void Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeGoBackFromAndroidActivity(JNIEnv *env, jobject instance) { + AndroidHelper::instance().goBackFromAndroidActivity(); } +// HifiUtils +JNIEXPORT jstring JNICALL Java_io_highfidelity_hifiinterface_HifiUtils_getCurrentAddress(JNIEnv *env, jobject instance) { + QSharedPointer addressManager = DependencyManager::get(); + if (!addressManager) { + return env->NewString(nullptr, 0); + } + QString str; + if (!addressManager->getPlaceName().isEmpty()) { + str = addressManager->getPlaceName(); + } else if (!addressManager->getHost().isEmpty()) { + str = addressManager->getHost(); + } + + QRegExp pathRegEx("(\\/[^\\/]+)"); + if (!addressManager->currentPath().isEmpty() && addressManager->currentPath().contains(pathRegEx) && pathRegEx.matchedLength() > 0) { + str += pathRegEx.cap(0); + } + + return env->NewStringUTF(str.toLatin1().data()); +} + +} diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/GotoActivity.java b/android/app/src/main/java/io/highfidelity/hifiinterface/GotoActivity.java new file mode 100644 index 0000000000..995e64c2a5 --- /dev/null +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/GotoActivity.java @@ -0,0 +1,94 @@ +package io.highfidelity.hifiinterface; + +import android.content.Intent; +import android.os.Bundle; +import android.support.v7.app.ActionBar; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.AppCompatButton; +import android.support.v7.widget.Toolbar; +import android.view.KeyEvent; +import android.view.MenuItem; +import android.view.View; +import android.view.inputmethod.EditorInfo; +import android.widget.EditText; + +import java.net.URI; +import java.net.URISyntaxException; + +public class GotoActivity extends AppCompatActivity { + + public static final String PARAM_DOMAIN_URL = "domain_url"; + + private EditText mUrlEditText; + private AppCompatButton mGoBtn; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_goto); + + Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); + toolbar.setTitleTextAppearance(this, R.style.HomeActionBarTitleStyle); + setSupportActionBar(toolbar); + + ActionBar actionbar = getSupportActionBar(); + actionbar.setDisplayHomeAsUpEnabled(true); + + mUrlEditText = (EditText) findViewById(R.id.url_text); + mUrlEditText.setOnKeyListener(new View.OnKeyListener() { + @Override + public boolean onKey(View view, int i, KeyEvent keyEvent) { + if (i == KeyEvent.KEYCODE_ENTER) { + actionGo(); + return true; + } + return false; + } + }); + + mUrlEditText.setText(HifiUtils.getInstance().getCurrentAddress()); + + mGoBtn = (AppCompatButton) findViewById(R.id.go_btn); + + mGoBtn.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + actionGo(); + } + }); + } + + private void actionGo() { + String urlString = mUrlEditText.getText().toString(); + if (!urlString.trim().isEmpty()) { + URI uri; + try { + uri = new URI(urlString); + } catch (URISyntaxException e) { + return; + } + if (uri.getScheme() == null || uri.getScheme().isEmpty()) { + urlString = "hifi://" + urlString; + } + + Intent intent = new Intent(); + intent.putExtra(GotoActivity.PARAM_DOMAIN_URL, urlString); + setResult(RESULT_OK, intent); + finish(); + } + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + // Handle action bar item clicks here. The action bar will + // automatically handle clicks on the Home/Up button, so long + // as you specify a parent activity in AndroidManifest.xml. + int id = item.getItemId(); + switch (id) { + case android.R.id.home: + finish(); + return true; + } + return super.onOptionsItemSelected(item); + } +} diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/HifiUtils.java b/android/app/src/main/java/io/highfidelity/hifiinterface/HifiUtils.java new file mode 100644 index 0000000000..15d716548f --- /dev/null +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/HifiUtils.java @@ -0,0 +1,23 @@ +package io.highfidelity.hifiinterface; + +/** + * Created by Gabriel Calero & Cristian Duarte on 4/13/18. + */ + +public class HifiUtils { + + private static HifiUtils instance; + + private HifiUtils() { + } + + public static HifiUtils getInstance() { + if (instance == null) { + instance = new HifiUtils(); + } + return instance; + } + + public native String getCurrentAddress(); + +} diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/HomeActivity.java b/android/app/src/main/java/io/highfidelity/hifiinterface/HomeActivity.java new file mode 100644 index 0000000000..611c8f50cc --- /dev/null +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/HomeActivity.java @@ -0,0 +1,220 @@ +package io.highfidelity.hifiinterface; + +import android.app.ProgressDialog; +import android.content.Intent; +import android.graphics.Color; +import android.os.AsyncTask; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.design.widget.NavigationView; +import android.support.v4.view.GravityCompat; +import android.support.v4.widget.DrawerLayout; +import android.support.v7.app.ActionBar; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.GridLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.support.v7.widget.Toolbar; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.widget.SearchView; +import android.widget.TabHost; +import android.widget.TabWidget; +import android.widget.TextView; + +import io.highfidelity.hifiinterface.QtPreloader.QtPreloader; +import io.highfidelity.hifiinterface.view.DomainAdapter; + +public class HomeActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener { + + /** + * Set this intent extra param to NOT start a new InterfaceActivity after a domain is selected" + */ + public static final String PARAM_NOT_START_INTERFACE_ACTIVITY = "not_start_interface_activity"; + + public static final int ENTER_DOMAIN_URL = 1; + + private DomainAdapter domainAdapter; + private DrawerLayout mDrawerLayout; + private ProgressDialog mDialog; + private NavigationView mNavigationView; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_home); + Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); + toolbar.setTitleTextAppearance(this, R.style.HomeActionBarTitleStyle); + setSupportActionBar(toolbar); + + ActionBar actionbar = getSupportActionBar(); + actionbar.setDisplayHomeAsUpEnabled(true); + actionbar.setHomeAsUpIndicator(R.drawable.ic_menu); + + mDrawerLayout = findViewById(R.id.drawer_layout); + + mNavigationView = (NavigationView) findViewById(R.id.nav_view); + mNavigationView.setNavigationItemSelectedListener(this); + + TabHost tabs = (TabHost)findViewById(R.id.tabhost); + tabs.setup(); + + TabHost.TabSpec spec=tabs.newTabSpec("featured"); + spec.setContent(R.id.featured); + spec.setIndicator(getString(R.string.featured)); + tabs.addTab(spec); + + spec = tabs.newTabSpec("popular"); + spec.setContent(R.id.popular); + spec.setIndicator(getString(R.string.popular)); + tabs.addTab(spec); + + spec = tabs.newTabSpec("bookmarks"); + spec.setContent(R.id.bookmarks); + spec.setIndicator(getString(R.string.bookmarks)); + tabs.addTab(spec); + + tabs.setCurrentTab(0); + + TabWidget tabwidget=tabs.getTabWidget(); + for(int i=0; i libraryList = new ArrayList<>(); + String localPrefix = m_context.getApplicationInfo().dataDir + "/"; + String pluginsPrefix = localPrefix + "qt-reserved-files/"; + cleanOldCacheIfNecessary(localPrefix, pluginsPrefix); + extractBundledPluginsAndImports(pluginsPrefix); + + for (String lib : m_qtLibs) { + libraryList.add(localPrefix + "lib/lib" + lib + ".so"); + } + + if (m_contextInfo.metaData.containsKey("android.app.load_local_libs")) { + String[] extraLibs = m_contextInfo.metaData.getString("android.app.load_local_libs").split(":"); + for (String lib : extraLibs) { + if (lib.length() > 0) { + if (lib.startsWith("lib/")) { + libraryList.add(localPrefix + lib); + } else { + libraryList.add(pluginsPrefix + lib); + } + } + } + } + + Bundle loaderParams = new Bundle(); + loaderParams.putString(DEX_PATH_KEY, new String()); + + loaderParams.putStringArrayList(NATIVE_LIBRARIES_KEY, libraryList); + + loaderParams.putString(ENVIRONMENT_VARIABLES_KEY, ENVIRONMENT_VARIABLES + + "\tQML2_IMPORT_PATH=" + pluginsPrefix + "/qml" + + "\tQML_IMPORT_PATH=" + pluginsPrefix + "/imports" + + "\tQT_PLUGIN_PATH=" + pluginsPrefix + "/plugins"); + + + // add all bundled Qt libs to loader params + ArrayList libs = new ArrayList<>(); + + String libName = m_contextInfo.metaData.getString("android.app.lib_name"); + loaderParams.putString(MAIN_LIBRARY_KEY, libName); //main library contains main() function + loaderParams.putStringArrayList(BUNDLED_LIBRARIES_KEY, libs); + + // load and start QtLoader class + DexClassLoader classLoader = new DexClassLoader(loaderParams.getString(DEX_PATH_KEY), // .jar/.apk files + m_context.getDir("outdex", Context.MODE_PRIVATE).getAbsolutePath(), // directory where optimized DEX files should be written. + loaderParams.containsKey(LIB_PATH_KEY) ? loaderParams.getString(LIB_PATH_KEY) : null, // libs folder (if exists) + m_context.getClassLoader()); // parent loader + + Class loaderClass = classLoader.loadClass(loaderClassName()); // load QtLoader class + Object qtLoader = loaderClass.newInstance(); // create an instance + Method prepareAppMethod = qtLoader.getClass().getMethod("loadApplication", + contextClassName(), + ClassLoader.class, + Bundle.class); + prepareAppMethod.invoke(qtLoader, m_context, classLoader, loaderParams); + + // now load the application library so it's accessible from this class loader + if (libName != null) { + System.loadLibrary(libName); + } + } catch (Exception e) { + Log.e(QtApplication.QtTAG, "Error pre-loading HiFi Qt app", e); + } + } + + protected String loaderClassName() { + return "org.qtproject.qt5.android.QtActivityDelegate"; + } + + protected Class contextClassName() { + return android.app.Activity.class; + } + + + private void deleteRecursively(File directory) { + File[] files = directory.listFiles(); + if (files != null) { + for (File file : files) { + if (file.isDirectory()) { + deleteRecursively(file); + } else { + file.delete(); + } + } + + directory.delete(); + } + } + + private void cleanOldCacheIfNecessary(String oldLocalPrefix, String localPrefix) { + File newCache = new File(localPrefix); + if (!newCache.exists()) { + { + File oldPluginsCache = new File(oldLocalPrefix + "plugins/"); + if (oldPluginsCache.exists() && oldPluginsCache.isDirectory()) { + deleteRecursively(oldPluginsCache); + } + } + + { + File oldImportsCache = new File(oldLocalPrefix + "imports/"); + if (oldImportsCache.exists() && oldImportsCache.isDirectory()) { + deleteRecursively(oldImportsCache); + } + } + + { + File oldQmlCache = new File(oldLocalPrefix + "qml/"); + if (oldQmlCache.exists() && oldQmlCache.isDirectory()) { + deleteRecursively(oldQmlCache); + } + } + } + } + + static private void copyFile(InputStream inputStream, OutputStream outputStream) + throws IOException { + byte[] buffer = new byte[BUFFER_SIZE]; + + int count; + while ((count = inputStream.read(buffer)) > 0) { + outputStream.write(buffer, 0, count); + } + } + + private void copyAsset(String source, String destination) + throws IOException { + // Already exists, we don't have to do anything + File destinationFile = new File(destination); + if (destinationFile.exists()) { + return; + } + + File parentDirectory = destinationFile.getParentFile(); + if (!parentDirectory.exists()) { + parentDirectory.mkdirs(); + } + + destinationFile.createNewFile(); + + AssetManager assetsManager = m_context.getAssets(); + InputStream inputStream = assetsManager.open(source); + OutputStream outputStream = new FileOutputStream(destinationFile); + copyFile(inputStream, outputStream); + + inputStream.close(); + outputStream.close(); + } + + private static void createBundledBinary(String source, String destination) + throws IOException { + // Already exists, we don't have to do anything + File destinationFile = new File(destination); + if (destinationFile.exists()) { + return; + } + + File parentDirectory = destinationFile.getParentFile(); + if (!parentDirectory.exists()) { + parentDirectory.mkdirs(); + } + + destinationFile.createNewFile(); + + InputStream inputStream = new FileInputStream(source); + OutputStream outputStream = new FileOutputStream(destinationFile); + copyFile(inputStream, outputStream); + + inputStream.close(); + outputStream.close(); + } + + private boolean cleanCacheIfNecessary(String pluginsPrefix, long packageVersion) { + File versionFile = new File(pluginsPrefix + "cache.version"); + + long cacheVersion = 0; + if (versionFile.exists() && versionFile.canRead()) { + try { + DataInputStream inputStream = new DataInputStream(new FileInputStream(versionFile)); + cacheVersion = inputStream.readLong(); + inputStream.close(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + if (cacheVersion != packageVersion) { + deleteRecursively(new File(pluginsPrefix)); + return true; + } else { + return false; + } + } + + private void extractBundledPluginsAndImports(String pluginsPrefix) throws IOException { + String libsDir = m_context.getApplicationInfo().nativeLibraryDir + "/"; + long packageVersion = -1; + try { + PackageInfo packageInfo = m_context.getPackageManager().getPackageInfo(m_context.getPackageName(), 0); + packageVersion = packageInfo.lastUpdateTime; + } catch (Exception e) { + e.printStackTrace(); + } + + + if (!cleanCacheIfNecessary(pluginsPrefix, packageVersion)) { + return; + } + + { + File versionFile = new File(pluginsPrefix + "cache.version"); + + File parentDirectory = versionFile.getParentFile(); + if (!parentDirectory.exists()) { + parentDirectory.mkdirs(); + } + + versionFile.createNewFile(); + + DataOutputStream outputStream = new DataOutputStream(new FileOutputStream(versionFile)); + outputStream.writeLong(packageVersion); + outputStream.close(); + } + + { + String key = BUNDLED_IN_LIB_RESOURCE_ID_KEY; + if (m_contextInfo.metaData.containsKey(key)) { + String[] list = m_context.getResources().getStringArray(m_contextInfo.metaData.getInt(key)); + + for (String bundledImportBinary : list) { + String[] split = bundledImportBinary.split(":"); + String sourceFileName = libsDir + split[0]; + String destinationFileName = pluginsPrefix + split[1]; + createBundledBinary(sourceFileName, destinationFileName); + } + } + } + + { + String key = BUNDLED_IN_ASSETS_RESOURCE_ID_KEY; + if (m_contextInfo.metaData.containsKey(key)) { + String[] list = m_context.getResources().getStringArray(m_contextInfo.metaData.getInt(key)); + + for (String fileName : list) { + String[] split = fileName.split(":"); + String sourceFileName = split[0]; + String destinationFileName = pluginsPrefix + split[1]; + copyAsset(sourceFileName, destinationFileName); + } + } + + } + } +} diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/view/DomainAdapter.java b/android/app/src/main/java/io/highfidelity/hifiinterface/view/DomainAdapter.java new file mode 100644 index 0000000000..1129ec586f --- /dev/null +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/view/DomainAdapter.java @@ -0,0 +1,131 @@ +package io.highfidelity.hifiinterface.view; + +import android.content.Context; +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 org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; + +import io.highfidelity.hifiinterface.R; + +/** + * Created by Gabriel Calero & Cristian Duarte on 3/20/18. + */ +public class DomainAdapter extends RecyclerView.Adapter { + + private static final java.lang.String DOMAINS_FILE = "hifi_domains.json"; + private static final String TAG = "HiFi Interface"; + private Context mContext; + private LayoutInflater mInflater; + private ItemClickListener mClickListener; + + public class Domain { + public String name; + public String url; + public String thumbnail; + Domain(String name, String url, String thumbnail) { + this.name = name; + this.thumbnail = thumbnail; + this.url = url; + } + } + + // references to our domains + private Domain[] mDomains = {}; + + public DomainAdapter(Context c) { + mContext = c; + this.mInflater = LayoutInflater.from(mContext); + loadDomains(); + } + + private void loadDomains() { + try { + JSONObject json = new JSONObject(loadJSONFromAsset()); + JSONArray hifiDomains = json.getJSONArray("hifi_domains"); + List domains = new ArrayList<>(); + for (int i = 0; i < hifiDomains.length(); i++) { + JSONObject jDomain = hifiDomains.getJSONObject(i); + + String domainName = jDomain.getString("name"); + String domainUrl = jDomain.getString("url"); + String domainThumb = jDomain.getString("thumbnail"); + + domains.add(new Domain(domainName, domainUrl, domainThumb)); + } + mDomains = domains.toArray(mDomains); + } catch (IOException e) { + Log.e(TAG, "Error loading domains from local file", e); + } catch (JSONException e) { + Log.e(TAG, "Error loading domains from local file", e); + } + } + + public String loadJSONFromAsset() throws IOException { + String json = null; + InputStream is = mContext.getAssets().open(DOMAINS_FILE); + int size = is.available(); + byte[] buffer = new byte[size]; + is.read(buffer); + is.close(); + json = new String(buffer, "UTF-8"); + return json; + } + + @Override + public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View view = mInflater.inflate(R.layout.domain_view, parent, false); + return new ViewHolder(view); + } + + @Override + public void onBindViewHolder(ViewHolder holder, int position) { + // TODO + //holder.thumbnail.setImageResource(mDomains[position].thumbnail); + holder.mDomainName.setText(mDomains[position].name); + } + + @Override + public int getItemCount() { + return mDomains.length; + } + + public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener { + TextView mDomainName; + ImageView mThumbnail; + + ViewHolder(View itemView) { + super(itemView); + mThumbnail = (ImageView) itemView.findViewById(R.id.domainThumbnail); + mDomainName = (TextView) itemView.findViewById(R.id.domainName); + itemView.setOnClickListener(this); + } + + @Override + public void onClick(View view) { + int position = getAdapterPosition(); + if (mClickListener != null) mClickListener.onItemClick(view, position, mDomains[position]); + } + } + + // allows clicks events to be caught + public void setClickListener(ItemClickListener itemClickListener) { + this.mClickListener = itemClickListener; + } + // parent activity will implement this method to respond to click events + public interface ItemClickListener { + void onItemClick(View view, int position, Domain domain); + } +} diff --git a/android/app/src/main/res/drawable/domain_bg.xml b/android/app/src/main/res/drawable/domain_bg.xml new file mode 100644 index 0000000000..d30d6413e1 --- /dev/null +++ b/android/app/src/main/res/drawable/domain_bg.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/drawable/ic_bookmark.xml b/android/app/src/main/res/drawable/ic_bookmark.xml new file mode 100644 index 0000000000..ddf03e340b --- /dev/null +++ b/android/app/src/main/res/drawable/ic_bookmark.xml @@ -0,0 +1,4 @@ + + + diff --git a/android/app/src/main/res/drawable/ic_menu.xml b/android/app/src/main/res/drawable/ic_menu.xml new file mode 100644 index 0000000000..cf37e2a393 --- /dev/null +++ b/android/app/src/main/res/drawable/ic_menu.xml @@ -0,0 +1,9 @@ + + + diff --git a/android/app/src/main/res/drawable/ic_person.xml b/android/app/src/main/res/drawable/ic_person.xml new file mode 100644 index 0000000000..cf57059c77 --- /dev/null +++ b/android/app/src/main/res/drawable/ic_person.xml @@ -0,0 +1,5 @@ + + + + diff --git a/android/app/src/main/res/drawable/ic_share.xml b/android/app/src/main/res/drawable/ic_share.xml new file mode 100644 index 0000000000..91b01694da --- /dev/null +++ b/android/app/src/main/res/drawable/ic_share.xml @@ -0,0 +1,4 @@ + + + diff --git a/android/app/src/main/res/drawable/search_bg.xml b/android/app/src/main/res/drawable/search_bg.xml new file mode 100644 index 0000000000..fd1a9ea42e --- /dev/null +++ b/android/app/src/main/res/drawable/search_bg.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/font/raleway.ttf b/android/app/src/main/res/font/raleway.ttf new file mode 100644 index 0000000000..e570a2d5c3 Binary files /dev/null and b/android/app/src/main/res/font/raleway.ttf differ diff --git a/android/app/src/main/res/font/raleway_bold.xml b/android/app/src/main/res/font/raleway_bold.xml new file mode 100644 index 0000000000..136472c0b3 --- /dev/null +++ b/android/app/src/main/res/font/raleway_bold.xml @@ -0,0 +1,7 @@ + + + diff --git a/android/app/src/main/res/font/raleway_light_italic.xml b/android/app/src/main/res/font/raleway_light_italic.xml new file mode 100644 index 0000000000..4acab05089 --- /dev/null +++ b/android/app/src/main/res/font/raleway_light_italic.xml @@ -0,0 +1,7 @@ + + + diff --git a/android/app/src/main/res/font/raleway_medium.xml b/android/app/src/main/res/font/raleway_medium.xml new file mode 100644 index 0000000000..3894c95b10 --- /dev/null +++ b/android/app/src/main/res/font/raleway_medium.xml @@ -0,0 +1,7 @@ + + + diff --git a/android/app/src/main/res/font/raleway_semibold.xml b/android/app/src/main/res/font/raleway_semibold.xml new file mode 100644 index 0000000000..39cde5a30b --- /dev/null +++ b/android/app/src/main/res/font/raleway_semibold.xml @@ -0,0 +1,7 @@ + + + diff --git a/android/app/src/main/res/layout/activity_goto.xml b/android/app/src/main/res/layout/activity_goto.xml new file mode 100644 index 0000000000..06e1383da5 --- /dev/null +++ b/android/app/src/main/res/layout/activity_goto.xml @@ -0,0 +1,39 @@ + + + + + + + + + diff --git a/android/app/src/main/res/layout/activity_home.xml b/android/app/src/main/res/layout/activity_home.xml new file mode 100644 index 0000000000..144ca84a0f --- /dev/null +++ b/android/app/src/main/res/layout/activity_home.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + diff --git a/android/app/src/main/res/layout/content_home.xml b/android/app/src/main/res/layout/content_home.xml new file mode 100644 index 0000000000..f25d9d8f7b --- /dev/null +++ b/android/app/src/main/res/layout/content_home.xml @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/android/app/src/main/res/layout/domain_view.xml b/android/app/src/main/res/layout/domain_view.xml new file mode 100644 index 0000000000..d0ed2d3a44 --- /dev/null +++ b/android/app/src/main/res/layout/domain_view.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/menu/menu_home.xml b/android/app/src/main/res/menu/menu_home.xml new file mode 100644 index 0000000000..c27233a6c3 --- /dev/null +++ b/android/app/src/main/res/menu/menu_home.xml @@ -0,0 +1,15 @@ + + + + diff --git a/android/app/src/main/res/values/colors.xml b/android/app/src/main/res/values/colors.xml index 344907f039..0325881f1b 100644 --- a/android/app/src/main/res/values/colors.xml +++ b/android/app/src/main/res/values/colors.xml @@ -1,4 +1,13 @@ #ffffff + #272727 + #000000 + #54D7FD + #1EB5EC + #333333 + #4F4F4F + #33999999 + #212121 + #9e9e9e diff --git a/android/app/src/main/res/values/dimens.xml b/android/app/src/main/res/values/dimens.xml index a9ec657aa9..440adcf6b9 100644 --- a/android/app/src/main/res/values/dimens.xml +++ b/android/app/src/main/res/values/dimens.xml @@ -9,4 +9,6 @@ 14dp 12dp + 12dp + 8dp \ No newline at end of file diff --git a/android/app/src/main/res/values/font_certs.xml b/android/app/src/main/res/values/font_certs.xml new file mode 100644 index 0000000000..d2226ac01c --- /dev/null +++ b/android/app/src/main/res/values/font_certs.xml @@ -0,0 +1,17 @@ + + + + @array/com_google_android_gms_fonts_certs_dev + @array/com_google_android_gms_fonts_certs_prod + + + + MIIEqDCCA5CgAwIBAgIJANWFuGx90071MA0GCSqGSIb3DQEBBAUAMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTAeFw0wODA0MTUyMzM2NTZaFw0zNTA5MDEyMzM2NTZaMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTCCASAwDQYJKoZIhvcNAQEBBQADggENADCCAQgCggEBANbOLggKv+IxTdGNs8/TGFy0PTP6DHThvbbR24kT9ixcOd9W+EaBPWW+wPPKQmsHxajtWjmQwWfna8mZuSeJS48LIgAZlKkpFeVyxW0qMBujb8X8ETrWy550NaFtI6t9+u7hZeTfHwqNvacKhp1RbE6dBRGWynwMVX8XW8N1+UjFaq6GCJukT4qmpN2afb8sCjUigq0GuMwYXrFVee74bQgLHWGJwPmvmLHC69EH6kWr22ijx4OKXlSIx2xT1AsSHee70w5iDBiK4aph27yH3TxkXy9V89TDdexAcKk/cVHYNnDBapcavl7y0RiQ4biu8ymM8Ga/nmzhRKya6G0cGw8CAQOjgfwwgfkwHQYDVR0OBBYEFI0cxb6VTEM8YYY6FbBMvAPyT+CyMIHJBgNVHSMEgcEwgb6AFI0cxb6VTEM8YYY6FbBMvAPyT+CyoYGapIGXMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbYIJANWFuGx90071MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADggEBABnTDPEF+3iSP0wNfdIjIz1AlnrPzgAIHVvXxunW7SBrDhEglQZBbKJEk5kT0mtKoOD1JMrSu1xuTKEBahWRbqHsXclaXjoBADb0kkjVEJu/Lh5hgYZnOjvlba8Ld7HCKePCVePoTJBdI4fvugnL8TsgK05aIskyY0hKI9L8KfqfGTl1lzOv2KoWD0KWwtAWPoGChZxmQ+nBli+gwYMzM1vAkP+aayLe0a1EQimlOalO762r0GXO0ks+UeXde2Z4e+8S/pf7pITEI/tP+MxJTALw9QUWEv9lKTk+jkbqxbsh8nfBUapfKqYn0eidpwq2AzVp3juYl7//fKnaPhJD9gs= + + + + + MIIEQzCCAyugAwIBAgIJAMLgh0ZkSjCNMA0GCSqGSIb3DQEBBAUAMHQxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtHb29nbGUgSW5jLjEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDAeFw0wODA4MjEyMzEzMzRaFw0zNjAxMDcyMzEzMzRaMHQxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtHb29nbGUgSW5jLjEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDCCASAwDQYJKoZIhvcNAQEBBQADggENADCCAQgCggEBAKtWLgDYO6IIrgqWbxJOKdoR8qtW0I9Y4sypEwPpt1TTcvZApxsdyxMJZ2JORland2qSGT2y5b+3JKkedxiLDmpHpDsz2WCbdxgxRczfey5YZnTJ4VZbH0xqWVW/8lGmPav5xVwnIiJS6HXk+BVKZF+JcWjAsb/GEuq/eFdpuzSqeYTcfi6idkyugwfYwXFU1+5fZKUaRKYCwkkFQVfcAs1fXA5V+++FGfvjJ/CxURaSxaBvGdGDhfXE28LWuT9ozCl5xw4Yq5OGazvV24mZVSoOO0yZ31j7kYvtwYK6NeADwbSxDdJEqO4k//0zOHKrUiGYXtqw/A0LFFtqoZKFjnkCAQOjgdkwgdYwHQYDVR0OBBYEFMd9jMIhF1Ylmn/Tgt9r45jk14alMIGmBgNVHSMEgZ4wgZuAFMd9jMIhF1Ylmn/Tgt9r45jk14aloXikdjB0MQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEUMBIGA1UEChMLR29vZ2xlIEluYy4xEDAOBgNVBAsTB0FuZHJvaWQxEDAOBgNVBAMTB0FuZHJvaWSCCQDC4IdGZEowjTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBAUAA4IBAQBt0lLO74UwLDYKqs6Tm8/yzKkEu116FmH4rkaymUIE0P9KaMftGlMexFlaYjzmB2OxZyl6euNXEsQH8gjwyxCUKRJNexBiGcCEyj6z+a1fuHHvkiaai+KL8W1EyNmgjmyy8AW7P+LLlkR+ho5zEHatRbM/YAnqGcFh5iZBqpknHf1SKMXFh4dd239FJ1jWYfbMDMy3NS5CTMQ2XFI1MvcyUTdZPErjQfTbQe3aDQsQcafEQPD+nqActifKZ0Np0IS9L9kR/wbNvyz6ENwPiTrjV2KRkEjH78ZMcUQXg0L3BYHJ3lc69Vs5Ddf9uUGGMYldX3WfMBEmh/9iFBDAaTCK + + + diff --git a/android/app/src/main/res/values/preloaded_fonts.xml b/android/app/src/main/res/values/preloaded_fonts.xml new file mode 100644 index 0000000000..6f9d0e424e --- /dev/null +++ b/android/app/src/main/res/values/preloaded_fonts.xml @@ -0,0 +1,9 @@ + + + + @font/raleway_bold + @font/raleway_light_italic + @font/raleway_medium + @font/raleway_semibold + + diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml index b8080fae0f..6ce0670dd8 100644 --- a/android/app/src/main/res/values/strings.xml +++ b/android/app/src/main/res/values/strings.xml @@ -1,8 +1,17 @@ Interface + Home + Go To Open in browser Share link Shared a link Share link + FEATURED + POPULAR + BOOKMARKS + Settings + Go To + Type a domain url + Go diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml index 23fe67f029..55c9b2af11 100644 --- a/android/app/src/main/res/values/styles.xml +++ b/android/app/src/main/res/values/styles.xml @@ -1,14 +1,41 @@ - - - + + + + + + + @@ -21,5 +48,11 @@ @dimen/text_size_subtitle_material_toolbar + diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index 1df901dd98..0b373e511b 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -548,16 +548,21 @@ void Agent::setIsAvatar(bool isAvatar) { if (_isAvatar && !_avatarIdentityTimer) { // set up the avatar timers _avatarIdentityTimer = new QTimer(this); + _avatarViewTimer = new QTimer(this); // connect our slot connect(_avatarIdentityTimer, &QTimer::timeout, this, &Agent::sendAvatarIdentityPacket); + connect(_avatarViewTimer, &QTimer::timeout, this, &Agent::sendAvatarViewFrustum); + + static const int AVATAR_IDENTITY_PACKET_SEND_INTERVAL_MSECS = 1000; + static const int AVATAR_VIEW_PACKET_SEND_INTERVAL_MSECS = 1000; // start the timers _avatarIdentityTimer->start(AVATAR_IDENTITY_PACKET_SEND_INTERVAL_MSECS); // FIXME - we shouldn't really need to constantly send identity packets + _avatarViewTimer->start(AVATAR_VIEW_PACKET_SEND_INTERVAL_MSECS); // tell the avatarAudioTimer to start ticking QMetaObject::invokeMethod(&_avatarAudioTimer, "start"); - } if (!_isAvatar) { @@ -567,6 +572,10 @@ void Agent::setIsAvatar(bool isAvatar) { delete _avatarIdentityTimer; _avatarIdentityTimer = nullptr; + _avatarViewTimer->stop(); + delete _avatarViewTimer; + _avatarViewTimer = nullptr; + // The avatar mixer never times out a connection (e.g., based on identity or data packets) // but rather keeps avatars in its list as long as "connected". As a result, clients timeout // when we stop sending identity, but then get woken up again by the mixer itself, which sends @@ -585,6 +594,7 @@ void Agent::setIsAvatar(bool isAvatar) { nodeList->sendPacket(std::move(packet), *node); }); } + QMetaObject::invokeMethod(&_avatarAudioTimer, "stop"); } } @@ -597,6 +607,25 @@ void Agent::sendAvatarIdentityPacket() { } } +void Agent::sendAvatarViewFrustum() { + auto scriptedAvatar = DependencyManager::get(); + + ViewFrustum view; + view.setPosition(scriptedAvatar->getWorldPosition()); + view.setOrientation(scriptedAvatar->getHeadOrientation()); + view.calculate(); + + uint8_t numFrustums = 1; + auto viewFrustumByteArray = view.toByteArray(); + + auto avatarPacket = NLPacket::create(PacketType::ViewFrustum, viewFrustumByteArray.size() + sizeof(numFrustums)); + avatarPacket->writePrimitive(numFrustums); + avatarPacket->write(viewFrustumByteArray); + + DependencyManager::get()->broadcastToNodes(std::move(avatarPacket), + { NodeType::AvatarMixer }); +} + void Agent::processAgentAvatar() { if (!_scriptEngine->isFinished() && _isAvatar) { auto scriptedAvatar = DependencyManager::get(); diff --git a/assignment-client/src/Agent.h b/assignment-client/src/Agent.h index 1229f06276..d144f0bc01 100644 --- a/assignment-client/src/Agent.h +++ b/assignment-client/src/Agent.h @@ -97,6 +97,7 @@ private: void setAvatarSound(SharedSoundPointer avatarSound) { _avatarSound = avatarSound; } void sendAvatarIdentityPacket(); + void sendAvatarViewFrustum(); QString _scriptContents; QTimer* _scriptRequestTimeout { nullptr }; @@ -106,6 +107,7 @@ private: int _numAvatarSoundSentBytes = 0; bool _isAvatar = false; QTimer* _avatarIdentityTimer = nullptr; + QTimer* _avatarViewTimer = nullptr; QHash _outgoingScriptAudioSequenceNumbers; AudioGate _audioGate; diff --git a/assignment-client/src/assets/AssetServer.h b/assignment-client/src/assets/AssetServer.h index fb88df0171..f83545c25c 100644 --- a/assignment-client/src/assets/AssetServer.h +++ b/assignment-client/src/assets/AssetServer.h @@ -63,7 +63,7 @@ struct AssetMeta { AssetMeta() { } - BakeVersion bakeVersion; + BakeVersion bakeVersion { INITIAL_BAKE_VERSION }; bool failedLastBake { false }; QString lastBakeErrors; }; diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index 29340f6474..6353a1664f 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -521,11 +521,9 @@ void AvatarMixer::handleViewFrustumPacket(QSharedPointer messag auto start = usecTimestampNow(); getOrCreateClientData(senderNode); - if (senderNode->getLinkedData()) { - AvatarMixerClientData* nodeData = dynamic_cast(senderNode->getLinkedData()); - if (nodeData != nullptr) { - nodeData->readViewFrustumPacket(message->getMessage()); - } + AvatarMixerClientData* nodeData = dynamic_cast(senderNode->getLinkedData()); + if (nodeData) { + nodeData->readViewFrustumPacket(message->getMessage()); } auto end = usecTimestampNow(); diff --git a/assignment-client/src/avatars/AvatarMixerClientData.cpp b/assignment-client/src/avatars/AvatarMixerClientData.cpp index 268aba62d6..a8e5a7c541 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.cpp +++ b/assignment-client/src/avatars/AvatarMixerClientData.cpp @@ -19,8 +19,6 @@ AvatarMixerClientData::AvatarMixerClientData(const QUuid& nodeID) : NodeData(nodeID) { - _currentViewFrustum.invalidate(); - // in case somebody calls getSessionUUID on the AvatarData instance, make sure it has the right ID _avatar->setID(nodeID); } @@ -128,12 +126,27 @@ void AvatarMixerClientData::removeFromRadiusIgnoringSet(SharedNodePointer self, } } -void AvatarMixerClientData::readViewFrustumPacket(const QByteArray& message) { - _currentViewFrustum.fromByteArray(message); +void AvatarMixerClientData::readViewFrustumPacket(QByteArray message) { + _currentViewFrustums.clear(); + + uint8_t numFrustums = 0; + memcpy(&numFrustums, message.constData(), sizeof(numFrustums)); + message.remove(0, sizeof(numFrustums)); + + for (uint8_t i = 0; i < numFrustums; ++i) { + ViewFrustum frustum; + auto bytesRead = frustum.fromByteArray(message); + message.remove(0, bytesRead); + + _currentViewFrustums.push_back(frustum); + } } bool AvatarMixerClientData::otherAvatarInView(const AABox& otherAvatarBox) { - return _currentViewFrustum.boxIntersectsKeyhole(otherAvatarBox); + return std::any_of(std::begin(_currentViewFrustums), std::end(_currentViewFrustums), + [&](const ViewFrustum& viewFrustum) { + return viewFrustum.boxIntersectsKeyhole(otherAvatarBox); + }); } void AvatarMixerClientData::loadJSONStats(QJsonObject& jsonObject) const { diff --git a/assignment-client/src/avatars/AvatarMixerClientData.h b/assignment-client/src/avatars/AvatarMixerClientData.h index 6963f4df0d..f17404b79f 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.h +++ b/assignment-client/src/avatars/AvatarMixerClientData.h @@ -98,7 +98,7 @@ public: void removeFromRadiusIgnoringSet(SharedNodePointer self, const QUuid& other); void ignoreOther(SharedNodePointer self, SharedNodePointer other); - void readViewFrustumPacket(const QByteArray& message); + void readViewFrustumPacket(QByteArray message); bool otherAvatarInView(const AABox& otherAvatarBox); @@ -110,7 +110,7 @@ public: bool getRequestsDomainListData() { return _requestsDomainListData; } void setRequestsDomainListData(bool requesting) { _requestsDomainListData = requesting; } - ViewFrustum getViewFrustum() const { return _currentViewFrustum; } + const ViewFrustums& getViewFrustums() const { return _currentViewFrustums; } uint64_t getLastOtherAvatarEncodeTime(QUuid otherAvatar) const; void setLastOtherAvatarEncodeTime(const QUuid& otherAvatar, uint64_t time); @@ -150,7 +150,7 @@ private: SimpleMovingAverage _avgOtherAvatarDataRate; std::unordered_set _radiusIgnoredOthers; - ViewFrustum _currentViewFrustum; + ViewFrustums _currentViewFrustums; int _recentOtherAvatarsInView { 0 }; int _recentOtherAvatarsOutOfView { 0 }; diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp index 6f19b73cc5..30d94ed772 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp @@ -222,8 +222,8 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) }; // prepare to sort - ViewFrustum cameraView = nodeData->getViewFrustum(); - PrioritySortUtil::PriorityQueue sortedAvatars(cameraView, + const auto& cameraViews = nodeData->getViewFrustums(); + PrioritySortUtil::PriorityQueue sortedAvatars(cameraViews, AvatarData::_avatarSortCoefficientSize, AvatarData::_avatarSortCoefficientCenter, AvatarData::_avatarSortCoefficientAge); diff --git a/assignment-client/src/entities/EntityPriorityQueue.cpp b/assignment-client/src/entities/EntityPriorityQueue.cpp index 999a05f2e2..88dee58f9d 100644 --- a/assignment-client/src/entities/EntityPriorityQueue.cpp +++ b/assignment-client/src/entities/EntityPriorityQueue.cpp @@ -15,7 +15,7 @@ const float PrioritizedEntity::DO_NOT_SEND = -1.0e-6f; const float PrioritizedEntity::FORCE_REMOVE = -1.0e-5f; const float PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY = 1.0f; -void ConicalView::set(const ViewFrustum& viewFrustum) { +void ConicalViewFrustum::set(const ViewFrustum& viewFrustum) { // The ConicalView has two parts: a central sphere (same as ViewFrustum) and a circular cone that bounds the frustum part. // Why? Because approximate intersection tests are much faster to compute for a cone than for a frustum. _position = viewFrustum.getPosition(); @@ -31,7 +31,7 @@ void ConicalView::set(const ViewFrustum& viewFrustum) { _radius = viewFrustum.getCenterRadius(); } -float ConicalView::computePriority(const AACube& cube) const { +float ConicalViewFrustum::computePriority(const AACube& cube) const { glm::vec3 p = cube.calcCenter() - _position; // position of bounding sphere in view-frame float d = glm::length(p); // distance to center of bounding sphere float r = 0.5f * cube.getScale(); // radius of bounding sphere @@ -51,3 +51,26 @@ float ConicalView::computePriority(const AACube& cube) const { } return PrioritizedEntity::DO_NOT_SEND; } + + +void ConicalView::set(const DiffTraversal::View& view) { + auto size = view.viewFrustums.size(); + _conicalViewFrustums.resize(size); + for (size_t i = 0; i < size; ++i) { + _conicalViewFrustums[i].set(view.viewFrustums[i]); + } +} + +float ConicalView::computePriority(const AACube& cube) const { + if (_conicalViewFrustums.empty()) { + return PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY; + } + + float priority = PrioritizedEntity::DO_NOT_SEND; + + for (const auto& view : _conicalViewFrustums) { + priority = std::max(priority, view.computePriority(cube)); + } + + return priority; +} diff --git a/assignment-client/src/entities/EntityPriorityQueue.h b/assignment-client/src/entities/EntityPriorityQueue.h index e308d9b549..9210ac549f 100644 --- a/assignment-client/src/entities/EntityPriorityQueue.h +++ b/assignment-client/src/entities/EntityPriorityQueue.h @@ -13,18 +13,20 @@ #define hifi_EntityPriorityQueue_h #include +#include #include +#include #include const float SQRT_TWO_OVER_TWO = 0.7071067811865f; const float DEFAULT_VIEW_RADIUS = 10.0f; -// ConicalView is an approximation of a ViewFrustum for fast calculation of sort priority. -class ConicalView { +// ConicalViewFrustum is an approximation of a ViewFrustum for fast calculation of sort priority. +class ConicalViewFrustum { public: - ConicalView() {} - ConicalView(const ViewFrustum& viewFrustum) { set(viewFrustum); } + ConicalViewFrustum() {} + ConicalViewFrustum(const ViewFrustum& viewFrustum) { set(viewFrustum); } void set(const ViewFrustum& viewFrustum); float computePriority(const AACube& cube) const; private: @@ -35,6 +37,16 @@ private: float _radius { DEFAULT_VIEW_RADIUS }; }; +// Simple wrapper around a set of conical view frustums +class ConicalView { +public: + ConicalView() {} + void set(const DiffTraversal::View& view); + float computePriority(const AACube& cube) const; +private: + std::vector _conicalViewFrustums; +}; + // PrioritizedEntity is a placeholder in a sorted queue. class PrioritizedEntity { public: @@ -61,6 +73,50 @@ private: bool _forceRemove; }; -using EntityPriorityQueue = std::priority_queue< PrioritizedEntity, std::vector, PrioritizedEntity::Compare >; +class EntityPriorityQueue { +public: + inline bool empty() const { + assert(_queue.empty() == _entities.empty()); + return _queue.empty(); + } + + inline const PrioritizedEntity& top() const { + assert(!_queue.empty()); + return _queue.top(); + } + + inline bool contains(const EntityItem* entity) const { + return _entities.find(entity) != std::end(_entities); + } + + inline void emplace(const EntityItemPointer& entity, float priority, bool forceRemove = false) { + assert(entity && !contains(entity.get())); + _queue.emplace(entity, priority, forceRemove); + _entities.insert(entity.get()); + assert(_queue.size() == _entities.size()); + } + + inline void pop() { + assert(!empty()); + _entities.erase(_queue.top().getRawEntityPointer()); + _queue.pop(); + assert(_queue.size() == _entities.size()); + } + + inline void swap(EntityPriorityQueue& other) { + std::swap(_queue, other._queue); + std::swap(_entities, other._entities); + } + +private: + using PriorityQueue = std::priority_queue, + PrioritizedEntity::Compare>; + + PriorityQueue _queue; + // Keep dictionary of all the entities in the queue for fast contain checks. + std::unordered_set _entities; + +}; #endif // hifi_EntityPriorityQueue_h diff --git a/assignment-client/src/entities/EntityTreeSendThread.cpp b/assignment-client/src/entities/EntityTreeSendThread.cpp index 4aa52922c0..2e57f2e00f 100644 --- a/assignment-client/src/entities/EntityTreeSendThread.cpp +++ b/assignment-client/src/entities/EntityTreeSendThread.cpp @@ -103,48 +103,58 @@ void EntityTreeSendThread::preDistributionProcessing() { void EntityTreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, OctreeQueryNode* nodeData, bool viewFrustumChanged, bool isFullScene) { if (viewFrustumChanged || _traversal.finished()) { - ViewFrustum viewFrustum; - nodeData->copyCurrentViewFrustum(viewFrustum); EntityTreeElementPointer root = std::dynamic_pointer_cast(_myServer->getOctree()->getRoot()); + + + DiffTraversal::View newView; + + ViewFrustum viewFrustum; + if (nodeData->hasMainViewFrustum()) { + nodeData->copyCurrentMainViewFrustum(viewFrustum); + newView.viewFrustums.push_back(viewFrustum); + } + if (nodeData->hasSecondaryViewFrustum()) { + nodeData->copyCurrentSecondaryViewFrustum(viewFrustum); + newView.viewFrustums.push_back(viewFrustum); + } + int32_t lodLevelOffset = nodeData->getBoundaryLevelAdjust() + (viewFrustumChanged ? LOW_RES_MOVING_ADJUST : NO_BOUNDARY_ADJUST); - startNewTraversal(viewFrustum, root, lodLevelOffset, nodeData->getUsesFrustum()); + newView.lodScaleFactor = powf(2.0f, lodLevelOffset); + + startNewTraversal(newView, root); // When the viewFrustum changed the sort order may be incorrect, so we re-sort // and also use the opportunity to cull anything no longer in view if (viewFrustumChanged && !_sendQueue.empty()) { EntityPriorityQueue prevSendQueue; - _sendQueue.swap(prevSendQueue); - _entitiesInQueue.clear(); + std::swap(_sendQueue, prevSendQueue); + assert(_sendQueue.empty()); + // Re-add elements from previous traversal if they still need to be sent - float lodScaleFactor = _traversal.getCurrentLODScaleFactor(); - glm::vec3 viewPosition = _traversal.getCurrentView().getPosition(); while (!prevSendQueue.empty()) { EntityItemPointer entity = prevSendQueue.top().getEntity(); bool forceRemove = prevSendQueue.top().shouldForceRemove(); prevSendQueue.pop(); if (entity) { - if (!forceRemove) { + float priority = PrioritizedEntity::DO_NOT_SEND; + + if (forceRemove) { + priority = PrioritizedEntity::FORCE_REMOVE; + } else { bool success = false; AACube cube = entity->getQueryAACube(success); if (success) { - if (_traversal.getCurrentView().cubeIntersectsKeyhole(cube)) { - float priority = _conicalView.computePriority(cube); - if (priority != PrioritizedEntity::DO_NOT_SEND) { - float distance = glm::distance(cube.calcCenter(), viewPosition) + MIN_VISIBLE_DISTANCE; - float angularDiameter = cube.getScale() / distance; - if (angularDiameter > MIN_ENTITY_ANGULAR_DIAMETER * lodScaleFactor) { - _sendQueue.push(PrioritizedEntity(entity, priority)); - _entitiesInQueue.insert(entity.get()); - } - } + const auto& view = _traversal.getCurrentView(); + if (view.intersects(cube) && view.isBigEnough(cube)) { + priority = _conicalView.computePriority(cube); } } else { - _sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY)); - _entitiesInQueue.insert(entity.get()); + priority = PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY; } - } else { - _sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::FORCE_REMOVE, true)); - _entitiesInQueue.insert(entity.get()); + } + + if (priority != PrioritizedEntity::DO_NOT_SEND) { + _sendQueue.emplace(entity, priority, forceRemove); } } } @@ -215,10 +225,9 @@ bool EntityTreeSendThread::addDescendantsToExtraFlaggedEntities(const QUuid& fil return hasNewChild || hasNewDescendants; } -void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTreeElementPointer root, int32_t lodLevelOffset, - bool usesViewFrustum) { +void EntityTreeSendThread::startNewTraversal(const DiffTraversal::View& view, EntityTreeElementPointer root) { - DiffTraversal::Type type = _traversal.prepareNewTraversal(view, root, lodLevelOffset, usesViewFrustum); + DiffTraversal::Type type = _traversal.prepareNewTraversal(view, root); // there are three types of traversal: // // (1) FirstTime = at login --> find everything in view @@ -236,161 +245,114 @@ void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTree case DiffTraversal::First: // When we get to a First traversal, clear the _knownState _knownState.clear(); - if (usesViewFrustum) { - float lodScaleFactor = _traversal.getCurrentLODScaleFactor(); - glm::vec3 viewPosition = _traversal.getCurrentView().getPosition(); - _traversal.setScanCallback([=](DiffTraversal::VisibleElement& next) { - next.element->forEachEntity([=](EntityItemPointer entity) { - // Bail early if we've already checked this entity this frame - if (_entitiesInQueue.find(entity.get()) != _entitiesInQueue.end()) { - return; - } - bool success = false; - AACube cube = entity->getQueryAACube(success); - if (success) { - if (_traversal.getCurrentView().cubeIntersectsKeyhole(cube)) { - // Check the size of the entity, it's possible that a "too small to see" entity is included in a - // larger octree cell because of its position (for example if it crosses the boundary of a cell it - // pops to the next higher cell. So we want to check to see that the entity is large enough to be seen - // before we consider including it. - float distance = glm::distance(cube.calcCenter(), viewPosition) + MIN_VISIBLE_DISTANCE; - float angularDiameter = cube.getScale() / distance; - if (angularDiameter > MIN_ENTITY_ANGULAR_DIAMETER * lodScaleFactor) { - float priority = _conicalView.computePriority(cube); - _sendQueue.push(PrioritizedEntity(entity, priority)); - _entitiesInQueue.insert(entity.get()); - } - } - } else { - _sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY)); - _entitiesInQueue.insert(entity.get()); - } - }); - }); - } else { - _traversal.setScanCallback([this](DiffTraversal::VisibleElement& next) { - next.element->forEachEntity([this](EntityItemPointer entity) { - // Bail early if we've already checked this entity this frame - if (_entitiesInQueue.find(entity.get()) != _entitiesInQueue.end()) { - return; - } - _sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY)); - _entitiesInQueue.insert(entity.get()); - }); - }); - } - break; - case DiffTraversal::Repeat: - if (usesViewFrustum) { - float lodScaleFactor = _traversal.getCurrentLODScaleFactor(); - glm::vec3 viewPosition = _traversal.getCurrentView().getPosition(); - _traversal.setScanCallback([=](DiffTraversal::VisibleElement& next) { - uint64_t startOfCompletedTraversal = _traversal.getStartOfCompletedTraversal(); - if (next.element->getLastChangedContent() > startOfCompletedTraversal) { - next.element->forEachEntity([=](EntityItemPointer entity) { - // Bail early if we've already checked this entity this frame - if (_entitiesInQueue.find(entity.get()) != _entitiesInQueue.end()) { - return; - } - auto knownTimestamp = _knownState.find(entity.get()); - if (knownTimestamp == _knownState.end()) { - bool success = false; - AACube cube = entity->getQueryAACube(success); - if (success) { - if (next.intersection == ViewFrustum::INSIDE || _traversal.getCurrentView().cubeIntersectsKeyhole(cube)) { - // See the DiffTraversal::First case for an explanation of the "entity is too small" check - float distance = glm::distance(cube.calcCenter(), viewPosition) + MIN_VISIBLE_DISTANCE; - float angularDiameter = cube.getScale() / distance; - if (angularDiameter > MIN_ENTITY_ANGULAR_DIAMETER * lodScaleFactor) { - float priority = _conicalView.computePriority(cube); - _sendQueue.push(PrioritizedEntity(entity, priority)); - _entitiesInQueue.insert(entity.get()); - } - } - } else { - _sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY)); - _entitiesInQueue.insert(entity.get()); - } - } else if (entity->getLastEdited() > knownTimestamp->second - || entity->getLastChangedOnServer() > knownTimestamp->second) { - // it is known and it changed --> put it on the queue with any priority - // TODO: sort these correctly - _sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY)); - _entitiesInQueue.insert(entity.get()); - } - }); - } - }); - } else { - _traversal.setScanCallback([this](DiffTraversal::VisibleElement& next) { - uint64_t startOfCompletedTraversal = _traversal.getStartOfCompletedTraversal(); - if (next.element->getLastChangedContent() > startOfCompletedTraversal) { - next.element->forEachEntity([this](EntityItemPointer entity) { - // Bail early if we've already checked this entity this frame - if (_entitiesInQueue.find(entity.get()) != _entitiesInQueue.end()) { - return; - } - auto knownTimestamp = _knownState.find(entity.get()); - if (knownTimestamp == _knownState.end() - || entity->getLastEdited() > knownTimestamp->second - || entity->getLastChangedOnServer() > knownTimestamp->second) { - _sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY)); - _entitiesInQueue.insert(entity.get()); - } - }); - } - }); - } - break; - case DiffTraversal::Differential: - assert(usesViewFrustum); - float lodScaleFactor = _traversal.getCurrentLODScaleFactor(); - glm::vec3 viewPosition = _traversal.getCurrentView().getPosition(); - float completedLODScaleFactor = _traversal.getCompletedLODScaleFactor(); - glm::vec3 completedViewPosition = _traversal.getCompletedView().getPosition(); - _traversal.setScanCallback([=] (DiffTraversal::VisibleElement& next) { - next.element->forEachEntity([=](EntityItemPointer entity) { + _traversal.setScanCallback([this](DiffTraversal::VisibleElement& next) { + next.element->forEachEntity([&](EntityItemPointer entity) { // Bail early if we've already checked this entity this frame - if (_entitiesInQueue.find(entity.get()) != _entitiesInQueue.end()) { + if (_sendQueue.contains(entity.get())) { return; } + float priority = PrioritizedEntity::DO_NOT_SEND; + + + bool success = false; + AACube cube = entity->getQueryAACube(success); + if (success) { + const auto& view = _traversal.getCurrentView(); + // Check the size of the entity, it's possible that a "too small to see" entity is included in a + // larger octree cell because of its position (for example if it crosses the boundary of a cell it + // pops to the next higher cell. So we want to check to see that the entity is large enough to be seen + // before we consider including it. + if ((next.intersection == ViewFrustum::INSIDE || view.intersects(cube)) && + view.isBigEnough(cube)) { + priority = _conicalView.computePriority(cube); + } + } else { + priority = PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY; + } + + + if (priority != PrioritizedEntity::DO_NOT_SEND) { + _sendQueue.emplace(entity, priority); + } + }); + }); + break; + case DiffTraversal::Repeat: + _traversal.setScanCallback([this](DiffTraversal::VisibleElement& next) { + uint64_t startOfCompletedTraversal = _traversal.getStartOfCompletedTraversal(); + if (next.element->getLastChangedContent() > startOfCompletedTraversal) { + next.element->forEachEntity([&](EntityItemPointer entity) { + // Bail early if we've already checked this entity this frame + if (_sendQueue.contains(entity.get())) { + return; + } + float priority = PrioritizedEntity::DO_NOT_SEND; + + + auto knownTimestamp = _knownState.find(entity.get()); + if (knownTimestamp == _knownState.end()) { + bool success = false; + AACube cube = entity->getQueryAACube(success); + if (success) { + const auto& view = _traversal.getCurrentView(); + // See the DiffTraversal::First case for an explanation of the "entity is too small" check + if ((next.intersection == ViewFrustum::INSIDE || view.intersects(cube)) && + view.isBigEnough(cube)) { + priority = _conicalView.computePriority(cube); + } + } else { + priority = PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY; + } + } else if (entity->getLastEdited() > knownTimestamp->second || + entity->getLastChangedOnServer() > knownTimestamp->second) { + // it is known and it changed --> put it on the queue with any priority + // TODO: sort these correctly + priority = PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY; + } + + + if (priority != PrioritizedEntity::DO_NOT_SEND) { + _sendQueue.emplace(entity, priority); + } + }); + } + }); + break; + case DiffTraversal::Differential: + assert(view.usesViewFrustums()); + _traversal.setScanCallback([this] (DiffTraversal::VisibleElement& next) { + next.element->forEachEntity([&](EntityItemPointer entity) { + // Bail early if we've already checked this entity this frame + if (_sendQueue.contains(entity.get())) { + return; + } + float priority = PrioritizedEntity::DO_NOT_SEND; + + auto knownTimestamp = _knownState.find(entity.get()); if (knownTimestamp == _knownState.end()) { bool success = false; AACube cube = entity->getQueryAACube(success); if (success) { - if (_traversal.getCurrentView().cubeIntersectsKeyhole(cube)) { - // See the DiffTraversal::First case for an explanation of the "entity is too small" check - float distance = glm::distance(cube.calcCenter(), viewPosition) + MIN_VISIBLE_DISTANCE; - float angularDiameter = cube.getScale() / distance; - if (angularDiameter > MIN_ENTITY_ANGULAR_DIAMETER * lodScaleFactor) { - if (!_traversal.getCompletedView().cubeIntersectsKeyhole(cube)) { - float priority = _conicalView.computePriority(cube); - _sendQueue.push(PrioritizedEntity(entity, priority)); - _entitiesInQueue.insert(entity.get()); - } else { - // If this entity was skipped last time because it was too small, we still need to send it - distance = glm::distance(cube.calcCenter(), completedViewPosition) + MIN_VISIBLE_DISTANCE; - angularDiameter = cube.getScale() / distance; - if (angularDiameter <= MIN_ENTITY_ANGULAR_DIAMETER * completedLODScaleFactor) { - // this object was skipped in last completed traversal - float priority = _conicalView.computePriority(cube); - _sendQueue.push(PrioritizedEntity(entity, priority)); - _entitiesInQueue.insert(entity.get()); - } - } - } + const auto& view = _traversal.getCurrentView(); + // See the DiffTraversal::First case for an explanation of the "entity is too small" check + if ((next.intersection == ViewFrustum::INSIDE || view.intersects(cube)) && + view.isBigEnough(cube)) { + priority = _conicalView.computePriority(cube); } } else { - _sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY)); - _entitiesInQueue.insert(entity.get()); + priority = PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY; } - } else if (entity->getLastEdited() > knownTimestamp->second - || entity->getLastChangedOnServer() > knownTimestamp->second) { + } else if (entity->getLastEdited() > knownTimestamp->second || + entity->getLastChangedOnServer() > knownTimestamp->second) { // it is known and it changed --> put it on the queue with any priority // TODO: sort these correctly - _sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY)); - _entitiesInQueue.insert(entity.get()); + priority = PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY; + } + + + if (priority != PrioritizedEntity::DO_NOT_SEND) { + _sendQueue.emplace(entity, priority); } }); }); @@ -479,11 +441,10 @@ bool EntityTreeSendThread::traverseTreeAndBuildNextPacketPayload(EncodeBitstream } } _sendQueue.pop(); - _entitiesInQueue.erase(entity.get()); } nodeData->stats.encodeStopped(); if (_sendQueue.empty()) { - assert(_entitiesInQueue.empty()); + assert(_sendQueue.empty()); params.stopReason = EncodeBitstreamParams::FINISHED; _extraEncodeData->entities.clear(); } @@ -501,18 +462,16 @@ bool EntityTreeSendThread::traverseTreeAndBuildNextPacketPayload(EncodeBitstream void EntityTreeSendThread::editingEntityPointer(const EntityItemPointer& entity) { if (entity) { - if (_entitiesInQueue.find(entity.get()) == _entitiesInQueue.end() && _knownState.find(entity.get()) != _knownState.end()) { + if (!_sendQueue.contains(entity.get()) && _knownState.find(entity.get()) != _knownState.end()) { bool success = false; AACube cube = entity->getQueryAACube(success); if (success) { // We can force a removal from _knownState if the current view is used and entity is out of view - if (_traversal.doesCurrentUseViewFrustum() && !_traversal.getCurrentView().cubeIntersectsKeyhole(cube)) { - _sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::FORCE_REMOVE, true)); - _entitiesInQueue.insert(entity.get()); + if (_traversal.doesCurrentUseViewFrustum() && !_traversal.getCurrentView().intersects(cube)) { + _sendQueue.emplace(entity, PrioritizedEntity::FORCE_REMOVE, true); } } else { - _sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY, true)); - _entitiesInQueue.insert(entity.get()); + _sendQueue.emplace(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY, true); } } } diff --git a/assignment-client/src/entities/EntityTreeSendThread.h b/assignment-client/src/entities/EntityTreeSendThread.h index 1e2bd15429..9eea98b7fd 100644 --- a/assignment-client/src/entities/EntityTreeSendThread.h +++ b/assignment-client/src/entities/EntityTreeSendThread.h @@ -41,8 +41,7 @@ private: bool addAncestorsToExtraFlaggedEntities(const QUuid& filteredEntityID, EntityItem& entityItem, EntityNodeData& nodeData); bool addDescendantsToExtraFlaggedEntities(const QUuid& filteredEntityID, EntityItem& entityItem, EntityNodeData& nodeData); - void startNewTraversal(const ViewFrustum& viewFrustum, EntityTreeElementPointer root, int32_t lodLevelOffset, - bool usesViewFrustum); + void startNewTraversal(const DiffTraversal::View& viewFrustum, EntityTreeElementPointer root); bool traverseTreeAndBuildNextPacketPayload(EncodeBitstreamParams& params, const QJsonObject& jsonFilters) override; void preDistributionProcessing() override; @@ -51,7 +50,6 @@ private: DiffTraversal _traversal; EntityPriorityQueue _sendQueue; - std::unordered_set _entitiesInQueue; std::unordered_map _knownState; ConicalView _conicalView; // cached optimized view for fast priority calculations diff --git a/assignment-client/src/octree/OctreeHeadlessViewer.cpp b/assignment-client/src/octree/OctreeHeadlessViewer.cpp index d3b20fb623..6d91a134c2 100644 --- a/assignment-client/src/octree/OctreeHeadlessViewer.cpp +++ b/assignment-client/src/octree/OctreeHeadlessViewer.cpp @@ -14,32 +14,18 @@ #include #include - -OctreeHeadlessViewer::OctreeHeadlessViewer() { - _viewFrustum.setProjection(glm::perspective(glm::radians(DEFAULT_FIELD_OF_VIEW_DEGREES), DEFAULT_ASPECT_RATIO, DEFAULT_NEAR_CLIP, DEFAULT_FAR_CLIP)); -} - void OctreeHeadlessViewer::queryOctree() { char serverType = getMyNodeType(); PacketType packetType = getMyQueryMessageType(); - _octreeQuery.setCameraPosition(_viewFrustum.getPosition()); - _octreeQuery.setCameraOrientation(_viewFrustum.getOrientation()); - _octreeQuery.setCameraFov(_viewFrustum.getFieldOfView()); - _octreeQuery.setCameraAspectRatio(_viewFrustum.getAspectRatio()); - _octreeQuery.setCameraNearClip(_viewFrustum.getNearClip()); - _octreeQuery.setCameraFarClip(_viewFrustum.getFarClip()); - _octreeQuery.setCameraEyeOffsetPosition(glm::vec3()); - _octreeQuery.setCameraCenterRadius(_viewFrustum.getCenterRadius()); - _octreeQuery.setOctreeSizeScale(_voxelSizeScale); - _octreeQuery.setBoundaryLevelAdjust(_boundaryLevelAdjust); + if (_hasViewFrustum) { + _octreeQuery.setMainViewFrustum(_viewFrustum); + } auto nodeList = DependencyManager::get(); auto node = nodeList->soloNodeOfType(serverType); if (node && node->getActiveSocket()) { - _octreeQuery.setMaxQueryPacketsPerSecond(getMaxPacketsPerSecond()); - auto queryPacket = NLPacket::create(packetType); // encode the query data diff --git a/assignment-client/src/octree/OctreeHeadlessViewer.h b/assignment-client/src/octree/OctreeHeadlessViewer.h index feb8211c39..dea91ce66f 100644 --- a/assignment-client/src/octree/OctreeHeadlessViewer.h +++ b/assignment-client/src/octree/OctreeHeadlessViewer.h @@ -20,9 +20,6 @@ class OctreeHeadlessViewer : public OctreeProcessor { Q_OBJECT public: - OctreeHeadlessViewer(); - virtual ~OctreeHeadlessViewer() {}; - OctreeQuery& getOctreeQuery() { return _octreeQuery; } static int parseOctreeStats(QSharedPointer message, SharedNodePointer sourceNode); @@ -32,34 +29,32 @@ public slots: void queryOctree(); // setters for camera attributes - void setPosition(const glm::vec3& position) { _viewFrustum.setPosition(position); } - void setOrientation(const glm::quat& orientation) { _viewFrustum.setOrientation(orientation); } - void setCenterRadius(float radius) { _viewFrustum.setCenterRadius(radius); } - void setKeyholeRadius(float radius) { _viewFrustum.setCenterRadius(radius); } // TODO: remove this legacy support + void setPosition(const glm::vec3& position) { _hasViewFrustum = true; _viewFrustum.setPosition(position); } + void setOrientation(const glm::quat& orientation) { _hasViewFrustum = true; _viewFrustum.setOrientation(orientation); } + void setCenterRadius(float radius) { _hasViewFrustum = true; _viewFrustum.setCenterRadius(radius); } + void setKeyholeRadius(float radius) { _hasViewFrustum = true; _viewFrustum.setCenterRadius(radius); } // TODO: remove this legacy support // setters for LOD and PPS - void setVoxelSizeScale(float sizeScale) { _voxelSizeScale = sizeScale; } - void setBoundaryLevelAdjust(int boundaryLevelAdjust) { _boundaryLevelAdjust = boundaryLevelAdjust; } - void setMaxPacketsPerSecond(int maxPacketsPerSecond) { _maxPacketsPerSecond = maxPacketsPerSecond; } + void setVoxelSizeScale(float sizeScale) { _octreeQuery.setOctreeSizeScale(sizeScale) ; } + void setBoundaryLevelAdjust(int boundaryLevelAdjust) { _octreeQuery.setBoundaryLevelAdjust(boundaryLevelAdjust); } + void setMaxPacketsPerSecond(int maxPacketsPerSecond) { _octreeQuery.setMaxQueryPacketsPerSecond(maxPacketsPerSecond); } // getters for camera attributes const glm::vec3& getPosition() const { return _viewFrustum.getPosition(); } const glm::quat& getOrientation() const { return _viewFrustum.getOrientation(); } // getters for LOD and PPS - float getVoxelSizeScale() const { return _voxelSizeScale; } - int getBoundaryLevelAdjust() const { return _boundaryLevelAdjust; } - int getMaxPacketsPerSecond() const { return _maxPacketsPerSecond; } + float getVoxelSizeScale() const { return _octreeQuery.getOctreeSizeScale(); } + int getBoundaryLevelAdjust() const { return _octreeQuery.getBoundaryLevelAdjust(); } + int getMaxPacketsPerSecond() const { return _octreeQuery.getMaxQueryPacketsPerSecond(); } unsigned getOctreeElementsCount() const { return _tree->getOctreeElementsCount(); } private: OctreeQuery _octreeQuery; + bool _hasViewFrustum { false }; ViewFrustum _viewFrustum; - float _voxelSizeScale { DEFAULT_OCTREE_SIZE_SCALE }; - int _boundaryLevelAdjust { 0 }; - int _maxPacketsPerSecond { DEFAULT_MAX_OCTREE_PPS }; }; #endif // hifi_OctreeHeadlessViewer_h diff --git a/assignment-client/src/octree/OctreeSendThread.cpp b/assignment-client/src/octree/OctreeSendThread.cpp index de49bd461c..40c052659d 100644 --- a/assignment-client/src/octree/OctreeSendThread.cpp +++ b/assignment-client/src/octree/OctreeSendThread.cpp @@ -330,8 +330,9 @@ int OctreeSendThread::packetDistributor(SharedNodePointer node, OctreeQueryNode* } else { // we aren't forcing a full scene, check if something else suggests we should isFullScene = nodeData->haveJSONParametersChanged() || - (nodeData->getUsesFrustum() - && ((!viewFrustumChanged && nodeData->getViewFrustumJustStoppedChanging()) || nodeData->hasLodChanged())); + (nodeData->hasMainViewFrustum() && + (nodeData->getViewFrustumJustStoppedChanging() || + nodeData->hasLodChanged())); } if (nodeData->isPacketWaiting()) { @@ -445,7 +446,6 @@ void OctreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, Octre params.trackSend = [this](const QUuid& dataID, quint64 dataEdited) { _myServer->trackSend(dataID, dataEdited, _nodeUuid); }; - nodeData->copyCurrentViewFrustum(params.viewFrustum); bool somethingToSend = true; // assume we have something bool hadSomething = hasSomethingToSend(nodeData); diff --git a/assignment-client/src/scripts/EntityScriptServer.cpp b/assignment-client/src/scripts/EntityScriptServer.cpp index d242b393bf..eea8e8b470 100644 --- a/assignment-client/src/scripts/EntityScriptServer.cpp +++ b/assignment-client/src/scripts/EntityScriptServer.cpp @@ -294,7 +294,6 @@ void EntityScriptServer::run() { queryJSONParameters[EntityJSONQueryProperties::FLAGS_PROPERTY] = queryFlags; // setup the JSON parameters so that OctreeQuery does not use a frustum and uses our JSON filter - _entityViewer.getOctreeQuery().setUsesFrustum(false); _entityViewer.getOctreeQuery().setJSONParameters(queryJSONParameters); entityScriptingInterface->setEntityTree(_entityViewer.getTree()); diff --git a/cmake/externals/quazip/CMakeLists.txt b/cmake/externals/quazip/CMakeLists.txt index f2690e0a7d..7bf6f05d9f 100644 --- a/cmake/externals/quazip/CMakeLists.txt +++ b/cmake/externals/quazip/CMakeLists.txt @@ -41,6 +41,9 @@ if (APPLE) elseif (WIN32) set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${INSTALL_DIR}/lib/quazip5.lib CACHE FILEPATH "Location of QuaZip release library") set(${EXTERNAL_NAME_UPPER}_LIBRARY_DEBUG ${INSTALL_DIR}/lib/quazip5d.lib CACHE FILEPATH "Location of QuaZip release library") +elseif (CMAKE_SYSTEM_NAME MATCHES "Linux") + set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${INSTALL_DIR}/lib/libquazip5.so CACHE FILEPATH "Location of QuaZip release library") + set(${EXTERNAL_NAME_UPPER}_LIBRARY_DEBUG ${INSTALL_DIR}/lib/libquazip5d.so CACHE FILEPATH "Location of QuaZip release library") else () set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${INSTALL_DIR}/lib/libquazip5.so CACHE FILEPATH "Location of QuaZip release library") set(${EXTERNAL_NAME_UPPER}_LIBRARY_DEBUG ${INSTALL_DIR}/lib/libquazip5.so CACHE FILEPATH "Location of QuaZip release library") diff --git a/domain-server/resources/web/wizard/index.shtml b/domain-server/resources/web/wizard/index.shtml index 5a3286296d..3bc7503b44 100644 --- a/domain-server/resources/web/wizard/index.shtml +++ b/domain-server/resources/web/wizard/index.shtml @@ -26,7 +26,7 @@ Place names are similar to web addresses. Users who want to visit your domain can enter its Place Name in High Fidelity's Interface. You can choose a Place Name for your domain.
- People can also use your domain's IP address (shown below) to visit your High Fidelity domain. + Your domain may also be reachable by IP address.
@@ -35,10 +35,10 @@ diff --git a/interface/resources/icons/+android/avatar-a.svg b/interface/resources/icons/+android/avatar-a.svg deleted file mode 100755 index 165b39943e..0000000000 --- a/interface/resources/icons/+android/avatar-a.svg +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - - - - - - diff --git a/interface/resources/icons/+android/avatar-i.svg b/interface/resources/icons/+android/avatar-i.svg deleted file mode 100755 index c1557487ea..0000000000 --- a/interface/resources/icons/+android/avatar-i.svg +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - - - - - - diff --git a/interface/resources/icons/+android/backward.svg b/interface/resources/icons/+android/backward.svg old mode 100644 new mode 100755 index ae0893fc66..6b4c560768 --- a/interface/resources/icons/+android/backward.svg +++ b/interface/resources/icons/+android/backward.svg @@ -1,70 +1,13 @@ - - - -image/svg+xml \ No newline at end of file + + + + + + + + diff --git a/interface/resources/icons/+android/goto-a.svg b/interface/resources/icons/+android/goto-a.svg deleted file mode 100755 index 5fb3e52e4c..0000000000 --- a/interface/resources/icons/+android/goto-a.svg +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - - - - - - diff --git a/interface/resources/icons/+android/goto-i.svg b/interface/resources/icons/+android/goto-i.svg deleted file mode 100644 index 7613beb9e7..0000000000 --- a/interface/resources/icons/+android/goto-i.svg +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - - - - - - diff --git a/interface/resources/icons/+android/home.svg b/interface/resources/icons/+android/home.svg deleted file mode 100644 index 414c179e79..0000000000 --- a/interface/resources/icons/+android/home.svg +++ /dev/null @@ -1,82 +0,0 @@ - - - -image/svg+xml \ No newline at end of file diff --git a/interface/resources/icons/+android/login-a.svg b/interface/resources/icons/+android/login-a.svg deleted file mode 100755 index 8a7f097ed7..0000000000 --- a/interface/resources/icons/+android/login-a.svg +++ /dev/null @@ -1,990 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/interface/resources/icons/+android/login-i.svg b/interface/resources/icons/+android/login-i.svg deleted file mode 100755 index 6f011e1d13..0000000000 --- a/interface/resources/icons/+android/login-i.svg +++ /dev/null @@ -1,990 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/interface/resources/icons/+android/mic-unmute-a.svg b/interface/resources/icons/+android/mic-unmute-a.svg old mode 100644 new mode 100755 index bb28dc0f2b..8717636a34 --- a/interface/resources/icons/+android/mic-unmute-a.svg +++ b/interface/resources/icons/+android/mic-unmute-a.svg @@ -1,70 +1,22 @@ - - - -image/svg+xml \ No newline at end of file + + + + + + + + + + + + + + diff --git a/interface/resources/icons/+android/myview-a.svg b/interface/resources/icons/+android/myview-a.svg index 307b559c95..e2c183caf2 100755 --- a/interface/resources/icons/+android/myview-a.svg +++ b/interface/resources/icons/+android/myview-a.svg @@ -1,56 +1,19 @@ - - - -image/svg+xmlAsset 3 \ No newline at end of file + + + + + + + + + + + diff --git a/interface/resources/icons/+android/radar-a.svg b/interface/resources/icons/+android/radar-a.svg deleted file mode 100755 index e4b157f827..0000000000 --- a/interface/resources/icons/+android/radar-a.svg +++ /dev/null @@ -1 +0,0 @@ -Asset 1 \ No newline at end of file diff --git a/interface/resources/icons/+android/radar-hover.svg b/interface/resources/icons/+android/radar-hover.svg deleted file mode 100755 index e4b157f827..0000000000 --- a/interface/resources/icons/+android/radar-hover.svg +++ /dev/null @@ -1 +0,0 @@ -Asset 1 \ No newline at end of file diff --git a/interface/resources/icons/+android/radar-i.svg b/interface/resources/icons/+android/radar-i.svg deleted file mode 100755 index 3994a775d3..0000000000 --- a/interface/resources/icons/+android/radar-i.svg +++ /dev/null @@ -1 +0,0 @@ -Asset 1 \ No newline at end of file diff --git a/interface/resources/images/fly.png b/interface/resources/images/fly.png index 0edfcab21b..b8b96645f8 100644 Binary files a/interface/resources/images/fly.png and b/interface/resources/images/fly.png differ diff --git a/interface/resources/qml/+android/AddressBarDialog.qml b/interface/resources/qml/+android/AddressBarDialog.qml deleted file mode 100644 index 4477d512fc..0000000000 --- a/interface/resources/qml/+android/AddressBarDialog.qml +++ /dev/null @@ -1,232 +0,0 @@ -// -// AddressBarDialog.qml -// -// Created by Austin Davis on 2015/04/14 -// 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 -// - -import Hifi 1.0 -import QtQuick 2.4 -import "../controls" -import "../styles" -import "../hifi" as QmlHifi -import "../hifi/toolbars" -import "../styles-uit" as HifiStyles -import "../controls-uit" as HifiControls - -Item { - QmlHifi.HifiConstants { id: android } - - width: parent ? parent.width - android.dimen.windowLessWidth : 0 - height: parent ? parent.height - android.dimen.windowLessHeight : 0 - z: android.dimen.windowZ - anchors { horizontalCenter: parent.horizontalCenter; bottom: parent.bottom } - - id: bar - property bool isCursorVisible: false // Override default cursor visibility. - property bool shown: true - - onShownChanged: { - bar.visible = shown; - sendToScript({method: 'shownChanged', params: { shown: shown }}); - if (shown) { - addressLine.text=""; - updateLocationText(addressLine.text.length > 0); - } - } - - function hide() { - shown = false; - sendToScript ({ type: "hide" }); - } - - Component.onCompleted: { - updateLocationText(addressLine.text.length > 0); - } - - HifiConstants { id: hifi } - HifiStyles.HifiConstants { id: hifiStyleConstants } - - signal sendToScript(var message); - - AddressBarDialog { - id: addressBarDialog - } - - - Rectangle { - id: background - gradient: Gradient { - GradientStop { position: 0.0; color: android.color.gradientTop } - GradientStop { position: 1.0; color: android.color.gradientBottom } - } - anchors { - fill: parent - } - - MouseArea { - anchors.fill: parent - } - - QmlHifi.WindowHeader { - id: header - iconSource: "../../../icons/goto-i.svg" - titleText: "GO TO" - } - - HifiStyles.RalewayRegular { - id: notice - text: "YOUR LOCATION" - font.pixelSize: (hifi.fonts.pixelSize * 2.15) * (android.dimen.atLeast1440p ? 1 : .75); - color: "#2CD7FF" - anchors { - bottom: addressBackground.top - bottomMargin: android.dimen.atLeast1440p ? 45 : 34 - left: addressBackground.left - leftMargin: android.dimen.atLeast1440p ? 60 : 45 - } - - } - - property int inputAreaHeight: android.dimen.atLeast1440p ? 210 : 156 - property int inputAreaStep: (height - inputAreaHeight) / 2 - - ToolbarButton { - id: homeButton - y: android.dimen.atLeast1440p ? 280 : 210 - imageURL: "../../icons/home.svg" - onClicked: { - addressBarDialog.loadHome(); - bar.shown = false; - } - anchors { - leftMargin: android.dimen.atLeast1440p ? 75 : 56 - left: parent.left - } - size: android.dimen.atLeast1440p ? 150 : 150//112 - } - - ToolbarButton { - id: backArrow; - imageURL: "../../icons/backward.svg"; - onClicked: addressBarDialog.loadBack(); - anchors { - left: homeButton.right - leftMargin: android.dimen.atLeast1440p ? 70 : 52 - verticalCenter: homeButton.verticalCenter - } - size: android.dimen.atLeast1440p ? 150 : 150 - } - ToolbarButton { - id: forwardArrow; - imageURL: "../../icons/forward.svg"; - onClicked: addressBarDialog.loadForward(); - anchors { - left: backArrow.right - leftMargin: android.dimen.atLeast1440p ? 60 : 45 - verticalCenter: homeButton.verticalCenter - } - size: android.dimen.atLeast1440p ? 150 : 150 - } - - HifiStyles.FiraSansRegular { - id: location; - font.pixelSize: addressLine.font.pixelSize; - color: "lightgray"; - clip: true; - anchors.fill: addressLine; - visible: addressLine.text.length === 0 - z: 1 - } - - Rectangle { - id: addressBackground - x: android.dimen.atLeast1440p ? 780 : 585 - y: android.dimen.atLeast1440p ? 280 : 235 // tweaking by hand - width: android.dimen.atLeast1440p ? 1270 : 952 - height: android.dimen.atLeast1440p ? 150 : 112 - color: "#FFFFFF" - } - - TextInput { - id: addressLine - focus: true - x: android.dimen.atLeast1440p ? 870 : 652 - y: android.dimen.atLeast1440p ? 300 : 245 // tweaking by hand - width: android.dimen.atLeast1440p ? 1200 : 900 - height: android.dimen.atLeast1440p ? 120 : 90 - inputMethodHints: Qt.ImhNoPredictiveText - //helperText: "Hint is here" - font.pixelSize: hifi.fonts.pixelSize * 3.75 - onTextChanged: { - //filterChoicesByText(); - updateLocationText(addressLine.text.length > 0); - if (!isCursorVisible && text.length > 0) { - isCursorVisible = true; - cursorVisible = true; - } - } - - onActiveFocusChanged: { - //cursorVisible = isCursorVisible && focus; - } - } - - - - function toggleOrGo() { - if (addressLine.text !== "") { - addressBarDialog.loadAddress(addressLine.text); - } - bar.shown = false; - } - - Keys.onPressed: { - switch (event.key) { - case Qt.Key_Escape: - case Qt.Key_Back: - clearAddressLineTimer.start(); - event.accepted = true - bar.shown = false; - break - case Qt.Key_Enter: - case Qt.Key_Return: - toggleOrGo(); - clearAddressLineTimer.start(); - event.accepted = true - break - } - } - - } - - Timer { - // Delay clearing address line so as to avoid flicker of "not connected" being displayed after entering an address. - id: clearAddressLineTimer - running: false - interval: 100 // ms - repeat: false - onTriggered: { - addressLine.text = ""; - isCursorVisible = false; - } - } - - function updateLocationText(enteringAddress) { - if (enteringAddress) { - notice.text = "Go to a place, @user, path or network address"; - notice.color = "#ffffff"; // hifiStyleConstants.colors.baseGrayHighlight; - location.visible = false; - } else { - notice.text = AddressManager.isConnected ? "YOUR LOCATION:" : "NOT CONNECTED"; - notice.color = AddressManager.isConnected ? hifiStyleConstants.colors.blueHighlight : hifiStyleConstants.colors.redHighlight; - // Display hostname, which includes ip address, localhost, and other non-placenames. - location.text = (AddressManager.placename || AddressManager.hostname || '') + (AddressManager.pathname ? AddressManager.pathname.match(/\/[^\/]+/)[0] : ''); - location.visible = true; - } - } - -} diff --git a/interface/resources/qml/+android/LoginDialog.qml b/interface/resources/qml/+android/LoginDialog.qml deleted file mode 100644 index 567cca9bcf..0000000000 --- a/interface/resources/qml/+android/LoginDialog.qml +++ /dev/null @@ -1,95 +0,0 @@ -// -// LoginDialog.qml -// -// Created by David Rowe on 3 Jun 2015 -// Copyright 2015 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -import Hifi 1.0 -import QtQuick 2.4 - -import "controls-uit" -import "styles-uit" -import "windows" - -import "LoginDialog" - -ModalWindow { - id: root - HifiConstants { id: hifi } - - objectName: "LoginDialog" - implicitWidth: 1560 - implicitHeight: 450 - y:0 - destroyOnCloseButton: true - destroyOnHidden: true - visible: true - - property string iconText: "" - property int iconSize: 105 - - property string title: "" - property int titleWidth: 0 - - keyboardOverride: true // Disable ModalWindow's keyboard. - - function tryDestroy() { - Controller.setVPadHidden(false); - root.destroy(); - } - - LoginDialog { - id: loginDialog - - Loader { - id: bodyLoader - source: loginDialog.isSteamRunning() ? "LoginDialog/+android/SignInBody.qml" : "LoginDialog/+android/LinkAccountBody.qml" - } - } - - Component.onCompleted: { - this.anchors.centerIn = undefined; - this.y = 150; - this.x = (parent.width - this.width) / 2; - Controller.setVPadHidden(true); - } - - Keys.onPressed: { - if (!visible) { - return - } - - if (event.modifiers === Qt.ControlModifier) - switch (event.key) { - case Qt.Key_A: - event.accepted = true - detailedText.selectAll() - break - case Qt.Key_C: - event.accepted = true - detailedText.copy() - break - case Qt.Key_Period: - if (Qt.platform.os === "osx") { - event.accepted = true - content.reject() - } - break - } else switch (event.key) { - case Qt.Key_Escape: - case Qt.Key_Back: - event.accepted = true - destroy() - break - - case Qt.Key_Enter: - case Qt.Key_Return: - event.accepted = true - break - } - } -} diff --git a/interface/resources/qml/controls/FlickableWebViewCore.qml b/interface/resources/qml/controls/FlickableWebViewCore.qml index 8e7db44b7d..943f15e1de 100644 --- a/interface/resources/qml/controls/FlickableWebViewCore.qml +++ b/interface/resources/qml/controls/FlickableWebViewCore.qml @@ -21,6 +21,7 @@ Item { signal newViewRequestedCallback(var request) signal loadingChangedCallback(var loadRequest) + width: parent.width property bool interactive: false @@ -29,6 +30,10 @@ Item { id: hifi } + function stop() { + webViewCore.stop(); + } + function unfocus() { webViewCore.runJavaScript("if (document.activeElement) document.activeElement.blur();", function(result) { console.log('unfocus completed: ', result); diff --git a/interface/resources/qml/controls/WebView.qml b/interface/resources/qml/controls/WebView.qml index 931c64e1ef..71bf69fdc8 100644 --- a/interface/resources/qml/controls/WebView.qml +++ b/interface/resources/qml/controls/WebView.qml @@ -21,6 +21,10 @@ Item { property bool passwordField: false property alias flickable: webroot.interactive + function stop() { + webroot.stop(); + } + // FIXME - Keyboard HMD only: Make Interface either set keyboardRaised property directly in OffscreenQmlSurface // or provide HMDinfo object to QML in RenderableWebEntityItem and do the following. /* diff --git a/interface/resources/qml/hifi/+android/ActionBar.qml b/interface/resources/qml/hifi/+android/ActionBar.qml new file mode 100644 index 0000000000..d487901d6f --- /dev/null +++ b/interface/resources/qml/hifi/+android/ActionBar.qml @@ -0,0 +1,71 @@ +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import QtQuick.Controls.Styles 1.4 +import QtQuick.Layouts 1.3 +import Qt.labs.settings 1.0 +import "../../styles-uit" +import "../../controls-uit" as HifiControlsUit +import "../../controls" as HifiControls +import ".." + +Item { + id: actionBar + x:0 + y:0 + width: 300 + height: 300 + z: -1 + + signal sendToScript(var message); + signal windowClosed(); + + property bool shown: true + + onShownChanged: { + actionBar.visible = shown; + } + + Rectangle { + anchors.fill : parent + color: "transparent" + Flow { + id: flowMain + spacing: 10 + flow: Flow.TopToBottom + layoutDirection: Flow.TopToBottom + anchors.fill: parent + anchors.margins: 4 + } + } + + Component.onCompleted: { + // put on bottom + x = 7; + y = 7; + width = 300; + height = 300; + } + + function addButton(properties) { + var component = Qt.createComponent("button.qml"); + if (component.status == Component.Ready) { + var button = component.createObject(flowMain); + // copy all properites to button + var keys = Object.keys(properties).forEach(function (key) { + button[key] = properties[key]; + }); + return button; + } else if( component.status == Component.Error) { + console.log("Load button errors " + component.errorString()); + } + } + + function urlHelper(src) { + if (src.match(/\bhttp/)) { + return src; + } else { + return "../../../" + src; + } + } + +} diff --git a/interface/resources/qml/hifi/+android/AudioBar.qml b/interface/resources/qml/hifi/+android/AudioBar.qml index f524595ef5..6cc17fccf7 100644 --- a/interface/resources/qml/hifi/+android/AudioBar.qml +++ b/interface/resources/qml/hifi/+android/AudioBar.qml @@ -38,13 +38,26 @@ Item { } } - Component.onCompleted: { - // put on bottom - x = 0; - y = 0; + function relocateAndResize(newWindowWidth, newWindowHeight) { + x = newWindowWidth-328; + y = 19; width = 300; height = 300; } + + function onWindowGeometryChanged(rect) { + relocateAndResize(rect.width, rect.height); + } + + Component.onCompleted: { + relocateAndResize(parent.width, parent.height); + Window.geometryChanged.connect(onWindowGeometryChanged); // In devices with bars appearing at startup we should listen for this + } + + Component.onDestruction: { + Window.geometryChanged.disconnect(onWindowGeometryChanged); + } + function addButton(properties) { var component = Qt.createComponent("button.qml"); diff --git a/interface/resources/qml/hifi/+android/avatarSelection.qml b/interface/resources/qml/hifi/+android/avatarSelection.qml deleted file mode 100644 index afa5634575..0000000000 --- a/interface/resources/qml/hifi/+android/avatarSelection.qml +++ /dev/null @@ -1,179 +0,0 @@ -// -// avatarSelection.qml -// interface/resources/qml/android -// -// Created by Gabriel Calero & Cristian Duarte on 21 Sep 2017 -// Copyright 2017 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// -import QtQuick 2.5 -import QtQuick.Layouts 1.3 -import Hifi 1.0 - -import "../../styles" -import "." -import ".." -import ".." as QmlHifi -import "../../styles-uit" as HifiStyles - - -Item { - - id: top - - HifiConstants { id: android } - width: parent ? parent.width - android.dimen.windowLessWidth : 0 - height: parent ? parent.height - android.dimen.windowLessHeight : 0 - z: android.dimen.windowZ - anchors { horizontalCenter: parent.horizontalCenter; bottom: parent.bottom } - - signal sendToScript(var message); - - property bool shown: true - - onShownChanged: { - top.visible = shown; - } - - - HifiConstants { id: hifi } - HifiStyles.HifiConstants { id: hifiStyleConstants } - - property int cardWidth: 250 *3; - property int cardHeight: 240 *3; - property int gap: 14 *3; - - property var avatarsArray: []; - property var extraOptionsArray: []; - - function hide() { - shown = false; - sendToScript ({ method: "hide" }); - } - - Rectangle { - - width: parent ? parent.width : 0 - height: parent ? parent.height : 0 - - MouseArea { - anchors.fill: parent - } - - gradient: Gradient { - GradientStop { position: 0.0; color: android.color.gradientTop } - GradientStop { position: 1.0; color: android.color.gradientBottom } - } - - QmlHifi.WindowHeader { - id: header - iconSource: "../../../../icons/avatar-i.svg" - titleText: "AVATAR" - } - - ListModel { id: avatars } - - ListView { - id: scroll - height: 250*3 - property int stackedCardShadowHeight: 10*3; - spacing: gap; - clip: true; - anchors { - left: parent.left - right: parent.right - top: header.bottom - topMargin: gap - leftMargin: gap - rightMargin: gap - } - model: avatars; - orientation: ListView.Horizontal; - delegate: QmlHifi.AvatarOption { - type: model.type; - thumbnailUrl: model.thumbnailUrl; - avatarUrl: model.avatarUrl; - avatarName: model.avatarName; - avatarSelected: model.avatarSelected; - methodName: model.methodName; - actionText: model.actionText; - } - highlightMoveDuration: -1; - highlightMoveVelocity: -1; - } - - } - - function escapeRegExp(str) { - return str.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1"); - } - function replaceAll(str, find, replace) { - return str.replace(new RegExp(escapeRegExp(find), 'g'), replace); - } - - function refreshSelected(selectedAvatarUrl) { - // URL as ID? - avatarsArray.forEach(function (avatarData) { - avatarData.avatarSelected = (selectedAvatarUrl == avatarData.avatarUrl); - console.log('[avatarSelection] avatar : ', avatarData.avatarName, ' is selected? ' , avatarData.avatarSelected); - }); - } - - function addAvatar(name, thumbnailUrl, avatarUrl) { - avatarsArray.push({ - type: "avatar", - thumbnailUrl: thumbnailUrl, - avatarUrl: avatarUrl, - avatarName: name, - avatarSelected: false, - methodName: "", - actionText: "" - }); - } - - function showAvatars() { - avatars.clear(); - avatarsArray.forEach(function (avatarData) { - avatars.append(avatarData); - console.log('[avatarSelection] adding avatar to model: ', JSON.stringify(avatarData)); - }); - extraOptionsArray.forEach(function (extraData) { - avatars.append(extraData); - console.log('[avatarSelection] adding extra option to model: ', JSON.stringify(extraData)); - }); - } - - function addExtraOption(showName, thumbnailUrl, methodNameWhenClicked, actionText) { - extraOptionsArray.push({ - type: "extra", - thumbnailUrl: thumbnailUrl, - avatarUrl: "", - avatarName: showName, - avatarSelected: false, - methodName: methodNameWhenClicked, - actionText: actionText - }); - } - - function fromScript(message) { - //console.log("[CHAT] fromScript " + JSON.stringify(message)); - switch (message.type) { - case "addAvatar": - addAvatar(message.name, message.thumbnailUrl, message.avatarUrl); - break; - case "addExtraOption": - //(showName, thumbnailUrl, methodNameWhenClicked, actionText) - addExtraOption(message.showName, message.thumbnailUrl, message.methodNameWhenClicked, message.actionText); - break; - case "refreshSelected": - refreshSelected(message.selectedAvatarUrl); - break; - case "showAvatars": - showAvatars(); - break; - default: - } - } -} \ No newline at end of file diff --git a/interface/resources/qml/hifi/+android/bottomHudOptions.qml b/interface/resources/qml/hifi/+android/bottomHudOptions.qml index 860298149f..22beccf531 100644 --- a/interface/resources/qml/hifi/+android/bottomHudOptions.qml +++ b/interface/resources/qml/hifi/+android/bottomHudOptions.qml @@ -47,17 +47,14 @@ Item { spacing: 0 flow: Flow.LeftToRight layoutDirection: Flow.LeftToRight - anchors.fill: parent + anchors.horizontalCenter: parent.horizontalCenter anchors.margins: 12 Rectangle { id: hideButton - height: android.dimen.headerHideWidth - width: android.dimen.headerHideHeight + height: android.dimen.headerHideHeight + width: android.dimen.headerHideWidth color: "#00000000" - anchors { - horizontalCenter: parent.horizontalCenter - } Image { id: hideIcon source: "../../../icons/show-up.svg" diff --git a/interface/resources/qml/hifi/+android/bottombar.qml b/interface/resources/qml/hifi/+android/bottombar.qml deleted file mode 100644 index 66117d0389..0000000000 --- a/interface/resources/qml/hifi/+android/bottombar.qml +++ /dev/null @@ -1,151 +0,0 @@ -// -// bottomHudOptions.qml -// interface/resources/qml/android -// -// Created by Gabriel Calero & Cristian Duarte on 19 Jan 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 -// - -import Hifi 1.0 -import QtQuick 2.5 -import QtQuick.Controls 1.4 -import QtQuick.Controls.Styles 1.4 -import QtQuick.Layouts 1.3 -import Qt.labs.settings 1.0 -import "../../styles" as Styles -import "../../styles-uit" -import "../../controls-uit" as HifiControlsUit -import "../../controls" as HifiControls -import ".." -import "." - -Item { - id: bar - x:0 - height: 255 - - property bool shown: true - - signal sendToScript(var message); - - onShownChanged: { - bar.visible = shown; - } - - function hide() { - //shown = false; - sendToScript({ method: "hide" }); - } - - Styles.HifiConstants { id: hifi } - HifiConstants { id: android } - MouseArea { - anchors.fill: parent - } - - Rectangle { - id: background - anchors.fill : parent - color: "#FF000000" - border.color: "#FFFFFF" - anchors.bottomMargin: -1 - anchors.leftMargin: -1 - anchors.rightMargin: -1 - Flow { - id: flowMain - spacing: 10 - anchors.fill: parent - anchors.topMargin: 12 - anchors.bottomMargin: 12 - anchors.rightMargin: 12 - anchors.leftMargin: 72 - } - - - Rectangle { - id: hideButton - height: android.dimen.headerHideWidth - width: android.dimen.headerHideHeight - color: "#00000000" - anchors { - right: parent.right - rightMargin: android.dimen.headerHideRightMargin - top: parent.top - topMargin: android.dimen.headerHideTopMargin - } - - Image { - id: hideIcon - source: "../../../icons/hide.svg" - width: android.dimen.headerHideIconWidth - height: android.dimen.headerHideIconHeight - anchors { - horizontalCenter: parent.horizontalCenter - top: parent.top - } - } - FiraSansRegular { - anchors { - top: hideIcon.bottom - horizontalCenter: hideIcon.horizontalCenter - topMargin: 12 - } - text: "HIDE" - color: "#FFFFFF" - font.pixelSize: hifi.fonts.pixelSize * 2.5; - } - - MouseArea { - anchors.fill: parent - onClicked: { - hide(); - } - } - } - } - - function relocateAndResize(newWindowWidth, newWindowHeight) { - width = newWindowWidth; - y = newWindowHeight - height; - } - - function onWindowGeometryChanged(rect) { - relocateAndResize(rect.width, rect.height); - } - - Component.onCompleted: { - // put on bottom - relocateAndResize(Window.innerWidth, Window.innerHeight); - Window.geometryChanged.connect(onWindowGeometryChanged); // In devices with bars appearing at startup we should listen for this - } - - Component.onDestruction: { - Window.geometryChanged.disconnect(onWindowGeometryChanged); - } - - function addButton(properties) { - var component = Qt.createComponent("button.qml"); - if (component.status == Component.Ready) { - var button = component.createObject(flowMain); - // copy all properites to button - var keys = Object.keys(properties).forEach(function (key) { - button[key] = properties[key]; - }); - return button; - } else if( component.status == Component.Error) { - console.log("Load button errors " + component.errorString()); - } - } - - function urlHelper(src) { - if (src.match(/\bhttp/)) { - return src; - } else { - return "../../../" + src; - } - } - -} diff --git a/interface/resources/qml/hifi/+android/button.qml b/interface/resources/qml/hifi/+android/button.qml index 3e9ce39351..a4c65b3c6f 100644 --- a/interface/resources/qml/hifi/+android/button.qml +++ b/interface/resources/qml/hifi/+android/button.qml @@ -32,6 +32,9 @@ Item { property string hoverTextColor: "#ffffff" property string activeTextColor: "#ffffff" property string activeHoverTextColor: "#ffffff" + property string fontFamily: "FiraSans" + property bool fontBold: false + property int bottomMargin: 30 property bool isEntered: false @@ -92,8 +95,8 @@ Item { id: text color: "#ffffff" text: button.text - font.family: "FiraSans" - //font.bold: true + font.family: button.fontFamily + font.bold: button.fontBold font.pixelSize: textSize anchors.bottom: parent.bottom anchors.bottomMargin: bottomMargin @@ -143,15 +146,12 @@ Item { PropertyChanges { target: buttonBg - //color: "#cfcfcf" - //opacity: 1 color: button.hoverBgColor opacity: button.hoverBgOpacity } PropertyChanges { target: text - //color: "#ffffff" color: button.hoverTextColor text: button.hoverText } @@ -166,15 +166,12 @@ Item { PropertyChanges { target: buttonBg - //color: "#1fc6a6" - //opacity: 1 color: button.activeBgColor opacity: button.activeBgOpacity } PropertyChanges { target: text - //color: "#333333" color: button.activeTextColor text: button.activeText } @@ -189,15 +186,12 @@ Item { PropertyChanges { target: buttonBg - //color: "#ff0000" - //opacity: 1 color: button.activeHoverBgColor opacity: button.activeHoverBgOpacity } PropertyChanges { target: text - //color: "#333333" color: button.activeHoverTextColor text: button.activeHoverText } @@ -212,15 +206,12 @@ Item { PropertyChanges { target: buttonBg - //color: "#9A9A9A" - //opacity: 0.1 color: button.bgColor opacity: button.bgOpacity } PropertyChanges { target: text - //color: "#ffffff" color: button.textColor text: button.text } diff --git a/interface/resources/qml/hifi/+android/modesbar.qml b/interface/resources/qml/hifi/+android/modesbar.qml index fe71314ece..994bf1efe4 100644 --- a/interface/resources/qml/hifi/+android/modesbar.qml +++ b/interface/resources/qml/hifi/+android/modesbar.qml @@ -10,24 +10,25 @@ import ".." Item { id: modesbar - y:60 - Rectangle { - anchors.fill : parent - color: "transparent" - Flow { - id: flowMain - spacing: 0 - flow: Flow.TopToBottom - layoutDirection: Flow.TopToBottom - anchors.fill: parent - anchors.margins: 4 - } - } + y:5 + + function relocateAndResize(newWindowWidth, newWindowHeight) { + width = 300; + height = 300; + x = newWindowWidth - 565; + } + + function onWindowGeometryChanged(rect) { + relocateAndResize(rect.width, rect.height); + } Component.onCompleted: { - width = 300 + 30; // That 30 is extra regardless the qty of items shown - height = 300 + 30; - x=Window.innerWidth - width; + relocateAndResize(parent.width, parent.height); + Window.geometryChanged.connect(onWindowGeometryChanged); // In devices with bars appearing at startup we should listen for this + } + + Component.onDestruction: { + Window.geometryChanged.disconnect(onWindowGeometryChanged); } function addButton(properties) { @@ -35,7 +36,7 @@ Item { console.log("load button"); if (component.status == Component.Ready) { console.log("load button 2"); - var button = component.createObject(flowMain); + var button = component.createObject(modesbar); // copy all properites to button var keys = Object.keys(properties).forEach(function (key) { button[key] = properties[key]; @@ -59,14 +60,12 @@ Item { function fromScript(message) { switch (message.type) { - case "allButtonsShown": - modesbar.height = flowMain.children.length * 300 + 30; // That 30 is extra regardless the qty of items shown - break; - case "inactiveButtonsHidden": - modesbar.height = 300 + 30; - break; + case "switch": + // message.params.to + // still not needed + break; default: - break; + break; } } diff --git a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml index 6b48f8d51d..d79b8d09fa 100644 --- a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml +++ b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml @@ -729,7 +729,7 @@ Rectangle { } lightboxPopup.button2text = "CONFIRM"; lightboxPopup.button2method = function() { - MyAvatar.skeletonModelURL = ''; + MyAvatar.useFullAvatarURL(''); root.activeView = "giftAsset"; lightboxPopup.visible = false; }; diff --git a/interface/src/AndroidHelper.cpp b/interface/src/AndroidHelper.cpp new file mode 100644 index 0000000000..b04a6ff2c4 --- /dev/null +++ b/interface/src/AndroidHelper.cpp @@ -0,0 +1,20 @@ +// +// AndroidHelper.cpp +// interface/src +// +// Created by Gabriel Calero & Cristian Duarte on 3/30/18. +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#include "AndroidHelper.h" +#include + +void AndroidHelper::requestActivity(const QString &activityName) { + emit androidActivityRequested(activityName); +} + +void AndroidHelper::goBackFromAndroidActivity() { + emit backFromAndroidActivity(); +} \ No newline at end of file diff --git a/interface/src/AndroidHelper.h b/interface/src/AndroidHelper.h new file mode 100644 index 0000000000..34f32b461b --- /dev/null +++ b/interface/src/AndroidHelper.h @@ -0,0 +1,37 @@ +// +// AndroidHelper.h +// interface/src +// +// Created by Gabriel Calero & Cristian Duarte on 3/30/18. +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_Android_Helper_h +#define hifi_Android_Helper_h + +#include + +class AndroidHelper : public QObject { + Q_OBJECT +public: + static AndroidHelper& instance() { + static AndroidHelper instance; + return instance; + } + void requestActivity(const QString &activityName); + void goBackFromAndroidActivity(); + + AndroidHelper(AndroidHelper const&) = delete; + void operator=(AndroidHelper const&) = delete; +signals: + void androidActivityRequested(const QString &activityName); + void backFromAndroidActivity(); + +private: + AndroidHelper() {} +}; + +#endif \ No newline at end of file diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 6c11286d03..2e20643192 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -252,6 +252,7 @@ extern "C" { #if defined(Q_OS_ANDROID) #include +#include "AndroidHelper.h" #endif enum ApplicationEvent { @@ -1258,6 +1259,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo connect(scriptEngines, &ScriptEngines::scriptsReloading, scriptEngines, [this] { getEntities()->reloadEntityScripts(); + loadAvatarScripts(getMyAvatar()->getScriptUrls()); }, Qt::QueuedConnection); connect(scriptEngines, &ScriptEngines::scriptLoadError, @@ -2731,6 +2733,9 @@ void Application::initializeUi() { offscreenUi->resume(); connect(_window, &MainWindow::windowGeometryChanged, [this](const QRect& r){ resizeGL(); + if (_touchscreenVirtualPadDevice) { + _touchscreenVirtualPadDevice->resize(); + } }); // This will set up the input plugins UI @@ -3166,8 +3171,13 @@ void Application::handleSandboxStatus(QNetworkReply* reply) { // If this is a first run we short-circuit the address passed in if (firstRun.get()) { +#if defined(Q_OS_ANDROID) + qCDebug(interfaceapp) << "First run... going to" << qPrintable(addressLookupString.isEmpty() ? QString("default location") : addressLookupString); + DependencyManager::get()->loadSettings(addressLookupString); +#else DependencyManager::get()->goToEntry(); sentTo = SENT_TO_ENTRY; +#endif firstRun.set(false); } else { @@ -3737,6 +3747,12 @@ void Application::keyPressEvent(QKeyEvent* event) { void Application::keyReleaseEvent(QKeyEvent* event) { _keysPressed.remove(event->key()); +#if defined(Q_OS_ANDROID) + if (event->key() == Qt::Key_Back) { + event->accept(); + openAndroidActivity("Home"); + } +#endif _controllerScriptingInterface->emitKeyReleaseEvent(event); // send events to any registered scripts // if one of our scripts have asked to capture this event, then stop processing it @@ -4804,6 +4820,31 @@ void Application::init() { }, Qt::QueuedConnection); } +void Application::loadAvatarScripts(const QVector& urls) { + auto scriptEngines = DependencyManager::get(); + auto runningScripts = scriptEngines->getRunningScripts(); + for (auto url : urls) { + int index = runningScripts.indexOf(url); + if (index < 0) { + auto scriptEnginePointer = scriptEngines->loadScript(url, false); + if (scriptEnginePointer) { + scriptEnginePointer->setType(ScriptEngine::Type::AVATAR); + } + } + } +} + +void Application::unloadAvatarScripts() { + auto scriptEngines = DependencyManager::get(); + auto urls = scriptEngines->getRunningScripts(); + for (auto url : urls) { + auto scriptEngine = scriptEngines->getScriptEngine(url); + if (scriptEngine->getType() == ScriptEngine::Type::AVATAR) { + scriptEngines->stopScript(url, false); + } + } +} + void Application::updateLOD(float deltaTime) const { PerformanceTimer perfTimer("LOD"); // adjust it unless we were asked to disable this feature, or if we're currently in throttleRendering mode @@ -5176,6 +5217,78 @@ void Application::updateDialogs(float deltaTime) const { } } +void Application::updateSecondaryCameraViewFrustum() { + // TODO: Fix this by modeling the way the secondary camera works on how the main camera works + // ie. Use a camera object stored in the game logic and informs the Engine on where the secondary + // camera should be. + + // Code based on SecondaryCameraJob + auto renderConfig = _renderEngine->getConfiguration(); + assert(renderConfig); + auto camera = dynamic_cast(renderConfig->getConfig("SecondaryCamera")); + assert(camera); + + if (!camera->isEnabled()) { + _hasSecondaryViewFrustum = false; + return; + } + + if (camera->mirrorProjection && !camera->attachedEntityId.isNull()) { + auto entityScriptingInterface = DependencyManager::get(); + auto entityProperties = entityScriptingInterface->getEntityProperties(camera->attachedEntityId); + glm::vec3 mirrorPropertiesPosition = entityProperties.getPosition(); + glm::quat mirrorPropertiesRotation = entityProperties.getRotation(); + glm::vec3 mirrorPropertiesDimensions = entityProperties.getDimensions(); + glm::vec3 halfMirrorPropertiesDimensions = 0.5f * mirrorPropertiesDimensions; + + // setup mirror from world as inverse of world from mirror transformation using inverted x and z for mirrored image + // TODO: we are assuming here that UP is world y-axis + glm::mat4 worldFromMirrorRotation = glm::mat4_cast(mirrorPropertiesRotation) * glm::scale(vec3(-1.0f, 1.0f, -1.0f)); + glm::mat4 worldFromMirrorTranslation = glm::translate(mirrorPropertiesPosition); + glm::mat4 worldFromMirror = worldFromMirrorTranslation * worldFromMirrorRotation; + glm::mat4 mirrorFromWorld = glm::inverse(worldFromMirror); + + // get mirror camera position by reflecting main camera position's z coordinate in mirror space + glm::vec3 mainCameraPositionWorld = getCamera().getPosition(); + glm::vec3 mainCameraPositionMirror = vec3(mirrorFromWorld * vec4(mainCameraPositionWorld, 1.0f)); + glm::vec3 mirrorCameraPositionMirror = vec3(mainCameraPositionMirror.x, mainCameraPositionMirror.y, + -mainCameraPositionMirror.z); + glm::vec3 mirrorCameraPositionWorld = vec3(worldFromMirror * vec4(mirrorCameraPositionMirror, 1.0f)); + + // set frustum position to be mirrored camera and set orientation to mirror's adjusted rotation + glm::quat mirrorCameraOrientation = glm::quat_cast(worldFromMirrorRotation); + _secondaryViewFrustum.setPosition(mirrorCameraPositionWorld); + _secondaryViewFrustum.setOrientation(mirrorCameraOrientation); + + // build frustum using mirror space translation of mirrored camera + float nearClip = mirrorCameraPositionMirror.z + mirrorPropertiesDimensions.z * 2.0f; + glm::vec3 upperRight = halfMirrorPropertiesDimensions - mirrorCameraPositionMirror; + glm::vec3 bottomLeft = -halfMirrorPropertiesDimensions - mirrorCameraPositionMirror; + glm::mat4 frustum = glm::frustum(bottomLeft.x, upperRight.x, bottomLeft.y, upperRight.y, nearClip, camera->farClipPlaneDistance); + _secondaryViewFrustum.setProjection(frustum); + } else { + if (!camera->attachedEntityId.isNull()) { + auto entityScriptingInterface = DependencyManager::get(); + auto entityProperties = entityScriptingInterface->getEntityProperties(camera->attachedEntityId); + _secondaryViewFrustum.setPosition(entityProperties.getPosition()); + _secondaryViewFrustum.setOrientation(entityProperties.getRotation()); + } else { + _secondaryViewFrustum.setPosition(camera->position); + _secondaryViewFrustum.setOrientation(camera->orientation); + } + + float aspectRatio = (float)camera->textureWidth / (float)camera->textureHeight; + _secondaryViewFrustum.setProjection(camera->vFoV, + aspectRatio, + camera->nearClipPlaneDistance, + camera->farClipPlaneDistance); + } + // Without calculating the bound planes, the secondary camera will use the same culling frustum as the main camera, + // which is not what we want here. + _secondaryViewFrustum.calculate(); + _hasSecondaryViewFrustum = true; +} + static bool domainLoadingInProgress = false; void Application::update(float deltaTime) { @@ -5529,6 +5642,11 @@ void Application::update(float deltaTime) { { QMutexLocker viewLocker(&_viewMutex); _myCamera.loadViewFrustum(_viewFrustum); + + // TODO: Fix this by modeling the way the secondary camera works on how the main camera works + // ie. Use a camera object stored in the game logic and informs the Engine on where the secondary + // camera should be. + updateSecondaryCameraViewFrustum(); } quint64 now = usecTimestampNow(); @@ -5542,6 +5660,8 @@ void Application::update(float deltaTime) { const quint64 TOO_LONG_SINCE_LAST_QUERY = 3 * USECS_PER_SECOND; bool queryIsDue = sinceLastQuery > TOO_LONG_SINCE_LAST_QUERY; bool viewIsDifferentEnough = !_lastQueriedViewFrustum.isVerySimilar(_viewFrustum); + viewIsDifferentEnough |= _hasSecondaryViewFrustum && !_lastQueriedSecondaryViewFrustum.isVerySimilar(_secondaryViewFrustum); + // if it's been a while since our last query or the view has significantly changed then send a query, otherwise suppress it if (queryIsDue || viewIsDifferentEnough) { _lastQueriedTime = now; @@ -5550,6 +5670,7 @@ void Application::update(float deltaTime) { } sendAvatarViewFrustum(); _lastQueriedViewFrustum = _viewFrustum; + _lastQueriedSecondaryViewFrustum = _secondaryViewFrustum; } } @@ -5722,8 +5843,16 @@ void Application::update(float deltaTime) { } void Application::sendAvatarViewFrustum() { + uint8_t numFrustums = 1; QByteArray viewFrustumByteArray = _viewFrustum.toByteArray(); - auto avatarPacket = NLPacket::create(PacketType::ViewFrustum, viewFrustumByteArray.size()); + + if (_hasSecondaryViewFrustum) { + ++numFrustums; + viewFrustumByteArray += _secondaryViewFrustum.toByteArray(); + } + + auto avatarPacket = NLPacket::create(PacketType::ViewFrustum, viewFrustumByteArray.size() + sizeof(numFrustums)); + avatarPacket->writePrimitive(numFrustums); avatarPacket->write(viewFrustumByteArray); DependencyManager::get()->broadcastToNodes(std::move(avatarPacket), NodeSet() << NodeType::AvatarMixer); @@ -5788,14 +5917,15 @@ void Application::queryOctree(NodeType_t serverType, PacketType packetType) { ViewFrustum viewFrustum; copyViewFrustum(viewFrustum); - _octreeQuery.setCameraPosition(viewFrustum.getPosition()); - _octreeQuery.setCameraOrientation(viewFrustum.getOrientation()); - _octreeQuery.setCameraFov(viewFrustum.getFieldOfView()); - _octreeQuery.setCameraAspectRatio(viewFrustum.getAspectRatio()); - _octreeQuery.setCameraNearClip(viewFrustum.getNearClip()); - _octreeQuery.setCameraFarClip(viewFrustum.getFarClip()); - _octreeQuery.setCameraEyeOffsetPosition(glm::vec3()); - _octreeQuery.setCameraCenterRadius(viewFrustum.getCenterRadius()); + _octreeQuery.setMainViewFrustum(viewFrustum); + + if (hasSecondaryViewFrustum()) { + copySecondaryViewFrustum(viewFrustum); + _octreeQuery.setSecondaryViewFrustum(viewFrustum); + } else { + _octreeQuery.clearSecondaryViewFrustum(); + } + auto lodManager = DependencyManager::get(); _octreeQuery.setOctreeSizeScale(lodManager->getOctreeSizeScale()); _octreeQuery.setBoundaryLevelAdjust(lodManager->getBoundaryLevelAdjust()); @@ -5879,6 +6009,11 @@ void Application::copyDisplayViewFrustum(ViewFrustum& viewOut) const { viewOut = _displayViewFrustum; } +void Application::copySecondaryViewFrustum(ViewFrustum& viewOut) const { + QMutexLocker viewLocker(&_viewMutex); + viewOut = _secondaryViewFrustum; +} + void Application::resetSensors(bool andReload) { DependencyManager::get()->reset(); DependencyManager::get()->reset(); @@ -7825,6 +7960,26 @@ void Application::switchDisplayMode() { _previousHMDWornStatus = currentHMDWornStatus; } +void Application::setShowBulletWireframe(bool value) { + _physicsEngine->setShowBulletWireframe(value); +} + +void Application::setShowBulletAABBs(bool value) { + _physicsEngine->setShowBulletAABBs(value); +} + +void Application::setShowBulletContactPoints(bool value) { + _physicsEngine->setShowBulletContactPoints(value); +} + +void Application::setShowBulletConstraints(bool value) { + _physicsEngine->setShowBulletConstraints(value); +} + +void Application::setShowBulletConstraintLimits(bool value) { + _physicsEngine->setShowBulletConstraintLimits(value); +} + void Application::startHMDStandBySession() { _autoSwitchDisplayModeSupportedHMDPlugin->startStandBySession(); } @@ -8006,4 +8161,29 @@ void Application::saveNextPhysicsStats(QString filename) { _physicsEngine->saveNextPhysicsStats(filename); } +void Application::openAndroidActivity(const QString& activityName) { +#if defined(Q_OS_ANDROID) + AndroidHelper::instance().requestActivity(activityName); +#endif +} + +#if defined(Q_OS_ANDROID) +void Application::enterBackground() { + QMetaObject::invokeMethod(DependencyManager::get().data(), + "stop", Qt::BlockingQueuedConnection); + //GC: commenting it out until we fix it + //getActiveDisplayPlugin()->deactivate(); +} +void Application::enterForeground() { + QMetaObject::invokeMethod(DependencyManager::get().data(), + "start", Qt::BlockingQueuedConnection); + //GC: commenting it out until we fix it + /*if (!getActiveDisplayPlugin() || !getActiveDisplayPlugin()->activate()) { + qWarning() << "Could not re-activate display plugin"; + }*/ + +} +#endif + +#include "Application_jni.cpp" #include "Application.moc" diff --git a/interface/src/Application.h b/interface/src/Application.h index 6d611bc8e2..83e719333d 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -149,6 +149,8 @@ public: void initializeRenderEngine(); void initializeUi(); + void updateSecondaryCameraViewFrustum(); + void updateCamera(RenderArgs& renderArgs, float deltaTime); void paintGL(); void resizeGL(); @@ -173,11 +175,14 @@ public: Camera& getCamera() { return _myCamera; } const Camera& getCamera() const { return _myCamera; } // Represents the current view frustum of the avatar. - void copyViewFrustum(ViewFrustum& viewOut) const; + void copyViewFrustum(ViewFrustum& viewOut) const override; + void copySecondaryViewFrustum(ViewFrustum& viewOut) const override; + bool hasSecondaryViewFrustum() const override { return _hasSecondaryViewFrustum; } // Represents the view frustum of the current rendering pass, // which might be different from the viewFrustum, i.e. shadowmap // passes, mirror window passes, etc void copyDisplayViewFrustum(ViewFrustum& viewOut) const; + const OctreePacketProcessor& getOctreePacketProcessor() const { return _octreeProcessor; } QSharedPointer getEntities() const { return DependencyManager::get(); } QUndoStack* getUndoStack() { return &_undoStack; } @@ -243,6 +248,7 @@ public: bool isAboutToQuit() const { return _aboutToQuit; } bool isPhysicsEnabled() const { return _physicsEnabled; } + PhysicsEnginePointer getPhysicsEngine() { return _physicsEngine; } // the isHMDMode is true whenever we use the interface from an HMD and not a standard flat display // rendering of several elements depend on that @@ -290,6 +296,14 @@ public: void replaceDomainContent(const QString& url); + void loadAvatarScripts(const QVector& urls); + void unloadAvatarScripts(); + +#if defined(Q_OS_ANDROID) + void enterBackground(); + void enterForeground(); +#endif + signals: void svoImportRequested(const QString& url); @@ -400,6 +414,8 @@ public slots: Q_INVOKABLE bool askBeforeSetAvatarUrl(const QString& avatarUrl) { return askToSetAvatarUrl(avatarUrl); } void updateVerboseLogging(); + Q_INVOKABLE void openAndroidActivity(const QString& activityName); + private slots: void onDesktopRootItemCreated(QQuickItem* qmlContext); @@ -453,6 +469,12 @@ private slots: void handleSandboxStatus(QNetworkReply* reply); void switchDisplayMode(); + void setShowBulletWireframe(bool value); + void setShowBulletAABBs(bool value); + void setShowBulletContactPoints(bool value); + void setShowBulletConstraints(bool value); + void setShowBulletConstraintLimits(bool value); + private: void init(); bool handleKeyEventForFocusedEntityOrOverlay(QEvent* event); @@ -552,8 +574,11 @@ private: mutable QMutex _viewMutex { QMutex::Recursive }; ViewFrustum _viewFrustum; // current state of view frustum, perspective, orientation, etc. - ViewFrustum _lastQueriedViewFrustum; /// last view frustum used to query octree servers (voxels) + ViewFrustum _lastQueriedViewFrustum; // last view frustum used to query octree servers ViewFrustum _displayViewFrustum; + ViewFrustum _secondaryViewFrustum; + ViewFrustum _lastQueriedSecondaryViewFrustum; // last secondary view frustum used to query octree servers + bool _hasSecondaryViewFrustum; quint64 _lastQueriedTime; OctreeQuery _octreeQuery { true }; // NodeData derived class for querying octee cells from octree servers diff --git a/interface/src/Application_jni.cpp b/interface/src/Application_jni.cpp new file mode 100644 index 0000000000..5e9f1ac29e --- /dev/null +++ b/interface/src/Application_jni.cpp @@ -0,0 +1,24 @@ +#if defined(Q_OS_ANDROID) + +#include +#include "AndroidHelper.h" + +extern "C" { + +JNIEXPORT void +Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeEnterBackground(JNIEnv *env, jobject obj) { + if (qApp) { + qApp->enterBackground(); + } +} + +JNIEXPORT void +Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeEnterForeground(JNIEnv *env, jobject obj) { + if (qApp) { + qApp->enterForeground(); + } +} + + +} +#endif \ No newline at end of file diff --git a/interface/src/AvatarBookmarks.h b/interface/src/AvatarBookmarks.h index 177e6e493e..7b47ea8af7 100644 --- a/interface/src/AvatarBookmarks.h +++ b/interface/src/AvatarBookmarks.h @@ -18,6 +18,10 @@ /**jsdoc * This API helps manage adding and deleting avatar bookmarks. * @namespace AvatarBookmarks + * + * @hifi-interface + * @hifi-client-entity + * */ class AvatarBookmarks: public Bookmarks, public Dependency { diff --git a/interface/src/Crashpad.cpp b/interface/src/Crashpad.cpp index e39cd42d81..45f1d0778f 100644 --- a/interface/src/Crashpad.cpp +++ b/interface/src/Crashpad.cpp @@ -11,6 +11,8 @@ #include "Crashpad.h" +#include + #include #if HAS_CRASHPAD @@ -20,7 +22,7 @@ #include #include -#include +#include #include #include @@ -28,28 +30,27 @@ #include #include +#include + using namespace crashpad; static const std::string BACKTRACE_URL { CMAKE_BACKTRACE_URL }; static const std::string BACKTRACE_TOKEN { CMAKE_BACKTRACE_TOKEN }; -static std::wstring gIPCPipe; - extern QString qAppFileName(); +CrashpadClient* client { nullptr }; std::mutex annotationMutex; crashpad::SimpleStringDictionary* crashpadAnnotations { nullptr }; -#include - LONG WINAPI vectoredExceptionHandler(PEXCEPTION_POINTERS pExceptionInfo) { + if (!client) { + return EXCEPTION_CONTINUE_SEARCH; + } + if (pExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_HEAP_CORRUPTION || pExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_STACK_BUFFER_OVERRUN) { - CrashpadClient client; - if (gIPCPipe.length()) { - client.SetHandlerIPCPipe(gIPCPipe); - } - client.DumpAndCrash(pExceptionInfo); + client->DumpAndCrash(pExceptionInfo); } return EXCEPTION_CONTINUE_SEARCH; @@ -60,7 +61,8 @@ bool startCrashHandler() { return false; } - CrashpadClient client; + assert(!client); + client = new CrashpadClient(); std::vector arguments; std::map annotations; @@ -96,12 +98,9 @@ bool startCrashHandler() { // Enable automated uploads. database->GetSettings()->SetUploadsEnabled(true); - bool result = client.StartHandler(handler, db, db, BACKTRACE_URL, annotations, arguments, true, true); - gIPCPipe = client.GetHandlerIPCPipe(); - AddVectoredExceptionHandler(0, vectoredExceptionHandler); - return result; + return client->StartHandler(handler, db, db, BACKTRACE_URL, annotations, arguments, true, true); } void setCrashAnnotation(std::string name, std::string value) { diff --git a/interface/src/LODManager.h b/interface/src/LODManager.h index e8737d92ae..8f88da63a8 100644 --- a/interface/src/LODManager.h +++ b/interface/src/LODManager.h @@ -9,11 +9,6 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -/**jsdoc - * The LOD class manages your Level of Detail functions within interface - * @namespace LODManager - */ - #ifndef hifi_LODManager_h #define hifi_LODManager_h @@ -39,10 +34,36 @@ const float ADJUST_LOD_MIN_SIZE_SCALE = DEFAULT_OCTREE_SIZE_SCALE * 0.04f; class AABox; +/**jsdoc + * The LOD class manages your Level of Detail functions within Interface. + * @namespace LODManager + * + * @hifi-interface + * @hifi-client-entity + * + * @property {number} presentTime Read-only. + * @property {number} engineRunTime Read-only. + * @property {number} gpuTime Read-only. + * @property {number} avgRenderTime Read-only. + * @property {number} fps Read-only. + * @property {number} lodLevel Read-only. + * @property {number} lodDecreaseFPS Read-only. + * @property {number} lodIncreaseFPS Read-only. + */ + class LODManager : public QObject, public Dependency { Q_OBJECT SINGLETON_DEPENDENCY + Q_PROPERTY(float presentTime READ getPresentTime) + Q_PROPERTY(float engineRunTime READ getEngineRunTime) + Q_PROPERTY(float gpuTime READ getGPUTime) + Q_PROPERTY(float avgRenderTime READ getAverageRenderTime) + Q_PROPERTY(float fps READ getMaxTheoreticalFPS) + Q_PROPERTY(float lodLevel READ getLODLevel) + Q_PROPERTY(float lodDecreaseFPS READ getLODDecreaseFPS) + Q_PROPERTY(float lodIncreaseFPS READ getLODIncreaseFPS) + public: /**jsdoc @@ -138,28 +159,6 @@ public: */ Q_INVOKABLE float getLODIncreaseFPS() const; - /**jsdoc - * @namespace LODManager - * @property {number} presentTime Read-only. - * @property {number} engineRunTime Read-only. - * @property {number} gpuTime Read-only. - * @property {number} avgRenderTime Read-only. - * @property {number} fps Read-only. - * @property {number} lodLevel Read-only. - * @property {number} lodDecreaseFPS Read-only. - * @property {number} lodIncreaseFPS Read-only. - */ - - Q_PROPERTY(float presentTime READ getPresentTime) - Q_PROPERTY(float engineRunTime READ getEngineRunTime) - Q_PROPERTY(float gpuTime READ getGPUTime) - Q_PROPERTY(float avgRenderTime READ getAverageRenderTime) - Q_PROPERTY(float fps READ getMaxTheoreticalFPS) - Q_PROPERTY(float lodLevel READ getLODLevel) - - Q_PROPERTY(float lodDecreaseFPS READ getLODDecreaseFPS) - Q_PROPERTY(float lodIncreaseFPS READ getLODIncreaseFPS) - float getPresentTime() const { return _presentTime; } float getEngineRunTime() const { return _engineRunTime; } float getGPUTime() const { return _gpuTime; } diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 50ff65ad1a..60d5abf260 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -735,6 +735,12 @@ Menu::Menu() { } addCheckableActionToQMenuAndActionHash(physicsOptionsMenu, MenuOption::PhysicsShowHulls, 0, false, qApp->getEntities().data(), SIGNAL(setRenderDebugHulls())); + addCheckableActionToQMenuAndActionHash(physicsOptionsMenu, MenuOption::PhysicsShowBulletWireframe, 0, false, qApp, SLOT(setShowBulletWireframe(bool))); + addCheckableActionToQMenuAndActionHash(physicsOptionsMenu, MenuOption::PhysicsShowBulletAABBs, 0, false, qApp, SLOT(setShowBulletAABBs(bool))); + addCheckableActionToQMenuAndActionHash(physicsOptionsMenu, MenuOption::PhysicsShowBulletContactPoints, 0, false, qApp, SLOT(setShowBulletContactPoints(bool))); + addCheckableActionToQMenuAndActionHash(physicsOptionsMenu, MenuOption::PhysicsShowBulletConstraints, 0, false, qApp, SLOT(setShowBulletConstraints(bool))); + addCheckableActionToQMenuAndActionHash(physicsOptionsMenu, MenuOption::PhysicsShowBulletConstraintLimits, 0, false, qApp, SLOT(setShowBulletConstraintLimits(bool))); + // Developer > Ask to Reset Settings addCheckableActionToQMenuAndActionHash(developerMenu, MenuOption::AskToResetSettings, 0, false); diff --git a/interface/src/Menu.h b/interface/src/Menu.h index c8c8ee42df..20375a71b2 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -143,6 +143,11 @@ namespace MenuOption { const QString PhysicsShowHulls = "Draw Collision Shapes"; const QString PhysicsShowOwned = "Highlight Simulation Ownership"; const QString VerboseLogging = "Verbose Logging"; + const QString PhysicsShowBulletWireframe = "Show Bullet Collision"; + const QString PhysicsShowBulletAABBs = "Show Bullet Bounding Boxes"; + const QString PhysicsShowBulletContactPoints = "Show Bullet Contact Points"; + const QString PhysicsShowBulletConstraints = "Show Bullet Constraints"; + const QString PhysicsShowBulletConstraintLimits = "Show Bullet Constraint Limits"; const QString PipelineWarnings = "Log Render Pipeline Warnings"; const QString Preferences = "General..."; const QString Quit = "Quit"; diff --git a/interface/src/ModelPackager.cpp b/interface/src/ModelPackager.cpp index 5f4c7526e0..0e34eebc80 100644 --- a/interface/src/ModelPackager.cpp +++ b/interface/src/ModelPackager.cpp @@ -156,9 +156,11 @@ bool ModelPackager::zipModel() { QByteArray nameField = _mapping.value(NAME_FIELD).toByteArray(); tempDir.mkpath(nameField + "/textures"); + tempDir.mkpath(nameField + "/scripts"); QDir fbxDir(tempDir.path() + "/" + nameField); QDir texDir(fbxDir.path() + "/textures"); - + QDir scriptDir(fbxDir.path() + "/scripts"); + // Copy textures listTextures(); if (!_textures.empty()) { @@ -166,6 +168,23 @@ bool ModelPackager::zipModel() { _texDir = _modelFile.path() + "/" + texdirField; copyTextures(_texDir, texDir); } + + // Copy scripts + QByteArray scriptField = _mapping.value(SCRIPT_FIELD).toByteArray(); + _mapping.remove(SCRIPT_FIELD); + if (scriptField.size() > 1) { + tempDir.mkpath(nameField + "/scripts"); + _scriptDir = _modelFile.path() + "/" + scriptField; + QDir wdir = QDir(_scriptDir); + _mapping.remove(SCRIPT_FIELD); + wdir.setSorting(QDir::Name | QDir::Reversed); + auto list = wdir.entryList(QDir::NoDotAndDotDot | QDir::AllEntries); + for (auto script : list) { + auto sc = tempDir.relativeFilePath(scriptDir.path()) + "/" + QUrl(script).fileName(); + _mapping.insertMulti(SCRIPT_FIELD, sc); + } + copyDirectoryContent(wdir, scriptDir); + } // Copy LODs QVariantHash lodField = _mapping.value(LOD_FIELD).toHash(); @@ -189,7 +208,10 @@ bool ModelPackager::zipModel() { // Correct FST _mapping[FILENAME_FIELD] = tempDir.relativeFilePath(newPath); _mapping[TEXDIR_FIELD] = tempDir.relativeFilePath(texDir.path()); - + + for (auto multi : _mapping.values(SCRIPT_FIELD)) { + multi.fromValue(tempDir.relativeFilePath(scriptDir.path()) + multi.toString()); + } // Copy FST QFile fst(tempDir.path() + "/" + nameField + ".fst"); if (fst.open(QIODevice::WriteOnly)) { @@ -237,7 +259,9 @@ void ModelPackager::populateBasicMapping(QVariantHash& mapping, QString filename if (!mapping.contains(TEXDIR_FIELD)) { mapping.insert(TEXDIR_FIELD, "."); } - + if (!mapping.contains(SCRIPT_FIELD)) { + mapping.insert(SCRIPT_FIELD, "."); + } // mixamo/autodesk defaults if (!mapping.contains(SCALE_FIELD)) { mapping.insert(SCALE_FIELD, 1.0); diff --git a/interface/src/ModelPackager.h b/interface/src/ModelPackager.h index 10942833f9..60b3825c4d 100644 --- a/interface/src/ModelPackager.h +++ b/interface/src/ModelPackager.h @@ -37,10 +37,12 @@ private: QFileInfo _fbxInfo; FSTReader::ModelType _modelType; QString _texDir; + QString _scriptDir; QVariantHash _mapping; std::unique_ptr _geometry; QStringList _textures; + QStringList _scripts; }; diff --git a/interface/src/ModelPropertiesDialog.cpp b/interface/src/ModelPropertiesDialog.cpp index ae352974ae..35b07aa2b2 100644 --- a/interface/src/ModelPropertiesDialog.cpp +++ b/interface/src/ModelPropertiesDialog.cpp @@ -43,6 +43,9 @@ _geometry(geometry) form->addRow("Texture Directory:", _textureDirectory = new QPushButton()); connect(_textureDirectory, SIGNAL(clicked(bool)), SLOT(chooseTextureDirectory())); + form->addRow("Script Directory:", _scriptDirectory = new QPushButton()); + connect(_scriptDirectory, SIGNAL(clicked(bool)), SLOT(chooseScriptDirectory())); + form->addRow("Scale:", _scale = new QDoubleSpinBox()); _scale->setMaximum(FLT_MAX); _scale->setSingleStep(0.01); @@ -100,6 +103,7 @@ QVariantHash ModelPropertiesDialog::getMapping() const { mapping.insert(TYPE_FIELD, getType()); mapping.insert(NAME_FIELD, _name->text()); mapping.insert(TEXDIR_FIELD, _textureDirectory->text()); + mapping.insert(SCRIPT_FIELD, _scriptDirectory->text()); mapping.insert(SCALE_FIELD, QString::number(_scale->value())); // update the joint indices @@ -157,6 +161,7 @@ void ModelPropertiesDialog::reset() { _name->setText(_originalMapping.value(NAME_FIELD).toString()); _textureDirectory->setText(_originalMapping.value(TEXDIR_FIELD).toString()); _scale->setValue(_originalMapping.value(SCALE_FIELD).toDouble()); + _scriptDirectory->setText(_originalMapping.value(SCRIPT_FIELD).toString()); QVariantHash jointHash = _originalMapping.value(JOINT_FIELD).toHash(); @@ -207,6 +212,20 @@ void ModelPropertiesDialog::chooseTextureDirectory() { _textureDirectory->setText(directory.length() == _basePath.length() ? "." : directory.mid(_basePath.length() + 1)); } +void ModelPropertiesDialog::chooseScriptDirectory() { + QString directory = QFileDialog::getExistingDirectory(this, "Choose Script Directory", + _basePath + "/" + _scriptDirectory->text()); + if (directory.isEmpty()) { + return; + } + if (!directory.startsWith(_basePath)) { + OffscreenUi::asyncWarning(NULL, "Invalid script directory", "Script directory must be child of base path."); + return; + } + _scriptDirectory->setText(directory.length() == _basePath.length() ? "." : directory.mid(_basePath.length() + 1)); +} + + void ModelPropertiesDialog::updatePivotJoint() { _pivotJoint->setEnabled(!_pivotAboutCenter->isChecked()); } diff --git a/interface/src/ModelPropertiesDialog.h b/interface/src/ModelPropertiesDialog.h index 11abc5ab54..e3c2d8ed6a 100644 --- a/interface/src/ModelPropertiesDialog.h +++ b/interface/src/ModelPropertiesDialog.h @@ -37,6 +37,7 @@ public: private slots: void reset(); void chooseTextureDirectory(); + void chooseScriptDirectory(); void updatePivotJoint(); void createNewFreeJoint(const QString& joint = QString()); @@ -52,6 +53,7 @@ private: FBXGeometry _geometry; QLineEdit* _name = nullptr; QPushButton* _textureDirectory = nullptr; + QPushButton* _scriptDirectory = nullptr; QDoubleSpinBox* _scale = nullptr; QDoubleSpinBox* _translationX = nullptr; QDoubleSpinBox* _translationY = nullptr; diff --git a/interface/src/SpeechRecognizer.h b/interface/src/SpeechRecognizer.h index d5f9031cfc..b22ab73837 100644 --- a/interface/src/SpeechRecognizer.h +++ b/interface/src/SpeechRecognizer.h @@ -24,6 +24,9 @@ /**jsdoc * @namespace SpeechRecognizer + * + * @hifi-interface + * @hifi-client-entity */ class SpeechRecognizer : public QObject, public Dependency { Q_OBJECT diff --git a/interface/src/audio/AudioScope.h b/interface/src/audio/AudioScope.h index ff8bfda6dd..41cee8d17d 100644 --- a/interface/src/audio/AudioScope.h +++ b/interface/src/audio/AudioScope.h @@ -28,6 +28,10 @@ class AudioScope : public QObject, public Dependency { /**jsdoc * The AudioScope API helps control the Audio Scope features in Interface * @namespace AudioScope + * + * @hifi-interface + * @hifi-client-entity + * * @property {number} scopeInput Read-only. * @property {number} scopeOutputLeft Read-only. * @property {number} scopeOutputRight Read-only. diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index b71c060465..087e23a933 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -155,9 +155,19 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { AvatarSharedPointer _avatar; }; - ViewFrustum cameraView; - qApp->copyDisplayViewFrustum(cameraView); - PrioritySortUtil::PriorityQueue sortedAvatars(cameraView, + + ViewFrustums views; + + ViewFrustum view; + qApp->copyCurrentViewFrustum(view); + views.push_back(view); + + if (qApp->hasSecondaryViewFrustum()) { + qApp->copySecondaryViewFrustum(view); + views.push_back(view); + } + + PrioritySortUtil::PriorityQueue sortedAvatars(views, AvatarData::_avatarSortCoefficientSize, AvatarData::_avatarSortCoefficientCenter, AvatarData::_avatarSortCoefficientAge); diff --git a/interface/src/avatar/AvatarManager.h b/interface/src/avatar/AvatarManager.h index d2655914d2..7f5aa00466 100644 --- a/interface/src/avatar/AvatarManager.h +++ b/interface/src/avatar/AvatarManager.h @@ -30,6 +30,9 @@ /**jsdoc * The AvatarManager API has properties and methods which manage Avatars within the same domain. * @namespace AvatarManager + * + * @hifi-interface + * @hifi-client-entity */ class AvatarManager : public AvatarHashMap { diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 249a765d92..15b220c63b 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -121,6 +121,19 @@ MyAvatar::MyAvatar(QThread* thread) : _skeletonModel = std::make_shared(this, nullptr); connect(_skeletonModel.get(), &Model::setURLFinished, this, &Avatar::setModelURLFinished); + connect(_skeletonModel.get(), &Model::setURLFinished, this, [this](bool success) { + if (success) { + qApp->unloadAvatarScripts(); + _shouldLoadScripts = true; + } + }); + connect(_skeletonModel.get(), &Model::rigReady, this, [this]() { + if (_shouldLoadScripts) { + auto geometry = getSkeletonModel()->getFBXGeometry(); + qApp->loadAvatarScripts(geometry.scripts); + _shouldLoadScripts = false; + } + }); connect(_skeletonModel.get(), &Model::rigReady, this, &Avatar::rigReady); connect(_skeletonModel.get(), &Model::rigReset, this, &Avatar::rigReset); @@ -1473,6 +1486,15 @@ void MyAvatar::setSkeletonModelURL(const QUrl& skeletonModelURL) { std::shared_ptr skeletonConnection = std::make_shared(); *skeletonConnection = QObject::connect(_skeletonModel.get(), &SkeletonModel::skeletonLoaded, [this, skeletonModelChangeCount, skeletonConnection]() { if (skeletonModelChangeCount == _skeletonModelChangeCount) { + + if (_fullAvatarModelName.isEmpty()) { + // Store the FST file name into preferences + const auto& mapping = _skeletonModel->getGeometry()->getMapping(); + if (mapping.value("name").isValid()) { + _fullAvatarModelName = mapping.value("name").toString(); + } + } + initHeadBones(); _skeletonModel->setCauterizeBoneSet(_headBoneSet); _fstAnimGraphOverrideUrl = _skeletonModel->getGeometry()->getAnimGraphOverrideUrl(); @@ -1535,12 +1557,7 @@ void MyAvatar::useFullAvatarURL(const QUrl& fullAvatarURL, const QString& modelN if (_fullAvatarURLFromPreferences != fullAvatarURL) { _fullAvatarURLFromPreferences = fullAvatarURL; - if (modelName.isEmpty()) { - QVariantHash fullAvatarFST = FSTReader::downloadMapping(_fullAvatarURLFromPreferences.toString()); - _fullAvatarModelName = fullAvatarFST["name"].toString(); - } else { - _fullAvatarModelName = modelName; - } + _fullAvatarModelName = modelName; } const QString& urlString = fullAvatarURL.toString(); @@ -1548,8 +1565,8 @@ void MyAvatar::useFullAvatarURL(const QUrl& fullAvatarURL, const QString& modelN setSkeletonModelURL(fullAvatarURL); UserActivityLogger::getInstance().changedModel("skeleton", urlString); } + markIdentityDataChanged(); - } void MyAvatar::setAttachmentData(const QVector& attachmentData) { @@ -2037,12 +2054,14 @@ void MyAvatar::preDisplaySide(RenderArgs* renderArgs) { _attachmentData[i].jointName.compare("RightEye", Qt::CaseInsensitive) == 0 || _attachmentData[i].jointName.compare("HeadTop_End", Qt::CaseInsensitive) == 0 || _attachmentData[i].jointName.compare("Face", Qt::CaseInsensitive) == 0) { + uint8_t modelRenderTagBits = shouldDrawHead ? render::ItemKey::TAG_BITS_0 : render::ItemKey::TAG_BITS_NONE; + modelRenderTagBits |= render::ItemKey::TAG_BITS_1; + _attachmentModels[i]->setVisibleInScene(true, qApp->getMain3DScene(), + modelRenderTagBits, false); - _attachmentModels[i]->setVisibleInScene(shouldDrawHead, qApp->getMain3DScene(), - render::ItemKey::TAG_BITS_NONE, true); - - _attachmentModels[i]->setCanCastShadow(shouldDrawHead, qApp->getMain3DScene(), - render::ItemKey::TAG_BITS_NONE, true); + uint8_t castShadowRenderTagBits = render::ItemKey::TAG_BITS_0 | render::ItemKey::TAG_BITS_1; + _attachmentModels[i]->setCanCastShadow(true, qApp->getMain3DScene(), + castShadowRenderTagBits, false); } } } @@ -2839,6 +2858,11 @@ void MyAvatar::setWalkSpeed(float value) { _walkSpeed.set(value); } +QVector MyAvatar::getScriptUrls() { + QVector scripts = _skeletonModel->isLoaded() ? _skeletonModel->getFBXGeometry().scripts : QVector(); + return scripts; +} + glm::vec3 MyAvatar::getPositionForAudio() { glm::vec3 result; switch (_audioListenerMode) { diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 74f7a3c89f..ac3d3cd2f4 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -985,6 +985,8 @@ public: void setWalkSpeed(float value); float getWalkSpeed() const; + QVector getScriptUrls(); + public slots: /**jsdoc @@ -1322,7 +1324,6 @@ signals: private slots: void leaveDomain(); - protected: virtual void beParentOfChild(SpatiallyNestablePointer newChild) const override; virtual void forgetChild(SpatiallyNestablePointer newChild) const override; @@ -1564,6 +1565,9 @@ private: // max unscaled forward movement speed ThreadSafeValueCache _walkSpeed { DEFAULT_AVATAR_MAX_WALKING_SPEED }; float _walkSpeedScalar { AVATAR_WALK_SPEED_SCALAR }; + + // load avatar scripts once when rig is ready + bool _shouldLoadScripts { false }; }; QScriptValue audioListenModeToScriptValue(QScriptEngine* engine, const AudioListenerMode& audioListenerMode); diff --git a/interface/src/commerce/QmlCommerce.cpp b/interface/src/commerce/QmlCommerce.cpp index 568556cb22..722f29ba2f 100644 --- a/interface/src/commerce/QmlCommerce.cpp +++ b/interface/src/commerce/QmlCommerce.cpp @@ -301,7 +301,7 @@ bool QmlCommerce::uninstallApp(const QString& itemHref) { // Read from the file to know what .js script to stop QFile appFile(_appsPath + "/" + appHref.fileName()); if (!appFile.open(QIODevice::ReadOnly)) { - qCDebug(commerce) << "Couldn't open local .app.json file for deletion."; + qCDebug(commerce) << "Couldn't open local .app.json file for deletion. Cannot continue with app uninstallation. App filename is:" << appHref.fileName(); return false; } QJsonDocument appFileJsonDocument = QJsonDocument::fromJson(appFile.readAll()); @@ -309,15 +309,13 @@ bool QmlCommerce::uninstallApp(const QString& itemHref) { QString scriptUrl = appFileJsonObject["scriptURL"].toString(); if (!DependencyManager::get()->stopScript(scriptUrl.trimmed(), false)) { - qCDebug(commerce) << "Couldn't stop script."; - return false; + qCWarning(commerce) << "Couldn't stop script during app uninstall. Continuing anyway. ScriptURL is:" << scriptUrl.trimmed(); } // Delete the .app.json from the filesystem // remove() closes the file first. if (!appFile.remove()) { - qCDebug(commerce) << "Couldn't delete local .app.json file."; - return false; + qCWarning(commerce) << "Couldn't delete local .app.json file during app uninstall. Continuing anyway. App filename is:" << appHref.fileName(); } emit appUninstalled(itemHref); diff --git a/interface/src/commerce/Wallet.cpp b/interface/src/commerce/Wallet.cpp index 3e0e4adf18..35e6ca1c92 100644 --- a/interface/src/commerce/Wallet.cpp +++ b/interface/src/commerce/Wallet.cpp @@ -615,9 +615,15 @@ void Wallet::updateImageProvider() { securityImageProvider->setSecurityImage(_securityImage); // inform tablet security image provider - QQmlEngine* tabletEngine = DependencyManager::get()->getTablet("com.highfidelity.interface.tablet.system")->getTabletSurface()->getSurfaceContext()->engine(); - securityImageProvider = reinterpret_cast(tabletEngine->imageProvider(SecurityImageProvider::PROVIDER_NAME)); - securityImageProvider->setSecurityImage(_securityImage); + TabletProxy* tablet = DependencyManager::get()->getTablet("com.highfidelity.interface.tablet.system"); + if (tablet) { + OffscreenQmlSurface* tabletSurface = tablet->getTabletSurface(); + if (tabletSurface) { + QQmlEngine* tabletEngine = tabletSurface->getSurfaceContext()->engine(); + securityImageProvider = reinterpret_cast(tabletEngine->imageProvider(SecurityImageProvider::PROVIDER_NAME)); + securityImageProvider->setSecurityImage(_securityImage); + } + } } void Wallet::chooseSecurityImage(const QString& filename) { diff --git a/interface/src/devices/DdeFaceTracker.h b/interface/src/devices/DdeFaceTracker.h index d4af0bbd37..4fe36b582e 100644 --- a/interface/src/devices/DdeFaceTracker.h +++ b/interface/src/devices/DdeFaceTracker.h @@ -29,6 +29,9 @@ /**jsdoc * The FaceTracker API helps manage facial tracking hardware. * @namespace FaceTracker + * + * @hifi-interface + * @hifi-client-entity */ class DdeFaceTracker : public FaceTracker, public Dependency { diff --git a/interface/src/java/io/highfidelity/interface/InterfaceActivity.java b/interface/src/java/io/highfidelity/interface/InterfaceActivity.java index c7cbdd3dff..ae10c320f8 100644 --- a/interface/src/java/io/highfidelity/interface/InterfaceActivity.java +++ b/interface/src/java/io/highfidelity/interface/InterfaceActivity.java @@ -15,7 +15,6 @@ import android.content.Intent; import android.net.Uri; import android.os.Bundle; import android.view.WindowManager; -import android.util.Log; import org.qtproject.qt5.android.bindings.QtActivity; public class InterfaceActivity extends QtActivity { diff --git a/interface/src/raypick/PickScriptingInterface.h b/interface/src/raypick/PickScriptingInterface.h index f2cd9287a5..2568dd8457 100644 --- a/interface/src/raypick/PickScriptingInterface.h +++ b/interface/src/raypick/PickScriptingInterface.h @@ -18,6 +18,10 @@ * The Picks API lets you create and manage objects for repeatedly calculating intersections in different ways. * * @namespace Picks + * + * @hifi-interface + * @hifi-client-entity + * * @property PICK_NOTHING {number} A filter flag. Don't intersect with anything. * @property PICK_ENTITIES {number} A filter flag. Include entities when intersecting. * @property PICK_OVERLAYS {number} A filter flag. Include overlays when intersecting. diff --git a/interface/src/raypick/PointerScriptingInterface.h b/interface/src/raypick/PointerScriptingInterface.h index 1cc7b56503..e7acfd4037 100644 --- a/interface/src/raypick/PointerScriptingInterface.h +++ b/interface/src/raypick/PointerScriptingInterface.h @@ -19,6 +19,9 @@ * Pointers can also be configured to automatically generate PointerEvents. * * @namespace Pointers + * + * @hifi-interface + * @hifi-client-entity */ class PointerScriptingInterface : public QObject, public Dependency { diff --git a/interface/src/scripting/AccountServicesScriptingInterface.h b/interface/src/scripting/AccountServicesScriptingInterface.h index d38a84d8fa..5774ee1da5 100644 --- a/interface/src/scripting/AccountServicesScriptingInterface.h +++ b/interface/src/scripting/AccountServicesScriptingInterface.h @@ -38,6 +38,9 @@ class AccountServicesScriptingInterface : public QObject { /**jsdoc * The AccountServices API contains helper functions related to user connectivity * + * @hifi-interface + * @hifi-client-entity + * * @namespace AccountServices * @property {string} username Read-only. * @property {boolean} loggedIn Read-only. diff --git a/interface/src/scripting/Audio.h b/interface/src/scripting/Audio.h index c77d1522b5..f0a4328c2f 100644 --- a/interface/src/scripting/Audio.h +++ b/interface/src/scripting/Audio.h @@ -27,8 +27,14 @@ class Audio : public AudioScriptingInterface, protected ReadWriteLockable { /**jsdoc * The Audio API features tools to help control audio contexts and settings. - * + * * @namespace Audio + * + * @hifi-interface + * @hifi-client-entity + * @hifi-server-entity + * @hifi-assignment-client + * * @property {boolean} muted * @property {boolean} noiseReduction * @property {number} inputVolume diff --git a/interface/src/scripting/ClipboardScriptingInterface.h b/interface/src/scripting/ClipboardScriptingInterface.h index cce300e831..32b8c64a7d 100644 --- a/interface/src/scripting/ClipboardScriptingInterface.h +++ b/interface/src/scripting/ClipboardScriptingInterface.h @@ -21,6 +21,9 @@ * The Clipboard API enables you to export and import entities to and from JSON files. * * @namespace Clipboard + * + * @hifi-interface + * @hifi-client-entity */ class ClipboardScriptingInterface : public QObject { Q_OBJECT diff --git a/interface/src/scripting/ControllerScriptingInterface.h b/interface/src/scripting/ControllerScriptingInterface.h index f19caa8478..e16383e234 100644 --- a/interface/src/scripting/ControllerScriptingInterface.h +++ b/interface/src/scripting/ControllerScriptingInterface.h @@ -28,7 +28,7 @@ class ScriptEngine; /**jsdoc * The Controller API provides facilities to interact with computer and controller hardware. * - *
Functions:
+ *
Functions
* *

Properties

*
    @@ -143,9 +143,67 @@ class ScriptEngine; *
  • {@link Controller.stopInputPlayback|stopInputPlayback}
  • *
* + *
Entity Methods:
+ * + *

The default scripts implement hand controller actions that use {@link Entities.callEntityMethod} to call entity script + * methods, if present in the entity being interacted with.

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Method NameDescriptionExample
startFarTrigger
continueFarTrigger
stopFarTrigger
These methods are called when a user is more than 0.3m away from the entity, the entity is triggerable, and the + * user starts, continues, or stops squeezing the trigger.A light switch that can be toggled on and off from a distance.
startNearTrigger
continueNearTrigger
stopNearTrigger
These methods are called when a user is less than 0.3m away from the entity, the entity is triggerable, and the + * user starts, continues, or stops squeezing the trigger.A doorbell that can be rung when a user is near.
startDistanceGrab
continueDistanceGrab
These methods are called when a user is more than 0.3m away from the entity, the entity is either cloneable, or + * grabbable and not locked, and the user starts or continues to squeeze the trigger.A comet that emits icy particle trails when a user is dragging it through the sky.
startNearGrab
continueNearGrab
These methods are called when a user is less than 0.3m away from the entity, the entity is either cloneable, or + * grabbable and not locked, and the user starts or continues to squeeze the trigger.A ball that glows when it's being held close.
releaseGrabThis method is called when a user releases the trigger when having been either distance or near grabbing an + * entity.Turn off the ball glow or comet trail with the user finishes grabbing it.
startEquip
continueEquip
releaseEquip
These methods are called when a user starts, continues, or stops equipping an entity.A glass that stays in the user's hand after the trigger is clicked.
+ *

All the entity methods are called with the following two arguments:

+ *
    + *
  • The entity ID.
  • + *
  • A string, "hand,userID" — where "hand" is "left" or "right", and "userID" + * is the user's {@link MyAvatar|MyAvatar.sessionUUID}.
  • + *
+ * * @namespace Controller * - * @property {Controller.Actions} Actions - Predefined actions on Interface and the user's avatar. These can be used as end + * @hifi-interface + * @hifi-client-entity + * + * @property {Controller.Actions} Actions - Predefined actions on Interface and the user's avatar. These can be used as end * points in a {@link RouteObject} mapping. A synonym for Controller.Hardware.Actions. * Read-only.
* Default mappings are provided from the Controller.Hardware.Keyboard and Controller.Standard to diff --git a/interface/src/scripting/GooglePolyScriptingInterface.h b/interface/src/scripting/GooglePolyScriptingInterface.h index 5c37b394fa..fb5aed9759 100644 --- a/interface/src/scripting/GooglePolyScriptingInterface.h +++ b/interface/src/scripting/GooglePolyScriptingInterface.h @@ -18,6 +18,9 @@ /**jsdoc * The GooglePoly API allows you to interact with Google Poly models direct from inside High Fidelity. * @namespace GooglePoly + * + * @hifi-interface + * @hifi-client-entity */ class GooglePolyScriptingInterface : public QObject, public Dependency { diff --git a/interface/src/scripting/HMDScriptingInterface.h b/interface/src/scripting/HMDScriptingInterface.h index 9b2482e73a..d2a272851f 100644 --- a/interface/src/scripting/HMDScriptingInterface.h +++ b/interface/src/scripting/HMDScriptingInterface.h @@ -28,7 +28,11 @@ class QScriptEngine; * The HMD API provides access to the HMD used in VR display mode. * * @namespace HMD - * @property {Vec3} position - The position of the HMD if currently in VR display mode, otherwise + * + * @hifi-interface + * @hifi-client-entity + * + * @property {Vec3} position - The position of the HMD if currently in VR display mode, otherwise * {@link Vec3(0)|Vec3.ZERO}. Read-only. * @property {Quat} orientation - The orientation of the HMD if currently in VR display mode, otherwise * {@link Quat(0)|Quat.IDENTITY}. Read-only. diff --git a/interface/src/scripting/MenuScriptingInterface.h b/interface/src/scripting/MenuScriptingInterface.h index 649c444eaf..81cf775de8 100644 --- a/interface/src/scripting/MenuScriptingInterface.h +++ b/interface/src/scripting/MenuScriptingInterface.h @@ -32,6 +32,9 @@ class MenuItemProperties; * If a menu item doesn't belong to a group it is always displayed. * * @namespace Menu + * + * @hifi-interface + * @hifi-client-entity */ /** diff --git a/interface/src/scripting/SelectionScriptingInterface.h b/interface/src/scripting/SelectionScriptingInterface.h index 71ff41248a..8b5c12e3dc 100644 --- a/interface/src/scripting/SelectionScriptingInterface.h +++ b/interface/src/scripting/SelectionScriptingInterface.h @@ -86,6 +86,9 @@ protected: * The Selection API provides a means of grouping together avatars, entities, and overlays in named lists. * @namespace Selection * + * @hifi-interface + * @hifi-client-entity + * * @example Outline an entity when it is grabbed by a controller. * // Create a box and copy the following text into the entity's "Script URL" field. * (function () { @@ -131,7 +134,7 @@ public: /**jsdoc * Get the names of all the selection lists. * @function Selection.getListNames - * @return {list[]} An array of names of all the selection lists. + * @returns {list[]} An array of names of all the selection lists. */ Q_INVOKABLE QStringList getListNames() const; @@ -181,7 +184,7 @@ public: * Get the list of avatars, entities, and overlays stored in a selection list. * @function Selection.getList * @param {string} listName - The name of the selection list. - * @return {Selection.SelectedItemsList} The content of a selection list. If the list name doesn't exist, the function + * @returns {Selection.SelectedItemsList} The content of a selection list. If the list name doesn't exist, the function * returns an empty object with no properties. */ Q_INVOKABLE QVariantMap getSelectedItemsList(const QString& listName) const; @@ -189,7 +192,7 @@ public: /**jsdoc * Get the names of the highlighted selection lists. * @function Selection.getHighlightedListNames - * @return {string[]} An array of names of the selection list currently highlight enabled. + * @returns {string[]} An array of names of the selection list currently highlight enabled. */ Q_INVOKABLE QStringList getHighlightedListNames() const; diff --git a/interface/src/scripting/SettingsScriptingInterface.h b/interface/src/scripting/SettingsScriptingInterface.h index 9e0271601b..32d868bb24 100644 --- a/interface/src/scripting/SettingsScriptingInterface.h +++ b/interface/src/scripting/SettingsScriptingInterface.h @@ -18,6 +18,9 @@ /**jsdoc * The Settings API provides a facility to store and retrieve values that persist between Interface runs. * @namespace Settings + * + * @hifi-interface + * @hifi-client-entity */ class SettingsScriptingInterface : public QObject { diff --git a/interface/src/scripting/WindowScriptingInterface.h b/interface/src/scripting/WindowScriptingInterface.h index 0b766d2097..348882e0f8 100644 --- a/interface/src/scripting/WindowScriptingInterface.h +++ b/interface/src/scripting/WindowScriptingInterface.h @@ -28,7 +28,11 @@ * physics. * * @namespace Window - * @property {number} innerWidth - The width of the drawable area of the Interface window (i.e., without borders or other + * + * @hifi-interface + * @hifi-client-entity + * + * @property {number} innerWidth - The width of the drawable area of the Interface window (i.e., without borders or other * chrome), in pixels. Read-only. * @property {number} innerHeight - The height of the drawable area of the Interface window (i.e., without borders or other * chrome), in pixels. Read-only. diff --git a/interface/src/ui/AddressBarDialog.cpp b/interface/src/ui/AddressBarDialog.cpp index 87bf09a252..789a2a2bdf 100644 --- a/interface/src/ui/AddressBarDialog.cpp +++ b/interface/src/ui/AddressBarDialog.cpp @@ -58,9 +58,8 @@ void AddressBarDialog::loadHome() { qDebug() << "Called LoadHome"; auto locationBookmarks = DependencyManager::get(); QString homeLocation = locationBookmarks->addressForBookmark(LocationBookmarks::HOME_BOOKMARK); - const QString DEFAULT_HOME_LOCATION = "localhost"; if (homeLocation == "") { - homeLocation = DEFAULT_HOME_LOCATION; + homeLocation = DEFAULT_HIFI_ADDRESS; } DependencyManager::get()->handleLookupString(homeLocation); } diff --git a/interface/src/ui/AvatarInputs.cpp b/interface/src/ui/AvatarInputs.cpp index 3053cb8855..0aa352de23 100644 --- a/interface/src/ui/AvatarInputs.cpp +++ b/interface/src/ui/AvatarInputs.cpp @@ -18,7 +18,7 @@ static AvatarInputs* INSTANCE{ nullptr }; -Setting::Handle showAudioToolsSetting { QStringList { "AvatarInputs", "showAudioTools" }, false }; +Setting::Handle showAudioToolsSetting { QStringList { "AvatarInputs", "showAudioTools" }, true }; AvatarInputs* AvatarInputs::getInstance() { if (!INSTANCE) { diff --git a/interface/src/ui/AvatarInputs.h b/interface/src/ui/AvatarInputs.h index a9d1509770..e67d35e59f 100644 --- a/interface/src/ui/AvatarInputs.h +++ b/interface/src/ui/AvatarInputs.h @@ -26,6 +26,10 @@ class AvatarInputs : public QObject { /**jsdoc * API to help manage your Avatar's input * @namespace AvatarInputs + * + * @hifi-interface + * @hifi-client-entity + * * @property {boolean} cameraEnabled Read-only. * @property {boolean} cameraMuted Read-only. * @property {boolean} isHMD Read-only. diff --git a/interface/src/ui/overlays/ContextOverlayInterface.h b/interface/src/ui/overlays/ContextOverlayInterface.h index b80a3a70fb..808c3a4ee3 100644 --- a/interface/src/ui/overlays/ContextOverlayInterface.h +++ b/interface/src/ui/overlays/ContextOverlayInterface.h @@ -31,9 +31,6 @@ #include "EntityTree.h" #include "ContextOverlayLogging.h" -/**jsdoc -* @namespace ContextOverlay -*/ class ContextOverlayInterface : public QObject, public Dependency { Q_OBJECT diff --git a/interface/src/ui/overlays/Overlays.h b/interface/src/ui/overlays/Overlays.h index c2f6e3e693..cf1151b46a 100644 --- a/interface/src/ui/overlays/Overlays.h +++ b/interface/src/ui/overlays/Overlays.h @@ -76,6 +76,10 @@ void RayToOverlayIntersectionResultFromScriptValue(const QScriptValue& object, R * The Overlays API provides facilities to create and interact with overlays. Overlays are 2D and 3D objects visible only to * yourself and that aren't persisted to the domain. They are used for UI. * @namespace Overlays + * + * @hifi-interface + * @hifi-client-entity + * * @property {Uuid} keyboardFocusOverlay - Get or set the {@link Overlays.OverlayType|web3d} overlay that has keyboard focus. * If no overlay has keyboard focus, get returns null; set to null or {@link Uuid|Uuid.NULL} to * clear keyboard focus. diff --git a/libraries/animation/src/AnimationCache.h b/libraries/animation/src/AnimationCache.h index 03b37aef2f..d8f8a13cde 100644 --- a/libraries/animation/src/AnimationCache.h +++ b/libraries/animation/src/AnimationCache.h @@ -37,51 +37,49 @@ public: * API to manage animation cache resources. * @namespace AnimationCache * + * @hifi-interface + * @hifi-client-entity + * @hifi-assignment-client + * * @property {number} numTotal - Total number of total resources. Read-only. * @property {number} numCached - Total number of cached resource. Read-only. * @property {number} sizeTotal - Size in bytes of all resources. Read-only. * @property {number} sizeCached - Size in bytes of all cached resources. Read-only. */ - // Functions are copied over from ResourceCache (see ResourceCache.h for reason). + // Functions are copied over from ResourceCache (see ResourceCache.h for reason). - /**jsdoc + /**jsdoc * Get the list of all resource URLs. * @function AnimationCache.getResourceList - * @return {string[]} + * @returns {string[]} */ - /**jsdoc + /**jsdoc * @function AnimationCache.dirty * @returns {Signal} */ - /**jsdoc + /**jsdoc * @function AnimationCache.updateTotalSize * @param {number} deltaSize */ - /**jsdoc + /**jsdoc + * Prefetches a resource. * @function AnimationCache.prefetch - * @param {string} url - * @param {object} extra - * @returns {object} + * @param {string} url - URL of the resource to prefetch. + * @param {object} [extra=null] + * @returns {Resource} */ - /**jsdoc + /**jsdoc * Asynchronously loads a resource from the specified URL and returns it. * @function AnimationCache.getResource * @param {string} url - URL of the resource to load. * @param {string} [fallback=""] - Fallback URL if load of the desired URL fails. * @param {} [extra=null] - * @return {Resource} - */ - - /**jsdoc - * Prefetches a resource. - * @function AnimationCache.prefetch - * @param {string} url - URL of the resource to prefetch. - * @return {Resource} + * @returns {Resource} */ diff --git a/libraries/audio-client/src/AudioIOStats.h b/libraries/audio-client/src/AudioIOStats.h index 89db4942ec..45fcf365da 100644 --- a/libraries/audio-client/src/AudioIOStats.h +++ b/libraries/audio-client/src/AudioIOStats.h @@ -41,6 +41,10 @@ class AudioStreamStatsInterface : public QObject { /**jsdoc * @class AudioStats.AudioStreamStats + * + * @hifi-interface + * @hifi-client-entity + * * @property {number} lossRate Read-only. * @property {number} lossCount Read-only. * @property {number} lossRateWindow Read-only. @@ -185,6 +189,10 @@ class AudioStatsInterface : public QObject { /**jsdoc * Audio stats from the client. * @namespace AudioStats + * + * @hifi-interface + * @hifi-client-entity + * * @property {number} pingMs Read-only. * @property {number} inputReadMsMax Read-only. * @property {number} inputUnplayedMsMax Read-only. diff --git a/libraries/audio/src/AudioDynamics.h b/libraries/audio/src/AudioDynamics.h index a43833610a..8dbc7a75cc 100644 --- a/libraries/audio/src/AudioDynamics.h +++ b/libraries/audio/src/AudioDynamics.h @@ -10,9 +10,9 @@ // Inline functions to implement audio dynamics processing // -#include #include #include +#include #ifndef MAX #define MAX(a,b) ((a) > (b) ? (a) : (b)) @@ -21,7 +21,15 @@ #define MIN(a,b) ((a) < (b) ? (a) : (b)) #endif -#ifdef _MSC_VER +#if defined(_MSC_VER) +#define FORCEINLINE __forceinline +#elif defined(__GNUC__) +#define FORCEINLINE inline __attribute__((always_inline)) +#else +#define FORCEINLINE inline +#endif + +#if defined(_MSC_VER) #include #define MUL64(a,b) __emul((a), (b)) #else @@ -42,14 +50,14 @@ #include // convert float to int using round-to-nearest -static inline int32_t floatToInt(float x) { +FORCEINLINE static int32_t floatToInt(float x) { return _mm_cvt_ss2si(_mm_load_ss(&x)); } #else // convert float to int using round-to-nearest -static inline int32_t floatToInt(float x) { +FORCEINLINE static int32_t floatToInt(float x) { x += (x < 0.0f ? -0.5f : 0.5f); // round return (int32_t)x; } @@ -60,12 +68,12 @@ static const double FIXQ31 = 2147483648.0; // convert float to Q31 static const double DB_TO_LOG2 = 0.16609640474436813; // convert dB to log2 // convert dB to amplitude -static inline double dBToGain(double dB) { +FORCEINLINE static double dBToGain(double dB) { return pow(10.0, dB / 20.0); } // convert milliseconds to first-order time constant -static inline int32_t msToTc(double ms, double sampleRate) { +FORCEINLINE static int32_t msToTc(double ms, double sampleRate) { double tc = exp(-1000.0 / (ms * sampleRate)); return (int32_t)(FIXQ31 * tc); // Q31 } @@ -144,16 +152,16 @@ static const int IEEE754_EXPN_BIAS = 127; // x < 2^(31-LOG2_HEADROOM) returns 0x7fffffff // x > 2^LOG2_HEADROOM undefined // -static inline int32_t peaklog2(float* input) { +FORCEINLINE static int32_t peaklog2(float* input) { // float as integer bits - int32_t u = *(int32_t*)input; + uint32_t u = *(uint32_t*)input; // absolute value - int32_t peak = u & IEEE754_FABS_MASK; + uint32_t peak = u & IEEE754_FABS_MASK; // split into e and x - 1.0 - int32_t e = IEEE754_EXPN_BIAS - (peak >> IEEE754_MANT_BITS) + LOG2_HEADROOM; + int e = IEEE754_EXPN_BIAS - (peak >> IEEE754_MANT_BITS) + LOG2_HEADROOM; int32_t x = (peak << IEEE754_EXPN_BITS) & 0x7fffffff; // saturate @@ -180,19 +188,19 @@ static inline int32_t peaklog2(float* input) { // x < 2^(31-LOG2_HEADROOM) returns 0x7fffffff // x > 2^LOG2_HEADROOM undefined // -static inline int32_t peaklog2(float* input0, float* input1) { +FORCEINLINE static int32_t peaklog2(float* input0, float* input1) { // float as integer bits - int32_t u0 = *(int32_t*)input0; - int32_t u1 = *(int32_t*)input1; + uint32_t u0 = *(uint32_t*)input0; + uint32_t u1 = *(uint32_t*)input1; // max absolute value u0 &= IEEE754_FABS_MASK; u1 &= IEEE754_FABS_MASK; - int32_t peak = MAX(u0, u1); + uint32_t peak = MAX(u0, u1); // split into e and x - 1.0 - int32_t e = IEEE754_EXPN_BIAS - (peak >> IEEE754_MANT_BITS) + LOG2_HEADROOM; + int e = IEEE754_EXPN_BIAS - (peak >> IEEE754_MANT_BITS) + LOG2_HEADROOM; int32_t x = (peak << IEEE754_EXPN_BITS) & 0x7fffffff; // saturate @@ -219,23 +227,23 @@ static inline int32_t peaklog2(float* input0, float* input1) { // x < 2^(31-LOG2_HEADROOM) returns 0x7fffffff // x > 2^LOG2_HEADROOM undefined // -static inline int32_t peaklog2(float* input0, float* input1, float* input2, float* input3) { +FORCEINLINE static int32_t peaklog2(float* input0, float* input1, float* input2, float* input3) { // float as integer bits - int32_t u0 = *(int32_t*)input0; - int32_t u1 = *(int32_t*)input1; - int32_t u2 = *(int32_t*)input2; - int32_t u3 = *(int32_t*)input3; + uint32_t u0 = *(uint32_t*)input0; + uint32_t u1 = *(uint32_t*)input1; + uint32_t u2 = *(uint32_t*)input2; + uint32_t u3 = *(uint32_t*)input3; // max absolute value u0 &= IEEE754_FABS_MASK; u1 &= IEEE754_FABS_MASK; u2 &= IEEE754_FABS_MASK; u3 &= IEEE754_FABS_MASK; - int32_t peak = MAX(MAX(u0, u1), MAX(u2, u3)); + uint32_t peak = MAX(MAX(u0, u1), MAX(u2, u3)); // split into e and x - 1.0 - int32_t e = IEEE754_EXPN_BIAS - (peak >> IEEE754_MANT_BITS) + LOG2_HEADROOM; + int e = IEEE754_EXPN_BIAS - (peak >> IEEE754_MANT_BITS) + LOG2_HEADROOM; int32_t x = (peak << IEEE754_EXPN_BITS) & 0x7fffffff; // saturate @@ -261,7 +269,7 @@ static inline int32_t peaklog2(float* input0, float* input1, float* input2, floa // Count Leading Zeros // Emulates the CLZ (ARM) and LZCNT (x86) instruction // -static inline int CLZ(uint32_t u) { +FORCEINLINE static int CLZ(uint32_t u) { if (u == 0) { return 32; @@ -294,7 +302,7 @@ static inline int CLZ(uint32_t u) { // Compute -log2(x) for x=[0,1] in Q31, result in Q26 // x <= 0 returns 0x7fffffff // -static inline int32_t fixlog2(int32_t x) { +FORCEINLINE static int32_t fixlog2(int32_t x) { if (x <= 0) { return 0x7fffffff; @@ -303,8 +311,7 @@ static inline int32_t fixlog2(int32_t x) { // split into e and x - 1.0 uint32_t u = (uint32_t)x; int e = CLZ(u); - u <<= e; // normalize to [0x80000000, 0xffffffff] - x = u & 0x7fffffff; // x - 1.0 + x = (u << e) & 0x7fffffff; int k = x >> (31 - LOG2_TABBITS); @@ -324,7 +331,7 @@ static inline int32_t fixlog2(int32_t x) { // Compute exp2(-x) for x=[0,32] in Q26, result in Q31 // x <= 0 returns 0x7fffffff // -static inline int32_t fixexp2(int32_t x) { +FORCEINLINE static int32_t fixexp2(int32_t x) { if (x <= 0) { return 0x7fffffff; @@ -350,12 +357,12 @@ static inline int32_t fixexp2(int32_t x) { } // fast TPDF dither in [-1.0f, 1.0f] -static inline float dither() { +FORCEINLINE static float dither() { static uint32_t rz = 0; rz = rz * 69069 + 1; int32_t r0 = rz & 0xffff; int32_t r1 = rz >> 16; - return (int32_t)(r0 - r1) * (1/65536.0f); + return (r0 - r1) * (1/65536.0f); } // diff --git a/libraries/audio/src/AudioReverb.cpp b/libraries/audio/src/AudioReverb.cpp index c561231376..d457ce7a96 100644 --- a/libraries/audio/src/AudioReverb.cpp +++ b/libraries/audio/src/AudioReverb.cpp @@ -13,18 +13,11 @@ #include "AudioReverb.h" #ifdef _MSC_VER - #include -inline static int MULHI(int a, int b) { - long long c = __emul(a, b); - return ((int*)&c)[1]; -} - +#define MULHI(a,b) ((int32_t)(__emul(a, b) >> 32)) #else - -#define MULHI(a,b) (int)(((long long)(a) * (b)) >> 32) - -#endif // _MSC_VER +#define MULHI(a,b) ((int32_t)(((int64_t)(a) * (int64_t)(b)) >> 32)) +#endif #ifndef MAX #define MAX(a,b) (((a) > (b)) ? (a) : (b)) @@ -1954,7 +1947,7 @@ static inline float dither() { rz = rz * 69069 + 1; int32_t r0 = rz & 0xffff; int32_t r1 = rz >> 16; - return (int32_t)(r0 - r1) * (1/65536.0f); + return (r0 - r1) * (1/65536.0f); } // convert float to int16_t with dither, interleave stereo diff --git a/libraries/audio/src/AudioSRC.cpp b/libraries/audio/src/AudioSRC.cpp index 80cb756d04..fbdf890246 100644 --- a/libraries/audio/src/AudioSRC.cpp +++ b/libraries/audio/src/AudioSRC.cpp @@ -1200,7 +1200,7 @@ static inline float dither() { rz = rz * 69069 + 1; int32_t r0 = rz & 0xffff; int32_t r1 = rz >> 16; - return (int32_t)(r0 - r1) * (1/65536.0f); + return (r0 - r1) * (1/65536.0f); } // convert float to int16_t with dither, interleave stereo diff --git a/libraries/audio/src/InboundAudioStream.cpp b/libraries/audio/src/InboundAudioStream.cpp index 172ec9411a..d60c5ba4ab 100644 --- a/libraries/audio/src/InboundAudioStream.cpp +++ b/libraries/audio/src/InboundAudioStream.cpp @@ -150,6 +150,7 @@ int InboundAudioStream::parseData(ReceivedMessage& message) { // fall through to OnTime case } + // FALLTHRU case SequenceNumberStats::OnTime: { // Packet is on time; parse its data to the ringbuffer if (message.getType() == PacketType::SilentAudioFrame diff --git a/libraries/audio/src/SoundCache.h b/libraries/audio/src/SoundCache.h index d8c52635e0..347f324353 100644 --- a/libraries/audio/src/SoundCache.h +++ b/libraries/audio/src/SoundCache.h @@ -29,6 +29,11 @@ public: * API to manage sound cache resources. * @namespace SoundCache * + * @hifi-interface + * @hifi-client-entity + * @hifi-server-entity + * @hifi-assignment-client + * * @property {number} numTotal - Total number of total resources. Read-only. * @property {number} numCached - Total number of cached resource. Read-only. * @property {number} sizeTotal - Size in bytes of all resources. Read-only. @@ -36,12 +41,12 @@ public: */ - // Functions are copied over from ResourceCache (see ResourceCache.h for reason). + // Functions are copied over from ResourceCache (see ResourceCache.h for reason). /**jsdoc * Get the list of all resource URLs. * @function SoundCache.getResourceList - * @return {string[]} + * @returns {string[]} */ /**jsdoc @@ -55,10 +60,11 @@ public: */ /**jsdoc + * Prefetches a resource. * @function SoundCache.prefetch - * @param {string} url - * @param {object} extra - * @returns {object} + * @param {string} url - URL of the resource to prefetch. + * @param {object} [extra=null] + * @returns {Resource} */ /**jsdoc @@ -67,14 +73,7 @@ public: * @param {string} url - URL of the resource to load. * @param {string} [fallback=""] - Fallback URL if load of the desired URL fails. * @param {} [extra=null] - * @return {Resource} - */ - - /**jsdoc - * Prefetches a resource. - * @function SoundCache.prefetch - * @param {string} url - URL of the resource to prefetch. - * @return {Resource} + * @returns {Resource} */ diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 888c3bfb24..bbcdd3693d 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -277,8 +277,6 @@ namespace AvatarDataPacket { const float MAX_AUDIO_LOUDNESS = 1000.0f; // close enough for mouth animation -const int AVATAR_IDENTITY_PACKET_SEND_INTERVAL_MSECS = 1000; - // See also static AvatarData::defaultFullAvatarModelUrl(). const QString DEFAULT_FULL_AVATAR_MODEL_NAME = QString("Default"); diff --git a/libraries/controllers/src/controllers/impl/MappingBuilderProxy.h b/libraries/controllers/src/controllers/impl/MappingBuilderProxy.h index 86a43c0c13..5bc7357dd7 100644 --- a/libraries/controllers/src/controllers/impl/MappingBuilderProxy.h +++ b/libraries/controllers/src/controllers/impl/MappingBuilderProxy.h @@ -39,7 +39,9 @@ class UserInputMapper; * methods. *
  • Use {@link Controller.parseMapping} or {@link Controller.loadMapping} to load a {@link Controller.MappingJSON}.
  • * - *

    Enable the mapping using {@link MappingObject#enable|enable} or {@link Controller.enableMapping} for it to take effect. + * + *

    Enable the mapping using {@link MappingObject#enable|enable} or {@link Controller.enableMapping} for it to take + * effect.

    * *

    Mappings and their routes are applied according to the following rules:

    *
      @@ -49,9 +51,12 @@ class UserInputMapper; * output that already has a route the new route is ignored. *
    • New mappings override previous mappings: each output is processed using the route in the most recently enabled * mapping that contains that output.
    • - *

      + *
    * * @class MappingObject + * + * @hifi-interface + * @hifi-client-entity */ /**jsdoc diff --git a/libraries/controllers/src/controllers/impl/RouteBuilderProxy.h b/libraries/controllers/src/controllers/impl/RouteBuilderProxy.h index 0336638068..804709ebfa 100644 --- a/libraries/controllers/src/controllers/impl/RouteBuilderProxy.h +++ b/libraries/controllers/src/controllers/impl/RouteBuilderProxy.h @@ -29,12 +29,16 @@ class ScriptingInterface; *

    A route in a {@link MappingObject} used by the {@link Controller} API.

    * *

    Create a route using {@link MappingObject} methods and apply this object's methods to process it, terminating with - * {@link RouteObject#to} to apply it to a Standard control, action, or script function.

    + * {@link RouteObject#to} to apply it to a Standard control, action, or script function. Note: Loops are not + * permitted.

    * *

    Some methods apply to routes with number data, some apply routes with {@link Pose} data, and some apply to both route * types.

    * * @class RouteObject + * + * @hifi-interface + * @hifi-client-entity */ // TODO migrate functionality to a RouteBuilder class and make the proxy defer to that diff --git a/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.cpp index f33af1b580..59cd637ca0 100644 --- a/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.cpp @@ -147,20 +147,21 @@ void Basic2DWindowOpenGLDisplayPlugin::compositeExtra() { batch.setViewportTransform(ivec4(uvec2(0), getRecommendedRenderSize())); batch.draw(gpu::TRIANGLE_STRIP, 4); }); - - // render stick head - auto jumpTransform = DependencyManager::get()->getPoint2DTransform(virtualPadManager.getJumpButtonPosition(), - _virtualPadJumpBtnPixelSize, _virtualPadJumpBtnPixelSize); - render([&](gpu::Batch& batch) { - batch.enableStereo(false); - batch.setProjectionTransform(mat4()); - batch.setPipeline(_cursorPipeline); - batch.setResourceTexture(0, _virtualPadJumpBtnTexture); - batch.resetViewTransform(); - batch.setModelTransform(jumpTransform); - batch.setViewportTransform(ivec4(uvec2(0), getRecommendedRenderSize())); - batch.draw(gpu::TRIANGLE_STRIP, 4); - }); + if (!virtualPadManager.getLeftVirtualPad()->isBeingTouched()) { + // render stick head + auto jumpTransform = DependencyManager::get()->getPoint2DTransform(virtualPadManager.getJumpButtonPosition(), + _virtualPadJumpBtnPixelSize, _virtualPadJumpBtnPixelSize); + render([&](gpu::Batch& batch) { + batch.enableStereo(false); + batch.setProjectionTransform(mat4()); + batch.setPipeline(_cursorPipeline); + batch.setResourceTexture(0, _virtualPadJumpBtnTexture); + batch.resetViewTransform(); + batch.setModelTransform(jumpTransform); + batch.setViewportTransform(ivec4(uvec2(0), getRecommendedRenderSize())); + batch.draw(gpu::TRIANGLE_STRIP, 4); + }); + } } #endif Parent::compositeExtra(); diff --git a/libraries/display-plugins/src/display-plugins/CompositorHelper.h b/libraries/display-plugins/src/display-plugins/CompositorHelper.h index bc6ed63363..fb712c26fa 100644 --- a/libraries/display-plugins/src/display-plugins/CompositorHelper.h +++ b/libraries/display-plugins/src/display-plugins/CompositorHelper.h @@ -174,6 +174,10 @@ private: /**jsdoc * @namespace Reticle + * + * @hifi-interface + * @hifi-client-entity + * * @property {boolean} allowMouseCapture * @property {number} depth * @property {Vec2} maximumPosition diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index ba81922979..6dd13c7332 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -296,7 +296,8 @@ void EntityTreeRenderer::addPendingEntities(const render::ScenePointer& scene, r } } -void EntityTreeRenderer::updateChangedEntities(const render::ScenePointer& scene, const ViewFrustum& view, render::Transaction& transaction) { +void EntityTreeRenderer::updateChangedEntities(const render::ScenePointer& scene, const ViewFrustums& views, + render::Transaction& transaction) { PROFILE_RANGE_EX(simulation_physics, "ChangeInScene", 0xffff00ff, (uint64_t)_changedEntities.size()); PerformanceTimer pt("change"); std::unordered_set changedEntities; @@ -357,7 +358,7 @@ void EntityTreeRenderer::updateChangedEntities(const render::ScenePointer& scene // prioritize and sort the renderables uint64_t sortStart = usecTimestampNow(); - PrioritySortUtil::PriorityQueue sortedRenderables(view); + PrioritySortUtil::PriorityQueue sortedRenderables(views); { PROFILE_RANGE_EX(simulation_physics, "SortRenderables", 0xffff00ff, (uint64_t)_renderablesToUpdate.size()); std::unordered_map::iterator itr = _renderablesToUpdate.begin(); @@ -415,9 +416,20 @@ void EntityTreeRenderer::update(bool simulate) { if (scene) { render::Transaction transaction; addPendingEntities(scene, transaction); + + ViewFrustums views; + ViewFrustum view; - _viewState->copyCurrentViewFrustum(view); - updateChangedEntities(scene, view, transaction); + _viewState->copyViewFrustum(view); + views.push_back(view); + + if (_viewState->hasSecondaryViewFrustum()) { + _viewState->copySecondaryViewFrustum(view); + views.push_back(view); + } + + + updateChangedEntities(scene, views, transaction); scene->enqueueTransaction(transaction); } } diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.h b/libraries/entities-renderer/src/EntityTreeRenderer.h index f5cedfdd01..9ed4f9d21d 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.h +++ b/libraries/entities-renderer/src/EntityTreeRenderer.h @@ -148,7 +148,8 @@ protected: private: void addPendingEntities(const render::ScenePointer& scene, render::Transaction& transaction); - void updateChangedEntities(const render::ScenePointer& scene, const ViewFrustum& view, render::Transaction& transaction); + void updateChangedEntities(const render::ScenePointer& scene, const ViewFrustums& views, + render::Transaction& transaction); EntityRendererPointer renderableForEntity(const EntityItemPointer& entity) const { return renderableForEntityId(entity->getID()); } render::ItemID renderableIdForEntity(const EntityItemPointer& entity) const { return renderableIdForEntityId(entity->getID()); } diff --git a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp index b3a4a1a1ab..ee77646920 100644 --- a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp @@ -23,8 +23,8 @@ using namespace render::entities; static uint8_t CUSTOM_PIPELINE_NUMBER = 0; static gpu::Stream::FormatPointer _vertexFormat; static std::weak_ptr _texturedPipeline; -// FIXME: This is interfering with the uniform buffers in DeferredLightingEffect.cpp, so use 11 to avoid collisions -static int32_t PARTICLE_UNIFORM_SLOT { 11 }; +// FIXME: This is interfering with the uniform buffers in DeferredLightingEffect.cpp, so use 12 to avoid collisions +static int32_t PARTICLE_UNIFORM_SLOT { 12 }; static ShapePipelinePointer shapePipelineFactory(const ShapePlumber& plumber, const ShapeKey& key, gpu::Batch& batch) { auto texturedPipeline = _texturedPipeline.lock(); diff --git a/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp index 394ab08dfb..d571eac35c 100644 --- a/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp @@ -34,8 +34,8 @@ using namespace render::entities; static uint8_t CUSTOM_PIPELINE_NUMBER { 0 }; static const int32_t PAINTSTROKE_TEXTURE_SLOT { 0 }; -// FIXME: This is interfering with the uniform buffers in DeferredLightingEffect.cpp, so use 11 to avoid collisions -static const int32_t PAINTSTROKE_UNIFORM_SLOT { 11 }; +// FIXME: This is interfering with the uniform buffers in DeferredLightingEffect.cpp, so use 12 to avoid collisions +static const int32_t PAINTSTROKE_UNIFORM_SLOT { 12 }; static gpu::Stream::FormatPointer polylineFormat; static gpu::PipelinePointer polylinePipeline; #ifdef POLYLINE_ENTITY_USE_FADE_EFFECT diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp index f333e805ce..693e3d0cf4 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp @@ -308,12 +308,7 @@ bool WebEntityRenderer::buildWebSurface(const TypedEntityPointer& entity) { item->setProperty(URL_PROPERTY, _lastSourceUrl); }); } else if (_contentType == ContentType::QmlContent) { - _webSurface->load(_lastSourceUrl, [this](QQmlContext* context, QObject* item) { - if (item && item->objectName() == "tabletRoot") { - auto tabletScriptingInterface = DependencyManager::get(); - tabletScriptingInterface->setQmlTabletRoot("com.highfidelity.interface.tablet.system", _webSurface.data()); - } - }); + _webSurface->load(_lastSourceUrl); } _fadeStartTime = usecTimestampNow(); _webSurface->resume(); @@ -323,32 +318,21 @@ bool WebEntityRenderer::buildWebSurface(const TypedEntityPointer& entity) { void WebEntityRenderer::destroyWebSurface() { QSharedPointer webSurface; + ContentType contentType{ ContentType::NoContent }; withWriteLock([&] { webSurface.swap(_webSurface); + std::swap(contentType, _contentType); }); if (webSurface) { --_currentWebCount; QQuickItem* rootItem = webSurface->getRootItem(); - // Explicitly set the web URL to an empty string, in an effort to get a - // faster shutdown of any chromium processes interacting with audio - if (rootItem && _contentType == ContentType::HtmlContent) { - rootItem->setProperty(URL_PROPERTY, ""); - } - - if (rootItem && rootItem->objectName() == "tabletRoot") { - auto tabletScriptingInterface = DependencyManager::get(); - tabletScriptingInterface->setQmlTabletRoot("com.highfidelity.interface.tablet.system", nullptr); - } // Fix for crash in QtWebEngineCore when rapidly switching domains // Call stop on the QWebEngineView before destroying OffscreenQMLSurface. - if (rootItem) { - QObject* obj = rootItem->findChild("webEngineView"); - if (obj) { - // stop loading - QMetaObject::invokeMethod(obj, "stop"); - } + if (rootItem && contentType == ContentType::HtmlContent) { + // stop loading + QMetaObject::invokeMethod(rootItem, "stop"); } webSurface->pause(); diff --git a/libraries/entities/src/DiffTraversal.cpp b/libraries/entities/src/DiffTraversal.cpp index 764c420197..39328e11ad 100644 --- a/libraries/entities/src/DiffTraversal.cpp +++ b/libraries/entities/src/DiffTraversal.cpp @@ -13,7 +13,6 @@ #include - DiffTraversal::Waypoint::Waypoint(EntityTreeElementPointer& element) : _nextIndex(0) { assert(element); _weakElement = element; @@ -37,15 +36,14 @@ void DiffTraversal::Waypoint::getNextVisibleElementFirstTime(DiffTraversal::Visi EntityTreeElementPointer nextElement = element->getChildAtIndex(_nextIndex); ++_nextIndex; if (nextElement) { - if (!view.usesViewFrustum) { + const auto& cube = nextElement->getAACube(); + if (!view.usesViewFrustums()) { // No LOD truncation if we aren't using the view frustum next.element = nextElement; return; - } else if (view.viewFrustum.cubeIntersectsKeyhole(nextElement->getAACube())) { + } else if (view.intersects(cube)) { // check for LOD truncation - float distance = glm::distance(view.viewFrustum.getPosition(), nextElement->getAACube().calcCenter()) + MIN_VISIBLE_DISTANCE; - float angularDiameter = nextElement->getAACube().getScale() / distance; - if (angularDiameter > MIN_ELEMENT_ANGULAR_DIAMETER * view.lodScaleFactor) { + if (view.isBigEnough(cube, MIN_ELEMENT_ANGULAR_DIAMETER)) { next.element = nextElement; return; } @@ -76,17 +74,16 @@ void DiffTraversal::Waypoint::getNextVisibleElementRepeat( EntityTreeElementPointer nextElement = element->getChildAtIndex(_nextIndex); ++_nextIndex; if (nextElement && nextElement->getLastChanged() > lastTime) { - if (!view.usesViewFrustum) { + if (!view.usesViewFrustums()) { // No LOD truncation if we aren't using the view frustum next.element = nextElement; next.intersection = ViewFrustum::INSIDE; return; } else { // check for LOD truncation - float distance = glm::distance(view.viewFrustum.getPosition(), nextElement->getAACube().calcCenter()) + MIN_VISIBLE_DISTANCE; - float angularDiameter = nextElement->getAACube().getScale() / distance; - if (angularDiameter > MIN_ELEMENT_ANGULAR_DIAMETER * view.lodScaleFactor) { - ViewFrustum::intersection intersection = view.viewFrustum.calculateCubeKeyholeIntersection(nextElement->getAACube()); + const auto& cube = nextElement->getAACube(); + if (view.isBigEnough(cube, MIN_ELEMENT_ANGULAR_DIAMETER)) { + ViewFrustum::intersection intersection = view.calculateIntersection(cube); if (intersection != ViewFrustum::OUTSIDE) { next.element = nextElement; next.intersection = intersection; @@ -118,14 +115,13 @@ void DiffTraversal::Waypoint::getNextVisibleElementDifferential(DiffTraversal::V EntityTreeElementPointer nextElement = element->getChildAtIndex(_nextIndex); ++_nextIndex; if (nextElement) { - AACube cube = nextElement->getAACube(); // check for LOD truncation - float distance = glm::distance(view.viewFrustum.getPosition(), cube.calcCenter()) + MIN_VISIBLE_DISTANCE; - float angularDiameter = cube.getScale() / distance; - if (angularDiameter > MIN_ELEMENT_ANGULAR_DIAMETER * view.lodScaleFactor) { - if (view.viewFrustum.calculateCubeKeyholeIntersection(cube) != ViewFrustum::OUTSIDE) { + const auto& cube = nextElement->getAACube(); + if (view.isBigEnough(cube, MIN_ELEMENT_ANGULAR_DIAMETER)) { + ViewFrustum::intersection intersection = view.calculateIntersection(cube); + if (intersection != ViewFrustum::OUTSIDE) { next.element = nextElement; - next.intersection = ViewFrustum::OUTSIDE; + next.intersection = intersection; return; } } @@ -137,13 +133,83 @@ void DiffTraversal::Waypoint::getNextVisibleElementDifferential(DiffTraversal::V next.intersection = ViewFrustum::OUTSIDE; } +bool DiffTraversal::View::isBigEnough(const AACube& cube, float minDiameter) const { + if (viewFrustums.empty()) { + // Everything is big enough when not using view frustums + return true; + } + + bool isBigEnough = std::any_of(std::begin(viewFrustums), std::end(viewFrustums), + [&](const ViewFrustum& viewFrustum) { + return isAngularSizeBigEnough(viewFrustum.getPosition(), cube, lodScaleFactor, minDiameter); + }); + + return isBigEnough; +} + +bool DiffTraversal::View::intersects(const AACube& cube) const { + if (viewFrustums.empty()) { + // Everything intersects when not using view frustums + return true; + } + + bool intersects = std::any_of(std::begin(viewFrustums), std::end(viewFrustums), + [&](const ViewFrustum& viewFrustum) { + return viewFrustum.cubeIntersectsKeyhole(cube); + }); + + return intersects; +} + +ViewFrustum::intersection DiffTraversal::View::calculateIntersection(const AACube& cube) const { + if (viewFrustums.empty()) { + // Everything is inside when not using view frustums + return ViewFrustum::INSIDE; + } + + ViewFrustum::intersection intersection = ViewFrustum::OUTSIDE; + + for (const auto& viewFrustum : viewFrustums) { + switch (viewFrustum.calculateCubeKeyholeIntersection(cube)) { + case ViewFrustum::INSIDE: + return ViewFrustum::INSIDE; + case ViewFrustum::INTERSECT: + intersection = ViewFrustum::INTERSECT; + break; + default: + // DO NOTHING + break; + } + } + return intersection; +} + +bool DiffTraversal::View::usesViewFrustums() const { + return !viewFrustums.empty(); +} + +bool DiffTraversal::View::isVerySimilar(const View& view) const { + auto size = view.viewFrustums.size(); + + if (view.lodScaleFactor != lodScaleFactor || + viewFrustums.size() != size) { + return false; + } + + for (size_t i = 0; i < size; ++i) { + if (!viewFrustums[i].isVerySimilar(view.viewFrustums[i])) { + return false; + } + } + return true; +} + DiffTraversal::DiffTraversal() { const int32_t MIN_PATH_DEPTH = 16; _path.reserve(MIN_PATH_DEPTH); } -DiffTraversal::Type DiffTraversal::prepareNewTraversal(const ViewFrustum& viewFrustum, EntityTreeElementPointer root, - int32_t lodLevelOffset, bool usesViewFrustum) { +DiffTraversal::Type DiffTraversal::prepareNewTraversal(const DiffTraversal::View& view, EntityTreeElementPointer root) { assert(root); // there are three types of traversal: // @@ -159,29 +225,25 @@ DiffTraversal::Type DiffTraversal::prepareNewTraversal(const ViewFrustum& viewFr // // external code should update the _scanElementCallback after calling prepareNewTraversal // - _currentView.usesViewFrustum = usesViewFrustum; - float lodScaleFactor = powf(2.0f, lodLevelOffset); Type type; // If usesViewFrustum changes, treat it as a First traversal - if (_completedView.startTime == 0 || _currentView.usesViewFrustum != _completedView.usesViewFrustum) { + if (_completedView.startTime == 0 || _currentView.usesViewFrustums() != _completedView.usesViewFrustums()) { type = Type::First; - _currentView.viewFrustum = viewFrustum; - _currentView.lodScaleFactor = lodScaleFactor; + _currentView.viewFrustums = view.viewFrustums; + _currentView.lodScaleFactor = view.lodScaleFactor; _getNextVisibleElementCallback = [this](DiffTraversal::VisibleElement& next) { _path.back().getNextVisibleElementFirstTime(next, _currentView); }; - } else if (!_currentView.usesViewFrustum || - (_completedView.viewFrustum.isVerySimilar(viewFrustum) && - lodScaleFactor == _completedView.lodScaleFactor)) { + } else if (!_currentView.usesViewFrustums() || _completedView.isVerySimilar(view)) { type = Type::Repeat; _getNextVisibleElementCallback = [this](DiffTraversal::VisibleElement& next) { _path.back().getNextVisibleElementRepeat(next, _completedView, _completedView.startTime); }; } else { type = Type::Differential; - _currentView.viewFrustum = viewFrustum; - _currentView.lodScaleFactor = lodScaleFactor; + _currentView.viewFrustums = view.viewFrustums; + _currentView.lodScaleFactor = view.lodScaleFactor; _getNextVisibleElementCallback = [this](DiffTraversal::VisibleElement& next) { _path.back().getNextVisibleElementDifferential(next, _currentView, _completedView); }; diff --git a/libraries/entities/src/DiffTraversal.h b/libraries/entities/src/DiffTraversal.h index 69431d8db5..0fd014ac23 100644 --- a/libraries/entities/src/DiffTraversal.h +++ b/libraries/entities/src/DiffTraversal.h @@ -30,10 +30,15 @@ public: // View is a struct with a ViewFrustum and LOD parameters class View { public: - ViewFrustum viewFrustum; + bool isBigEnough(const AACube& cube, float minDiameter = MIN_ENTITY_ANGULAR_DIAMETER) const; + bool intersects(const AACube& cube) const; + bool usesViewFrustums() const; + bool isVerySimilar(const View& view) const; + ViewFrustum::intersection calculateIntersection(const AACube& cube) const; + + ViewFrustums viewFrustums; uint64_t startTime { 0 }; float lodScaleFactor { 1.0f }; - bool usesViewFrustum { true }; }; // Waypoint is an bookmark in a "path" of waypoints during a traversal. @@ -57,15 +62,12 @@ public: DiffTraversal(); - Type prepareNewTraversal(const ViewFrustum& viewFrustum, EntityTreeElementPointer root, int32_t lodLevelOffset, - bool usesViewFrustum); + Type prepareNewTraversal(const DiffTraversal::View& view, EntityTreeElementPointer root); - const ViewFrustum& getCurrentView() const { return _currentView.viewFrustum; } - const ViewFrustum& getCompletedView() const { return _completedView.viewFrustum; } + const View& getCurrentView() const { return _currentView; } + const View& getCompletedView() const { return _completedView; } - bool doesCurrentUseViewFrustum() const { return _currentView.usesViewFrustum; } - float getCurrentLODScaleFactor() const { return _currentView.lodScaleFactor; } - float getCompletedLODScaleFactor() const { return _completedView.lodScaleFactor; } + bool doesCurrentUseViewFrustum() const { return _currentView.usesViewFrustums(); } uint64_t getStartOfCompletedTraversal() const { return _completedView.startTime; } bool finished() const { return _path.empty(); } diff --git a/libraries/entities/src/EntityScriptingInterface.h b/libraries/entities/src/EntityScriptingInterface.h index 5e76d18515..8adb5138f2 100644 --- a/libraries/entities/src/EntityScriptingInterface.h +++ b/libraries/entities/src/EntityScriptingInterface.h @@ -94,6 +94,12 @@ void RayToEntityIntersectionResultFromScriptValue(const QScriptValue& object, Ra * Interface has displayed and so knows about. * * @namespace Entities + * + * @hifi-interface + * @hifi-client-entity + * @hifi-server-entity + * @hifi-assignment-client + * * @property {Uuid} keyboardFocusEntity - Get or set the {@link Entities.EntityType|Web} entity that has keyboard focus. * If no entity has keyboard focus, get returns null; set to null or {@link Uuid|Uuid.NULL} to * clear keyboard focus. diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index b56f367e0a..3149527216 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -1402,6 +1402,7 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c case PacketType::EntityAdd: isAdd = true; // fall through to next case + // FALLTHRU case PacketType::EntityPhysics: case PacketType::EntityEdit: { quint64 startDecode = 0, endDecode = 0; diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index 3289101967..d95dbf2990 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -27,9 +27,6 @@ using EntityTreePointer = std::shared_ptr; #include "MovingEntitiesOperator.h" class EntityEditFilters; -class Model; -using ModelPointer = std::shared_ptr; -using ModelWeakPointer = std::weak_ptr; class EntitySimulation; diff --git a/libraries/fbx/src/FBX.h b/libraries/fbx/src/FBX.h index a609d85fc8..ce3fc52c3a 100644 --- a/libraries/fbx/src/FBX.h +++ b/libraries/fbx/src/FBX.h @@ -298,6 +298,7 @@ public: bool hasSkeletonJoints; QVector meshes; + QVector scripts; QHash materials; diff --git a/libraries/fbx/src/FBXReader_Mesh.cpp b/libraries/fbx/src/FBXReader_Mesh.cpp index e8365e38b7..801edddb06 100644 --- a/libraries/fbx/src/FBXReader_Mesh.cpp +++ b/libraries/fbx/src/FBXReader_Mesh.cpp @@ -249,7 +249,7 @@ ExtractedMesh FBXReader::extractMesh(const FBXNode& object, unsigned int& meshIn indexToDirect = true; } } - if (indexToDirect && data.normalIndices.isEmpty()) { + if (indexToDirect && data.colorIndices.isEmpty()) { // hack to work around wacky Makehuman exports data.colorsByVertex = true; } diff --git a/libraries/fbx/src/FBXWriter.cpp b/libraries/fbx/src/FBXWriter.cpp index 511f253193..e6adff0df9 100644 --- a/libraries/fbx/src/FBXWriter.cpp +++ b/libraries/fbx/src/FBXWriter.cpp @@ -142,7 +142,6 @@ void FBXWriter::encodeFBXProperty(QDataStream& out, const QVariant& prop) { out << prop.toInt(); break; - encodeNode(out, FBXNode()); case QMetaType::Float: out.device()->write("F", 1); out << prop.toFloat(); diff --git a/libraries/fbx/src/FSTReader.cpp b/libraries/fbx/src/FSTReader.cpp index cc4a919445..d63a5b3cc4 100644 --- a/libraries/fbx/src/FSTReader.cpp +++ b/libraries/fbx/src/FSTReader.cpp @@ -84,7 +84,7 @@ void FSTReader::writeVariant(QBuffer& buffer, QVariantHash::const_iterator& it) QByteArray FSTReader::writeMapping(const QVariantHash& mapping) { static const QStringList PREFERED_ORDER = QStringList() << NAME_FIELD << TYPE_FIELD << SCALE_FIELD << FILENAME_FIELD - << TEXDIR_FIELD << JOINT_FIELD << FREE_JOINT_FIELD + << TEXDIR_FIELD << SCRIPT_FIELD << JOINT_FIELD << FREE_JOINT_FIELD << BLENDSHAPE_FIELD << JOINT_INDEX_FIELD; QBuffer buffer; buffer.open(QIODevice::WriteOnly); @@ -92,7 +92,7 @@ QByteArray FSTReader::writeMapping(const QVariantHash& mapping) { for (auto key : PREFERED_ORDER) { auto it = mapping.find(key); if (it != mapping.constEnd()) { - if (key == FREE_JOINT_FIELD) { // writeVariant does not handle strings added using insertMulti. + if (key == FREE_JOINT_FIELD || key == SCRIPT_FIELD) { // writeVariant does not handle strings added using insertMulti. for (auto multi : mapping.values(key)) { buffer.write(key.toUtf8()); buffer.write(" = "); @@ -187,6 +187,26 @@ FSTReader::ModelType FSTReader::predictModelType(const QVariantHash& mapping) { return ENTITY_MODEL; } +QVector FSTReader::getScripts(const QUrl& url, const QVariantHash& mapping) { + + auto fstMapping = mapping.isEmpty() ? downloadMapping(url.toString()) : mapping; + QVector scriptPaths; + if (!fstMapping.value(SCRIPT_FIELD).isNull()) { + auto scripts = fstMapping.values(SCRIPT_FIELD).toVector(); + for (auto &script : scripts) { + QString scriptPath = script.toString(); + if (QUrl(scriptPath).isRelative()) { + if (scriptPath.at(0) == '/') { + scriptPath = scriptPath.right(scriptPath.length() - 1); + } + scriptPath = url.resolved(QUrl(scriptPath)).toString(); + } + scriptPaths.push_back(scriptPath); + } + } + return scriptPaths; +} + QVariantHash FSTReader::downloadMapping(const QString& url) { QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); QNetworkRequest networkRequest = QNetworkRequest(url); diff --git a/libraries/fbx/src/FSTReader.h b/libraries/fbx/src/FSTReader.h index 981bae4feb..4a8574f0cf 100644 --- a/libraries/fbx/src/FSTReader.h +++ b/libraries/fbx/src/FSTReader.h @@ -28,6 +28,7 @@ static const QString TRANSLATION_Z_FIELD = "tz"; static const QString JOINT_FIELD = "joint"; static const QString FREE_JOINT_FIELD = "freeJoint"; static const QString BLENDSHAPE_FIELD = "bs"; +static const QString SCRIPT_FIELD = "script"; class FSTReader { public: @@ -49,6 +50,8 @@ public: /// Predicts the type of model by examining the mapping static ModelType predictModelType(const QVariantHash& mapping); + static QVector getScripts(const QUrl& fstUrl, const QVariantHash& mapping = QVariantHash()); + static QString getNameFromType(ModelType modelType); static FSTReader::ModelType getTypeFromName(const QString& name); static QVariantHash downloadMapping(const QString& url); diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLBackend.h b/libraries/gpu-gl-common/src/gpu/gl/GLBackend.h index 5bbb44f9e1..314bbee387 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLBackend.h +++ b/libraries/gpu-gl-common/src/gpu/gl/GLBackend.h @@ -91,7 +91,7 @@ public: // this is the maximum per shader stage on the low end apple // TODO make it platform dependant at init time - static const int MAX_NUM_UNIFORM_BUFFERS = 12; + static const int MAX_NUM_UNIFORM_BUFFERS = 14; size_t getMaxNumUniformBuffers() const { return MAX_NUM_UNIFORM_BUFFERS; } // this is the maximum per shader stage on the low end apple diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLBackendOutput.cpp b/libraries/gpu-gl-common/src/gpu/gl/GLBackendOutput.cpp index 2285b0e486..d1ab34da90 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLBackendOutput.cpp +++ b/libraries/gpu-gl-common/src/gpu/gl/GLBackendOutput.cpp @@ -46,10 +46,10 @@ void GLBackend::do_setFramebuffer(const Batch& batch, size_t paramOffset) { } void GLBackend::do_setFramebufferSwapChain(const Batch& batch, size_t paramOffset) { - auto swapChain = batch._swapChains.get(batch._params[paramOffset]._uint); + auto swapChain = std::static_pointer_cast(batch._swapChains.get(batch._params[paramOffset]._uint)); if (swapChain) { auto index = batch._params[paramOffset + 1]._uint; - FramebufferPointer framebuffer = static_cast(swapChain.get())->get(index); + const auto& framebuffer = swapChain->get(index); setFramebuffer(framebuffer); } } diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLBackendPipeline.cpp b/libraries/gpu-gl-common/src/gpu/gl/GLBackendPipeline.cpp index 58fcc51605..91f1d8bb8c 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLBackendPipeline.cpp +++ b/libraries/gpu-gl-common/src/gpu/gl/GLBackendPipeline.cpp @@ -268,7 +268,7 @@ void GLBackend::do_setResourceFramebufferSwapChainTexture(const Batch& batch, si return; } - SwapChainPointer swapChain = batch._swapChains.get(batch._params[paramOffset + 0]._uint); + auto swapChain = std::static_pointer_cast(batch._swapChains.get(batch._params[paramOffset + 0]._uint)); if (!swapChain) { releaseResourceTexture(slot); @@ -276,9 +276,8 @@ void GLBackend::do_setResourceFramebufferSwapChainTexture(const Batch& batch, si } auto index = batch._params[paramOffset + 2]._uint; auto renderBufferSlot = batch._params[paramOffset + 3]._uint; - FramebufferPointer resourceFramebuffer = static_cast(swapChain.get())->get(index); - TexturePointer resourceTexture = resourceFramebuffer->getRenderBuffer(renderBufferSlot); - + auto resourceFramebuffer = swapChain->get(index); + auto resourceTexture = resourceFramebuffer->getRenderBuffer(renderBufferSlot); setResourceTexture(slot, resourceTexture); } diff --git a/libraries/gpu/src/gpu/Batch.cpp b/libraries/gpu/src/gpu/Batch.cpp index 84a7c275f0..90115806b4 100644 --- a/libraries/gpu/src/gpu/Batch.cpp +++ b/libraries/gpu/src/gpu/Batch.cpp @@ -34,7 +34,7 @@ ProfileRangeBatch::~ProfileRangeBatch() { using namespace gpu; // FIXME make these backend / pipeline dependent. -static const int MAX_NUM_UNIFORM_BUFFERS = 12; +static const int MAX_NUM_UNIFORM_BUFFERS = 14; static const int MAX_NUM_RESOURCE_BUFFERS = 16; static const int MAX_NUM_RESOURCE_TEXTURES = 16; diff --git a/libraries/gpu/src/gpu/ResourceSwapChain.h b/libraries/gpu/src/gpu/ResourceSwapChain.h index 7b46b35521..28c5ff2ed3 100644 --- a/libraries/gpu/src/gpu/ResourceSwapChain.h +++ b/libraries/gpu/src/gpu/ResourceSwapChain.h @@ -15,18 +15,18 @@ namespace gpu { class SwapChain { public: - SwapChain(unsigned int size = 2U) : _size{ size } {} + SwapChain(uint8_t size = 2U) : _size{ size } {} virtual ~SwapChain() {} void advance() { _frontIndex = (_frontIndex + 1) % _size; } - unsigned int getSize() const { return _size; } + uint8_t getSize() const { return _size; } protected: - unsigned int _size; - unsigned int _frontIndex{ 0U }; + const uint8_t _size; + uint8_t _frontIndex{ 0U }; }; typedef std::shared_ptr SwapChainPointer; @@ -41,16 +41,13 @@ namespace gpu { using Type = R; using TypePointer = std::shared_ptr; + using TypeConstPointer = std::shared_ptr; - ResourceSwapChain(unsigned int size = 2U) : SwapChain{ size } {} - - void reset() { - for (auto& ptr : _resources) { - ptr.reset(); + ResourceSwapChain(const std::vector& v) : SwapChain{ std::min((uint8_t)v.size(), MAX_SIZE) } { + for (size_t i = 0; i < _size; ++i) { + _resources[i] = v[i]; } } - - TypePointer& edit(unsigned int index) { return _resources[(index + _frontIndex) % _size]; } const TypePointer& get(unsigned int index) const { return _resources[(index + _frontIndex) % _size]; } private: diff --git a/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.h b/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.h index 526352804b..b88c6345cf 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.h +++ b/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.h @@ -24,6 +24,9 @@ /**jsdoc * The experimental Graphics API (experimental) lets you query and manage certain graphics-related structures (like underlying meshes and textures) from scripting. * @namespace Graphics + * + * @hifi-interface + * @hifi-client-entity */ class GraphicsScriptingInterface : public QObject, public QScriptable, public Dependency { @@ -39,7 +42,7 @@ public slots: * * @function Graphics.getModel * @param {UUID} entityID - The objectID of the model whose meshes are to be retrieved. - * @return {Graphics.Model} the resulting Model object + * @returns {Graphics.Model} the resulting Model object */ scriptable::ScriptableModelPointer getModel(QUuid uuid); @@ -54,7 +57,7 @@ public slots: * * @function Graphics.newMesh * @param {Graphics.IFSData} ifsMeshData Index-Faced Set (IFS) arrays used to create the new mesh. - * @return {Graphics.Mesh} the resulting Mesh / Mesh Part object + * @returns {Graphics.Mesh} the resulting Mesh / Mesh Part object */ /**jsdoc * @typedef {object} Graphics.IFSData diff --git a/libraries/input-plugins/src/input-plugins/TouchscreenVirtualPadDevice.cpp b/libraries/input-plugins/src/input-plugins/TouchscreenVirtualPadDevice.cpp index 957104bd30..7ee2135325 100644 --- a/libraries/input-plugins/src/input-plugins/TouchscreenVirtualPadDevice.cpp +++ b/libraries/input-plugins/src/input-plugins/TouchscreenVirtualPadDevice.cpp @@ -43,6 +43,18 @@ void TouchscreenVirtualPadDevice::init() { _fixedPosition = true; // This should be config _viewTouchUpdateCount = 0; + resize(); + + auto& virtualPadManager = VirtualPad::Manager::instance(); + + if (_fixedPosition) { + virtualPadManager.getLeftVirtualPad()->setShown(virtualPadManager.isEnabled() && !virtualPadManager.isHidden()); // Show whenever it's enabled + } + + KeyboardMouseDevice::enableTouch(false); // Touch for view controls is managed by this plugin +} + +void TouchscreenVirtualPadDevice::resize() { QScreen* eventScreen = qApp->primaryScreen(); if (_screenDPIProvided != eventScreen->physicalDotsPerInch()) { _screenWidthCenter = eventScreen->size().width() / 2; @@ -59,12 +71,6 @@ void TouchscreenVirtualPadDevice::init() { auto& virtualPadManager = VirtualPad::Manager::instance(); setupControlsPositions(virtualPadManager, true); - - if (_fixedPosition) { - virtualPadManager.getLeftVirtualPad()->setShown(virtualPadManager.isEnabled() && !virtualPadManager.isHidden()); // Show whenever it's enabled - } - - KeyboardMouseDevice::enableTouch(false); // Touch for view controls is managed by this plugin } void TouchscreenVirtualPadDevice::setupControlsPositions(VirtualPad::Manager& virtualPadManager, bool force) { @@ -80,9 +86,10 @@ void TouchscreenVirtualPadDevice::setupControlsPositions(VirtualPad::Manager& vi virtualPadManager.getLeftVirtualPad()->setFirstTouch(_moveRefTouchPoint); // Jump button - float leftMargin = _screenDPI * VirtualPad::Manager::JUMP_BTN_LEFT_MARGIN_PIXELS / VirtualPad::Manager::DPI; + float jumpBtnPixelSize = _screenDPI * VirtualPad::Manager::JUMP_BTN_FULL_PIXELS / VirtualPad::Manager::DPI; + float rightMargin = _screenDPI * VirtualPad::Manager::JUMP_BTN_RIGHT_MARGIN_PIXELS / VirtualPad::Manager::DPI; float bottomMargin = _screenDPI * VirtualPad::Manager::JUMP_BTN_BOTTOM_MARGIN_PIXELS/ VirtualPad::Manager::DPI; - _jumpButtonPosition = glm::vec2( _jumpButtonRadius + leftMargin, eventScreen->size().height() - bottomMargin - _jumpButtonRadius - _extraBottomMargin); + _jumpButtonPosition = glm::vec2( eventScreen->size().width() - rightMargin - jumpBtnPixelSize, eventScreen->size().height() - bottomMargin - _jumpButtonRadius - _extraBottomMargin); virtualPadManager.setJumpButtonPosition(_jumpButtonPosition); } diff --git a/libraries/input-plugins/src/input-plugins/TouchscreenVirtualPadDevice.h b/libraries/input-plugins/src/input-plugins/TouchscreenVirtualPadDevice.h index 212b7633ec..e7e540b503 100644 --- a/libraries/input-plugins/src/input-plugins/TouchscreenVirtualPadDevice.h +++ b/libraries/input-plugins/src/input-plugins/TouchscreenVirtualPadDevice.h @@ -26,6 +26,7 @@ public: // Plugin functions virtual void init() override; + virtual void resize(); virtual bool isSupported() const override; virtual const QString getName() const override { return NAME; } diff --git a/libraries/model-networking/src/model-networking/ModelCache.cpp b/libraries/model-networking/src/model-networking/ModelCache.cpp index f17cdbb7e8..45f2462f57 100644 --- a/libraries/model-networking/src/model-networking/ModelCache.cpp +++ b/libraries/model-networking/src/model-networking/ModelCache.cpp @@ -63,16 +63,18 @@ void GeometryMappingResource::downloadFinished(const QByteArray& data) { PROFILE_ASYNC_BEGIN(resource_parse_geometry, "GeometryMappingResource::downloadFinished", _url.toString(), { { "url", _url.toString() } }); - auto mapping = FSTReader::readMapping(data); + // store parsed contents of FST file + _mapping = FSTReader::readMapping(data); + + QString filename = _mapping.value("filename").toString(); - QString filename = mapping.value("filename").toString(); if (filename.isNull()) { qCDebug(modelnetworking) << "Mapping file" << _url << "has no \"filename\" field"; finishedLoading(false); } else { QUrl url = _url.resolved(filename); - QString texdir = mapping.value(TEXDIR_FIELD).toString(); + QString texdir = _mapping.value(TEXDIR_FIELD).toString(); if (!texdir.isNull()) { if (!texdir.endsWith('/')) { texdir += '/'; @@ -82,7 +84,16 @@ void GeometryMappingResource::downloadFinished(const QByteArray& data) { _textureBaseUrl = url.resolved(QUrl(".")); } - auto animGraphVariant = mapping.value("animGraphUrl"); + auto scripts = FSTReader::getScripts(_url, _mapping); + if (scripts.size() > 0) { + _mapping.remove(SCRIPT_FIELD); + for (auto &scriptPath : scripts) { + _mapping.insertMulti(SCRIPT_FIELD, scriptPath); + } + } + + auto animGraphVariant = _mapping.value("animGraphUrl"); + if (animGraphVariant.isValid()) { QUrl fstUrl(animGraphVariant.toString()); if (fstUrl.isValid()) { @@ -95,7 +106,7 @@ void GeometryMappingResource::downloadFinished(const QByteArray& data) { } auto modelCache = DependencyManager::get(); - GeometryExtra extra{ mapping, _textureBaseUrl, false }; + GeometryExtra extra{ _mapping, _textureBaseUrl, false }; // Get the raw GeometryResource _geometryResource = modelCache->getResource(url, QUrl(), &extra).staticCast(); @@ -209,6 +220,14 @@ void GeometryReader::run() { throw QString("unsupported format"); } + // Add scripts to fbxgeometry + if (!_mapping.value(SCRIPT_FIELD).isNull()) { + QVariantList scripts = _mapping.values(SCRIPT_FIELD); + for (auto &script : scripts) { + fbxGeometry->scripts.push_back(script.toString()); + } + } + // Ensure the resource has not been deleted auto resource = _resource.toStrongRef(); if (!resource) { @@ -362,6 +381,7 @@ Geometry::Geometry(const Geometry& geometry) { } _animGraphOverrideUrl = geometry._animGraphOverrideUrl; + _mapping = geometry._mapping; } void Geometry::setTextures(const QVariantMap& textureMap) { diff --git a/libraries/model-networking/src/model-networking/ModelCache.h b/libraries/model-networking/src/model-networking/ModelCache.h index 9532f39ce0..7e911bc9bf 100644 --- a/libraries/model-networking/src/model-networking/ModelCache.h +++ b/libraries/model-networking/src/model-networking/ModelCache.h @@ -55,6 +55,7 @@ public: virtual bool areTexturesLoaded() const; const QUrl& getAnimGraphOverrideUrl() const { return _animGraphOverrideUrl; } + const QVariantHash& getMapping() const { return _mapping; } protected: friend class GeometryMappingResource; @@ -68,6 +69,7 @@ protected: NetworkMaterials _materials; QUrl _animGraphOverrideUrl; + QVariantHash _mapping; // parsed contents of FST file. private: mutable bool _areTexturesLoaded { false }; @@ -144,6 +146,9 @@ public: * API to manage model cache resources. * @namespace ModelCache * + * @hifi-interface + * @hifi-client-entity + * * @property {number} numTotal - Total number of total resources. Read-only. * @property {number} numCached - Total number of cached resource. Read-only. * @property {number} sizeTotal - Size in bytes of all resources. Read-only. @@ -156,7 +161,7 @@ public: /**jsdoc * Get the list of all resource URLs. * @function ModelCache.getResourceList - * @return {string[]} + * @returns {string[]} */ /**jsdoc @@ -170,10 +175,11 @@ public: */ /**jsdoc + * Prefetches a resource. * @function ModelCache.prefetch - * @param {string} url - * @param {object} extra - * @returns {object} + * @param {string} url - URL of the resource to prefetch. + * @param {object} [extra=null] + * @returns {Resource} */ /**jsdoc @@ -182,14 +188,7 @@ public: * @param {string} url - URL of the resource to load. * @param {string} [fallback=""] - Fallback URL if load of the desired URL fails. * @param {} [extra=null] - * @return {Resource} - */ - - /**jsdoc - * Prefetches a resource. - * @function ModelCache.prefetch - * @param {string} url - URL of the resource to prefetch. - * @return {Resource} + * @returns {Resource} */ diff --git a/libraries/model-networking/src/model-networking/TextureCache.h b/libraries/model-networking/src/model-networking/TextureCache.h index 3f46dc3074..4ea8a98976 100644 --- a/libraries/model-networking/src/model-networking/TextureCache.h +++ b/libraries/model-networking/src/model-networking/TextureCache.h @@ -151,6 +151,9 @@ public: * API to manage texture cache resources. * @namespace TextureCache * + * @hifi-interface + * @hifi-client-entity + * * @property {number} numTotal - Total number of total resources. Read-only. * @property {number} numCached - Total number of cached resource. Read-only. * @property {number} sizeTotal - Size in bytes of all resources. Read-only. @@ -160,44 +163,38 @@ public: // Functions are copied over from ResourceCache (see ResourceCache.h for reason). - /**jsdoc - * Get the list of all resource URLs. - * @function TextureCache.getResourceList - * @return {string[]} - */ + /**jsdoc + * Get the list of all resource URLs. + * @function TextureCache.getResourceList + * @returns {string[]} + */ - /**jsdoc - * @function TextureCache.dirty - * @returns {Signal} - */ + /**jsdoc + * @function TextureCache.dirty + * @returns {Signal} + */ - /**jsdoc - * @function TextureCache.updateTotalSize - * @param {number} deltaSize - */ + /**jsdoc + * @function TextureCache.updateTotalSize + * @param {number} deltaSize + */ - /**jsdoc - * @function TextureCache.prefetch - * @param {string} url - * @param {object} extra - * @returns {object} - */ + /**jsdoc + * Prefetches a resource. + * @function TextureCache.prefetch + * @param {string} url - URL of the resource to prefetch. + * @param {object} [extra=null] + * @returns {Resource} + */ - /**jsdoc - * Asynchronously loads a resource from the specified URL and returns it. - * @function TextureCache.getResource - * @param {string} url - URL of the resource to load. - * @param {string} [fallback=""] - Fallback URL if load of the desired URL fails. - * @param {} [extra=null] - * @return {Resource} - */ - - /**jsdoc - * Prefetches a resource. - * @function TextureCache.prefetch - * @param {string} url - URL of the resource to prefetch. - * @return {Resource} - */ + /**jsdoc + * Asynchronously loads a resource from the specified URL and returns it. + * @function TextureCache.getResource + * @param {string} url - URL of the resource to load. + * @param {string} [fallback=""] - Fallback URL if load of the desired URL fails. + * @param {} [extra=null] + * @returns {Resource} + */ /// Returns the ID of the permutation/normal texture used for Perlin noise shader programs. This texture @@ -246,10 +243,11 @@ signals: protected: /**jsdoc - * @function TextureCache.prefect + * @function TextureCache.prefetch * @param {string} url * @param {number} type * @param {number} [maxNumPixels=67108864] + * @returns {Resource} */ // Overload ResourceCache::prefetch to allow specifying texture type for loads Q_INVOKABLE ScriptableResource* prefetch(const QUrl& url, int type, int maxNumPixels = ABSOLUTE_MAX_TEXTURE_NUM_PIXELS); diff --git a/libraries/networking/src/AccountManager.cpp b/libraries/networking/src/AccountManager.cpp index f6264a19c4..049129b2ba 100644 --- a/libraries/networking/src/AccountManager.cpp +++ b/libraries/networking/src/AccountManager.cpp @@ -106,7 +106,11 @@ void AccountManager::logout() { } QString accountFileDir() { +#if defined(Q_OS_ANDROID) + return QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/../files"; +#else return QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); +#endif } QString accountFilePath() { diff --git a/libraries/networking/src/AddressManager.cpp b/libraries/networking/src/AddressManager.cpp index 055d94c3b3..edb2992128 100644 --- a/libraries/networking/src/AddressManager.cpp +++ b/libraries/networking/src/AddressManager.cpp @@ -114,8 +114,6 @@ QUrl AddressManager::currentFacingPublicAddress() const { void AddressManager::loadSettings(const QString& lookupString) { #if defined(USE_GLES) && defined(Q_OS_WIN) handleUrl(QUrl("hifi://127.0.0.0"), LookupTrigger::StartupFromSettings); -#elif defined(Q_OS_ANDROID) - handleUrl(QUrl("hifi://pikachu/167.11,0.745735,181.529/0,0.887027,0,-0.461717"), LookupTrigger::StartupFromSettings); #else if (lookupString.isEmpty()) { handleUrl(currentAddressHandle.get(), LookupTrigger::StartupFromSettings); diff --git a/libraries/networking/src/AddressManager.h b/libraries/networking/src/AddressManager.h index 94eff46bda..8e2553779b 100644 --- a/libraries/networking/src/AddressManager.h +++ b/libraries/networking/src/AddressManager.h @@ -33,11 +33,14 @@ const QString GET_PLACE = "/api/v1/places/%1"; * The location API provides facilities related to your current location in the metaverse. * * @namespace location + * + * @hifi-interface + * @hifi-client-entity + * @hifi-assignment-client + * * @property {Uuid} domainID - A UUID uniquely identifying the domain you're visiting. Is {@link Uuid|Uuid.NULL} if you're not * connected to the domain or are in a serverless domain. * Read-only. - * @property {Uuid} domainId - Synonym for domainId. Read-only. Deprecated: This property - * is deprecated and will soon be removed. * @property {string} hostname - The name of the domain for your current metaverse address (e.g., "AvatarIsland", * localhost, or an IP address). Is blank if you're in a serverless domain. * Read-only. @@ -68,7 +71,6 @@ class AddressManager : public QObject, public Dependency { Q_PROPERTY(QString pathname READ currentPath) Q_PROPERTY(QString placename READ getPlaceName) Q_PROPERTY(QString domainID READ getDomainID) - Q_PROPERTY(QString domainId READ getDomainID) public: using PositionGetter = std::function; using OrientationGetter = std::function; diff --git a/libraries/networking/src/LimitedNodeList.cpp b/libraries/networking/src/LimitedNodeList.cpp index 31500be682..0660f02fd6 100644 --- a/libraries/networking/src/LimitedNodeList.cpp +++ b/libraries/networking/src/LimitedNodeList.cpp @@ -302,6 +302,7 @@ bool LimitedNodeList::packetSourceAndHashMatchAndTrackBandwidth(const udt::Packe } } else { NLPacket::LocalID sourceLocalID = Node::NULL_LOCAL_ID; + // check if we were passed a sourceNode hint or if we need to look it up if (!sourceNode) { // figure out which node this is from @@ -314,6 +315,7 @@ bool LimitedNodeList::packetSourceAndHashMatchAndTrackBandwidth(const udt::Packe QUuid sourceID = sourceNode ? sourceNode->getUUID() : QUuid(); if (!sourceNode && + !isDomainServer() && sourceLocalID == getDomainLocalID() && packet.getSenderSockAddr() == getDomainSockAddr() && PacketTypeEnum::getDomainSourcedPackets().contains(headerType)) { @@ -608,6 +610,7 @@ bool LimitedNodeList::killNodeWithUUID(const QUuid& nodeUUID, ConnectionID newCo { QWriteLocker writeLocker(&_nodeMutex); + _localIDMap.unsafe_erase(matchingNode->getLocalID()); _nodeHash.unsafe_erase(it); } diff --git a/libraries/networking/src/MessagesClient.h b/libraries/networking/src/MessagesClient.h index 6ef3777d8c..f2ccfe33f4 100644 --- a/libraries/networking/src/MessagesClient.h +++ b/libraries/networking/src/MessagesClient.h @@ -37,6 +37,11 @@ * * * @namespace Messages + * + * @hifi-interface + * @hifi-client-entity + * @hifi-server-entity + * @hifi-assignment-client */ class MessagesClient : public QObject, public Dependency { Q_OBJECT diff --git a/libraries/networking/src/NodeList.h b/libraries/networking/src/NodeList.h index 9595c5da84..c5cf5e9524 100644 --- a/libraries/networking/src/NodeList.h +++ b/libraries/networking/src/NodeList.h @@ -167,7 +167,7 @@ private: HifiSockAddr _assignmentServerSocket; bool _isShuttingDown { false }; QTimer _keepAlivePingTimer; - bool _requestsDomainListData; + bool _requestsDomainListData { false }; mutable QReadWriteLock _ignoredSetLock; tbb::concurrent_unordered_set _ignoredNodeIDs; diff --git a/libraries/networking/src/ResourceCache.cpp b/libraries/networking/src/ResourceCache.cpp index 7ba7cca96d..d3583687e8 100644 --- a/libraries/networking/src/ResourceCache.cpp +++ b/libraries/networking/src/ResourceCache.cpp @@ -750,6 +750,7 @@ bool Resource::handleFailedRequest(ResourceRequest::Result result) { qCDebug(networking) << "Timed out loading" << _url << "received" << _bytesReceived << "total" << _bytesTotal; // Fall through to other cases } + // FALLTHRU case ResourceRequest::Result::ServerUnavailable: { _attempts++; _attemptsRemaining--; @@ -768,6 +769,7 @@ bool Resource::handleFailedRequest(ResourceRequest::Result result) { } // fall through to final failure } + // FALLTHRU default: { _attemptsRemaining = 0; qCDebug(networking) << "Error loading " << _url << "attempt:" << _attempts << "attemptsRemaining:" << _attemptsRemaining; diff --git a/libraries/networking/src/ResourceCache.h b/libraries/networking/src/ResourceCache.h index 609483bc56..e2df582aa3 100644 --- a/libraries/networking/src/ResourceCache.h +++ b/libraries/networking/src/ResourceCache.h @@ -89,6 +89,12 @@ class ScriptableResource : public QObject { /**jsdoc * @constructor Resource + * + * @hifi-interface + * @hifi-client-entity + * @hifi-server-entity + * @hifi-assignment-client + * * @property {string} url - URL of this resource. * @property {Resource.State} state - Current loading state. */ @@ -209,7 +215,7 @@ public: /**jsdoc * Get the list of all resource URLs. * @function ResourceCache.getResourceList - * @return {string[]} + * @returns {string[]} */ Q_INVOKABLE QVariantList getResourceList(); @@ -251,10 +257,11 @@ protected slots: void updateTotalSize(const qint64& deltaSize); /**jsdoc + * Prefetches a resource. * @function ResourceCache.prefetch - * @param {string} url - * @param {object} extra - * @returns {object} + * @param {string} url - URL of the resource to prefetch. + * @param {object} [extra=null] + * @returns {Resource} */ // Prefetches a resource to be held by the QScriptEngine. // Left as a protected member so subclasses can overload prefetch @@ -267,7 +274,7 @@ protected slots: * @param {string} url - URL of the resource to load. * @param {string} [fallback=""] - Fallback URL if load of the desired URL fails. * @param {} [extra=null] - * @return {Resource} + * @returns {Resource} */ /// Loads a resource from the specified URL and returns it. /// If the caller is on a different thread than the ResourceCache, @@ -285,12 +292,7 @@ protected: // Pointers created through this method should be owned by the caller, // which should be a QScriptEngine with ScriptableResource registered, so that // the QScriptEngine will delete the pointer when it is garbage collected. - /**jsdoc - * Prefetches a resource. - * @function ResourceCache.prefetch - * @param {string} url - URL of the resource to prefetch. - * @return {Resource} - */ + // JSDoc is provided on more general function signature. Q_INVOKABLE ScriptableResource* prefetch(const QUrl& url) { return prefetch(url, nullptr); } /// Creates a new resource. diff --git a/libraries/networking/src/udt/ConnectionStats.cpp b/libraries/networking/src/udt/ConnectionStats.cpp index e7efe3d5af..46d88e680f 100644 --- a/libraries/networking/src/udt/ConnectionStats.cpp +++ b/libraries/networking/src/udt/ConnectionStats.cpp @@ -9,6 +9,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include #include "ConnectionStats.h" using namespace udt; @@ -112,3 +113,31 @@ void ConnectionStats::recordPacketSendPeriod(int sample) { _currentSample.packetSendPeriod = sample; _total.packetSendPeriod = (int)((_total.packetSendPeriod * EWMA_PREVIOUS_SAMPLES_WEIGHT) + (sample * EWMA_CURRENT_SAMPLE_WEIGHT)); } + +QDebug& operator<<(QDebug&& debug, const udt::ConnectionStats::Stats& stats) { + debug << "Connection stats:\n"; +#define HIFI_LOG_EVENT(x) << " " #x " events: " << stats.events[ConnectionStats::Stats::Event::x] << "\n" + debug + 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) + ; +#undef HIFI_LOG_EVENT + + debug << " Sent packets: " << stats.sentPackets; + debug << "\n Received packets: " << stats.receivedPackets; + debug << "\n Sent util bytes: " << stats.sentUtilBytes; + debug << "\n Sent bytes: " << stats.sentBytes; + debug << "\n Received bytes: " << stats.receivedBytes << "\n"; + return debug; +} diff --git a/libraries/networking/src/udt/ConnectionStats.h b/libraries/networking/src/udt/ConnectionStats.h index 84cd6b2486..7ec7b163ee 100644 --- a/libraries/networking/src/udt/ConnectionStats.h +++ b/libraries/networking/src/udt/ConnectionStats.h @@ -101,4 +101,7 @@ private: } +class QDebug; +QDebug& operator<<(QDebug&& debug, const udt::ConnectionStats::Stats& stats); + #endif // hifi_ConnectionStats_h diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index 98b0e1d892..17b0d90cfe 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -34,7 +34,7 @@ PacketVersion versionForPacketType(PacketType packetType) { case PacketType::EntityPhysics: return static_cast(EntityVersion::MaterialData); case PacketType::EntityQuery: - return static_cast(EntityQueryPacketVersion::RemovedJurisdictions); + return static_cast(EntityQueryPacketVersion::MultiFrustumQuery); case PacketType::AvatarIdentity: case PacketType::AvatarData: case PacketType::BulkAvatarData: @@ -90,6 +90,8 @@ PacketVersion versionForPacketType(PacketType packetType) { return 18; // replace min_avatar_scale and max_avatar_scale with min_avatar_height and max_avatar_height case PacketType::Ping: return static_cast(PingVersion::IncludeConnectionID); + case PacketType::ViewFrustum: + return static_cast(ViewFrustumVersion::SendMultipleFrustums); default: return 20; } diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index e6b133c158..c72bbb0129 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -244,7 +244,8 @@ enum class EntityQueryPacketVersion: PacketVersion { JSONFilter = 18, JSONFilterWithFamilyTree = 19, ConnectionIdentifier = 20, - RemovedJurisdictions = 21 + RemovedJurisdictions = 21, + MultiFrustumQuery = 22 }; enum class AssetServerPacketVersion: PacketVersion { @@ -327,4 +328,8 @@ enum class PingVersion : PacketVersion { IncludeConnectionID = 18 }; +enum class ViewFrustumVersion : PacketVersion { + SendMultipleFrustums = 21 +}; + #endif // hifi_PacketHeaders_h diff --git a/libraries/octree/src/Octree.h b/libraries/octree/src/Octree.h index a2ad834e18..b827ed7cd0 100644 --- a/libraries/octree/src/Octree.h +++ b/libraries/octree/src/Octree.h @@ -59,7 +59,6 @@ const int LOW_RES_MOVING_ADJUST = 1; class EncodeBitstreamParams { public: - ViewFrustum viewFrustum; bool includeExistsBits; NodeData* nodeData; diff --git a/libraries/octree/src/OctreeQuery.cpp b/libraries/octree/src/OctreeQuery.cpp index 18766dd7f6..18e907cb8c 100644 --- a/libraries/octree/src/OctreeQuery.cpp +++ b/libraries/octree/src/OctreeQuery.cpp @@ -9,6 +9,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include "OctreeQuery.h" + #include #include @@ -16,23 +18,11 @@ #include #include -#include "OctreeConstants.h" -#include "OctreeQuery.h" - -const float DEFAULT_FOV = 45.0f; // degrees -const float DEFAULT_ASPECT_RATIO = 1.0f; -const float DEFAULT_NEAR_CLIP = 0.1f; -const float DEFAULT_FAR_CLIP = 3.0f; - -OctreeQuery::OctreeQuery(bool randomizeConnectionID) : - _cameraFov(DEFAULT_FOV), - _cameraAspectRatio(DEFAULT_ASPECT_RATIO), - _cameraNearClip(DEFAULT_NEAR_CLIP), - _cameraFarClip(DEFAULT_FAR_CLIP), - _cameraCenterRadius(DEFAULT_FAR_CLIP) -{ - _maxQueryPPS = DEFAULT_MAX_OCTREE_PPS; +using QueryFlags = uint8_t; +const QueryFlags QUERY_HAS_MAIN_FRUSTUM = 1U << 0; +const QueryFlags QUERY_HAS_SECONDARY_FRUSTUM = 1U << 1; +OctreeQuery::OctreeQuery(bool randomizeConnectionID) { if (randomizeConnectionID) { // randomize our initial octree query connection ID using random_device // the connection ID is 16 bits so we take a generated 32 bit value from random device and chop off the top @@ -47,26 +37,28 @@ int OctreeQuery::getBroadcastData(unsigned char* destinationBuffer) { // pack the connection ID so the server can detect when we start a new connection memcpy(destinationBuffer, &_connectionID, sizeof(_connectionID)); destinationBuffer += sizeof(_connectionID); - - // back a boolean (cut to 1 byte) to designate if this query uses the sent view frustum - memcpy(destinationBuffer, &_usesFrustum, sizeof(_usesFrustum)); - destinationBuffer += sizeof(_usesFrustum); - - if (_usesFrustum) { - // TODO: DRY this up to a shared method - // that can pack any type given the number of bytes - // and return the number of bytes to push the pointer - - // camera details - memcpy(destinationBuffer, &_cameraPosition, sizeof(_cameraPosition)); - destinationBuffer += sizeof(_cameraPosition); - destinationBuffer += packOrientationQuatToBytes(destinationBuffer, _cameraOrientation); - destinationBuffer += packFloatAngleToTwoByte(destinationBuffer, _cameraFov); - destinationBuffer += packFloatRatioToTwoByte(destinationBuffer, _cameraAspectRatio); - destinationBuffer += packClipValueToTwoByte(destinationBuffer, _cameraNearClip); - destinationBuffer += packClipValueToTwoByte(destinationBuffer, _cameraFarClip); - memcpy(destinationBuffer, &_cameraEyeOffsetPosition, sizeof(_cameraEyeOffsetPosition)); - destinationBuffer += sizeof(_cameraEyeOffsetPosition); + + // flags for wether the frustums are present + QueryFlags frustumFlags = 0; + if (_hasMainFrustum) { + frustumFlags |= QUERY_HAS_MAIN_FRUSTUM; + } + if (_hasSecondaryFrustum) { + frustumFlags |= QUERY_HAS_SECONDARY_FRUSTUM; + } + memcpy(destinationBuffer, &frustumFlags, sizeof(frustumFlags)); + destinationBuffer += sizeof(frustumFlags); + + if (_hasMainFrustum) { + auto byteArray = _mainViewFrustum.toByteArray(); + memcpy(destinationBuffer, byteArray.constData(), byteArray.size()); + destinationBuffer += byteArray.size(); + } + + if (_hasSecondaryFrustum) { + auto byteArray = _secondaryViewFrustum.toByteArray(); + memcpy(destinationBuffer, byteArray.constData(), byteArray.size()); + destinationBuffer += byteArray.size(); } // desired Max Octree PPS @@ -80,9 +72,6 @@ int OctreeQuery::getBroadcastData(unsigned char* destinationBuffer) { // desired boundaryLevelAdjust memcpy(destinationBuffer, &_boundaryLevelAdjust, sizeof(_boundaryLevelAdjust)); destinationBuffer += sizeof(_boundaryLevelAdjust); - - memcpy(destinationBuffer, &_cameraCenterRadius, sizeof(_cameraCenterRadius)); - destinationBuffer += sizeof(_cameraCenterRadius); // create a QByteArray that holds the binary representation of the JSON parameters QByteArray binaryParametersDocument; @@ -110,6 +99,7 @@ int OctreeQuery::getBroadcastData(unsigned char* destinationBuffer) { int OctreeQuery::parseData(ReceivedMessage& message) { const unsigned char* startPosition = reinterpret_cast(message.getRawMessage()); + const unsigned char* endPosition = startPosition + message.getSize(); const unsigned char* sourceBuffer = startPosition; // unpack the connection ID @@ -133,20 +123,23 @@ int OctreeQuery::parseData(ReceivedMessage& message) { } // check if this query uses a view frustum - memcpy(&_usesFrustum, sourceBuffer, sizeof(_usesFrustum)); - sourceBuffer += sizeof(_usesFrustum); - - if (_usesFrustum) { - // unpack camera details - memcpy(&_cameraPosition, sourceBuffer, sizeof(_cameraPosition)); - sourceBuffer += sizeof(_cameraPosition); - sourceBuffer += unpackOrientationQuatFromBytes(sourceBuffer, _cameraOrientation); - sourceBuffer += unpackFloatAngleFromTwoByte((uint16_t*) sourceBuffer, &_cameraFov); - sourceBuffer += unpackFloatRatioFromTwoByte(sourceBuffer,_cameraAspectRatio); - sourceBuffer += unpackClipValueFromTwoByte(sourceBuffer,_cameraNearClip); - sourceBuffer += unpackClipValueFromTwoByte(sourceBuffer,_cameraFarClip); - memcpy(&_cameraEyeOffsetPosition, sourceBuffer, sizeof(_cameraEyeOffsetPosition)); - sourceBuffer += sizeof(_cameraEyeOffsetPosition); + QueryFlags frustumFlags { 0 }; + memcpy(&frustumFlags, sourceBuffer, sizeof(frustumFlags)); + sourceBuffer += sizeof(frustumFlags); + + _hasMainFrustum = frustumFlags & QUERY_HAS_MAIN_FRUSTUM; + _hasSecondaryFrustum = frustumFlags & QUERY_HAS_SECONDARY_FRUSTUM; + + if (_hasMainFrustum) { + auto bytesLeft = endPosition - sourceBuffer; + auto byteArray = QByteArray::fromRawData(reinterpret_cast(sourceBuffer), bytesLeft); + sourceBuffer += _mainViewFrustum.fromByteArray(byteArray); + } + + if (_hasSecondaryFrustum) { + auto bytesLeft = endPosition - sourceBuffer; + auto byteArray = QByteArray::fromRawData(reinterpret_cast(sourceBuffer), bytesLeft); + sourceBuffer += _secondaryViewFrustum.fromByteArray(byteArray); } // desired Max Octree PPS @@ -161,9 +154,6 @@ int OctreeQuery::parseData(ReceivedMessage& message) { memcpy(&_boundaryLevelAdjust, sourceBuffer, sizeof(_boundaryLevelAdjust)); sourceBuffer += sizeof(_boundaryLevelAdjust); - memcpy(&_cameraCenterRadius, sourceBuffer, sizeof(_cameraCenterRadius)); - sourceBuffer += sizeof(_cameraCenterRadius); - // check if we have a packed JSON filter uint16_t binaryParametersBytes; memcpy(&binaryParametersBytes, sourceBuffer, sizeof(binaryParametersBytes)); @@ -184,8 +174,3 @@ int OctreeQuery::parseData(ReceivedMessage& message) { return sourceBuffer - startPosition; } - -glm::vec3 OctreeQuery::calculateCameraDirection() const { - glm::vec3 direction = glm::vec3(_cameraOrientation * glm::vec4(IDENTITY_FORWARD, 0.0f)); - return direction; -} diff --git a/libraries/octree/src/OctreeQuery.h b/libraries/octree/src/OctreeQuery.h index 21ce2e7fac..ef52e29f51 100644 --- a/libraries/octree/src/OctreeQuery.h +++ b/libraries/octree/src/OctreeQuery.h @@ -12,16 +12,14 @@ #ifndef hifi_OctreeQuery_h #define hifi_OctreeQuery_h -#include - -#include -#include - #include #include #include +#include + +#include "OctreeConstants.h" class OctreeQuery : public NodeData { Q_OBJECT @@ -30,31 +28,22 @@ public: OctreeQuery(bool randomizeConnectionID = false); virtual ~OctreeQuery() {} + OctreeQuery(const OctreeQuery&) = delete; + OctreeQuery& operator=(const OctreeQuery&) = delete; + int getBroadcastData(unsigned char* destinationBuffer); int parseData(ReceivedMessage& message) override; - // getters for camera details - const glm::vec3& getCameraPosition() const { return _cameraPosition; } - const glm::quat& getCameraOrientation() const { return _cameraOrientation; } - float getCameraFov() const { return _cameraFov; } - float getCameraAspectRatio() const { return _cameraAspectRatio; } - float getCameraNearClip() const { return _cameraNearClip; } - float getCameraFarClip() const { return _cameraFarClip; } - const glm::vec3& getCameraEyeOffsetPosition() const { return _cameraEyeOffsetPosition; } - float getCameraCenterRadius() const { return _cameraCenterRadius; } + bool hasMainViewFrustum() const { return _hasMainFrustum; } + void setMainViewFrustum(const ViewFrustum& viewFrustum) { _hasMainFrustum = true; _mainViewFrustum = viewFrustum; } + void clearMainViewFrustum() { _hasMainFrustum = false; } + const ViewFrustum& getMainViewFrustum() const { return _mainViewFrustum; } - glm::vec3 calculateCameraDirection() const; + bool hasSecondaryViewFrustum() const { return _hasSecondaryFrustum; } + void setSecondaryViewFrustum(const ViewFrustum& viewFrustum) { _hasSecondaryFrustum = true; _secondaryViewFrustum = viewFrustum; } + void clearSecondaryViewFrustum() { _hasSecondaryFrustum = false; } + const ViewFrustum& getSecondaryViewFrustum() const { return _secondaryViewFrustum; } - // setters for camera details - void setCameraPosition(const glm::vec3& position) { _cameraPosition = position; } - void setCameraOrientation(const glm::quat& orientation) { _cameraOrientation = orientation; } - void setCameraFov(float fov) { _cameraFov = fov; } - void setCameraAspectRatio(float aspectRatio) { _cameraAspectRatio = aspectRatio; } - void setCameraNearClip(float nearClip) { _cameraNearClip = nearClip; } - void setCameraFarClip(float farClip) { _cameraFarClip = farClip; } - void setCameraEyeOffsetPosition(const glm::vec3& eyeOffsetPosition) { _cameraEyeOffsetPosition = eyeOffsetPosition; } - void setCameraCenterRadius(float radius) { _cameraCenterRadius = radius; } - // getters/setters for JSON filter QJsonObject getJSONParameters() { QReadLocker locker { &_jsonParametersLock }; return _jsonParameters; } void setJSONParameters(const QJsonObject& jsonParameters) @@ -64,9 +53,6 @@ public: int getMaxQueryPacketsPerSecond() const { return _maxQueryPPS; } float getOctreeSizeScale() const { return _octreeElementSizeScale; } int getBoundaryLevelAdjust() const { return _boundaryLevelAdjust; } - - bool getUsesFrustum() { return _usesFrustum; } - void setUsesFrustum(bool usesFrustum) { _usesFrustum = usesFrustum; } void incrementConnectionID() { ++_connectionID; } @@ -81,33 +67,22 @@ public slots: void setBoundaryLevelAdjust(int boundaryLevelAdjust) { _boundaryLevelAdjust = boundaryLevelAdjust; } protected: - // camera details for the avatar - glm::vec3 _cameraPosition { glm::vec3(0.0f) }; - glm::quat _cameraOrientation { glm::quat() }; - float _cameraFov; - float _cameraAspectRatio; - float _cameraNearClip; - float _cameraFarClip; - float _cameraCenterRadius; - glm::vec3 _cameraEyeOffsetPosition { glm::vec3(0.0f) }; + bool _hasMainFrustum { false }; + ViewFrustum _mainViewFrustum; + bool _hasSecondaryFrustum { false }; + ViewFrustum _secondaryViewFrustum; // octree server sending items int _maxQueryPPS = DEFAULT_MAX_OCTREE_PPS; float _octreeElementSizeScale = DEFAULT_OCTREE_SIZE_SCALE; /// used for LOD calculations int _boundaryLevelAdjust = 0; /// used for LOD calculations - - uint8_t _usesFrustum = true; + uint16_t _connectionID; // query connection ID, randomized to start, increments with each new connection to server QJsonObject _jsonParameters; QReadWriteLock _jsonParametersLock; bool _hasReceivedFirstQuery { false }; - -private: - // privatize the copy constructor and assignment operator so they cannot be called - OctreeQuery(const OctreeQuery&); - OctreeQuery& operator= (const OctreeQuery&); }; #endif // hifi_OctreeQuery_h diff --git a/libraries/octree/src/OctreeQueryNode.cpp b/libraries/octree/src/OctreeQueryNode.cpp index 16542b697e..d6e9343896 100644 --- a/libraries/octree/src/OctreeQueryNode.cpp +++ b/libraries/octree/src/OctreeQueryNode.cpp @@ -139,9 +139,14 @@ void OctreeQueryNode::writeToPacket(const unsigned char* buffer, unsigned int by } } -void OctreeQueryNode::copyCurrentViewFrustum(ViewFrustum& viewOut) const { +void OctreeQueryNode::copyCurrentMainViewFrustum(ViewFrustum& viewOut) const { QMutexLocker viewLocker(&_viewMutex); - viewOut = _currentViewFrustum; + viewOut = _currentMainViewFrustum; +} + +void OctreeQueryNode::copyCurrentSecondaryViewFrustum(ViewFrustum& viewOut) const { + QMutexLocker viewLocker(&_viewMutex); + viewOut = _currentSecondaryViewFrustum; } bool OctreeQueryNode::updateCurrentViewFrustum() { @@ -150,70 +155,50 @@ bool OctreeQueryNode::updateCurrentViewFrustum() { return false; } - if (!_usesFrustum) { + if (!_hasMainFrustum && !_hasSecondaryFrustum) { // this client does not use a view frustum so the view frustum for this query has not changed return false; - } else { - bool currentViewFrustumChanged = false; - - ViewFrustum newestViewFrustum; - // get position and orientation details from the camera - newestViewFrustum.setPosition(getCameraPosition()); - newestViewFrustum.setOrientation(getCameraOrientation()); - - newestViewFrustum.setCenterRadius(getCameraCenterRadius()); - - // Also make sure it's got the correct lens details from the camera - float originalFOV = getCameraFov(); - float wideFOV = originalFOV + VIEW_FRUSTUM_FOV_OVERSEND; - - if (0.0f != getCameraAspectRatio() && - 0.0f != getCameraNearClip() && - 0.0f != getCameraFarClip() && - getCameraNearClip() != getCameraFarClip()) { - newestViewFrustum.setProjection(glm::perspective( - glm::radians(wideFOV), // hack - getCameraAspectRatio(), - getCameraNearClip(), - getCameraFarClip())); - newestViewFrustum.calculate(); - } - - - { // if there has been a change, then recalculate - QMutexLocker viewLocker(&_viewMutex); - if (!newestViewFrustum.isVerySimilar(_currentViewFrustum)) { - _currentViewFrustum = newestViewFrustum; - currentViewFrustumChanged = true; - } - } - - // Also check for LOD changes from the client - if (_lodInitialized) { - if (_lastClientBoundaryLevelAdjust != getBoundaryLevelAdjust()) { - _lastClientBoundaryLevelAdjust = getBoundaryLevelAdjust(); - _lodChanged = true; - } - if (_lastClientOctreeSizeScale != getOctreeSizeScale()) { - _lastClientOctreeSizeScale = getOctreeSizeScale(); - _lodChanged = true; - } - } else { - _lodInitialized = true; - _lastClientOctreeSizeScale = getOctreeSizeScale(); - _lastClientBoundaryLevelAdjust = getBoundaryLevelAdjust(); - _lodChanged = false; - } - - // When we first detect that the view stopped changing, we record this. - // but we don't change it back to false until we've completely sent this - // scene. - if (_viewFrustumChanging && !currentViewFrustumChanged) { - _viewFrustumJustStoppedChanging = true; - } - _viewFrustumChanging = currentViewFrustumChanged; - return currentViewFrustumChanged; } + + bool currentViewFrustumChanged = false; + + { // if there has been a change, then recalculate + QMutexLocker viewLocker(&_viewMutex); + if (_hasMainFrustum && !_mainViewFrustum.isVerySimilar(_currentMainViewFrustum)) { + _currentMainViewFrustum = _mainViewFrustum; + currentViewFrustumChanged = true; + } + if (_hasSecondaryFrustum && !_secondaryViewFrustum.isVerySimilar(_currentSecondaryViewFrustum)) { + _currentSecondaryViewFrustum = _secondaryViewFrustum; + currentViewFrustumChanged = true; + } + } + + // Also check for LOD changes from the client + if (_lodInitialized) { + if (_lastClientBoundaryLevelAdjust != getBoundaryLevelAdjust()) { + _lastClientBoundaryLevelAdjust = getBoundaryLevelAdjust(); + _lodChanged = true; + } + if (_lastClientOctreeSizeScale != getOctreeSizeScale()) { + _lastClientOctreeSizeScale = getOctreeSizeScale(); + _lodChanged = true; + } + } else { + _lodInitialized = true; + _lastClientOctreeSizeScale = getOctreeSizeScale(); + _lastClientBoundaryLevelAdjust = getBoundaryLevelAdjust(); + _lodChanged = false; + } + + // When we first detect that the view stopped changing, we record this. + // but we don't change it back to false until we've completely sent this + // scene. + if (_viewFrustumChanging && !currentViewFrustumChanged) { + _viewFrustumJustStoppedChanging = true; + } + _viewFrustumChanging = currentViewFrustumChanged; + return currentViewFrustumChanged; } void OctreeQueryNode::setViewSent(bool viewSent) { diff --git a/libraries/octree/src/OctreeQueryNode.h b/libraries/octree/src/OctreeQueryNode.h index 640a7c7ddc..d8f05c4043 100644 --- a/libraries/octree/src/OctreeQueryNode.h +++ b/libraries/octree/src/OctreeQueryNode.h @@ -49,7 +49,8 @@ public: OctreeElementExtraEncodeData extraEncodeData; - void copyCurrentViewFrustum(ViewFrustum& viewOut) const; + void copyCurrentMainViewFrustum(ViewFrustum& viewOut) const; + void copyCurrentSecondaryViewFrustum(ViewFrustum& viewOut) const; // These are not classic setters because they are calculating and maintaining state // which is set asynchronously through the network receive @@ -87,9 +88,6 @@ public: void setShouldForceFullScene(bool shouldForceFullScene) { _shouldForceFullScene = shouldForceFullScene; } private: - OctreeQueryNode(const OctreeQueryNode &); - OctreeQueryNode& operator= (const OctreeQueryNode&); - bool _viewSent { false }; std::unique_ptr _octreePacket; bool _octreePacketWaiting; @@ -99,7 +97,8 @@ private: quint64 _firstSuppressedPacket { usecTimestampNow() }; mutable QMutex _viewMutex { QMutex::Recursive }; - ViewFrustum _currentViewFrustum; + ViewFrustum _currentMainViewFrustum; + ViewFrustum _currentSecondaryViewFrustum; bool _viewFrustumChanging { false }; bool _viewFrustumJustStoppedChanging { true }; diff --git a/libraries/octree/src/OctreeUtils.cpp b/libraries/octree/src/OctreeUtils.cpp index 8980504431..8eaf22e198 100644 --- a/libraries/octree/src/OctreeUtils.cpp +++ b/libraries/octree/src/OctreeUtils.cpp @@ -16,6 +16,7 @@ #include #include +#include float calculateRenderAccuracy(const glm::vec3& position, const AABox& bounds, @@ -73,4 +74,10 @@ float getOrthographicAccuracySize(float octreeSizeScale, int boundaryLevelAdjust // Smallest visible element is 1cm const float smallestSize = 0.01f; return (smallestSize * MAX_VISIBILITY_DISTANCE_FOR_UNIT_ELEMENT) / boundaryDistanceForRenderLevel(boundaryLevelAdjust, octreeSizeScale); -} \ No newline at end of file +} + +bool isAngularSizeBigEnough(glm::vec3 position, const AACube& cube, float lodScaleFactor, float minDiameter) { + float distance = glm::distance(cube.calcCenter(), position) + MIN_VISIBLE_DISTANCE; + float angularDiameter = cube.getScale() / distance; + return angularDiameter > minDiameter * lodScaleFactor; +} diff --git a/libraries/octree/src/OctreeUtils.h b/libraries/octree/src/OctreeUtils.h index d5008376ea..58ab366d8d 100644 --- a/libraries/octree/src/OctreeUtils.h +++ b/libraries/octree/src/OctreeUtils.h @@ -15,6 +15,7 @@ #include "OctreeConstants.h" class AABox; +class AACube; class QJsonDocument; /// renderAccuracy represents a floating point "visibility" of an object based on it's view from the camera. At a simple @@ -36,4 +37,6 @@ const float SQRT_THREE = 1.73205080f; const float MIN_ENTITY_ANGULAR_DIAMETER = MIN_ELEMENT_ANGULAR_DIAMETER * SQRT_THREE; const float MIN_VISIBLE_DISTANCE = 0.0001f; // helps avoid divide-by-zero check +bool isAngularSizeBigEnough(glm::vec3 position, const AACube& cube, float lodScaleFactor, float minDiameter); + #endif // hifi_OctreeUtils_h diff --git a/libraries/physics/src/EntityMotionState.cpp b/libraries/physics/src/EntityMotionState.cpp index c2bacd4949..68f21eea87 100644 --- a/libraries/physics/src/EntityMotionState.cpp +++ b/libraries/physics/src/EntityMotionState.cpp @@ -447,7 +447,12 @@ bool EntityMotionState::shouldSendUpdate(uint32_t simulationStep) { // this case is prevented by setting _ownershipState to UNOWNABLE in EntityMotionState::ctor assert(!(_entity->getClientOnly() && _entity->getOwningAvatarID() != Physics::getSessionUUID())); - if (_entity->dynamicDataNeedsTransmit() || _entity->queryAACubeNeedsUpdate()) { + // shouldSendUpdate() sould NOT be triggering updates to maintain the queryAACube of dynamic entities. + // The server is supposed to predict the transform of such moving things. The client performs a "double prediction" + // where it predicts what the the server is doing, and only sends updates whent the entity's true transform + // differs significantly. That is what the remoteSimulationOutOfSync() logic is all about. + if (_entity->dynamicDataNeedsTransmit() || + (!_entity->getDynamic() && _entity->queryAACubeNeedsUpdate())) { return true; } diff --git a/libraries/physics/src/PhysicalEntitySimulation.cpp b/libraries/physics/src/PhysicalEntitySimulation.cpp index ab7c2ec252..3ea3616b15 100644 --- a/libraries/physics/src/PhysicalEntitySimulation.cpp +++ b/libraries/physics/src/PhysicalEntitySimulation.cpp @@ -153,6 +153,8 @@ void PhysicalEntitySimulation::clearEntitiesInternal() { // remove the objects (aka MotionStates) from physics _physicsEngine->removeSetOfObjects(_physicalObjects); + clearOwnershipData(); + // delete the MotionStates for (auto stateItr : _physicalObjects) { EntityMotionState* motionState = static_cast(&(*stateItr)); @@ -165,7 +167,6 @@ void PhysicalEntitySimulation::clearEntitiesInternal() { _physicalObjects.clear(); // clear all other lists specific to this derived class - clearOwnershipData(); _entitiesToRemoveFromPhysics.clear(); _entitiesToAddToPhysics.clear(); _incomingChanges.clear(); diff --git a/libraries/physics/src/PhysicsDebugDraw.cpp b/libraries/physics/src/PhysicsDebugDraw.cpp new file mode 100644 index 0000000000..b2911219d5 --- /dev/null +++ b/libraries/physics/src/PhysicsDebugDraw.cpp @@ -0,0 +1,54 @@ +// +// PhysicsDebugDraw.cpp +// libraries/physics/src +// +// Created by Anthony Thibault 2018-4-18 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "PhysicsDebugDraw.h" +#include "BulletUtil.h" +#include "PhysicsLogging.h" + +#include +#include + +void PhysicsDebugDraw::drawLine(const btVector3& from, const btVector3& to, const btVector3& color) { + DebugDraw::getInstance().drawRay(bulletToGLM(from), bulletToGLM(to), glm::vec4(color.getX(), color.getY(), color.getZ(), 1.0f)); +} + +void PhysicsDebugDraw::drawContactPoint(const btVector3& PointOnB, const btVector3& normalOnB, btScalar distance, + int lifeTime, const btVector3& color) { + + glm::vec3 normal, tangent, biNormal; + generateBasisVectors(bulletToGLM(normalOnB), Vectors::UNIT_X, normal, tangent, biNormal); + btVector3 u = glmToBullet(normal); + btVector3 v = glmToBullet(tangent); + btVector3 w = glmToBullet(biNormal); + + // x marks the spot, green is along the normal. + const float CONTACT_POINT_RADIUS = 0.1f; + const btVector3 GREEN(0.0f, 1.0f, 0.0f); + const btVector3 WHITE(1.0f, 1.0f, 1.0f); + drawLine(PointOnB - u * CONTACT_POINT_RADIUS, PointOnB + u * CONTACT_POINT_RADIUS, GREEN); + drawLine(PointOnB - v * CONTACT_POINT_RADIUS, PointOnB + v * CONTACT_POINT_RADIUS, WHITE); + drawLine(PointOnB - w * CONTACT_POINT_RADIUS, PointOnB + w * CONTACT_POINT_RADIUS, WHITE); +} + +void PhysicsDebugDraw::reportErrorWarning(const char* warningString) { + qCWarning(physics) << "BULLET:" << warningString; +} + +void PhysicsDebugDraw::draw3dText(const btVector3& location, const char* textString) { +} + +void PhysicsDebugDraw::setDebugMode(int debugMode) { + _debugDrawMode = debugMode; +} + +int PhysicsDebugDraw::getDebugMode() const { + return _debugDrawMode; +} diff --git a/libraries/physics/src/PhysicsDebugDraw.h b/libraries/physics/src/PhysicsDebugDraw.h new file mode 100644 index 0000000000..f653136474 --- /dev/null +++ b/libraries/physics/src/PhysicsDebugDraw.h @@ -0,0 +1,34 @@ +// +// PhysicsDebugDraw.h +// libraries/physics/src +// +// Created by Anthony Thibault 2018-4-18 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +// http://bulletphysics.org/Bullet/BulletFull/classbtIDebugDraw.html + +#ifndef hifi_PhysicsDebugDraw_h +#define hifi_PhysicsDebugDraw_h + +#include +#include + +class PhysicsDebugDraw : public btIDebugDraw { +public: + using btIDebugDraw::drawLine; + virtual void drawLine(const btVector3& from, const btVector3& to, const btVector3& color) override; + virtual void drawContactPoint(const btVector3& PointOnB, const btVector3& normalOnB, btScalar distance, + int lifeTime, const btVector3& color) override; + virtual void reportErrorWarning(const char* warningString) override; + virtual void draw3dText(const btVector3& location, const char* textString) override; + virtual void setDebugMode(int debugMode) override; + virtual int getDebugMode() const override; + +protected: + uint32_t _debugDrawMode; +}; + +#endif // hifi_PhysicsDebugDraw_h diff --git a/libraries/physics/src/PhysicsEngine.cpp b/libraries/physics/src/PhysicsEngine.cpp index 7c2ad55405..50d516c256 100644 --- a/libraries/physics/src/PhysicsEngine.cpp +++ b/libraries/physics/src/PhysicsEngine.cpp @@ -22,6 +22,7 @@ #include "ObjectMotionState.h" #include "PhysicsEngine.h" #include "PhysicsHelpers.h" +#include "PhysicsDebugDraw.h" #include "ThreadSafeDynamicsWorld.h" #include "PhysicsLogging.h" @@ -49,6 +50,10 @@ void PhysicsEngine::init() { _broadphaseFilter = new btDbvtBroadphase(); _constraintSolver = new btSequentialImpulseConstraintSolver; _dynamicsWorld = new ThreadSafeDynamicsWorld(_collisionDispatcher, _broadphaseFilter, _constraintSolver, _collisionConfig); + _physicsDebugDraw.reset(new PhysicsDebugDraw()); + + // hook up debug draw renderer + _dynamicsWorld->setDebugDrawer(_physicsDebugDraw.get()); _ghostPairCallback = new btGhostPairCallback(); _dynamicsWorld->getPairCache()->setInternalGhostPairCallback(_ghostPairCallback); @@ -333,6 +338,10 @@ void PhysicsEngine::stepSimulation() { _hasOutgoingChanges = true; } + + if (_physicsDebugDraw->getDebugMode()) { + _dynamicsWorld->debugDrawWorld(); + } } class CProfileOperator { @@ -785,3 +794,49 @@ void PhysicsEngine::forEachDynamic(std::function act } } } + +void PhysicsEngine::setShowBulletWireframe(bool value) { + int mode = _physicsDebugDraw->getDebugMode(); + if (value) { + _physicsDebugDraw->setDebugMode(mode | btIDebugDraw::DBG_DrawWireframe); + } else { + _physicsDebugDraw->setDebugMode(mode & ~btIDebugDraw::DBG_DrawWireframe); + } +} + +void PhysicsEngine::setShowBulletAABBs(bool value) { + int mode = _physicsDebugDraw->getDebugMode(); + if (value) { + _physicsDebugDraw->setDebugMode(mode | btIDebugDraw::DBG_DrawAabb); + } else { + _physicsDebugDraw->setDebugMode(mode & ~btIDebugDraw::DBG_DrawAabb); + } +} + +void PhysicsEngine::setShowBulletContactPoints(bool value) { + int mode = _physicsDebugDraw->getDebugMode(); + if (value) { + _physicsDebugDraw->setDebugMode(mode | btIDebugDraw::DBG_DrawContactPoints); + } else { + _physicsDebugDraw->setDebugMode(mode & ~btIDebugDraw::DBG_DrawContactPoints); + } +} + +void PhysicsEngine::setShowBulletConstraints(bool value) { + int mode = _physicsDebugDraw->getDebugMode(); + if (value) { + _physicsDebugDraw->setDebugMode(mode | btIDebugDraw::DBG_DrawConstraints); + } else { + _physicsDebugDraw->setDebugMode(mode & ~btIDebugDraw::DBG_DrawConstraints); + } +} + +void PhysicsEngine::setShowBulletConstraintLimits(bool value) { + int mode = _physicsDebugDraw->getDebugMode(); + if (value) { + _physicsDebugDraw->setDebugMode(mode | btIDebugDraw::DBG_DrawConstraintLimits); + } else { + _physicsDebugDraw->setDebugMode(mode & ~btIDebugDraw::DBG_DrawConstraintLimits); + } +} + diff --git a/libraries/physics/src/PhysicsEngine.h b/libraries/physics/src/PhysicsEngine.h index 92d2e6885a..0dfe3a7a7c 100644 --- a/libraries/physics/src/PhysicsEngine.h +++ b/libraries/physics/src/PhysicsEngine.h @@ -30,6 +30,7 @@ const float HALF_SIMULATION_EXTENT = 512.0f; // meters class CharacterController; +class PhysicsDebugDraw; // simple class for keeping track of contacts class ContactKey { @@ -96,6 +97,12 @@ public: void removeDynamic(const QUuid dynamicID); void forEachDynamic(std::function actor); + void setShowBulletWireframe(bool value); + void setShowBulletAABBs(bool value); + void setShowBulletContactPoints(bool value); + void setShowBulletConstraints(bool value); + void setShowBulletConstraintLimits(bool value); + private: QList removeDynamicsForBody(btRigidBody* body); void addObjectToDynamicsWorld(ObjectMotionState* motionState); @@ -114,6 +121,7 @@ private: btSequentialImpulseConstraintSolver* _constraintSolver = NULL; ThreadSafeDynamicsWorld* _dynamicsWorld = NULL; btGhostPairCallback* _ghostPairCallback = NULL; + std::unique_ptr _physicsDebugDraw; ContactMap _contactMap; CollisionEvents _collisionEvents; diff --git a/libraries/qml/src/qml/impl/SharedObject.cpp b/libraries/qml/src/qml/impl/SharedObject.cpp index 9253c41b39..2fde057ca8 100644 --- a/libraries/qml/src/qml/impl/SharedObject.cpp +++ b/libraries/qml/src/qml/impl/SharedObject.cpp @@ -90,19 +90,23 @@ SharedObject::~SharedObject() { _renderControl = nullptr; } + if (_rootItem) { + delete _rootItem; + _rootItem = nullptr; + } + if (_quickWindow) { _quickWindow->destroy(); delete _quickWindow; _quickWindow = nullptr; } - // _rootItem is parented to the quickWindow, so needs no explicit destruction - //if (_rootItem) { - // delete _rootItem; - // _rootItem = nullptr; - //} - - releaseEngine(_qmlContext->engine()); + if (_qmlContext) { + auto engine = _qmlContext->engine(); + delete _qmlContext; + _qmlContext = nullptr; + releaseEngine(engine); + } } void SharedObject::create(OffscreenSurface* surface) { @@ -210,9 +214,9 @@ QQmlEngine* SharedObject::acquireEngine(OffscreenSurface* surface) { if (!globalEngine) { Q_ASSERT(0 == globalEngineRefCount); globalEngine = new QQmlEngine(); - surface->initializeQmlEngine(result); - ++globalEngineRefCount; + surface->initializeEngine(result); } + ++globalEngineRefCount; result = globalEngine; #else result = new QQmlEngine(); diff --git a/libraries/render-utils/src/AbstractViewStateInterface.h b/libraries/render-utils/src/AbstractViewStateInterface.h index 54fdc903ca..9d781b7d18 100644 --- a/libraries/render-utils/src/AbstractViewStateInterface.h +++ b/libraries/render-utils/src/AbstractViewStateInterface.h @@ -31,6 +31,10 @@ public: /// copies the current view frustum for rendering the view state virtual void copyCurrentViewFrustum(ViewFrustum& viewOut) const = 0; + virtual void copyViewFrustum(ViewFrustum& viewOut) const = 0; + virtual void copySecondaryViewFrustum(ViewFrustum& viewOut) const = 0; + virtual bool hasSecondaryViewFrustum() const = 0; + virtual QThread* getMainThread() = 0; virtual PickRay computePickRay(float x, float y) const = 0; diff --git a/libraries/render-utils/src/AnimDebugDraw.cpp b/libraries/render-utils/src/AnimDebugDraw.cpp index 7086b65f4c..eca500f36c 100644 --- a/libraries/render-utils/src/AnimDebugDraw.cpp +++ b/libraries/render-utils/src/AnimDebugDraw.cpp @@ -52,9 +52,9 @@ public: batch.setInputFormat(_vertexFormat); batch.setInputBuffer(0, _vertexBuffer, 0, sizeof(Vertex)); - batch.setIndexBuffer(gpu::UINT16, _indexBuffer, 0); + batch.setIndexBuffer(gpu::UINT32, _indexBuffer, 0); - auto numIndices = _indexBuffer->getSize() / sizeof(uint16_t); + auto numIndices = _indexBuffer->getSize() / sizeof(uint32_t); batch.drawIndexed(gpu::LINES, (int)numIndices); } @@ -135,9 +135,9 @@ AnimDebugDraw::AnimDebugDraw() : AnimDebugDrawData::Vertex { glm::vec3(1.0, 1.0f, 1.0f), toRGBA(0, 0, 255, 255) }, AnimDebugDrawData::Vertex { glm::vec3(1.0, 1.0f, 2.0f), toRGBA(0, 0, 255, 255) }, }); - static std::vector indices({ 0, 1, 2, 3, 4, 5 }); + static std::vector indices({ 0, 1, 2, 3, 4, 5 }); _animDebugDrawData->_vertexBuffer->setSubData(0, vertices); - _animDebugDrawData->_indexBuffer->setSubData(0, indices); + _animDebugDrawData->_indexBuffer->setSubData(0, indices); } AnimDebugDraw::~AnimDebugDraw() { @@ -425,9 +425,9 @@ void AnimDebugDraw::update() { data._isVisible = (numVerts > 0); - data._indexBuffer->resize(sizeof(uint16_t) * numVerts); + data._indexBuffer->resize(sizeof(uint32_t) * numVerts); for (int i = 0; i < numVerts; i++) { - data._indexBuffer->setSubData(i, (uint16_t)i);; + data._indexBuffer->setSubData(i, (uint32_t)i);; } }); scene->enqueueTransaction(transaction); diff --git a/libraries/render-utils/src/AntialiasingEffect.cpp b/libraries/render-utils/src/AntialiasingEffect.cpp index dd4bda2e37..c9aa1b8f71 100644 --- a/libraries/render-utils/src/AntialiasingEffect.cpp +++ b/libraries/render-utils/src/AntialiasingEffect.cpp @@ -189,7 +189,6 @@ const int AntialiasingPass_NextMapSlot = 4; Antialiasing::Antialiasing(bool isSharpenEnabled) : _isSharpenEnabled{ isSharpenEnabled } { - _antialiasingBuffers = std::make_shared(2U); } Antialiasing::~Antialiasing() { @@ -325,25 +324,25 @@ void Antialiasing::run(const render::RenderContextPointer& renderContext, const int width = sourceBuffer->getWidth(); int height = sourceBuffer->getHeight(); - if (_antialiasingBuffers->get(0)) { - if (_antialiasingBuffers->get(0)->getSize() != uvec2(width, height)) {// || (sourceBuffer && (_antialiasingBuffer->getRenderBuffer(1) != sourceBuffer->getRenderBuffer(0)))) { - _antialiasingBuffers->edit(0).reset(); - _antialiasingBuffers->edit(1).reset(); - _antialiasingTextures[0].reset(); - _antialiasingTextures[1].reset(); - } + if (_antialiasingBuffers && _antialiasingBuffers->get(0) && _antialiasingBuffers->get(0)->getSize() != uvec2(width, height)) { + _antialiasingBuffers.reset(); + _antialiasingTextures[0].reset(); + _antialiasingTextures[1].reset(); } - if (!_antialiasingBuffers->get(0)) { + + if (!_antialiasingBuffers) { + std::vector antiAliasingBuffers; // Link the antialiasing FBO to texture auto format = sourceBuffer->getRenderBuffer(0)->getTexelFormat(); auto defaultSampler = gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_LINEAR, gpu::Sampler::WRAP_CLAMP); for (int i = 0; i < 2; i++) { - auto& antiAliasingBuffer = _antialiasingBuffers->edit(i); - antiAliasingBuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("antialiasing")); + antiAliasingBuffers.emplace_back(gpu::Framebuffer::create("antialiasing")); + const auto& antiAliasingBuffer = antiAliasingBuffers.back(); _antialiasingTextures[i] = gpu::Texture::createRenderBuffer(format, width, height, gpu::Texture::SINGLE_MIP, defaultSampler); antiAliasingBuffer->setRenderBuffer(0, _antialiasingTextures[i]); } + _antialiasingBuffers = std::make_shared(antiAliasingBuffers); } gpu::doInBatch("Antialiasing::run", args->_context, [&](gpu::Batch& batch) { diff --git a/libraries/script-engine/src/AssetScriptingInterface.h b/libraries/script-engine/src/AssetScriptingInterface.h index 5cb1136b74..eb9a628ae3 100644 --- a/libraries/script-engine/src/AssetScriptingInterface.h +++ b/libraries/script-engine/src/AssetScriptingInterface.h @@ -27,6 +27,11 @@ /**jsdoc * The Assets API allows you to communicate with the Asset Browser. * @namespace Assets + * + * @hifi-interface + * @hifi-client-entity + * @hifi-server-entity + * @hifi-assignment-client */ class AssetScriptingInterface : public BaseAssetScriptingInterface, QScriptable { Q_OBJECT diff --git a/libraries/script-engine/src/Quat.h b/libraries/script-engine/src/Quat.h index e6e395d9bf..254757dece 100644 --- a/libraries/script-engine/src/Quat.h +++ b/libraries/script-engine/src/Quat.h @@ -35,6 +35,12 @@ * of gimbal lock. * @namespace Quat * @variation 0 + * + * @hifi-interface + * @hifi-client-entity + * @hifi-server-entity + * @hifi-assignment-client + * * @property IDENTITY {Quat} { x: 0, y: 0, z: 0, w: 1 } : The identity rotation, i.e., no rotation. * Read-only. * @example Print the IDENTITY value. diff --git a/libraries/script-engine/src/RecordingScriptingInterface.h b/libraries/script-engine/src/RecordingScriptingInterface.h index 0e4f90b928..29d9b31049 100644 --- a/libraries/script-engine/src/RecordingScriptingInterface.h +++ b/libraries/script-engine/src/RecordingScriptingInterface.h @@ -25,6 +25,10 @@ class QScriptValue; /**jsdoc * @namespace Recording + * + * @hifi-interface + * @hifi-client-entity + * @hifi-assignment-client */ class RecordingScriptingInterface : public QObject, public Dependency { Q_OBJECT diff --git a/libraries/script-engine/src/SceneScriptingInterface.h b/libraries/script-engine/src/SceneScriptingInterface.h index c69cd7090d..fdfbc6f6c0 100644 --- a/libraries/script-engine/src/SceneScriptingInterface.h +++ b/libraries/script-engine/src/SceneScriptingInterface.h @@ -112,6 +112,10 @@ namespace SceneScripting { /**jsdoc * @class Scene.Stage + * + * @hifi-interface + * @hifi-client-entity + * * @property {string} backgroundMode * @property {Scene.Stage.KeyLight} keyLight * @property {Scene.Stage.Location} location @@ -171,6 +175,10 @@ namespace SceneScripting { /**jsdoc * @namespace Scene + * + * @hifi-interface + * @hifi-client-entity + * * @property {boolean} shouldRenderAvatars * @property {boolean} shouldRenderEntities * @property {Scene.Stage} stage diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index c79ffffec7..4915a2dc8b 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -180,6 +180,21 @@ ScriptEngine::ScriptEngine(Context context, const QString& scriptContents, const // don't delete `ScriptEngines` until all `ScriptEngine`s are gone _scriptEngines(DependencyManager::get()) { + switch (_context) { + case Context::CLIENT_SCRIPT: + _type = Type::CLIENT; + break; + case Context::ENTITY_CLIENT_SCRIPT: + _type = Type::ENTITY_CLIENT; + break; + case Context::ENTITY_SERVER_SCRIPT: + _type = Type::ENTITY_SERVER; + break; + case Context::AGENT_SCRIPT: + _type = Type::AGENT; + break; + } + connect(this, &QScriptEngine::signalHandlerException, this, [this](const QScriptValue& exception) { if (hasUncaughtException()) { // the engine's uncaughtException() seems to produce much better stack traces here @@ -2161,6 +2176,32 @@ void ScriptEngine::loadEntityScript(const EntityItemID& entityID, const QString& }, forceRedownload); } +/**jsdoc + * Triggered when the script starts for a user. + *

    Note: Can only be connected to via this.preload = function (...) { ... } in the entity script.

    + *
    Available in:Client Entity ScriptsServer Entity Scripts
    + * @function Entities.preload + * @param {Uuid} entityID - The ID of the entity that the script is running in. + * @returns {Signal} + * @example Get the ID of the entity that a client entity script is running in. + * var entityScript = (function () { + * this.entityID = Uuid.NULL; + * + * this.preload = function (entityID) { + * this.entityID = entityID; + * print("Entity ID: " + this.entityID); + * }; + * ); + * + * var entityID = Entities.addEntity({ + * type: "Box", + * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -5 })), + * dimensions: { x: 0.5, y: 0.5, z: 0.5 }, + * color: { red: 255, green: 0, blue: 0 }, + * script: "(" + entityScript + ")", // Could host the script on a Web server instead. + * lifetime: 300 // Delete after 5 minutes. + * }); + */ // since all of these operations can be asynch we will always do the actual work in the response handler // for the download void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, const QString& scriptOrURL, const QString& contents, bool isURL, bool success , const QString& status) { @@ -2345,6 +2386,13 @@ void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, co processDeferredEntityLoads(entityScript, entityID); } +/**jsdoc + * Triggered when the script terminates for a user. + *

    Note: Can only be connected to via this.unoad = function () { ... } in the entity script.

    + *
    Available in:Client Entity ScriptsServer Entity Scripts
    + * @function Entities.unload + * @returns {Signal} + */ void ScriptEngine::unloadEntityScript(const EntityItemID& entityID, bool shouldRemoveFromMap) { if (QThread::currentThread() != thread()) { #ifdef THREAD_DEBUGGING diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index 63a4ba4f90..7a9af2278c 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -89,6 +89,12 @@ public: /**jsdoc * @namespace Script + * + * @hifi-interface + * @hifi-client-entity + * @hifi-server-entity + * @hifi-assignment-client + * * @property {string} context */ class ScriptEngine : public BaseScriptEngine, public EntitiesScriptEngineProvider { @@ -103,6 +109,14 @@ public: AGENT_SCRIPT }; + enum Type { + CLIENT, + ENTITY_CLIENT, + ENTITY_SERVER, + AGENT, + AVATAR + }; + static int processLevelMaxRetries; ScriptEngine(Context context, const QString& scriptContents = NO_SCRIPT, const QString& fileNameString = QString("about:ScriptEngine")); ~ScriptEngine(); @@ -493,6 +507,9 @@ public: */ Q_INVOKABLE QUuid generateUUID() { return QUuid::createUuid(); } + void setType(Type type) { _type = type; }; + Type getType() { return _type; }; + bool isFinished() const { return _isFinished; } // used by Application and ScriptWidget bool isRunning() const { return _isRunning; } // used by ScriptWidget @@ -724,6 +741,7 @@ protected: void callWithEnvironment(const EntityItemID& entityID, const QUrl& sandboxURL, QScriptValue function, QScriptValue thisObject, QScriptValueList args); Context _context; + Type _type; QString _scriptContents; QString _parentURL; std::atomic _isFinished { false }; diff --git a/libraries/script-engine/src/ScriptEngines.cpp b/libraries/script-engine/src/ScriptEngines.cpp index a788cd9f0e..f2ed296b63 100644 --- a/libraries/script-engine/src/ScriptEngines.cpp +++ b/libraries/script-engine/src/ScriptEngines.cpp @@ -427,11 +427,13 @@ bool ScriptEngines::stopScript(const QString& rawScriptURL, bool restart) { if (_scriptEnginesHash.contains(scriptURL)) { ScriptEnginePointer scriptEngine = _scriptEnginesHash[scriptURL]; if (restart) { + bool isUserLoaded = scriptEngine->isUserLoaded(); + ScriptEngine::Type type = scriptEngine->getType(); auto scriptCache = DependencyManager::get(); scriptCache->deleteScript(scriptURL); connect(scriptEngine.data(), &ScriptEngine::finished, - this, [this](QString scriptName, ScriptEnginePointer engine) { - reloadScript(scriptName); + this, [this, isUserLoaded, type](QString scriptName, ScriptEnginePointer engine) { + reloadScript(scriptName, isUserLoaded)->setType(type); }); } scriptEngine->stop(); diff --git a/libraries/script-engine/src/ScriptEngines.h b/libraries/script-engine/src/ScriptEngines.h index 1200168420..376bae4827 100644 --- a/libraries/script-engine/src/ScriptEngines.h +++ b/libraries/script-engine/src/ScriptEngines.h @@ -29,6 +29,10 @@ class ScriptEngine; /**jsdoc * @namespace ScriptDiscoveryService + * + * @hifi-interface + * @hifi-client-entity + * * @property {string} debugScriptUrl * @property {string} defaultScriptsPath * @property {ScriptsModel} scriptsModel @@ -259,7 +263,7 @@ protected slots: protected: friend class ScriptEngine; - void reloadScript(const QString& scriptName) { loadScript(scriptName, true, false, false, true); } + ScriptEnginePointer reloadScript(const QString& scriptName, bool isUserLoaded = true) { return loadScript(scriptName, isUserLoaded, false, false, true); } void removeScriptEngine(ScriptEnginePointer); void onScriptEngineLoaded(const QString& scriptFilename); void onScriptEngineError(const QString& scriptFilename); diff --git a/libraries/script-engine/src/ScriptUUID.h b/libraries/script-engine/src/ScriptUUID.h index 303a871d1d..9b61f451c5 100644 --- a/libraries/script-engine/src/ScriptUUID.h +++ b/libraries/script-engine/src/ScriptUUID.h @@ -23,6 +23,12 @@ * hexadecimal digits. * * @namespace Uuid + * + * @hifi-interface + * @hifi-client-entity + * @hifi-server-entity + * @hifi-assignment-client + * * @property NULL {Uuid} The null UUID, {00000000-0000-0000-0000-000000000000}. */ diff --git a/libraries/script-engine/src/ScriptsModel.h b/libraries/script-engine/src/ScriptsModel.h index a4ffc192f9..2466347baa 100644 --- a/libraries/script-engine/src/ScriptsModel.h +++ b/libraries/script-engine/src/ScriptsModel.h @@ -68,6 +68,9 @@ public: *

    Has properties and functions below in addition to those of * http://doc.qt.io/qt-5/qabstractitemmodel.html.

    * @class ScriptsModel + * + * @hifi-interface + * @hifi-client-entity */ class ScriptsModel : public QAbstractItemModel { Q_OBJECT diff --git a/libraries/script-engine/src/ScriptsModelFilter.h b/libraries/script-engine/src/ScriptsModelFilter.h index 26efde02e8..05a76334bb 100644 --- a/libraries/script-engine/src/ScriptsModelFilter.h +++ b/libraries/script-engine/src/ScriptsModelFilter.h @@ -20,6 +20,9 @@ *

    Has properties and functions per * http://doc.qt.io/qt-5/qsortfilterproxymodel.html.

    * @class ScriptsModelFilter + * + * @hifi-interface + * @hifi-client-entity */ class ScriptsModelFilter : public QSortFilterProxyModel { Q_OBJECT diff --git a/libraries/script-engine/src/UsersScriptingInterface.h b/libraries/script-engine/src/UsersScriptingInterface.h index 6728c471f6..f214c3f11c 100644 --- a/libraries/script-engine/src/UsersScriptingInterface.h +++ b/libraries/script-engine/src/UsersScriptingInterface.h @@ -18,7 +18,12 @@ /**jsdoc * @namespace Users - * @property {boolean} canKick - true if the domain server allows the node or avatar to kick (ban) avatars, + * + * @hifi-interface + * @hifi-client-entity + * @hifi-assignment-client + * + * @property {boolean} canKick - true if the domain server allows the node or avatar to kick (ban) avatars, * otherwise false. Read-only. * @property {boolean} requestsDomainListData - true if the avatar requests extra data from the mixers (such as * positional data of an avatar you've ignored). Read-only. diff --git a/libraries/script-engine/src/Vec3.h b/libraries/script-engine/src/Vec3.h index 635f2a530c..eb9438c5c2 100644 --- a/libraries/script-engine/src/Vec3.h +++ b/libraries/script-engine/src/Vec3.h @@ -47,6 +47,12 @@ * * @namespace Vec3 * @variation 0 + * + * @hifi-interface + * @hifi-client-entity + * @hifi-server-entity + * @hifi-assignment-client + * * @property {Vec3} UNIT_X - { x: 1, y: 0, z: 0 } : Unit vector in the x-axis direction. Read-only. * @property {Vec3} UNIT_Y - { x: 0, y: 1, z: 0 } : Unit vector in the y-axis direction. Read-only. * @property {Vec3} UNIT_Z - { x: 0, y: 0, z: 1 } : Unit vector in the z-axis direction. Read-only. diff --git a/libraries/shared/src/CrashHelpers.cpp b/libraries/shared/src/CrashHelpers.cpp new file mode 100644 index 0000000000..f8ca90bc4c --- /dev/null +++ b/libraries/shared/src/CrashHelpers.cpp @@ -0,0 +1,77 @@ +// +// CrashHelpers.cpp +// libraries/shared/src +// +// Created by Clement Brisset on 4/30/18. +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "CrashHelpers.h" + +namespace crash { + +class B; +class A { +public: + A(B* b) : _b(b) { } + ~A(); + virtual void virtualFunction() = 0; + +private: + B* _b; +}; + +class B : public A { +public: + B() : A(this) { } + virtual void virtualFunction() override { } +}; + +A::~A() { + _b->virtualFunction(); +} + +void pureVirtualCall() { + qCDebug(shared) << "About to make a pure virtual call"; + B b; +} + +void doubleFree() { + qCDebug(shared) << "About to double delete memory"; + int* blah = new int(200); + delete blah; + delete blah; +} + +void nullDeref() { + qCDebug(shared) << "About to dereference a null pointer"; + int* p = nullptr; + *p = 1; +} + +void doAbort() { + qCDebug(shared) << "About to abort"; + abort(); +} + +void outOfBoundsVectorCrash() { + qCDebug(shared) << "std::vector out of bounds crash!"; + std::vector v; + v[0] = 42; +} + +void newFault() { + qCDebug(shared) << "About to crash inside new fault"; + + // Force crash with multiple large allocations + while (true) { + const size_t GIGABYTE = 1024 * 1024 * 1024; + new char[GIGABYTE]; + } + +} + +} diff --git a/libraries/shared/src/CrashHelpers.h b/libraries/shared/src/CrashHelpers.h index 1cc6749182..ad988c8906 100644 --- a/libraries/shared/src/CrashHelpers.h +++ b/libraries/shared/src/CrashHelpers.h @@ -18,66 +18,12 @@ namespace crash { -class B; -class A { -public: - A(B* b) : _b(b) { } - ~A(); - virtual void virtualFunction() = 0; - -private: - B* _b; -}; - -class B : public A { -public: - B() : A(this) { } - virtual void virtualFunction() override { } -}; - -A::~A() { - _b->virtualFunction(); -} - -void pureVirtualCall() { - qCDebug(shared) << "About to make a pure virtual call"; - B b; -} - -void doubleFree() { - qCDebug(shared) << "About to double delete memory"; - int* blah = new int(200); - delete blah; - delete blah; -} - -void nullDeref() { - qCDebug(shared) << "About to dereference a null pointer"; - int* p = nullptr; - *p = 1; -} - -void doAbort() { - qCDebug(shared) << "About to abort"; - abort(); -} - -void outOfBoundsVectorCrash() { - qCDebug(shared) << "std::vector out of bounds crash!"; - std::vector v; - v[0] = 42; -} - -void newFault() { - qCDebug(shared) << "About to crash inside new fault"; - - // Force crash with multiple large allocations - while (true) { - const size_t GIGABYTE = 1024 * 1024 * 1024; - new char[GIGABYTE]; - } - -} +void pureVirtualCall(); +void doubleFree(); +void nullDeref(); +void doAbort(); +void outOfBoundsVectorCrash(); +void newFault(); } diff --git a/libraries/shared/src/DebugDraw.h b/libraries/shared/src/DebugDraw.h index 64327585fb..7dd19415c9 100644 --- a/libraries/shared/src/DebugDraw.h +++ b/libraries/shared/src/DebugDraw.h @@ -25,6 +25,11 @@ * Helper functions to render ephemeral debug markers and lines. * DebugDraw markers and lines are only visible locally, they are not visible by other users. * @namespace DebugDraw + * + * @hifi-interface + * @hifi-client-entity + * @hifi-server-entity + * @hifi-assignment-client */ class DebugDraw : public QObject { Q_OBJECT diff --git a/libraries/shared/src/GLMHelpers.cpp b/libraries/shared/src/GLMHelpers.cpp index 72710a6a7d..e963345ecd 100644 --- a/libraries/shared/src/GLMHelpers.cpp +++ b/libraries/shared/src/GLMHelpers.cpp @@ -76,13 +76,15 @@ glm::quat safeMix(const glm::quat& q1, const glm::quat& q2, float proportion) { // Allows sending of fixed-point numbers: radix 1 makes 15.1 number, radix 8 makes 8.8 number, etc int packFloatScalarToSignedTwoByteFixed(unsigned char* buffer, float scalar, int radix) { - int16_t outVal = (int16_t)(scalar * (float)(1 << radix)); - memcpy(buffer, &outVal, sizeof(uint16_t)); - return sizeof(uint16_t); + int16_t twoByteFixed = (int16_t)(scalar * (float)(1 << radix)); + memcpy(buffer, &twoByteFixed, sizeof(int16_t)); + return sizeof(int16_t); } int unpackFloatScalarFromSignedTwoByteFixed(const int16_t* byteFixedPointer, float* destinationPointer, int radix) { - *destinationPointer = *byteFixedPointer / (float)(1 << radix); + int16_t twoByteFixed; + memcpy(&twoByteFixed, byteFixedPointer, sizeof(int16_t)); + *destinationPointer = twoByteFixed / (float)(1 << radix); return sizeof(int16_t); } @@ -102,18 +104,19 @@ int unpackFloatVec3FromSignedTwoByteFixed(const unsigned char* sourceBuffer, glm return sourceBuffer - startPosition; } - int packFloatAngleToTwoByte(unsigned char* buffer, float degrees) { const float ANGLE_CONVERSION_RATIO = (std::numeric_limits::max() / 360.0f); - uint16_t angleHolder = floorf((degrees + 180.0f) * ANGLE_CONVERSION_RATIO); - memcpy(buffer, &angleHolder, sizeof(uint16_t)); + uint16_t twoByteAngle = floorf((degrees + 180.0f) * ANGLE_CONVERSION_RATIO); + memcpy(buffer, &twoByteAngle, sizeof(uint16_t)); return sizeof(uint16_t); } int unpackFloatAngleFromTwoByte(const uint16_t* byteAnglePointer, float* destinationPointer) { - *destinationPointer = (*byteAnglePointer / (float) std::numeric_limits::max()) * 360.0f - 180.0f; + uint16_t twoByteAngle; + memcpy(&twoByteAngle, byteAnglePointer, sizeof(uint16_t)); + *destinationPointer = (twoByteAngle / (float) std::numeric_limits::max()) * 360.0f - 180.0f; return sizeof(uint16_t); } diff --git a/libraries/shared/src/Gzip.cpp b/libraries/shared/src/Gzip.cpp index a77f459fc9..25e214fffb 100644 --- a/libraries/shared/src/Gzip.cpp +++ b/libraries/shared/src/Gzip.cpp @@ -60,6 +60,7 @@ bool gunzip(QByteArray source, QByteArray &destination) { switch (status) { case Z_NEED_DICT: status = Z_DATA_ERROR; + // FALLTHRU case Z_DATA_ERROR: case Z_MEM_ERROR: case Z_STREAM_ERROR: diff --git a/libraries/shared/src/PathUtils.h b/libraries/shared/src/PathUtils.h index d879ac968d..fc933b6b8c 100644 --- a/libraries/shared/src/PathUtils.h +++ b/libraries/shared/src/PathUtils.h @@ -22,6 +22,10 @@ * The Paths API provides absolute paths to the scripts and resources directories. * * @namespace Paths + * + * @hifi-interface + * @hifi-client-entity + * * @deprecated The Paths API is deprecated. Use {@link Script.resolvePath} and {@link Script.resourcesPath} instead. * @readonly * @property {string} defaultScripts - The path to the scripts directory. Read-only. diff --git a/libraries/shared/src/PrioritySortUtil.h b/libraries/shared/src/PrioritySortUtil.h index 279fa42ea4..ba15222611 100644 --- a/libraries/shared/src/PrioritySortUtil.h +++ b/libraries/shared/src/PrioritySortUtil.h @@ -84,14 +84,12 @@ namespace PrioritySortUtil { class PriorityQueue { public: PriorityQueue() = delete; - - PriorityQueue(const ViewFrustum& view) : _view(view) { } - - PriorityQueue(const ViewFrustum& view, float angularWeight, float centerWeight, float ageWeight) - : _view(view), _angularWeight(angularWeight), _centerWeight(centerWeight), _ageWeight(ageWeight) + PriorityQueue(const ViewFrustums& views) : _views(views) { } + PriorityQueue(const ViewFrustums& views, float angularWeight, float centerWeight, float ageWeight) + : _views(views), _angularWeight(angularWeight), _centerWeight(centerWeight), _ageWeight(ageWeight) { } - void setView(const ViewFrustum& view) { _view = view; } + void setViews(const ViewFrustums& views) { _views = views; } void setWeights(float angularWeight, float centerWeight, float ageWeight) { _angularWeight = angularWeight; @@ -109,7 +107,18 @@ namespace PrioritySortUtil { bool empty() const { return _queue.empty(); } private: + float computePriority(const T& thing) const { + float priority = std::numeric_limits::min(); + + for (const auto& view : _views) { + priority = std::max(priority, computePriority(view, thing)); + } + + return priority; + } + + float computePriority(const ViewFrustum& view, const T& thing) const { // priority = weighted linear combination of multiple values: // (a) angular size // (b) proximity to center of view @@ -117,11 +126,11 @@ namespace PrioritySortUtil { // where the relative "weights" are tuned to scale the contributing values into units of "priority". glm::vec3 position = thing.getPosition(); - glm::vec3 offset = position - _view.getPosition(); + glm::vec3 offset = position - view.getPosition(); float distance = glm::length(offset) + 0.001f; // add 1mm to avoid divide by zero const float MIN_RADIUS = 0.1f; // WORKAROUND for zero size objects (we still want them to sort by distance) float radius = glm::min(thing.getRadius(), MIN_RADIUS); - float cosineAngle = (glm::dot(offset, _view.getDirection()) / distance); + float cosineAngle = (glm::dot(offset, view.getDirection()) / distance); float age = (float)(usecTimestampNow() - thing.getTimestamp()); // we modulatate "age" drift rate by the cosineAngle term to make periphrial objects sort forward @@ -134,8 +143,8 @@ namespace PrioritySortUtil { + _ageWeight * cosineAngleFactor * age; // decrement priority of things outside keyhole - if (distance - radius > _view.getCenterRadius()) { - if (!_view.sphereIntersectsFrustum(position, radius)) { + if (distance - radius > view.getCenterRadius()) { + if (!view.sphereIntersectsFrustum(position, radius)) { constexpr float OUT_OF_VIEW_PENALTY = -10.0f; priority += OUT_OF_VIEW_PENALTY; } @@ -143,7 +152,7 @@ namespace PrioritySortUtil { return priority; } - ViewFrustum _view; + ViewFrustums _views; std::priority_queue _queue; float _angularWeight { DEFAULT_ANGULAR_COEF }; float _centerWeight { DEFAULT_CENTER_COEF }; diff --git a/libraries/shared/src/RegisteredMetaTypes.h b/libraries/shared/src/RegisteredMetaTypes.h index 689d1a3f42..467d6374a5 100644 --- a/libraries/shared/src/RegisteredMetaTypes.h +++ b/libraries/shared/src/RegisteredMetaTypes.h @@ -361,6 +361,12 @@ using MeshPointer = std::shared_ptr; /**jsdoc * A handle for a mesh in an entity, such as returned by {@link Entities.getMeshes}. * @class MeshProxy + * + * @hifi-interface + * @hifi-client-entity + * @hifi-server-entity + * @hifi-assignment-client + * * @deprecated Use the {@link Graphics} API instead. */ class MeshProxy : public QObject { diff --git a/libraries/shared/src/Trace.h b/libraries/shared/src/Trace.h index 93e2c6c4c2..1e1326968f 100644 --- a/libraries/shared/src/Trace.h +++ b/libraries/shared/src/Trace.h @@ -102,6 +102,9 @@ private: }; inline void traceEvent(const QLoggingCategory& category, const QString& name, EventType type, const QString& id = "", const QVariantMap& args = {}, const QVariantMap& extra = {}) { + if (!DependencyManager::isSet()) { + return; + } const auto& tracer = DependencyManager::get(); if (tracer) { tracer->traceEvent(category, name, type, id, args, extra); diff --git a/libraries/shared/src/ViewFrustum.cpp b/libraries/shared/src/ViewFrustum.cpp index 2a2eebc0a7..c2b92aac5f 100644 --- a/libraries/shared/src/ViewFrustum.cpp +++ b/libraries/shared/src/ViewFrustum.cpp @@ -75,6 +75,10 @@ void ViewFrustum::setProjection(const glm::mat4& projection) { _width = _corners[TOP_RIGHT_NEAR].x - _corners[TOP_LEFT_NEAR].x; } +void ViewFrustum::setProjection(float cameraFov, float cameraAspectRatio, float cameraNearClip, float cameraFarClip) { + setProjection(glm::perspective(glm::radians(cameraFov), cameraAspectRatio, cameraNearClip, cameraFarClip)); +} + // ViewFrustum::calculate() // // Description: this will calculate the view frustum bounds for a given position and direction @@ -134,7 +138,7 @@ const char* ViewFrustum::debugPlaneName (int plane) const { return "Unknown"; } -void ViewFrustum::fromByteArray(const QByteArray& input) { +int ViewFrustum::fromByteArray(const QByteArray& input) { // From the wire! glm::vec3 cameraPosition; @@ -168,14 +172,12 @@ void ViewFrustum::fromByteArray(const QByteArray& input) { 0.0f != cameraNearClip && 0.0f != cameraFarClip && cameraNearClip != cameraFarClip) { - setProjection(glm::perspective( - glm::radians(cameraFov), - cameraAspectRatio, - cameraNearClip, - cameraFarClip)); + setProjection(cameraFov, cameraAspectRatio, cameraNearClip, cameraFarClip); calculate(); } + + return sourceBuffer - startPosition; } diff --git a/libraries/shared/src/ViewFrustum.h b/libraries/shared/src/ViewFrustum.h index 981aabe70c..128a717df8 100644 --- a/libraries/shared/src/ViewFrustum.h +++ b/libraries/shared/src/ViewFrustum.h @@ -48,6 +48,7 @@ public: // setters for lens attributes void setProjection(const glm::mat4 & projection); + void setProjection(float cameraFov, float cameraAspectRatio, float cameraNearClip, float cameraFarClip); void setFocalLength(float focalLength) { _focalLength = focalLength; } bool isPerspective() const; @@ -147,7 +148,7 @@ public: void invalidate(); // causes all reasonable intersection tests to fail QByteArray toByteArray(); - void fromByteArray(const QByteArray& input); + int fromByteArray(const QByteArray& input); private: glm::mat4 _view; @@ -188,5 +189,6 @@ private: }; using ViewFrustumPointer = std::shared_ptr; +using ViewFrustums = std::vector; #endif // hifi_ViewFrustum_h diff --git a/libraries/shared/src/shared/Camera.h b/libraries/shared/src/shared/Camera.h index ea2e9cddab..32e753d0f9 100644 --- a/libraries/shared/src/shared/Camera.h +++ b/libraries/shared/src/shared/Camera.h @@ -40,6 +40,10 @@ class Camera : public QObject { * The Camera API provides access to the "camera" that defines your view in desktop and HMD display modes. * * @namespace Camera + * + * @hifi-interface + * @hifi-client-entity + * * @property position {Vec3} The position of the camera. You can set this value only when the camera is in independent mode. * @property orientation {Quat} The orientation of the camera. You can set this value only when the camera is in independent * mode. diff --git a/libraries/shared/src/shared/FileLogger.cpp b/libraries/shared/src/shared/FileLogger.cpp index 8ceb378574..6a10629ee5 100644 --- a/libraries/shared/src/shared/FileLogger.cpp +++ b/libraries/shared/src/shared/FileLogger.cpp @@ -36,17 +36,14 @@ protected: private: const FileLogger& _logger; QMutex _fileMutex; - uint64_t _lastRollTime; }; - - static const QString FILENAME_FORMAT = "hifi-log_%1%2.txt"; static const QString DATETIME_FORMAT = "yyyy-MM-dd_hh.mm.ss"; static const QString LOGS_DIRECTORY = "Logs"; -static const QString IPADDR_WILDCARD = "[0-9]*.[0-9]*.[0-9]*.[0-9]*"; -static const QString DATETIME_WILDCARD = "20[0-9][0-9]-[0,1][0-9]-[0-3][0-9]_[0-2][0-9].[0-6][0-9].[0-6][0-9]"; -static const QString FILENAME_WILDCARD = "hifi-log_" + IPADDR_WILDCARD + "_" + DATETIME_WILDCARD + ".txt"; +static const QString DATETIME_WILDCARD = "20[0-9][0-9]-[01][0-9]-[0-3][0-9]_[0-2][0-9]\\.[0-6][0-9]\\.[0-6][0-9]"; +static const QString SESSION_WILDCARD = "[0-9a-z]{8}(-[0-9a-z]{4}){3}-[0-9a-z]{12}"; +static QRegExp LOG_FILENAME_REGEX { "hifi-log_" + DATETIME_WILDCARD + "(_" + SESSION_WILDCARD + ")?\\.txt" }; static QUuid SESSION_ID; // Max log size is 512 KB. We send log files to our crash reporter, so we want to keep this relatively @@ -54,8 +51,6 @@ static QUuid SESSION_ID; static const qint64 MAX_LOG_SIZE = 512 * 1024; // Max log files found in the log directory is 100. static const qint64 MAX_LOG_DIR_SIZE = 512 * 1024 * 100; -// Max log age is 1 hour -static const uint64_t MAX_LOG_AGE_USECS = USECS_PER_SECOND * 3600; static FilePersistThread* _persistThreadInstance; @@ -86,38 +81,34 @@ FilePersistThread::FilePersistThread(const FileLogger& logger) : _logger(logger) if (file.exists()) { rollFileIfNecessary(file, false); } - _lastRollTime = usecTimestampNow(); } void FilePersistThread::rollFileIfNecessary(QFile& file, bool notifyListenersIfRolled) { - uint64_t now = usecTimestampNow(); - if ((file.size() > MAX_LOG_SIZE) || (now - _lastRollTime) > MAX_LOG_AGE_USECS) { + if (file.size() > MAX_LOG_SIZE) { QString newFileName = getLogRollerFilename(); if (file.copy(newFileName)) { file.open(QIODevice::WriteOnly | QIODevice::Truncate); file.close(); - qCDebug(shared) << "Rolled log file:" << newFileName; if (notifyListenersIfRolled) { emit rollingLogFile(newFileName); } - - _lastRollTime = now; } - QStringList nameFilters; - nameFilters << FILENAME_WILDCARD; - QDir logQDir(FileUtils::standardPath(LOGS_DIRECTORY)); - logQDir.setNameFilters(nameFilters); - logQDir.setSorting(QDir::Time); - QFileInfoList filesInDir = logQDir.entryInfoList(); + QDir logDir(FileUtils::standardPath(LOGS_DIRECTORY)); + logDir.setSorting(QDir::Time); + logDir.setFilter(QDir::Files); qint64 totalSizeOfDir = 0; - foreach(QFileInfo dirItm, filesInDir){ - if (totalSizeOfDir < MAX_LOG_DIR_SIZE){ - totalSizeOfDir += dirItm.size(); - } else { - QFile file(dirItm.filePath()); - file.remove(); + QFileInfoList filesInDir = logDir.entryInfoList(); + for (auto& fileInfo : filesInDir) { + if (!LOG_FILENAME_REGEX.exactMatch(fileInfo.fileName())) { + continue; + } + totalSizeOfDir += fileInfo.size(); + if (totalSizeOfDir > MAX_LOG_DIR_SIZE){ + qDebug() << "Removing log file: " << fileInfo.fileName(); + QFile oldLogFile(fileInfo.filePath()); + oldLogFile.remove(); } } } @@ -129,7 +120,7 @@ bool FilePersistThread::processQueueItems(const Queue& messages) { rollFileIfNecessary(file); if (file.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Text)) { QTextStream out(&file); - foreach(const QString& message, messages) { + for (const QString& message : messages) { out << message; } } diff --git a/libraries/ui/src/VirtualPadManager.cpp b/libraries/ui/src/VirtualPadManager.cpp index c786110bdf..dae19b52c2 100644 --- a/libraries/ui/src/VirtualPadManager.cpp +++ b/libraries/ui/src/VirtualPadManager.cpp @@ -39,9 +39,9 @@ namespace VirtualPad { const float Manager::BASE_MARGIN_PIXELS = 59.0f; const float Manager::STICK_RADIUS_PIXELS = 105.0f; const float Manager::JUMP_BTN_TRIMMED_RADIUS_PIXELS = 67.0f; - const float Manager::JUMP_BTN_FULL_PIXELS = 134.0f; - const float Manager::JUMP_BTN_BOTTOM_MARGIN_PIXELS = 67.0f; - const float Manager::JUMP_BTN_LEFT_MARGIN_PIXELS = 547.0f; + const float Manager::JUMP_BTN_FULL_PIXELS = 164.0f; + const float Manager::JUMP_BTN_BOTTOM_MARGIN_PIXELS = 80.0f; + const float Manager::JUMP_BTN_RIGHT_MARGIN_PIXELS = 13.0f; Manager::Manager() { diff --git a/libraries/ui/src/VirtualPadManager.h b/libraries/ui/src/VirtualPadManager.h index 68b3d4f10f..6f7fbcc921 100644 --- a/libraries/ui/src/VirtualPadManager.h +++ b/libraries/ui/src/VirtualPadManager.h @@ -54,7 +54,7 @@ namespace VirtualPad { static const float JUMP_BTN_TRIMMED_RADIUS_PIXELS; static const float JUMP_BTN_FULL_PIXELS; static const float JUMP_BTN_BOTTOM_MARGIN_PIXELS; - static const float JUMP_BTN_LEFT_MARGIN_PIXELS; + static const float JUMP_BTN_RIGHT_MARGIN_PIXELS; private: Instance _leftVPadInstance; diff --git a/libraries/ui/src/ui/OffscreenQmlSurface.cpp b/libraries/ui/src/ui/OffscreenQmlSurface.cpp index ac4194ed55..48e778c063 100644 --- a/libraries/ui/src/ui/OffscreenQmlSurface.cpp +++ b/libraries/ui/src/ui/OffscreenQmlSurface.cpp @@ -115,6 +115,7 @@ private: class UrlHandler : public QObject { Q_OBJECT public: + UrlHandler(QObject* parent = nullptr) : QObject(parent) {} Q_INVOKABLE bool canHandleUrl(const QString& url) { static auto handler = dynamic_cast(qApp); return handler && handler->canAcceptURL(url); @@ -223,6 +224,17 @@ void AudioHandler::run() { qDebug() << "QML Audio changed to " << _newTargetDevice; } +OffscreenQmlSurface::~OffscreenQmlSurface() { + clearFocusItem(); +} + +void OffscreenQmlSurface::clearFocusItem() { + if (_currentFocusItem) { + disconnect(_currentFocusItem, &QObject::destroyed, this, &OffscreenQmlSurface::focusDestroyed); + } + _currentFocusItem = nullptr; +} + void OffscreenQmlSurface::initializeEngine(QQmlEngine* engine) { Parent::initializeEngine(engine); new QQmlFileSelector(engine); @@ -246,7 +258,7 @@ void OffscreenQmlSurface::initializeEngine(QQmlEngine* engine) { auto rootContext = engine->rootContext(); rootContext->setContextProperty("GL", ::getGLContextData()); - rootContext->setContextProperty("urlHandler", new UrlHandler()); + rootContext->setContextProperty("urlHandler", new UrlHandler(rootContext)); rootContext->setContextProperty("resourceDirectoryUrl", QUrl::fromLocalFile(PathUtils::resourcesPath())); rootContext->setContextProperty("ApplicationInterface", qApp); auto javaScriptToInject = getEventBridgeJavascript(); @@ -545,17 +557,15 @@ bool OffscreenQmlSurface::handlePointerEvent(const PointerEvent& event, class QT } void OffscreenQmlSurface::focusDestroyed(QObject* obj) { - if (_currentFocusItem) { - disconnect(_currentFocusItem, &QObject::destroyed, this, &OffscreenQmlSurface::focusDestroyed); - } - _currentFocusItem = nullptr; + clearFocusItem(); } void OffscreenQmlSurface::onFocusObjectChanged(QObject* object) { + clearFocusItem(); + QQuickItem* item = static_cast(object); if (!item) { setFocusText(false); - _currentFocusItem = nullptr; return; } @@ -563,10 +573,6 @@ void OffscreenQmlSurface::onFocusObjectChanged(QObject* object) { qApp->sendEvent(object, &query); setFocusText(query.value(Qt::ImEnabled).toBool()); - if (_currentFocusItem) { - disconnect(_currentFocusItem, &QObject::destroyed, this, 0); - } - // Raise and lower keyboard for QML text fields. // HTML text fields are handled in emitWebEvent() methods - testing READ_ONLY_PROPERTY prevents action for HTML files. const char* READ_ONLY_PROPERTY = "readOnly"; diff --git a/libraries/ui/src/ui/OffscreenQmlSurface.h b/libraries/ui/src/ui/OffscreenQmlSurface.h index 9fa86f12a3..b95a8f117d 100644 --- a/libraries/ui/src/ui/OffscreenQmlSurface.h +++ b/libraries/ui/src/ui/OffscreenQmlSurface.h @@ -22,7 +22,8 @@ class OffscreenQmlSurface : public hifi::qml::OffscreenSurface { Q_OBJECT Q_PROPERTY(bool focusText READ isFocusText NOTIFY focusTextChanged) public: - + ~OffscreenQmlSurface(); + static void addWhitelistContextHandler(const std::initializer_list& urls, const QmlContextCallback& callback); static void addWhitelistContextHandler(const QUrl& url, const QmlContextCallback& callback) { addWhitelistContextHandler({ { url } }, callback); }; @@ -58,6 +59,7 @@ public slots: void sendToQml(const QVariant& message); protected: + void clearFocusItem(); void setFocusText(bool newFocusText); void initializeEngine(QQmlEngine* engine) override; void onRootContextCreated(QQmlContext* qmlContext) override; diff --git a/libraries/ui/src/ui/TabletScriptingInterface.h b/libraries/ui/src/ui/TabletScriptingInterface.h index bab15fc7b6..e74b846f02 100644 --- a/libraries/ui/src/ui/TabletScriptingInterface.h +++ b/libraries/ui/src/ui/TabletScriptingInterface.h @@ -40,6 +40,9 @@ class OffscreenQmlSurface; /**jsdoc * @namespace Tablet + * + * @hifi-interface + * @hifi-client-entity */ class TabletScriptingInterface : public QObject, public Dependency { Q_OBJECT @@ -176,6 +179,10 @@ Q_DECLARE_METATYPE(TabletButtonsProxyModel*); /**jsdoc * @class TabletProxy + * + * @hifi-interface + * @hifi-client-entity + * * @property {string} name - Name of this tablet. Read-only. * @property {boolean} toolbarMode - Used to transition this tablet into and out of toolbar mode. * When tablet is in toolbar mode, all its buttons will appear in a floating toolbar. @@ -410,6 +417,10 @@ Q_DECLARE_METATYPE(TabletProxy*); /**jsdoc * @class TabletButtonProxy + * + * @hifi-interface + * @hifi-client-entity + * * @property {Uuid} uuid - Uniquely identifies this button. Read-only. * @property {TabletButtonProxy.ButtonProperties} properties */ diff --git a/scripts/+android/defaultScripts.js b/scripts/+android/defaultScripts.js index 11aee6a9d2..98fbb4b1a7 100644 --- a/scripts/+android/defaultScripts.js +++ b/scripts/+android/defaultScripts.js @@ -14,7 +14,7 @@ var DEFAULT_SCRIPTS_COMBINED = [ "system/progress.js", "system/+android/touchscreenvirtualpad.js", - "system/+android/bottombar.js", + "system/+android/actionbar.js", "system/+android/audio.js" , "system/+android/modes.js", "system/+android/stats.js"/*, diff --git a/scripts/system/+android/actionbar.js b/scripts/system/+android/actionbar.js new file mode 100644 index 0000000000..1f0872d5ee --- /dev/null +++ b/scripts/system/+android/actionbar.js @@ -0,0 +1,53 @@ +"use strict"; +// +// backbutton.js +// scripts/system/+android +// +// Created by Gabriel Calero & Cristian Duarte on Apr 06, 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 +// +(function() { // BEGIN LOCAL_SCOPE + +var actionbar; +var backButton; + +var logEnabled = true; + +function printd(str) { + if (logEnabled) + print("[actionbar.js] " + str); +} + +function init() { + actionbar = new QmlFragment({ + qml: "hifi/ActionBar.qml" + }); + backButton = actionbar.addButton({ + icon: "icons/+android/backward.svg", + activeIcon: "icons/+android/backward.svg", + text: "", + bgOpacity: 0.0, + hoverBgOpacity: 0.0, + activeBgOpacity: 0.0 + }); + + backButton.clicked.connect(onBackPressed); +} + +function onBackPressed() { + App.openAndroidActivity("Home"); +} + + +Script.scriptEnding.connect(function() { + if(backButton) { + backButton.clicked.disconnect(onBackPressed); + } +}); + +init(); + +}()); // END LOCAL_SCOPE diff --git a/scripts/system/+android/audio.js b/scripts/system/+android/audio.js index 955f74d63a..4d6d8db008 100644 --- a/scripts/system/+android/audio.js +++ b/scripts/system/+android/audio.js @@ -32,8 +32,9 @@ function init() { activeIcon: "icons/mic-mute-a.svg", text: "", bgOpacity: 0.0, - activeBgOpacity: 0.0, - bgColor: "#FFFFFF" + hoverBgOpacity: 0.0, + activeHoverBgOpacity: 0.0, + activeBgOpacity: 0.0 }); onMuteToggled(); diff --git a/scripts/system/+android/avatarSelection.js b/scripts/system/+android/avatarSelection.js deleted file mode 100644 index 2946e541b5..0000000000 --- a/scripts/system/+android/avatarSelection.js +++ /dev/null @@ -1,164 +0,0 @@ -"use strict"; -// -// avatarSelection.js -// scripts/system/ -// -// Created by Gabriel Calero & Cristian Duarte on 21 Sep 2017 -// Copyright 2017 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -var window; - -var logEnabled = true; -var isVisible = false; - -function printd(str) { - if (logEnabled) - print("[avatarSelection.js] " + str); -} - -function fromQml(message) { // messages are {method, params}, like json-rpc. See also sendToQml. - var data; - printd("fromQml " + JSON.stringify(message)); - switch (message.method) { - case 'selectAvatar': - // use this message.params.avatarUrl - printd("Selected Avatar: [" + message.params.avatarUrl + "]"); - App.askBeforeSetAvatarUrl(message.params.avatarUrl); - break; - case 'openAvatarMarket': - // good - App.openUrl("https://metaverse.highfidelity.com/marketplace?category=avatars"); - break; - case 'hide': - Controller.setVPadHidden(false); - module.exports.hide(); - module.exports.onHidden(); - break; - default: - print('[avatarSelection.js] Unrecognized message from avatarSelection.qml:', JSON.stringify(message)); - } -} - -function sendToQml(message) { - if (!window) { - print("[avatarSelection.js] There is no window object"); - return; - } - window.sendToQml(message); -} - -function refreshSelected(currentAvatarURL) { - sendToQml({ - type: "refreshSelected", - selectedAvatarUrl: currentAvatarURL - }); - - sendToQml({ - type: "showAvatars" - }); -} - -function init() { - if (!window) { - print("[avatarSelection.js] There is no window object for init()"); - return; - } - var DEFAULT_AVATAR_URL = "http://mpassets.highfidelity.com/7fe80a1e-f445-4800-9e89-40e677b03bee-v3/mannequin.fst"; - sendToQml({ - type: "addAvatar", - name: "Wooden Mannequin", - thumbnailUrl: "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/previews/7fe80a1e-f445-4800-9e89-40e677b03bee/thumbnail/hifi-mp-7fe80a1e-f445-4800-9e89-40e677b03bee.jpg", - avatarUrl: DEFAULT_AVATAR_URL - }); - sendToQml({ - type: "addAvatar", - name: "Cody", - thumbnailUrl: "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/previews/8c859fca-4cbd-4e82-aad1-5f4cb0ca5d53/thumbnail/hifi-mp-8c859fca-4cbd-4e82-aad1-5f4cb0ca5d53.jpg", - avatarUrl: "http://mpassets.highfidelity.com/8c859fca-4cbd-4e82-aad1-5f4cb0ca5d53-v1/cody.fst" - }); - sendToQml({ - type: "addAvatar", - name: "Mixamo Will", - thumbnailUrl: "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/previews/d029ae8d-2905-4eb7-ba46-4bd1b8cb9d73/thumbnail/hifi-mp-d029ae8d-2905-4eb7-ba46-4bd1b8cb9d73.jpg", - avatarUrl: "http://mpassets.highfidelity.com/d029ae8d-2905-4eb7-ba46-4bd1b8cb9d73-v1/4618d52e711fbb34df442b414da767bb.fst" - }); - sendToQml({ - type: "addAvatar", - name: "Albert", - thumbnailUrl: "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/previews/1e57c395-612e-4acd-9561-e79dbda0bc49/thumbnail/hifi-mp-1e57c395-612e-4acd-9561-e79dbda0bc49.jpg", - avatarUrl: "http://mpassets.highfidelity.com/1e57c395-612e-4acd-9561-e79dbda0bc49-v1/albert.fst" - }); - /* We need to implement the wallet, so let's skip this for the moment - sendToQml({ - type: "addExtraOption", - showName: "More choices", - thumbnailUrl: "../../../images/moreAvatars.png", - methodNameWhenClicked: "openAvatarMarket", - actionText: "MARKETPLACE" - }); - */ - var currentAvatarURL = Settings.getValue('Avatar/fullAvatarURL', DEFAULT_AVATAR_URL); - printd("Default Avatar: [" + DEFAULT_AVATAR_URL + "]"); - printd("Current Avatar: [" + currentAvatarURL + "]"); - if (!currentAvatarURL || 0 === currentAvatarURL.length) { - currentAvatarURL = DEFAULT_AVATAR_URL; - } - refreshSelected(currentAvatarURL); -} - -module.exports = { - init: function() { - window = new QmlFragment({ - qml: "hifi/avatarSelection.qml", - visible: false - }); - if (window) { - window.fromQml.connect(fromQml); - } - init(); - }, - show: function() { - Controller.setVPadHidden(true); - if (window) { - window.setVisible(true); - isVisible = true; - } - }, - hide: function() { - Controller.setVPadHidden(false); - if (window) { - window.setVisible(false); - } - isVisible = false; - }, - destroy: function() { - Controller.setVPadHidden(false); - if (window) { - window.fromQml.disconnect(fromQml); - window.close(); - window = null; - } - }, - isVisible: function() { - return isVisible; - }, - width: function() { - return window ? window.size.x : 0; - }, - height: function() { - return window ? window.size.y : 0; - }, - position: function() { - return window && isVisible ? window.position : null; - }, - refreshSelectedAvatar: function(currentAvatarURL) { - refreshSelected(currentAvatarURL); - }, - onHidden: function() { - Controller.setVPadHidden(false); - } -}; diff --git a/scripts/system/+android/bottombar.js b/scripts/system/+android/bottombar.js deleted file mode 100644 index 3435edb548..0000000000 --- a/scripts/system/+android/bottombar.js +++ /dev/null @@ -1,267 +0,0 @@ -"use strict"; -// -// bottombar.js -// scripts/system/ -// -// Created by Gabriel Calero & Cristian Duarte on Jan 18, 2018 -// Copyright 2018 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// -(function() { // BEGIN LOCAL_SCOPE - -var bottombar; -var bottomHudOptionsBar; -var gotoBtn; -var avatarBtn; -var bubbleBtn; -var loginBtn; - -var gotoScript = Script.require('./goto.js'); -var avatarSelection = Script.require('./avatarSelection.js'); - -var logEnabled = false; - -function printd(str) { - if (logEnabled) { - print("[bottombar.js] " + str); - } -} - -function init() { - gotoScript.init(); - gotoScript.setOnShownChange(function (shown) { - if (shown) { - showAddressBar(); - } else { - hideAddressBar(); - } - }); - avatarSelection.init(); - App.fullAvatarURLChanged.connect(processedNewAvatar); - - setupBottomBar(); - setupBottomHudOptionsBar(); - - raiseBottomBar(); - - GlobalServices.connected.connect(handleLogin); - GlobalServices.disconnected.connect(handleLogout); -} - -function shutdown() { - App.fullAvatarURLChanged.disconnect(processedNewAvatar); -} - -function setupBottomBar() { - bottombar = new QmlFragment({ - qml: "hifi/bottombar.qml" - }); - - bottombar.fromQml.connect(function(message) { - switch (message.method) { - case 'hide': - lowerBottomBar(); - break; - default: - print('[bottombar.js] Unrecognized message from bottomHud.qml:', JSON.stringify(message)); - } - }); - - avatarBtn = bottombar.addButton({ - icon: "icons/avatar-i.svg", - activeIcon: "icons/avatar-a.svg", - bgOpacity: 0, - height: 240, - width: 294, - hoverBgOpacity: 0, - activeBgOpacity: 0, - activeHoverBgOpacity: 0, - iconSize: 108, - textSize: 45, - text: "AVATAR" - }); - avatarBtn.clicked.connect(function() { - printd("Avatar button clicked"); - if (!avatarSelection.isVisible()) { - showAvatarSelection(); - } else { - hideAvatarSelection(); - } - }); - avatarSelection.onHidden = function() { - if (avatarBtn) { - avatarBtn.isActive = false; - } - }; - - gotoBtn = bottombar.addButton({ - icon: "icons/goto-i.svg", - activeIcon: "icons/goto-a.svg", - bgOpacity: 0, - hoverBgOpacity: 0, - activeBgOpacity: 0, - activeHoverBgOpacity: 0, - height: 240, - width: 294, - iconSize: 108, - textSize: 45, - text: "GO TO" - }); - - gotoBtn.clicked.connect(function() { - if (!gotoScript.isVisible()) { - showAddressBar(); - } else { - hideAddressBar(); - } - }); - - bubbleBtn = bottombar.addButton({ - icon: "icons/bubble-i.svg", - activeIcon: "icons/bubble-a.svg", - bgOpacity: 0, - hoverBgOpacity: 0, - activeBgOpacity: 0, - activeHoverBgOpacity: 0, - height: 240, - width: 294, - iconSize: 108, - textSize: 45, - text: "BUBBLE" - }); - - bubbleBtn.editProperties({isActive: Users.getIgnoreRadiusEnabled()}); - - bubbleBtn.clicked.connect(function() { - Users.toggleIgnoreRadius(); - bubbleBtn.editProperties({isActive: Users.getIgnoreRadiusEnabled()}); - }); - - loginBtn = bottombar.addButton({ - icon: "icons/login-i.svg", - activeIcon: "icons/login-a.svg", - height: 240, - width: 294, - iconSize: 108, - textSize: 45, - text: Account.isLoggedIn() ? "LOG OUT" : "LOG IN" - }); - loginBtn.clicked.connect(function() { - if (!Account.isLoggedIn()) { - Account.checkAndSignalForAccessToken(); - } else { - Menu.triggerOption("Login / Sign Up"); - } - }); - - // TODO: setup all the buttons or provide a dynamic interface - - raiseBottomBar(); - - -} - -var setupBottomHudOptionsBar = function() { - var bottomHud = new QmlFragment({ - qml: "hifi/bottomHudOptions.qml" - }); - - bottomHudOptionsBar = { - show: function() { - bottomHud.setVisible(true); - }, - hide: function() { - bottomHud.setVisible(false); - }, - qmlFragment: bottomHud - }; - bottomHud.fromQml.connect( - function(message) { - switch (message.method) { - case 'showUpBar': - printd('[bottombar.js] showUpBar message from bottomHudOptions.qml: ', JSON.stringify(message)); - raiseBottomBar(); - break; - default: - print('[bottombar.js] Unrecognized message from bottomHudOptions.qml:', JSON.stringify(message)); - } - } - ); -} - -function lowerBottomBar() { - if (bottombar) { - bottombar.setVisible(false); - } - if (bottomHudOptionsBar) { - bottomHudOptionsBar.show(); - } - Controller.setVPadExtraBottomMargin(0); -} - -function raiseBottomBar() { - print('[bottombar.js] raiseBottomBar begin'); - if (bottombar) { - bottombar.setVisible(true); - } - if (bottomHudOptionsBar) { - bottomHudOptionsBar.hide(); - } - Controller.setVPadExtraBottomMargin(255); // Height in bottombar.qml - print('[bottombar.js] raiseBottomBar end'); -} - -function showAddressBar() { - gotoScript.show(); - gotoBtn.isActive = true; -} - -function hideAddressBar() { - gotoScript.hide(); - gotoBtn.isActive = false; -} - -function showAvatarSelection() { - avatarSelection.show(); - avatarBtn.isActive = true; -} - -function hideAvatarSelection() { - avatarSelection.hide(); - avatarBtn.isActive = false; -} - -// TODO: Move to avatarSelection.js and make it possible to hide the window from there AND switch the button state here too -function processedNewAvatar(url, modelName) { - avatarSelection.refreshSelectedAvatar(url); - hideAvatarSelection(); -} - -function handleLogin() { - Script.setTimeout(function() { - if (Account.isLoggedIn()) { - MyAvatar.displayName=Account.getUsername(); - } - }, 2000); - if (loginBtn) { - loginBtn.editProperties({text: "LOG OUT"}); - } -} -function handleLogout() { - MyAvatar.displayName=""; - if (loginBtn) { - loginBtn.editProperties({text: "LOG IN"}); - } -} - -Script.scriptEnding.connect(function () { - shutdown(); - GlobalServices.connected.disconnect(handleLogin); - GlobalServices.disconnected.disconnect(handleLogout); -}); - -init(); - -}()); // END LOCAL_SCOPE diff --git a/scripts/system/+android/goto.js b/scripts/system/+android/goto.js deleted file mode 100644 index 2019af9077..0000000000 --- a/scripts/system/+android/goto.js +++ /dev/null @@ -1,96 +0,0 @@ -"use strict"; -// -// goto-android.js -// scripts/system/ -// -// Created by Gabriel Calero & Cristian Duarte on 12 Sep 2017 -// Copyright 2017 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -var window; - - -var logEnabled = false; -function printd(str) { - if (logEnabled) - print("[goto-android.js] " + str); -} - -function init() { -} - -function fromQml(message) { // messages are {method, params}, like json-rpc. See also sendToQml. - switch (message.method) { - case 'shownChanged': - if (notifyShownChange) { - notifyShownChange(message.params.shown); - } ; - break; - case 'hide': - module.exports.hide(); - module.exports.onHidden(); - break; - default: - print('[goto-android.js] Unrecognized message from AddressBarDialog.qml:', JSON.stringify(message)); - } -} - -function sendToQml(message) { - window.sendToQml(message); -} - -var isVisible = false; -var notifyShownChange; -module.exports = { - init: function() { - window = new QmlFragment({ - qml: "AddressBarDialog.qml", - visible: false - }); - }, - show: function() { - Controller.setVPadHidden(true); - if (window) { - window.fromQml.connect(fromQml); - window.setVisible(true); - isVisible = true; - } - }, - hide: function() { - Controller.setVPadHidden(false); - if (window) { - window.fromQml.disconnect(fromQml); - window.setVisible(false); - } - isVisible = false; - }, - destroy: function() { - if (window) { - window.close(); - window = null; - } - }, - isVisible: function() { - return isVisible; - }, - width: function() { - return window ? window.size.x : 0; - }, - height: function() { - return window ? window.size.y : 0; - }, - position: function() { - return window && isVisible ? window.position : null; - }, - setOnShownChange: function(f) { - notifyShownChange = f; - }, - onHidden: function() { } - - -}; - -init(); diff --git a/scripts/system/+android/modes.js b/scripts/system/+android/modes.js index c41ae1f327..f5b3609c26 100644 --- a/scripts/system/+android/modes.js +++ b/scripts/system/+android/modes.js @@ -11,15 +11,21 @@ // (function() { // BEGIN LOCAL_SCOPE -var modesbar; -var modesButtons; -var currentSelectedBtn; +var modeButton; +var currentMode; +var barQml; var SETTING_CURRENT_MODE_KEY = 'Android/Mode'; var MODE_VR = "VR", MODE_RADAR = "RADAR", MODE_MY_VIEW = "MY VIEW"; var DEFAULT_MODE = MODE_MY_VIEW; -var logEnabled = true; +var nextMode = {}; +nextMode[MODE_RADAR]=MODE_MY_VIEW; +nextMode[MODE_MY_VIEW]=MODE_RADAR; +var modeLabel = {}; +modeLabel[MODE_RADAR]="TOP VIEW"; +modeLabel[MODE_MY_VIEW]="MY VIEW"; +var logEnabled = false; var radar = Script.require('./radar.js'); var uniqueColor = Script.require('./uniqueColor.js'); @@ -32,89 +38,35 @@ function printd(str) { function init() { radar.setUniqueColor(uniqueColor); radar.init(); - setupModesBar(); + + barQml = new QmlFragment({ + qml: "hifi/modesbar.qml" + }); + modeButton = barQml.addButton({ + icon: "icons/myview-a.svg", + activeBgOpacity: 0.0, + hoverBgOpacity: 0.0, + activeHoverBgOpacity: 0.0, + text: "MODE", + height:240, + bottomMargin: 16, + textSize: 38, + fontFamily: "Raleway", + fontBold: true + + }); + + switchToMode(getCurrentModeSetting()); + + modeButton.clicked.connect(function() { + switchToMode(nextMode[currentMode]); + }); } function shutdown() { } -function setupModesBar() { - - var bar = new QmlFragment({ - qml: "hifi/modesbar.qml" - }); - var buttonRadarMode = bar.addButton({ - icon: "icons/radar-i.svg", - activeIcon: "icons/radar-a.svg", - hoverIcon: "icons/radar-a.svg", - activeBgOpacity: 0.0, - hoverBgOpacity: 0.0, - activeHoverBgOpacity: 0.0, - text: "RADAR", - height:240, - bottomMargin: 6, - textSize: 45 - }); - var buttonMyViewMode = bar.addButton({ - icon: "icons/myview-i.svg", - activeIcon: "icons/myview-a.svg", - hoverIcon: "icons/myview-a.svg", - activeBgOpacity: 0.0, - hoverBgOpacity: 0.0, - activeHoverBgOpacity: 0.0, - text: "MY VIEW", - height: 240, - bottomMargin: 6, - textSize: 45 - }); - - modesButtons = [buttonRadarMode, buttonMyViewMode]; - - var mode = getCurrentModeSetting(); - - var buttonsRevealed = false; - bar.sendToQml({type: "inactiveButtonsHidden"}); - - modesbar = { - restoreMyViewButton: function() { - switchModeButtons(buttonMyViewMode); - saveCurrentModeSetting(MODE_MY_VIEW); - }, - sendToQml: function(o) { bar.sendToQml(o); }, - qmlFragment: bar - }; - - buttonRadarMode.clicked.connect(function() { - //if (connections.isVisible()) return; - saveCurrentModeSetting(MODE_RADAR); - printd("Radar clicked"); - onButtonClicked(buttonRadarMode, function() { - radar.startRadarMode(); - }); - }); - buttonMyViewMode.clicked.connect(function() { - //if (connections.isVisible()) return; - saveCurrentModeSetting(MODE_MY_VIEW); - printd("My View clicked"); - onButtonClicked(buttonMyViewMode, function() { - if (currentSelectedBtn == buttonRadarMode) { - radar.endRadarMode(); - } - }); - }); - - var savedButton; - if (mode == MODE_MY_VIEW) { - savedButton = buttonMyViewMode; - } else { - savedButton = buttonRadarMode; - } - printd("[MODE] previous mode " + mode); - - savedButton.clicked(); -} - function saveCurrentModeSetting(mode) { Settings.setValue(SETTING_CURRENT_MODE_KEY, mode); } @@ -123,62 +75,29 @@ function getCurrentModeSetting(mode) { return Settings.getValue(SETTING_CURRENT_MODE_KEY, DEFAULT_MODE); } -function showAllButtons() { - for (var i=0; i= 0) { that.selections.splice(idx, 1); Selection.removeFromSelectedItemsList(HIGHLIGHT_LIST_NAME, "entity", entityID); } + } + + that.removeEntity = function (entityID) { + removeEntityByID(entityID); + that._update(true); + }; + + that.removeEntities = function(entityIDs) { + for (var i = 0, length = entityIDs.length; i < length; i++) { + removeEntityByID(entityIDs[i]); + } that._update(true); }; diff --git a/tests/controllers/CMakeLists.txt b/tests/controllers/CMakeLists.txt index b5e866ccce..ce1c150ed4 100644 --- a/tests/controllers/CMakeLists.txt +++ b/tests/controllers/CMakeLists.txt @@ -23,5 +23,9 @@ if (WIN32) add_dependency_external_projects(wasapi) endif() +if (CMAKE_SYSTEM_NAME MATCHES "Linux") + target_link_libraries(${TARGET_NAME} atomic) +endif() + package_libraries_for_deployment() -endif() \ No newline at end of file +endif() diff --git a/tests/qml/qml/controls/WebEntityView.qml b/tests/qml/qml/controls/WebEntityView.qml new file mode 100644 index 0000000000..5bd29ef457 --- /dev/null +++ b/tests/qml/qml/controls/WebEntityView.qml @@ -0,0 +1,47 @@ +// +// WebEntityView.qml +// +// Created by Kunal Gosar on 16 March 2017 +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.5 +import QtWebEngine 1.5 +import Hifi 1.0 + +/* +TestItem { + Rectangle { + anchors.fill: parent + anchors.margins: 10 + color: "blue" + property string url: "" + ColorAnimation on color { + loops: Animation.Infinite; from: "blue"; to: "yellow"; duration: 1000 + } + } +} +*/ + +WebEngineView { + id: webViewCore + objectName: "webEngineView" + width: parent !== null ? parent.width : undefined + height: parent !== null ? parent.height : undefined + + onFeaturePermissionRequested: { + grantFeaturePermission(securityOrigin, feature, true); + } + + //disable popup + onContextMenuRequested: { + request.accepted = true; + } + + onNewViewRequested: { + newViewRequestedCallback(request) + } +} diff --git a/tests/qml/src/main.cpp b/tests/qml/src/main.cpp index 022f7290f4..349ac55d88 100644 --- a/tests/qml/src/main.cpp +++ b/tests/qml/src/main.cpp @@ -28,52 +28,87 @@ #include #include #include - +#include #include #include +#include + #include -#include #include #include #include #include #include #include +#include +#include +namespace gl { +extern void initModuleGl(); +} -class OffscreenQmlSurface : public hifi::qml::OffscreenSurface { +class QTestItem : public QQuickItem { + Q_OBJECT +public: + QTestItem(QQuickItem* parent = nullptr) : QQuickItem(parent) { qDebug() << __FUNCTION__; } + ~QTestItem() { qDebug() << __FUNCTION__; } +}; + +QUrl getTestResource(const QString& relativePath) { + static QString dir; + if (dir.isEmpty()) { + QDir path(__FILE__); + path.cdUp(); + dir = path.cleanPath(path.absoluteFilePath("../")) + "/"; + qDebug() << "Resources Path: " << dir; + } + return QUrl::fromLocalFile(dir + relativePath); +} + +#define DIVISIONS_X 5 +#define DIVISIONS_Y 5 + +using QmlPtr = QSharedPointer; +using TextureAndFence = hifi::qml::OffscreenSurface::TextureAndFence; + +struct QmlInfo { + QmlPtr surface; + GLuint texture{ 0 }; + uint64_t lifetime{ 0 }; }; class TestWindow : public QWindow { - public: TestWindow(); - private: - using TextureAndFence = hifi::qml::OffscreenSurface::TextureAndFence; QOpenGLContext _glContext; OffscreenGLCanvas _sharedContext; - OffscreenQmlSurface _offscreenQml; + std::array, DIVISIONS_X> _surfaces; + QOpenGLFunctions_4_5_Core _glf; - uint32_t _currentTexture{ 0 }; - GLsync _readFence{ 0 }; std::function _discardLamdba; QSize _size; + size_t _surfaceCount{ 0 }; GLuint _fbo{ 0 }; const QSize _qmlSize{ 640, 480 }; bool _aboutToQuit{ false }; + uint64_t _createStopTime; void initGl(); + void updateSurfaces(); + void buildSurface(QmlInfo& qmlInfo, bool allowVideo); + void destroySurface(QmlInfo& qmlInfo); void resizeWindow(const QSize& size); void draw(); void resizeEvent(QResizeEvent* ev) override; }; TestWindow::TestWindow() { - setSurfaceType(QSurface::OpenGLSurface); + Setting::init(); + setSurfaceType(QSurface::OpenGLSurface); QSurfaceFormat format; format.setDepthBufferSize(24); format.setStencilBufferSize(8); @@ -83,13 +118,16 @@ TestWindow::TestWindow() { QSurfaceFormat::setDefaultFormat(format); setFormat(format); + qmlRegisterType("Hifi", 1, 0, "TestItem"); + show(); + _createStopTime = usecTimestampNow() + (3000u * USECS_PER_SECOND); resize(QSize(800, 600)); auto timer = new QTimer(this); timer->setTimerType(Qt::PreciseTimer); - timer->setInterval(5); + timer->setInterval(30); connect(timer, &QTimer::timeout, [&] { draw(); }); timer->start(); @@ -97,7 +135,6 @@ TestWindow::TestWindow() { timer->stop(); _aboutToQuit = true; }); - } void TestWindow::initGl() { @@ -105,6 +142,7 @@ void TestWindow::initGl() { if (!_glContext.create() || !_glContext.makeCurrent(this)) { qFatal("Unable to intialize Window GL context"); } + gl::initModuleGl(); _glf.initializeOpenGLFunctions(); _glf.glCreateFramebuffers(1, &_fbo); @@ -113,15 +151,97 @@ void TestWindow::initGl() { qFatal("Unable to intialize Shared GL context"); } hifi::qml::OffscreenSurface::setSharedContext(_sharedContext.getContext()); - _discardLamdba = _offscreenQml.getDiscardLambda(); - _offscreenQml.resize({ 640, 480 }); - _offscreenQml.load(QUrl::fromLocalFile("C:/Users/bdavi/Git/hifi/tests/qml/qml/main.qml")); + _discardLamdba = hifi::qml::OffscreenSurface::getDiscardLambda(); } void TestWindow::resizeWindow(const QSize& size) { _size = size; } +static const int DEFAULT_MAX_FPS = 10; +static const QString CONTROL_URL{ "/qml/controls/WebEntityView.qml" }; +static const char* URL_PROPERTY{ "url" }; + +QString getSourceUrl(bool video) { + static const std::vector SOURCE_URLS{ + "https://www.reddit.com/wiki/random", + "https://en.wikipedia.org/wiki/Wikipedia:Random", + "https://slashdot.org/", + }; + + static const std::vector VIDEO_SOURCE_URLS{ + "https://www.youtube.com/watch?v=gDXwhHm4GhM", + "https://www.youtube.com/watch?v=Ch_hoYPPeGc", + }; + + const auto& sourceUrls = video ? VIDEO_SOURCE_URLS : SOURCE_URLS; + auto index = rand() % sourceUrls.size(); + return sourceUrls[index]; +} + +void TestWindow::buildSurface(QmlInfo& qmlInfo, bool video) { + ++_surfaceCount; + auto lifetimeSecs = (uint32_t)(5.0f + (randFloat() * 10.0f)); + auto lifetimeUsecs = (USECS_PER_SECOND * lifetimeSecs); + qmlInfo.lifetime = lifetimeUsecs + usecTimestampNow(); + qmlInfo.texture = 0; + qmlInfo.surface.reset(new hifi::qml::OffscreenSurface()); + qmlInfo.surface->load(getTestResource(CONTROL_URL), [video](QQmlContext* context, QQuickItem* item) { + item->setProperty(URL_PROPERTY, getSourceUrl(video)); + }); + qmlInfo.surface->setMaxFps(DEFAULT_MAX_FPS); + qmlInfo.surface->resize(_qmlSize); + qmlInfo.surface->resume(); +} + +void TestWindow::destroySurface(QmlInfo& qmlInfo) { + auto& surface = qmlInfo.surface; + auto webView = surface->getRootItem(); + if (webView) { + // stop loading + QMetaObject::invokeMethod(webView, "stop"); + webView->setProperty(URL_PROPERTY, "about:blank"); + } + surface->pause(); + surface.reset(); +} + +void TestWindow::updateSurfaces() { + auto now = usecTimestampNow(); + // Fetch any new textures + for (size_t x = 0; x < DIVISIONS_X; ++x) { + for (size_t y = 0; y < DIVISIONS_Y; ++y) { + auto& qmlInfo = _surfaces[x][y]; + if (!qmlInfo.surface) { + if (now < _createStopTime && randFloat() > 0.99f) { + buildSurface(qmlInfo, x == 0 && y == 0); + } else { + continue; + } + } + + if (now > qmlInfo.lifetime) { + destroySurface(qmlInfo); + continue; + } + + auto& surface = qmlInfo.surface; + auto& currentTexture = qmlInfo.texture; + + TextureAndFence newTextureAndFence; + if (surface->fetchTexture(newTextureAndFence)) { + if (currentTexture != 0) { + auto readFence = _glf.glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); + glFlush(); + _discardLamdba(currentTexture, readFence); + } + currentTexture = newTextureAndFence.first; + _glf.glWaitSync((GLsync)newTextureAndFence.second, 0, GL_TIMEOUT_IGNORED); + } + } + } +} + void TestWindow::draw() { if (_aboutToQuit) { return; @@ -140,38 +260,30 @@ void TestWindow::draw() { return; } + updateSurfaces(); + + auto size = this->geometry().size(); + auto incrementX = size.width() / DIVISIONS_X; + auto incrementY = size.height() / DIVISIONS_Y; + _glf.glViewport(0, 0, size.width(), size.height()); _glf.glClearColor(1, 0, 0, 1); _glf.glClear(GL_COLOR_BUFFER_BIT); - - TextureAndFence newTextureAndFence; - if (_offscreenQml.fetchTexture(newTextureAndFence)) { - if (_currentTexture) { - _discardLamdba(_currentTexture, _readFence); - _readFence = 0; + for (uint32_t x = 0; x < DIVISIONS_X; ++x) { + for (uint32_t y = 0; y < DIVISIONS_Y; ++y) { + auto& qmlInfo = _surfaces[x][y]; + if (!qmlInfo.surface || !qmlInfo.texture) { + continue; + } + _glf.glNamedFramebufferTexture(_fbo, GL_COLOR_ATTACHMENT0, qmlInfo.texture, 0); + _glf.glBlitNamedFramebuffer(_fbo, 0, + // src coordinates + 0, 0, _qmlSize.width() - 1, _qmlSize.height() - 1, + // dst coordinates + incrementX * x, incrementY * y, incrementX * (x + 1), incrementY * (y + 1), + // blit mask and filter + GL_COLOR_BUFFER_BIT, GL_NEAREST); } - - _currentTexture = newTextureAndFence.first; - _glf.glWaitSync((GLsync)newTextureAndFence.second, 0, GL_TIMEOUT_IGNORED); - _glf.glNamedFramebufferTexture(_fbo, GL_COLOR_ATTACHMENT0, _currentTexture, 0); } - - auto diff = _size - _qmlSize; - diff /= 2; - auto qmlExtent = diff + _qmlSize; - - if (_currentTexture) { - _glf.glBlitNamedFramebuffer(_fbo, 0, - 0, 0, _qmlSize.width() - 1, _qmlSize.height() - 1, - diff.width(), diff.height(), qmlExtent.width() - 1, qmlExtent.height() - 2, - GL_COLOR_BUFFER_BIT, GL_NEAREST); - } - - if (_readFence) { - _glf.glDeleteSync(_readFence); - } - _readFence = _glf.glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); - _glf.glFlush(); - _glContext.swapBuffers(this); } @@ -180,11 +292,9 @@ void TestWindow::resizeEvent(QResizeEvent* ev) { } int main(int argc, char** argv) { - setupHifiApplication("QML Test"); - QGuiApplication app(argc, argv); TestWindow window; - app.exec(); - return 0; + return app.exec(); } +#include "main.moc" diff --git a/tests/render-perf/CMakeLists.txt b/tests/render-perf/CMakeLists.txt index fd4d8d88dd..d688474379 100644 --- a/tests/render-perf/CMakeLists.txt +++ b/tests/render-perf/CMakeLists.txt @@ -27,6 +27,10 @@ if (WIN32) add_dependency_external_projects(wasapi) endif() +if (CMAKE_SYSTEM_NAME MATCHES "Linux") + target_link_libraries(${TARGET_NAME} atomic) +endif() + package_libraries_for_deployment() diff --git a/tests/render-perf/src/main.cpp b/tests/render-perf/src/main.cpp index 9249b3d957..8c17d7c5c2 100644 --- a/tests/render-perf/src/main.cpp +++ b/tests/render-perf/src/main.cpp @@ -441,6 +441,16 @@ protected: viewOut = _viewFrustum; } + void copyViewFrustum(ViewFrustum& viewOut) const override { + viewOut = _viewFrustum; + } + + void copySecondaryViewFrustum(ViewFrustum& viewOut) const override {} + + bool hasSecondaryViewFrustum() const override { + return false; + } + QThread* getMainThread() override { return QThread::currentThread(); }