diff --git a/INSTALL.md b/INSTALL.md index 90e8712b19..00be5f2f8f 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -21,6 +21,7 @@ To produce an executable installer on Windows, the following are required: - [NSISpcre Plug-in for Nullsoft](http://nsis.sourceforge.net/NSISpcre_plug-in) - 1.0 - [nsisSlideshow Plug-in for Nullsoft](http://nsis.sourceforge.net/NsisSlideshow_plug-in) - 1.7 - [Nsisunz plug-in for Nullsoft](http://nsis.sourceforge.net/Nsisunz_plug-in) +- [ApplicationID plug-in for Nullsoft](http://nsis.sourceforge.net/ApplicationID_plug-in) - 1.0 Run the `package` target to create an executable installer using the Nullsoft Scriptable Install System. diff --git a/android/app/build.gradle b/android/app/build.gradle index 46de9642d9..f780abdea0 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -4,12 +4,15 @@ android { compileSdkVersion 26 //buildToolsVersion '27.0.3' + def appVersionCode = Integer.valueOf(RELEASE_NUMBER ?: 1) + def appVersionName = RELEASE_NUMBER ?: "1.0" + defaultConfig { applicationId "io.highfidelity.hifiinterface" minSdkVersion 24 targetSdkVersion 26 - versionCode 1 - versionName "1.0" + versionCode appVersionCode + versionName appVersionName ndk { abiFilters 'arm64-v8a' } externalNativeBuild { cmake { @@ -22,7 +25,7 @@ android { '-DHIFI_ANDROID_PRECOMPILED=' + HIFI_ANDROID_PRECOMPILED, '-DRELEASE_NUMBER=' + RELEASE_NUMBER, '-DRELEASE_TYPE=' + RELEASE_TYPE, - '-DBUILD_BRANCH=' + BUILD_BRANCH, + '-DSTABLE_BUILD=' + STABLE_BUILD, '-DDISABLE_QML=OFF', '-DDISABLE_KTX_CACHE=OFF' } @@ -128,4 +131,3 @@ dependencies { implementation fileTree(include: ['*.jar'], dir: 'libs') } - diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/InterfaceActivity.java b/android/app/src/main/java/io/highfidelity/hifiinterface/InterfaceActivity.java index 2165339918..28acc77609 100644 --- a/android/app/src/main/java/io/highfidelity/hifiinterface/InterfaceActivity.java +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/InterfaceActivity.java @@ -14,12 +14,16 @@ package io.highfidelity.hifiinterface; import android.content.Intent; import android.content.res.AssetManager; import android.net.Uri; +import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.Vibrator; import android.view.HapticFeedbackConstants; import android.view.WindowManager; import android.util.Log; + +import org.qtproject.qt5.android.QtLayout; +import org.qtproject.qt5.android.QtSurface; import org.qtproject.qt5.android.bindings.QtActivity; /*import com.google.vr.cardboard.DisplaySynchronizer; @@ -31,6 +35,9 @@ import android.content.pm.ActivityInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.view.View; +import android.widget.FrameLayout; + +import java.lang.reflect.Field; public class InterfaceActivity extends QtActivity { @@ -134,6 +141,7 @@ public class InterfaceActivity extends QtActivity { protected void onResume() { super.onResume(); nativeEnterForeground(); + surfacesWorkaround(); //gvrApi.resumeTracking(); } @@ -158,6 +166,41 @@ public class InterfaceActivity extends QtActivity { Log.w("[VR]", "Portrait detected but not in VR mode. Should not happen"); } } + surfacesWorkaround(); + } + + private void surfacesWorkaround() { + FrameLayout fl = findViewById(android.R.id.content); + if (fl.getChildCount() > 0) { + QtLayout qtLayout = (QtLayout) fl.getChildAt(0); + if (qtLayout.getChildCount() > 1) { + QtSurface s1 = (QtSurface) qtLayout.getChildAt(0); + QtSurface s2 = (QtSurface) qtLayout.getChildAt(1); + Integer subLayer1 = 0; + Integer subLayer2 = 0; + try { + String field; + if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + field = "mSubLayer"; + } else { + field = "mWindowType"; + } + Field f = s1.getClass().getSuperclass().getDeclaredField(field); + f.setAccessible(true); + subLayer1 = (Integer) f.get(s1); + subLayer2 = (Integer) f.get(s2); + if (subLayer1 < subLayer2) { + s1.setVisibility(View.VISIBLE); + s2.setVisibility(View.INVISIBLE); + } else { + s1.setVisibility(View.INVISIBLE); + s2.setVisibility(View.VISIBLE); + } + } catch (ReflectiveOperationException e) { + Log.e(TAG, "Workaround failed"); + } + } + } } public void openUrlInAndroidWebView(String urlString) { diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/HomeFragment.java b/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/HomeFragment.java index e05b25f3c3..b98849d051 100644 --- a/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/HomeFragment.java +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/HomeFragment.java @@ -64,7 +64,11 @@ public class HomeFragment extends Fragment { mDomainsView.setLayoutManager(gridLayoutMgr); mDomainAdapter = new DomainAdapter(getContext(), HifiUtils.getInstance().protocolVersionSignature(), nativeGetLastLocation()); mDomainAdapter.setClickListener((view, position, domain) -> { - new Handler(getActivity().getMainLooper()).postDelayed(() -> mListener.onSelectedDomain(domain.url), 400); // a delay so the ripple effect can be seen + new Handler(getActivity().getMainLooper()).postDelayed(() -> { + if (mListener != null) { + mListener.onSelectedDomain(domain.url); + } + }, 400); // a delay so the ripple effect can be seen }); mDomainAdapter.setListener(new DomainAdapter.AdapterListener() { @Override @@ -116,7 +120,9 @@ public class HomeFragment extends Fragment { if (!urlString.trim().isEmpty()) { urlString = HifiUtils.getInstance().sanitizeHifiUrl(urlString); } - mListener.onSelectedDomain(urlString); + if (mListener != null) { + mListener.onSelectedDomain(urlString); + } return true; } return false; diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/provider/UserStoryDomainProvider.java b/android/app/src/main/java/io/highfidelity/hifiinterface/provider/UserStoryDomainProvider.java index ea03864695..ca5e0c17bd 100644 --- a/android/app/src/main/java/io/highfidelity/hifiinterface/provider/UserStoryDomainProvider.java +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/provider/UserStoryDomainProvider.java @@ -1,10 +1,11 @@ package io.highfidelity.hifiinterface.provider; import android.util.Log; -import android.util.MutableInt; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Set; import io.highfidelity.hifiinterface.HifiUtils; import io.highfidelity.hifiinterface.view.DomainAdapter; @@ -47,24 +48,42 @@ public class UserStoryDomainProvider implements DomainProvider { suggestions = new ArrayList<>(); } + @Override + public synchronized void retrieve(String filterText, DomainCallback domainCallback) { + if (!startedToGetFromAPI) { + startedToGetFromAPI = true; + fillDestinations(filterText, domainCallback); + } else { + filterChoicesByText(filterText, domainCallback); + } + } + private void fillDestinations(String filterText, DomainCallback domainCallback) { StoriesFilter filter = new StoriesFilter(filterText); - final MutableInt counter = new MutableInt(0); - allStories.clear(); - getUserStoryPage(1, + + List taggedStories = new ArrayList<>(); + Set taggedStoriesIds = new HashSet<>(); + getUserStoryPage(1, taggedStories, TAGS_TO_SEARCH, e -> { - allStories.subList(counter.value, allStories.size()).forEach(userStory -> { - filter.filterOrAdd(userStory); - }); - if (domainCallback != null) { - domainCallback.retrieveOk(suggestions); //ended - } - }, - a -> { - allStories.forEach(userStory -> { - counter.value++; - filter.filterOrAdd(userStory); + taggedStories.forEach(userStory -> { + taggedStoriesIds.add(userStory.id); }); + + allStories.clear(); + getUserStoryPage(1, allStories, null, + ex -> { + allStories.forEach(userStory -> { + if (taggedStoriesIds.contains(userStory.id)) { + userStory.tagFound = true; + } + filter.filterOrAdd(userStory); + }); + if (domainCallback != null) { + domainCallback.retrieveOk(suggestions); //ended + } + } + ); + } ); } @@ -73,25 +92,22 @@ public class UserStoryDomainProvider implements DomainProvider { restOfPagesCallback.callback(new Exception("Error accessing url [" + url + "]", t)); } - private void getUserStoryPage(int pageNumber, Callback restOfPagesCallback, Callback firstPageCallback) { + private void getUserStoryPage(int pageNumber, List userStoriesList, String tagsFilter, Callback restOfPagesCallback) { Call userStories = mUserStoryDomainProviderService.getUserStories( INCLUDE_ACTIONS_FOR_PLACES, "open", true, mProtocol, - TAGS_TO_SEARCH, + tagsFilter, pageNumber); Log.d("API-USER-STORY-DOMAINS", "Protocol [" + mProtocol + "] include_actions [" + INCLUDE_ACTIONS_FOR_PLACES + "]"); userStories.enqueue(new retrofit2.Callback() { @Override public void onResponse(Call call, Response response) { UserStories data = response.body(); - allStories.addAll(data.user_stories); + userStoriesList.addAll(data.user_stories); if (data.current_page < data.total_pages && data.current_page <= MAX_PAGES_TO_GET) { - if (pageNumber == 1 && firstPageCallback != null) { - firstPageCallback.callback(null); - } - getUserStoryPage(pageNumber + 1, restOfPagesCallback, null); + getUserStoryPage(pageNumber + 1, userStoriesList, tagsFilter, restOfPagesCallback); return; } restOfPagesCallback.callback(null); @@ -107,12 +123,16 @@ public class UserStoryDomainProvider implements DomainProvider { private class StoriesFilter { String[] mWords = new String[]{}; public StoriesFilter(String filterText) { - mWords = filterText.toUpperCase().split("\\s+"); + mWords = filterText.trim().toUpperCase().split("\\s+"); + if (mWords.length == 1 && (mWords[0] == null || mWords[0].length() <= 0 ) ) { + mWords = null; + } } private boolean matches(UserStory story) { - if (mWords.length <= 0) { - return true; + if (mWords == null || mWords.length <= 0) { + // No text filter? So filter by tag + return story.tagFound; } for (String word : mWords) { @@ -128,6 +148,9 @@ public class UserStoryDomainProvider implements DomainProvider { suggestions.add(story.toDomain()); } + /** + * if the story matches this filter criteria it's added into suggestions + * */ public void filterOrAdd(UserStory story) { if (matches(story)) { addToSuggestions(story); @@ -144,16 +167,6 @@ public class UserStoryDomainProvider implements DomainProvider { domainCallback.retrieveOk(suggestions); } - @Override - public synchronized void retrieve(String filterText, DomainCallback domainCallback) { - if (!startedToGetFromAPI) { - startedToGetFromAPI = true; - fillDestinations(filterText, domainCallback); - } else { - filterChoicesByText(filterText, domainCallback); - } - } - public interface UserStoryDomainProviderService { @GET("api/v1/user_stories") Call getUserStories(@Query("include_actions") String includeActions, @@ -166,12 +179,14 @@ public class UserStoryDomainProvider implements DomainProvider { class UserStory { public UserStory() {} + String id; String place_name; String path; String thumbnail_url; String place_id; String domain_id; private String searchText; + private boolean tagFound; // Locally used // New fields? tags, description 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 index 97558d2681..4f8b33b481 100644 --- a/android/app/src/main/java/io/highfidelity/hifiinterface/view/DomainAdapter.java +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/view/DomainAdapter.java @@ -54,27 +54,10 @@ public class DomainAdapter extends RecyclerView.Adapter domain) { if (filterText.length() == 0) { - Domain lastVisitedDomain = new Domain(mContext.getString(R.string.your_last_location), mLastLocation, DEFAULT_THUMBNAIL_PLACE); - if (!mLastLocation.isEmpty() && mLastLocation.contains("://")) { - int startIndex = mLastLocation.indexOf("://"); - int endIndex = mLastLocation.indexOf("/", startIndex + 3); - String toSearch = mLastLocation.substring(0, endIndex + 1).toLowerCase(); - for (Domain d : domain) { - if (d.url.toLowerCase().startsWith(toSearch)) { - lastVisitedDomain.thumbnail = d.thumbnail; - } - } - } - domain.add(0, lastVisitedDomain); + addLastLocation(domain); } - for (Domain d : domain) { - // we override the default picture added in the server by an android specific version - if (d.thumbnail != null && - d.thumbnail.endsWith("assets/places/thumbnail-default-place-e5a3f33e773ab699495774990a562f9f7693dc48ef90d8be6985c645a0280f75.png")) { - d.thumbnail = DEFAULT_THUMBNAIL_PLACE; - } - } + overrideDefaultThumbnails(domain); mDomains = new Domain[domain.size()]; mDomains = domain.toArray(mDomains); @@ -96,6 +79,31 @@ public class DomainAdapter extends RecyclerView.Adapter domain) { + for (Domain d : domain) { + // we override the default picture added in the server by an android specific version + if (d.thumbnail != null && + d.thumbnail.endsWith("assets/places/thumbnail-default-place-e5a3f33e773ab699495774990a562f9f7693dc48ef90d8be6985c645a0280f75.png")) { + d.thumbnail = DEFAULT_THUMBNAIL_PLACE; + } + } + } + + private void addLastLocation(List domain) { + Domain lastVisitedDomain = new Domain(mContext.getString(R.string.your_last_location), mLastLocation, DEFAULT_THUMBNAIL_PLACE); + if (!mLastLocation.isEmpty() && mLastLocation.contains("://")) { + int startIndex = mLastLocation.indexOf("://"); + int endIndex = mLastLocation.indexOf("/", startIndex + 3); + String toSearch = mLastLocation.substring(0, endIndex + 1).toLowerCase(); + for (Domain d : domain) { + if (d.url.toLowerCase().startsWith(toSearch)) { + lastVisitedDomain.thumbnail = d.thumbnail; + } + } + } + domain.add(0, lastVisitedDomain); + } + @Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View view = mInflater.inflate(R.layout.domain_view, parent, false); diff --git a/android/build.gradle b/android/build.gradle index 3719e548bc..f1fe4ffc7f 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -37,7 +37,7 @@ task clean(type: Delete) { ext { RELEASE_NUMBER = project.hasProperty('RELEASE_NUMBER') ? project.getProperty('RELEASE_NUMBER') : '0' RELEASE_TYPE = project.hasProperty('RELEASE_TYPE') ? project.getProperty('RELEASE_TYPE') : 'DEV' - BUILD_BRANCH = project.hasProperty('BUILD_BRANCH') ? project.getProperty('BUILD_BRANCH') : '' + STABLE_BUILD = project.hasProperty('STABLE_BUILD') ? project.getProperty('STABLE_BUILD') : '0' EXEC_SUFFIX = Os.isFamily(Os.FAMILY_WINDOWS) ? '.exe' : '' QT5_DEPS = [ 'Qt5Concurrent', @@ -66,19 +66,19 @@ ext { def baseFolder = new File(HIFI_ANDROID_PRECOMPILED) def appDir = new File(projectDir, 'app') def jniFolder = new File(appDir, 'src/main/jniLibs/arm64-v8a') -def baseUrl = '' +def baseUrl = 'https://hifi-public.s3.amazonaws.com/dependencies/android/' -def qtFile='https://hifi-public.s3.amazonaws.com/austin/android/qt-5.9.3_linux_armv8-libcpp_openssl.tgz' +def qtFile='qt-5.9.3_linux_armv8-libcpp_openssl.tgz' def qtChecksum='04599670ccca84bd2b15f6915568eb2d' -def qtVersionId='PeoqzN31n.YvLfs9JE2SgHgZ4.IaKAlt' +def qtVersionId='8QbCma4ryEPgBYn_8kgYgB10IvNx9I1W' if (Os.isFamily(Os.FAMILY_MAC)) { - qtFile = 'https://hifi-public.s3.amazonaws.com/austin/android/qt-5.9.3_osx_armv8-libcpp_openssl.tgz' + qtFile = 'qt-5.9.3_osx_armv8-libcpp_openssl.tgz' qtChecksum='4b02de9d67d6bfb202355a808d2d9c59' - qtVersionId='HygCmtMLPYioyil0DfXckGVzhw2SXZA9' + qtVersionId='2gfgoYCggJGyXxKiazaPGsMs1Gn9j4og' } else if (Os.isFamily(Os.FAMILY_WINDOWS)) { - qtFile = 'https://hifi-public.s3.amazonaws.com/austin/android/qt-5.9.3_win_armv8-libcpp_openssl.tgz' + qtFile = 'qt-5.9.3_win_armv8-libcpp_openssl.tgz' qtChecksum='c3e25db64002d0f43cf565e0ef708911' - qtVersionId='HeVObSVLCBoc7yY7He1oBMvPIH0VkClT' + qtVersionId='xKIteC6HO0xrmcWeMmhQcmKyPEsnUrcZ' } def packages = [ @@ -88,79 +88,84 @@ def packages = [ checksum: qtChecksum, ], bullet: [ - file: 'https://hifi-public.s3.amazonaws.com/dependencies/android/bullet-2.88_armv8-libcpp.tgz', + file: 'bullet-2.88_armv8-libcpp.tgz', versionId: 'S8YaoED0Cl8sSb8fSV7Q2G1lQJSNDxqg', checksum: '81642779ccb110f8c7338e8739ac38a0', ], draco: [ - file: 'https://hifi-public.s3.amazonaws.com/austin/android/draco_armv8-libcpp.tgz', - versionId: 'cA3tVJSmkvb1naA3l6D_Jv2Noh.4yc4m', + file: 'draco_armv8-libcpp.tgz', + versionId: '3.B.uBj31kWlgND3_R2xwQzT_TP6Dz_8', checksum: '617a80d213a5ec69fbfa21a1f2f738cd', ], glad: [ - file: 'https://hifi-public.s3.amazonaws.com/austin/android/glad_armv8-libcpp.zip', - versionId: 'Q9szthzeye8fFyAA.cY26Lgn2B8kezEE', + file: 'glad_armv8-libcpp.zip', + versionId: 'r5Zran.JSCtvrrB6Q4KaqfIoALPw3lYY', checksum: 'a8ee8584cf1ccd34766c7ddd9d5e5449', ], glm: [ - file: 'https://hifi-public.s3.amazonaws.com/dependencies/android/glm-0.9.8.5-patched.tgz', + file: 'glm-0.9.8.5-patched.tgz', versionId: 'cskfMoJrFlAeqI3WPxemyO_Cxt7rT9EJ', checksum: '067b5fe16b220b5b1a1039ba51b062ae', ], gvr: [ - file: 'https://hifi-public.s3.amazonaws.com/austin/android/gvrsdk_v1.101.0.tgz', - versionId: 'UTberAIFraEfF9IVjoV66u1DTPTopgeY', + file: 'gvrsdk_v1.101.0.tgz', + versionId: 'nqBV_j81Uc31rC7bKIrlya_Hah4v3y5r', checksum: '57fd02baa069176ba18597a29b6b4fc7', ], nvtt: [ - file: 'https://hifi-public.s3.amazonaws.com/austin/android/nvtt_armv8-libcpp.zip', - versionId: 'vLqrqThvpq4gp75BHMAqO6HhfTXaa0An', + file: 'nvtt_armv8-libcpp.zip', + versionId: 'lmkBVR5t4UF1UUwMwEirnk9H_8Nt90IO', checksum: 'eb46d0b683e66987190ed124aabf8910', sharedLibFolder: 'lib', includeLibs: ['libnvtt.so', 'libnvmath.so', 'libnvimage.so', 'libnvcore.so'], ], openssl: [ - file: 'https://hifi-public.s3.amazonaws.com/austin/android/openssl-1.1.0g_armv8.tgz', - versionId: 'DmahmSGFS4ltpHyTdyQvv35WOeUOiib9', + file: 'openssl-1.1.0g_armv8.tgz', + versionId: 'AiiPjmgUZTgNj7YV1EEx2lL47aDvvvAW', checksum: 'cabb681fbccd79594f65fcc266e02f32', ], polyvox: [ - file: 'https://hifi-public.s3.amazonaws.com/austin/android/polyvox_armv8-libcpp.tgz', - versionId: 'LDJtzMTvdm4SAc2KYg8Cg6uwWk4Vq3e3', - checksum: '349ad5b72aaf2749ca95d847e60c5314', + file: 'polyvox_armv8-libcpp.tgz', + versionId: 'A2kbKiNhpIenGq23bKRRzg7IMAI5BI92', + checksum: 'dba88b3a098747af4bb169e9eb9af57e', sharedLibFolder: 'lib', includeLibs: ['Release/libPolyVoxCore.so', 'libPolyVoxUtil.so'], ], tbb: [ - file: 'https://hifi-public.s3.amazonaws.com/austin/android/tbb-2018_U1_armv8_libcpp.tgz', - versionId: 'YZliDD8.Menh1IVXKEuLPeO3xAjJ1UdF', + file: 'tbb-2018_U1_armv8_libcpp.tgz', + versionId: 'mrRbWnv4O4evcM1quRH43RJqimlRtaKB', checksum: '20768f298f53b195e71b414b0ae240c4', sharedLibFolder: 'lib/release', includeLibs: ['libtbb.so', 'libtbbmalloc.so'], ], hifiAC: [ - file: 'https://hifi-public.s3.amazonaws.com/austin/android/libplugins_libhifiCodec.zip', - versionId: 'mzKhsRCgVmloqq5bvE.0IwYK1NjGQc_G', + file: 'libplugins_libhifiCodec.zip', + versionId: 'i31pW.qNbvFOXRxbyiJUxg3sphaFNmZU', checksum: '9412a8e12c88a4096c1fc843bb9fe52d', sharedLibFolder: '', includeLibs: ['libplugins_libhifiCodec.so'] + ], + etc2comp: [ + file: 'etc2comp-patched-armv8-libcpp.tgz', + versionId: 'bHhGECRAQR1vkpshBcK6ByNc1BQIM8gU', + checksum: '14b02795d774457a33bbc60e00a786bc' ] ] def scribeLocalFile='scribe' + EXEC_SUFFIX -def scribeFile='https://hifi-public.s3.amazonaws.com/austin/android/scribe_linux_x86_64' +def scribeFile='scribe_linux_x86_64' def scribeChecksum='ca4b904f52f4f993c29175ba96798fa6' -def scribeVersion='wgpf4dB2Ltzg4Lb2jJ4nPFsHoDkmK_OO' +def scribeVersion='u_iTrJDaE95i2abTPXOpPZckGBIim53G' if (Os.isFamily(Os.FAMILY_MAC)) { - scribeFile = 'https://hifi-public.s3.amazonaws.com/austin/android/scribe_osx_x86_64' + scribeFile = 'scribe_osx_x86_64' scribeChecksum='72db9d32d4e1e50add755570ac5eb749' - scribeVersion='o_NbPrktzEYtBkQf3Tn7zc1nZWzM52w6' + scribeVersion='DAW0DmnjCRib4MD8x93bgc2Z2MpPojZC' } else if (Os.isFamily(Os.FAMILY_WINDOWS)) { - scribeFile = 'https://hifi-public.s3.amazonaws.com/austin/android/scribe_win32_x86_64.exe' + scribeFile = 'scribe_win32_x86_64.exe' scribeChecksum='678e43d290c90fda670c6fefe038a06d' - scribeVersion='GCCJxlmd2irvNOFWfZR0U1UCLHndHQrC' + scribeVersion='PuullrA_bPlO9kXZRt8rLe536X1UI.m7' } def options = [ @@ -361,6 +366,7 @@ task verifyOpenSSL(type: Verify) { def p = packages['openssl']; src new File(bas task verifyPolyvox(type: Verify) { def p = packages['polyvox']; src new File(baseFolder, p['file']); checksum p['checksum'] } task verifyTBB(type: Verify) { def p = packages['tbb']; src new File(baseFolder, p['file']); checksum p['checksum'] } task verifyHifiAC(type: Verify) { def p = packages['hifiAC']; src new File(baseFolder, p['file']); checksum p['checksum'] } +task verifyEtc2Comp(type: Verify) { def p = packages['etc2comp']; src new File(baseFolder, p['file']); checksum p['checksum'] } task verifyDependencyDownloads(dependsOn: downloadDependencies) { } verifyDependencyDownloads.dependsOn verifyQt @@ -371,6 +377,7 @@ verifyDependencyDownloads.dependsOn verifyOpenSSL verifyDependencyDownloads.dependsOn verifyPolyvox verifyDependencyDownloads.dependsOn verifyTBB verifyDependencyDownloads.dependsOn verifyHifiAC +verifyDependencyDownloads.dependsOn verifyEtc2Comp task extractDependencies(dependsOn: verifyDependencyDownloads) { doLast { @@ -535,7 +542,7 @@ task cleanDependencies(type: Delete) { -// FIXME this code is prototyping the desired functionality for doing build time binary dependency resolution. +// FIXME this code is prototyping the desired functionality for doing build time binary dependency resolution. // See the comment on the qtBundle task above /* // FIXME derive the path from the gradle environment @@ -608,4 +615,4 @@ task testElf (dependsOn: 'externalNativeBuildDebug') { } } } -*/ +*/ \ No newline at end of file diff --git a/assignment-client/src/assets/AssetServer.cpp b/assignment-client/src/assets/AssetServer.cpp index 22ed01fd00..e0c35b7148 100644 --- a/assignment-client/src/assets/AssetServer.cpp +++ b/assignment-client/src/assets/AssetServer.cpp @@ -291,18 +291,6 @@ AssetServer::AssetServer(ReceivedMessage& message) : _bakingTaskPool(this), _filesizeLimit(AssetUtils::MAX_UPLOAD_SIZE) { - // store the current state of image compression so we can reset it when this assignment is complete - _wasColorTextureCompressionEnabled = image::isColorTexturesCompressionEnabled(); - _wasGrayscaleTextureCompressionEnabled = image::isGrayscaleTexturesCompressionEnabled(); - _wasNormalTextureCompressionEnabled = image::isNormalTexturesCompressionEnabled(); - _wasCubeTextureCompressionEnabled = image::isCubeTexturesCompressionEnabled(); - - // enable compression in image library - image::setColorTexturesCompressionEnabled(true); - image::setGrayscaleTexturesCompressionEnabled(true); - image::setNormalTexturesCompressionEnabled(true); - image::setCubeTexturesCompressionEnabled(true); - BAKEABLE_TEXTURE_EXTENSIONS = image::getSupportedFormats(); qDebug() << "Supported baking texture formats:" << BAKEABLE_MODEL_EXTENSIONS; @@ -354,12 +342,6 @@ void AssetServer::aboutToFinish() { while (_pendingBakes.size() > 0) { QCoreApplication::processEvents(); } - - // re-set defaults in image library - image::setColorTexturesCompressionEnabled(_wasCubeTextureCompressionEnabled); - image::setGrayscaleTexturesCompressionEnabled(_wasGrayscaleTextureCompressionEnabled); - image::setNormalTexturesCompressionEnabled(_wasNormalTextureCompressionEnabled); - image::setCubeTexturesCompressionEnabled(_wasCubeTextureCompressionEnabled); } void AssetServer::run() { diff --git a/assignment-client/src/assets/AssetServer.h b/assignment-client/src/assets/AssetServer.h index 96a220d64d..b3d0f18a8f 100644 --- a/assignment-client/src/assets/AssetServer.h +++ b/assignment-client/src/assets/AssetServer.h @@ -167,11 +167,6 @@ private: using RequestQueue = QVector, SharedNodePointer>>; RequestQueue _queuedRequests; - bool _wasColorTextureCompressionEnabled { false }; - bool _wasGrayscaleTextureCompressionEnabled { false }; - bool _wasNormalTextureCompressionEnabled { false }; - bool _wasCubeTextureCompressionEnabled { false }; - uint64_t _filesizeLimit; }; diff --git a/assignment-client/src/audio/AudioMixerClientData.cpp b/assignment-client/src/audio/AudioMixerClientData.cpp index d4d4f847ee..bc08c6f24a 100644 --- a/assignment-client/src/audio/AudioMixerClientData.cpp +++ b/assignment-client/src/audio/AudioMixerClientData.cpp @@ -624,8 +624,8 @@ AudioMixerClientData::IgnoreZone& AudioMixerClientData::IgnoreZoneMemo::get(unsi scale = MIN_IGNORE_BOX_SCALE; } - // quadruple the scale (this is arbitrary number chosen for comfort) - const float IGNORE_BOX_SCALE_FACTOR = 4.0f; + // (this is arbitrary number determined empirically for comfort) + const float IGNORE_BOX_SCALE_FACTOR = 2.4f; scale *= IGNORE_BOX_SCALE_FACTOR; // create the box (we use a box for the zone for convenience) diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp index 984884adb2..563aac879f 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp @@ -271,8 +271,9 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) if (glm::any(glm::lessThan(otherNodeBoxScale, minBubbleSize))) { otherNodeBox.setScaleStayCentered(minBubbleSize); } - // Quadruple the scale of both bounding boxes - otherNodeBox.embiggen(4.0f); + // Change the scale of both bounding boxes + // (This is an arbitrary number determined empirically) + otherNodeBox.embiggen(2.4f); // Perform the collision check between the two bounding boxes if (nodeBox.touches(otherNodeBox)) { @@ -395,21 +396,26 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) quint64 end = usecTimestampNow(); _stats.toByteArrayElapsedTime += (end - start); - static const int MAX_ALLOWED_AVATAR_DATA = (1400 - NUM_BYTES_RFC4122_UUID); - if (bytes.size() > MAX_ALLOWED_AVATAR_DATA) { - qCWarning(avatars) << "otherAvatar.toByteArray() resulted in very large buffer:" << bytes.size() << "... attempt to drop facial data"; + auto maxAvatarDataBytes = avatarPacketList->getMaxSegmentSize() - NUM_BYTES_RFC4122_UUID; + if (bytes.size() > maxAvatarDataBytes) { + qCWarning(avatars) << "otherAvatar.toByteArray() for" << otherNode->getUUID() + << "resulted in very large buffer of" << bytes.size() << "bytes - dropping facial data"; dropFaceTracking = true; // first try dropping the facial data bytes = otherAvatar->toByteArray(detail, lastEncodeForOther, lastSentJointsForOther, hasFlagsOut, dropFaceTracking, distanceAdjust, viewerPosition, &lastSentJointsForOther); - if (bytes.size() > MAX_ALLOWED_AVATAR_DATA) { - qCWarning(avatars) << "otherAvatar.toByteArray() without facial data resulted in very large buffer:" << bytes.size() << "... reduce to MinimumData"; + if (bytes.size() > maxAvatarDataBytes) { + qCWarning(avatars) << "otherAvatar.toByteArray() for" << otherNode->getUUID() + << "without facial data resulted in very large buffer of" << bytes.size() + << "bytes - reducing to MinimumData"; bytes = otherAvatar->toByteArray(AvatarData::MinimumData, lastEncodeForOther, lastSentJointsForOther, hasFlagsOut, dropFaceTracking, distanceAdjust, viewerPosition, &lastSentJointsForOther); - if (bytes.size() > MAX_ALLOWED_AVATAR_DATA) { - qCWarning(avatars) << "otherAvatar.toByteArray() MinimumData resulted in very large buffer:" << bytes.size() << "... FAIL!!"; + if (bytes.size() > maxAvatarDataBytes) { + qCWarning(avatars) << "otherAvatar.toByteArray() for" << otherNode->getUUID() + << "MinimumData resulted in very large buffer of" << bytes.size() + << "bytes - refusing to send avatar"; includeThisAvatar = false; } } diff --git a/assignment-client/src/octree/OctreeServer.cpp b/assignment-client/src/octree/OctreeServer.cpp index fad2c1f026..e993bea358 100644 --- a/assignment-client/src/octree/OctreeServer.cpp +++ b/assignment-client/src/octree/OctreeServer.cpp @@ -231,19 +231,19 @@ void OctreeServer::trackProcessWaitTime(float time) { OctreeServer::OctreeServer(ReceivedMessage& message) : ThreadedAssignment(message), _argc(0), - _argv(NULL), - _parsedArgV(NULL), - _httpManager(NULL), + _argv(nullptr), + _parsedArgV(nullptr), + _httpManager(nullptr), _statusPort(0), _packetsPerClientPerInterval(10), _packetsTotalPerInterval(DEFAULT_PACKETS_PER_INTERVAL), - _tree(NULL), + _tree(nullptr), _wantPersist(true), _debugSending(false), _debugReceiving(false), _verboseDebug(false), - _octreeInboundPacketProcessor(NULL), - _persistThread(NULL), + _octreeInboundPacketProcessor(nullptr), + _persistManager(nullptr), _started(time(0)), _startedUSecs(usecTimestampNow()) { @@ -266,11 +266,8 @@ OctreeServer::~OctreeServer() { _octreeInboundPacketProcessor->deleteLater(); } - if (_persistThread) { - _persistThread->terminating(); - _persistThread->terminate(); - _persistThread->deleteLater(); - } + qDebug() << "Waiting for persist thread to come down"; + _persistThread.wait(); // cleanup our tree here... qDebug() << qPrintable(_safeServerName) << "server START cleaning up octree... [" << this << "]"; @@ -285,7 +282,7 @@ void OctreeServer::initHTTPManager(int port) { QString documentRoot = QString("%1/web").arg(PathUtils::getAppDataPath()); // setup an httpManager with us as the request handler and the parent - _httpManager = new HTTPManager(QHostAddress::AnyIPv4, port, documentRoot, this, this); + _httpManager.reset(new HTTPManager(QHostAddress::AnyIPv4, port, documentRoot, this)); } bool OctreeServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url, bool skipSubHandler) { @@ -1053,19 +1050,13 @@ void OctreeServer::readConfiguration() { _persistAsFileType = "json.gz"; _persistInterval = OctreePersistThread::DEFAULT_PERSIST_INTERVAL; - readOptionInt(QString("persistInterval"), settingsSectionObject, _persistInterval); - qDebug() << "persistInterval=" << _persistInterval; - - bool noBackup; - readOptionBool(QString("NoBackup"), settingsSectionObject, noBackup); - _wantBackup = !noBackup; - qDebug() << "wantBackup=" << _wantBackup; - - if (!readOptionString("backupDirectoryPath", settingsSectionObject, _backupDirectoryPath)) { - _backupDirectoryPath = ""; + int result { -1 }; + readOptionInt(QString("persistInterval"), settingsSectionObject, result); + if (result != -1) { + _persistInterval = std::chrono::milliseconds(result); } - qDebug() << "backupDirectoryPath=" << _backupDirectoryPath; + qDebug() << "persistInterval=" << _persistInterval.count(); readOptionBool(QString("persistFileDownload"), settingsSectionObject, _persistFileDownload); qDebug() << "persistFileDownload=" << _persistFileDownload; @@ -1129,111 +1120,14 @@ void OctreeServer::run() { } void OctreeServer::domainSettingsRequestComplete() { - if (_state != OctreeServerState::WaitingForDomainSettings) { - qCWarning(octree_server) << "Received domain settings after they have already been received"; - return; - } - auto& packetReceiver = DependencyManager::get()->getPacketReceiver(); - packetReceiver.registerListener(getMyQueryMessageType(), this, "handleOctreeQueryPacket"); packetReceiver.registerListener(PacketType::OctreeDataNack, this, "handleOctreeDataNackPacket"); - - packetReceiver.registerListener(PacketType::OctreeDataFileReply, this, "handleOctreeDataFileReply"); + packetReceiver.registerListener(getMyQueryMessageType(), this, "handleOctreeQueryPacket"); qDebug(octree_server) << "Received domain settings"; readConfiguration(); - _state = OctreeServerState::WaitingForOctreeDataNegotation; - - auto nodeList = DependencyManager::get(); - const DomainHandler& domainHandler = nodeList->getDomainHandler(); - - auto packet = NLPacket::create(PacketType::OctreeDataFileRequest, -1, true, false); - - OctreeUtils::RawOctreeData data; - qCDebug(octree_server) << "Reading octree data from" << _persistAbsoluteFilePath; - if (data.readOctreeDataInfoFromFile(_persistAbsoluteFilePath)) { - qCDebug(octree_server) << "Current octree data: ID(" << data.id << ") DataVersion(" << data.version << ")"; - packet->writePrimitive(true); - auto id = data.id.toRfc4122(); - packet->write(id); - packet->writePrimitive(data.version); - } else { - qCWarning(octree_server) << "No octree data found"; - packet->writePrimitive(false); - } - - qCDebug(octree_server) << "Sending request for octree data to DS"; - nodeList->sendPacket(std::move(packet), domainHandler.getSockAddr()); -} - -void OctreeServer::handleOctreeDataFileReply(QSharedPointer message) { - if (_state != OctreeServerState::WaitingForOctreeDataNegotation) { - qCWarning(octree_server) << "Server received ocree data file reply but is not currently negotiating."; - return; - } - - bool includesNewData; - message->readPrimitive(&includesNewData); - QByteArray replaceData; - if (includesNewData) { - replaceData = message->readAll(); - qDebug() << "Got reply to octree data file request, new data sent"; - } else { - qDebug() << "Got reply to octree data file request, current entity data is sufficient"; - - OctreeUtils::RawEntityData data; - qCDebug(octree_server) << "Reading octree data from" << _persistAbsoluteFilePath; - if (data.readOctreeDataInfoFromFile(_persistAbsoluteFilePath)) { - if (data.id.isNull()) { - qCDebug(octree_server) << "Current octree data has a null id, updating"; - data.resetIdAndVersion(); - - QFile file(_persistAbsoluteFilePath); - if (file.open(QIODevice::WriteOnly)) { - auto entityData = data.toGzippedByteArray(); - file.write(entityData); - file.close(); - } else { - qCDebug(octree_server) << "Failed to update octree data"; - } - } - } - } - - _state = OctreeServerState::Running; - beginRunning(replaceData); -} - -void OctreeServer::beginRunning(QByteArray replaceData) { - if (_state != OctreeServerState::Running) { - qCWarning(octree_server) << "Server is not running"; - return; - } - - auto nodeList = DependencyManager::get(); - - // we need to ask the DS about agents so we can ping/reply with them - nodeList->addSetOfNodeTypesToNodeInterestSet({ NodeType::Agent, NodeType::EntityScriptServer }); - - beforeRun(); // after payload has been processed - - connect(nodeList.data(), SIGNAL(nodeAdded(SharedNodePointer)), SLOT(nodeAdded(SharedNodePointer))); - connect(nodeList.data(), SIGNAL(nodeKilled(SharedNodePointer)), SLOT(nodeKilled(SharedNodePointer))); - -#ifndef WIN32 - setvbuf(stdout, NULL, _IOLBF, 0); -#endif - - nodeList->linkedDataCreateCallback = [this](Node* node) { - auto queryNodeData = createOctreeQueryNode(); - queryNodeData->init(); - node->setLinkedData(std::move(queryNodeData)); - }; - - srand((unsigned)time(0)); - // if we want Persistence, set up the local file and persist thread if (_wantPersist) { static const QString ENTITY_PERSIST_EXTENSION = ".json.gz"; @@ -1289,40 +1183,40 @@ void OctreeServer::beginRunning(QByteArray replaceData) { } auto persistFileDirectory = QFileInfo(_persistAbsoluteFilePath).absolutePath(); - if (_backupDirectoryPath.isEmpty()) { - // Use the persist file's directory to store backups - _backupDirectoryPath = persistFileDirectory; - } else { - // The backup directory has been set. - // If relative, make it relative to the entities directory in the application data directory - // If absolute, no resolution is necessary - QDir backupDirectory { _backupDirectoryPath }; - QString absoluteBackupDirectory; - if (backupDirectory.isRelative()) { - absoluteBackupDirectory = QDir(PathUtils::getAppDataFilePath("entities/")).absoluteFilePath(_backupDirectoryPath); - absoluteBackupDirectory = QDir(absoluteBackupDirectory).absolutePath(); - } else { - absoluteBackupDirectory = backupDirectory.absolutePath(); - } - backupDirectory = QDir(absoluteBackupDirectory); - if (!backupDirectory.exists()) { - if (backupDirectory.mkpath(".")) { - qDebug() << "Created backup directory"; - } else { - qDebug() << "ERROR creating backup directory, using persist file directory"; - _backupDirectoryPath = persistFileDirectory; - } - } else { - _backupDirectoryPath = absoluteBackupDirectory; - } - } - qDebug() << "Backups will be stored in: " << _backupDirectoryPath; // now set up PersistThread - _persistThread = new OctreePersistThread(_tree, _persistAbsoluteFilePath, _backupDirectoryPath, _persistInterval, - _wantBackup, _settings, _debugTimestampNow, _persistAsFileType, replaceData); - _persistThread->initialize(true); + _persistManager = new OctreePersistThread(_tree, _persistAbsoluteFilePath, _persistInterval, _debugTimestampNow, + _persistAsFileType); + _persistManager->moveToThread(&_persistThread); + connect(&_persistThread, &QThread::finished, _persistManager, &QObject::deleteLater); + connect(&_persistThread, &QThread::started, _persistManager, &OctreePersistThread::start); + connect(_persistManager, &OctreePersistThread::loadCompleted, this, [this]() { + beginRunning(); + }); + _persistThread.start(); + } else { + beginRunning(); } +} + +void OctreeServer::beginRunning() { + auto nodeList = DependencyManager::get(); + + // we need to ask the DS about agents so we can ping/reply with them + nodeList->addSetOfNodeTypesToNodeInterestSet({ NodeType::Agent, NodeType::EntityScriptServer }); + + beforeRun(); // after payload has been processed + + connect(nodeList.data(), &NodeList::nodeAdded, this, &OctreeServer::nodeAdded); + connect(nodeList.data(), &NodeList::nodeKilled, this, &OctreeServer::nodeKilled); + + nodeList->linkedDataCreateCallback = [this](Node* node) { + auto queryNodeData = createOctreeQueryNode(); + queryNodeData->init(); + node->setLinkedData(std::move(queryNodeData)); + }; + + srand((unsigned)time(0)); // set up our OctreeServerPacketProcessor _octreeInboundPacketProcessor = new OctreeInboundPacketProcessor(this); @@ -1385,7 +1279,7 @@ void OctreeServer::aboutToFinish() { qDebug() << qPrintable(_safeServerName) << "inform Octree Inbound Packet Processor that we are shutting down..."; - // we're going down - set the NodeList linkedDataCallback to NULL so we do not create any more OctreeQueryNode objects. + // we're going down - set the NodeList linkedDataCallback to nullptr so we do not create any more OctreeQueryNode objects. // This ensures that we don't get any more newly connecting nodes DependencyManager::get()->linkedDataCreateCallback = nullptr; @@ -1403,9 +1297,8 @@ void OctreeServer::aboutToFinish() { // which waits on the thread to be done before returning _sendThreads.clear(); // Cleans up all the send threads. - if (_persistThread) { - _persistThread->aboutToFinish(); - _persistThread->terminating(); + if (_persistManager) { + _persistThread.quit(); } qDebug() << qPrintable(_safeServerName) << "server ENDING about to finish..."; diff --git a/assignment-client/src/octree/OctreeServer.h b/assignment-client/src/octree/OctreeServer.h index b25e537d70..07b1e334b1 100644 --- a/assignment-client/src/octree/OctreeServer.h +++ b/assignment-client/src/octree/OctreeServer.h @@ -33,12 +33,6 @@ Q_DECLARE_LOGGING_CATEGORY(octree_server) const int DEFAULT_PACKETS_PER_INTERVAL = 2000; // some 120,000 packets per second total -enum class OctreeServerState { - WaitingForDomainSettings, - WaitingForOctreeDataNegotation, - Running -}; - /// Handles assignments of type OctreeServer - sending octrees to various clients. class OctreeServer : public ThreadedAssignment, public HTTPRequestHandler { Q_OBJECT @@ -46,8 +40,6 @@ public: OctreeServer(ReceivedMessage& message); ~OctreeServer(); - OctreeServerState _state { OctreeServerState::WaitingForDomainSettings }; - /// allows setting of run arguments void setArguments(int argc, char** argv); @@ -68,12 +60,12 @@ public: static void clientConnected() { _clientCount++; } static void clientDisconnected() { _clientCount--; } - bool isInitialLoadComplete() const { return (_persistThread) ? _persistThread->isInitialLoadComplete() : true; } - bool isPersistEnabled() const { return (_persistThread) ? true : false; } - quint64 getLoadElapsedTime() const { return (_persistThread) ? _persistThread->getLoadElapsedTime() : 0; } - QString getPersistFilename() const { return (_persistThread) ? _persistThread->getPersistFilename() : ""; } - QString getPersistFileMimeType() const { return (_persistThread) ? _persistThread->getPersistFileMimeType() : "text/plain"; } - QByteArray getPersistFileContents() const { return (_persistThread) ? _persistThread->getPersistFileContents() : QByteArray(); } + bool isInitialLoadComplete() const { return (_persistManager) ? _persistManager->isInitialLoadComplete() : true; } + bool isPersistEnabled() const { return (_persistManager) ? true : false; } + quint64 getLoadElapsedTime() const { return (_persistManager) ? _persistManager->getLoadElapsedTime() : 0; } + QString getPersistFilename() const { return (_persistManager) ? _persistManager->getPersistFilename() : ""; } + QString getPersistFileMimeType() const { return (_persistManager) ? _persistManager->getPersistFileMimeType() : "text/plain"; } + QByteArray getPersistFileContents() const { return (_persistManager) ? _persistManager->getPersistFileContents() : QByteArray(); } // Subclasses must implement these methods virtual std::unique_ptr createOctreeQueryNode() = 0; @@ -149,7 +141,6 @@ private slots: void domainSettingsRequestComplete(); void handleOctreeQueryPacket(QSharedPointer message, SharedNodePointer senderNode); void handleOctreeDataNackPacket(QSharedPointer message, SharedNodePointer senderNode); - void handleOctreeDataFileReply(QSharedPointer message); void removeSendThread(); protected: @@ -171,7 +162,7 @@ protected: QString getConfiguration(); QString getStatusLink(); - void beginRunning(QByteArray replaceData); + void beginRunning(); UniqueSendThread createSendThread(const SharedNodePointer& node); virtual UniqueSendThread newSendThread(const SharedNodePointer& node) = 0; @@ -183,14 +174,13 @@ protected: bool _isShuttingDown = false; - HTTPManager* _httpManager; + std::unique_ptr _httpManager; int _statusPort; QString _statusHost; QString _persistFilePath; QString _persistAbsoluteFilePath; QString _persistAsFileType; - QString _backupDirectoryPath; int _packetsPerClientPerInterval; int _packetsTotalPerInterval; OctreePointer _tree; // this IS a reaveraging tree @@ -200,13 +190,11 @@ protected: bool _debugTimestampNow; bool _verboseDebug; OctreeInboundPacketProcessor* _octreeInboundPacketProcessor; - OctreePersistThread* _persistThread; + OctreePersistThread* _persistManager; + QThread _persistThread; - int _persistInterval; - bool _wantBackup; + std::chrono::milliseconds _persistInterval; bool _persistFileDownload; - QString _backupExtensionFormat; - int _backupInterval; int _maxBackupVersions; time_t _started; diff --git a/cmake/externals/LibOVR/CMakeLists.txt b/cmake/externals/LibOVR/CMakeLists.txt index 97508be0c5..ed76f181e7 100644 --- a/cmake/externals/LibOVR/CMakeLists.txt +++ b/cmake/externals/LibOVR/CMakeLists.txt @@ -17,8 +17,8 @@ if (WIN32) ExternalProject_Add( ${EXTERNAL_NAME} - URL https://static.oculus.com/sdk-downloads/1.11.0/Public/1486063832/ovr_sdk_win_1.11.0_public.zip - URL_MD5 ea484403757cbfdfa743b6577fb1f9d2 + URL http://hifi-public.s3.amazonaws.com/dependencies/ovr_sdk_win_1.26.0_public.zip + URL_MD5 06804ff9727b910dcd04a37c800053b5 CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH= PATCH_COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_CURRENT_SOURCE_DIR}/LibOVRCMakeLists.txt" /CMakeLists.txt LOG_DOWNLOAD 1 diff --git a/cmake/externals/LibOVR/LibOVRCMakeLists.txt b/cmake/externals/LibOVR/LibOVRCMakeLists.txt index 556533f0c2..a52cff5463 100644 --- a/cmake/externals/LibOVR/LibOVRCMakeLists.txt +++ b/cmake/externals/LibOVR/LibOVRCMakeLists.txt @@ -4,7 +4,7 @@ project(LibOVR) include_directories(LibOVR/Include LibOVR/Src) file(GLOB HEADER_FILES LibOVR/Include/*.h) file(GLOB EXTRA_HEADER_FILES LibOVR/Include/Extras/*.h) -file(GLOB_RECURSE SOURCE_FILES LibOVR/Src/*.c LibOVR/Src/*.cpp) +file(GLOB_RECURSE SOURCE_FILES LibOVR/Shim/*.c LibOVR/Shim/*.cpp) set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DOVR_BUILD_DEBUG") add_library(LibOVR STATIC ${SOURCE_FILES} ${HEADER_FILES} ${EXTRA_HEADER_FILES}) set_target_properties(LibOVR PROPERTIES DEBUG_POSTFIX "d") diff --git a/cmake/externals/etc2comp/CMakeLists.txt b/cmake/externals/etc2comp/CMakeLists.txt new file mode 100644 index 0000000000..d6d21d6703 --- /dev/null +++ b/cmake/externals/etc2comp/CMakeLists.txt @@ -0,0 +1,55 @@ +set(EXTERNAL_NAME etc2comp) + +if (ANDROID) + set(ANDROID_CMAKE_ARGS "-DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE}" "-DANDROID_NATIVE_API_LEVEL=19") +endif () + +if (APPLE) + set(EXTRA_CMAKE_FLAGS -DCMAKE_CXX_FLAGS=-stdlib=libc++ -DCMAKE_EXE_LINKER_FLAGS=-stdlib=libc++) +endif () + +include(ExternalProject) +# We use a patched version of etc2comp that properly generates all the necessary mips +# See https://github.com/google/etc2comp/pull/29 +# We also use part of https://github.com/google/etc2comp/pull/1, which fixes a bug +# that would override CMAKE_CXX_FLAGS +ExternalProject_Add( + ${EXTERNAL_NAME} + URL https://hifi-public.s3.amazonaws.com/dependencies/etc2comp-patched.zip + URL_MD5 4c96153eb179acbe619e3d99d3330595 + CMAKE_ARGS ${ANDROID_CMAKE_ARGS} ${EXTRA_CMAKE_FLAGS} + BINARY_DIR ${EXTERNAL_PROJECT_PREFIX}/build + INSTALL_COMMAND "" + LOG_DOWNLOAD 1 + LOG_CONFIGURE 1 + LOG_BUILD 1 +) + +# Hide this external target (for ide users) +set_target_properties(${EXTERNAL_NAME} PROPERTIES FOLDER "hidden/externals") + +ExternalProject_Get_Property(${EXTERNAL_NAME} SOURCE_DIR) +ExternalProject_Get_Property(${EXTERNAL_NAME} INSTALL_DIR) + +string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER) + +if (WIN32) + set(${EXTERNAL_NAME_UPPER}_LIBRARY_DEBUG ${INSTALL_DIR}/build/EtcLib/Debug/EtcLib.lib CACHE FILEPATH "Path to Etc2Comp debug library") + + # use generator expression to ensure the correct library is found when building different configurations in VS + set(_LIB_FOLDER "$<$:build/EtcLib/RelWithDebInfo>") + set(_LIB_FOLDER "${_LIB_FOLDER}$<$:build/EtcLib/MinSizeRel>") + set(_LIB_FOLDER "${_LIB_FOLDER}$<$,$>:build/EtcLib/Release>") + + set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${INSTALL_DIR}/${_LIB_FOLDER}/EtcLib.lib CACHE FILEPATH "Path to Etc2Comp release library") +elseif (APPLE) + set(${EXTERNAL_NAME_UPPER}_LIBRARY_DEBUG ${INSTALL_DIR}/build/EtcLib/Debug/libEtcLib.a CACHE FILEPATH "Path to EtcLib debug library") + set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${INSTALL_DIR}/build/EtcLib/Release/libEtcLib.a CACHE FILEPATH "Path to EtcLib release library") +else () + set(${EXTERNAL_NAME_UPPER}_LIBRARY_DEBUG "" CACHE FILEPATH "Path to EtcLib debug library") + set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${INSTALL_DIR}/build/EtcLib/libEtcLib.a CACHE FILEPATH "Path to EtcLib release library") +endif () + +set(ETC_INCLUDE_DIR ${SOURCE_DIR}/EtcLib/Etc CACHE FILEPATH "Path to Etc2Comp/Etc include directory") +set(ETCCODEC_INCLUDE_DIR ${SOURCE_DIR}/EtcLib/EtcCodec CACHE FILEPATH "Path to Etc2Comp/EtcCodec include directory") +# ETC2COMP_INCLUDE_DIRS will be set later by FindEtc2Comp \ No newline at end of file diff --git a/cmake/externals/serverless-content/CMakeLists.txt b/cmake/externals/serverless-content/CMakeLists.txt index aa1c59a86b..81b82e8651 100644 --- a/cmake/externals/serverless-content/CMakeLists.txt +++ b/cmake/externals/serverless-content/CMakeLists.txt @@ -4,8 +4,8 @@ set(EXTERNAL_NAME serverless-content) ExternalProject_Add( ${EXTERNAL_NAME} - URL http://cdn.highfidelity.com/content-sets/serverless-tutorial-RC68.zip - URL_MD5 a068f74d4045e257cfa7926fe6e38ad5 + URL http://cdn.highfidelity.com/content-sets/serverless-tutorial-RC68-v2.zip + URL_MD5 f7d290471baf7f5694c147217b8fc548 CONFIGURE_COMMAND "" BUILD_COMMAND "" INSTALL_COMMAND "" diff --git a/cmake/macros/SetPackagingParameters.cmake b/cmake/macros/SetPackagingParameters.cmake index 36e5a065df..2c8443d510 100644 --- a/cmake/macros/SetPackagingParameters.cmake +++ b/cmake/macros/SetPackagingParameters.cmake @@ -17,14 +17,13 @@ macro(SET_PACKAGING_PARAMETERS) set(DEV_BUILD 0) set(BUILD_GLOBAL_SERVICES "DEVELOPMENT") set(USE_STABLE_GLOBAL_SERVICES 0) + set(BUILD_NUMBER 0) + set(APP_USER_MODEL_ID "com.highfidelity.sandbox-dev") set_from_env(RELEASE_TYPE RELEASE_TYPE "DEV") set_from_env(RELEASE_NUMBER RELEASE_NUMBER "") - set_from_env(BUILD_BRANCH BRANCH "") - string(TOLOWER "${BUILD_BRANCH}" BUILD_BRANCH) + set_from_env(STABLE_BUILD STABLE_BUILD 0) - message(STATUS "The BUILD_BRANCH variable is: ${BUILD_BRANCH}") - message(STATUS "The BRANCH environment variable is: $ENV{BRANCH}") message(STATUS "The RELEASE_TYPE variable is: ${RELEASE_TYPE}") # setup component categories for installer @@ -46,17 +45,17 @@ macro(SET_PACKAGING_PARAMETERS) # if the build is a PRODUCTION_BUILD from the "stable" branch # then use the STABLE gobal services - if (BUILD_BRANCH STREQUAL "stable") - message(STATUS "The RELEASE_TYPE is PRODUCTION and the BUILD_BRANCH is stable...") + if (STABLE_BUILD) + message(STATUS "The RELEASE_TYPE is PRODUCTION and STABLE_BUILD is 1") set(BUILD_GLOBAL_SERVICES "STABLE") set(USE_STABLE_GLOBAL_SERVICES 1) - endif() + endif () elseif (RELEASE_TYPE STREQUAL "PR") set(DEPLOY_PACKAGE TRUE) set(PR_BUILD 1) set(BUILD_VERSION "PR${RELEASE_NUMBER}") - set(BUILD_ORGANIZATION "High Fidelity - ${BUILD_VERSION}") + set(BUILD_ORGANIZATION "High Fidelity - PR${RELEASE_NUMBER}") set(INTERFACE_BUNDLE_NAME "Interface") set(INTERFACE_ICON_PREFIX "interface-beta") @@ -75,6 +74,54 @@ macro(SET_PACKAGING_PARAMETERS) string(TIMESTAMP BUILD_TIME "%d/%m/%Y") + # if STABLE_BUILD is 1, PRODUCTION_BUILD must be 1 and + # DEV_BUILD and PR_BUILD must be 0 + if (STABLE_BUILD) + if ((NOT PRODUCTION_BUILD) OR PR_BUILD OR DEV_BUILD) + message(FATAL_ERROR "Cannot produce STABLE_BUILD without PRODUCTION_BUILD") + endif () + endif () + + if ((PRODUCTION_BUILD OR PR_BUILD) AND NOT STABLE_BUILD) + # append the abbreviated commit SHA to the build version + # since this is a PR build or master/nightly builds + + # for PR_BUILDS, we need to grab the abbreviated SHA + # for the second parent of HEAD (not HEAD) since that is the + # SHA of the commit merged to master for the build + if (PR_BUILD) + set(_GIT_LOG_FORMAT "%p") + else () + set(_GIT_LOG_FORMAT "%h") + endif () + + execute_process( + COMMAND git log -1 --abbrev=7 --format=${_GIT_LOG_FORMAT} + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + OUTPUT_VARIABLE _GIT_LOG_OUTPUT + ERROR_VARIABLE _GIT_LOG_ERROR + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + + if (PR_BUILD) + separate_arguments(_COMMIT_PARENTS UNIX_COMMAND ${_GIT_LOG_OUTPUT}) + list(GET _COMMIT_PARENTS 1 GIT_COMMIT_HASH) + else () + set(GIT_COMMIT_HASH ${_GIT_LOG_OUTPUT}) + endif () + + if (_GIT_LOG_ERROR OR NOT GIT_COMMIT_HASH) + message(FATAL_ERROR "Could not retreive abbreviated SHA for PR or production master build") + endif () + + set(BUILD_VERSION_NO_SHA ${BUILD_VERSION}) + set(BUILD_VERSION "${BUILD_VERSION}-${GIT_COMMIT_HASH}") + + # pass along a release number without the SHA in case somebody + # wants to compare master or PR builds as integers + set(BUILD_NUMBER ${RELEASE_NUMBER}) + endif () + if (DEPLOY_PACKAGE) # for deployed packages always grab the serverless content set(DOWNLOAD_SERVERLESS_CONTENT ON) @@ -126,9 +173,10 @@ macro(SET_PACKAGING_PARAMETERS) if (PRODUCTION_BUILD) set(INTERFACE_SHORTCUT_NAME "High Fidelity Interface") set(CONSOLE_SHORTCUT_NAME "Sandbox") + set(APP_USER_MODEL_ID "com.highfidelity.sandbox") else () - set(INTERFACE_SHORTCUT_NAME "High Fidelity Interface - ${BUILD_VERSION}") - set(CONSOLE_SHORTCUT_NAME "Sandbox - ${BUILD_VERSION}") + set(INTERFACE_SHORTCUT_NAME "High Fidelity Interface - ${BUILD_VERSION_NO_SHA}") + set(CONSOLE_SHORTCUT_NAME "Sandbox - ${BUILD_VERSION_NO_SHA}") endif () set(INTERFACE_HF_SHORTCUT_NAME "${INTERFACE_SHORTCUT_NAME}") diff --git a/cmake/macros/TargetEtc2Comp.cmake b/cmake/macros/TargetEtc2Comp.cmake new file mode 100644 index 0000000000..44152a58d2 --- /dev/null +++ b/cmake/macros/TargetEtc2Comp.cmake @@ -0,0 +1,22 @@ +# +# Copyright 2018 High Fidelity, Inc. +# Created by Sam Gondelman on 5/2/2018 +# +# Distributed under the Apache License, Version 2.0. +# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +# +macro(TARGET_ETC2COMP) + if (ANDROID) + set(INSTALL_DIR ${HIFI_ANDROID_PRECOMPILED}/etc2comp) + set(ETC2COMP_INCLUDE_DIRS "${INSTALL_DIR}/include/Etc" "${INSTALL_DIR}/include/EtcCodec") + set(ETC2COMP_LIBRARY_DEBUG ${INSTALL_DIR}/lib/libEtcLib.a) + set(ETC2COMP_LIBRARY_RELEASE ${INSTALL_DIR}/lib/libEtcLib.a) + select_library_configurations(ETC2COMP) + else() + add_dependency_external_projects(etc2comp) + find_package(Etc2Comp REQUIRED) + endif() + + target_include_directories(${TARGET_NAME} PRIVATE ${ETC2COMP_INCLUDE_DIRS}) + target_link_libraries(${TARGET_NAME} ${ETC2COMP_LIBRARIES}) +endmacro() diff --git a/cmake/modules/FindEtc2Comp.cmake b/cmake/modules/FindEtc2Comp.cmake new file mode 100644 index 0000000000..1b990368fd --- /dev/null +++ b/cmake/modules/FindEtc2Comp.cmake @@ -0,0 +1,37 @@ +# +# FindEtc2Comp.cmake +# +# Try to find the Etc2Comp compression library. +# +# Once done this will define +# +# ETC2COMP_FOUND - system found Etc2Comp +# ETC2COMP_INCLUDE_DIRS - the Etc2Comp include directory +# ETC2COMP_LIBRARIES - link to this to use Etc2Comp +# +# Created on 5/2/2018 by Sam Gondelman +# 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("${MACRO_DIR}/HifiLibrarySearchHints.cmake") +hifi_library_search_hints("etc2comp") + +find_path(ETC_INCLUDE_DIR NAMES Etc.h HINTS ${ETC2COMP_SEARCH_DIRS}) +find_path(ETCCODEC_INCLUDE_DIR NAMES EtcBlock4x4.h HINTS ${ETC2COMP_SEARCH_DIRS}) +set(ETC2COMP_INCLUDE_DIRS "${ETC_INCLUDE_DIR}" "${ETCCODEC_INCLUDE_DIR}") + +find_library(ETC2COMP_LIBRARY_DEBUG NAMES ETC2COMP ETC2COMP_LIB PATH_SUFFIXES EtcLib/Debug HINTS ${ETC2COMP_SEARCH_DIRS}) +find_library(ETC2COMP_LIBRARY_RELEASE NAMES ETC2COMP ETC2COMP_LIB PATH_SUFFIXES EtcLib/Release EtcLib HINTS ${ETC2COMP_SEARCH_DIRS}) + +include(SelectLibraryConfigurations) +select_library_configurations(ETC2COMP) + +set(ETC2COMP_LIBRARIES ${ETC2COMP_LIBRARY}) + +find_package_handle_standard_args(ETC2COMP "Could NOT find ETC2COMP, try to set the path to ETC2COMP root folder in the system variable ETC2COMP_ROOT_DIR or create a directory etc2comp in HIFI_LIB_DIR and paste the necessary files there" + ETC2COMP_INCLUDE_DIRS ETC2COMP_LIBRARIES) + +mark_as_advanced(ETC2COMP_INCLUDE_DIRS ETC2COMP_LIBRARIES ETC2COMP_SEARCH_DIRS) diff --git a/cmake/templates/BuildInfo.h.in b/cmake/templates/BuildInfo.h.in index 904d17293b..9fc9d9be81 100644 --- a/cmake/templates/BuildInfo.h.in +++ b/cmake/templates/BuildInfo.h.in @@ -24,8 +24,26 @@ namespace BuildInfo { const QString MODIFIED_ORGANIZATION = "@BUILD_ORGANIZATION@"; const QString ORGANIZATION_DOMAIN = "highfidelity.io"; const QString VERSION = "@BUILD_VERSION@"; - const QString BUILD_BRANCH = "@BUILD_BRANCH@"; + const QString BUILD_NUMBER = "@BUILD_NUMBER@"; const QString BUILD_GLOBAL_SERVICES = "@BUILD_GLOBAL_SERVICES@"; const QString BUILD_TIME = "@BUILD_TIME@"; -} + enum BuildType { + Dev, + PR, + Master, + Stable + }; + +#if defined(PR_BUILD) + const BuildType BUILD_TYPE = PR; + const QString BUILD_TYPE_STRING = "pr"; +#elif defined(PRODUCTION_BUILD) + const BuildType BUILD_TYPE = @STABLE_BUILD@ ? Stable : Master; + const QString BUILD_TYPE_STRING = @STABLE_BUILD@ ? "stable" : "master"; +#else + const BuildType BUILD_TYPE = Dev; + const QString BUILD_TYPE_STRING = "dev"; +#endif + +} diff --git a/cmake/templates/CPackProperties.cmake.in b/cmake/templates/CPackProperties.cmake.in index 80d86ac030..68fa098508 100644 --- a/cmake/templates/CPackProperties.cmake.in +++ b/cmake/templates/CPackProperties.cmake.in @@ -49,3 +49,4 @@ set(ADD_REMOVE_ICON_PATH "@ADD_REMOVE_ICON_PATH@") set(SERVER_COMPONENT_CONDITIONAL "@SERVER_COMPONENT_CONDITIONAL@") set(CLIENT_COMPONENT_CONDITIONAL "@CLIENT_COMPONENT_CONDITIONAL@") set(INSTALLER_TYPE "@INSTALLER_TYPE@") +set(APP_USER_MODEL_ID "@APP_USER_MODEL_ID@") diff --git a/cmake/templates/FixupBundlePostBuild.cmake.in b/cmake/templates/FixupBundlePostBuild.cmake.in index 57379bb48b..bb96fe49f3 100644 --- a/cmake/templates/FixupBundlePostBuild.cmake.in +++ b/cmake/templates/FixupBundlePostBuild.cmake.in @@ -11,6 +11,35 @@ include(BundleUtilities) +# replace copy_resolved_item_into_bundle +# +# The official version of copy_resolved_item_into_bundle will print out a "warning:" when +# the resolved item matches the resolved embedded item. This not not really an issue that +# should rise to the level of a "warning" so we replace this message with a "status:" +# +# Source: https://github.com/jherico/OculusMinimalExample/blob/master/cmake/templates/FixupBundlePostBuild.cmake.in +# +function(copy_resolved_item_into_bundle resolved_item resolved_embedded_item) + if (WIN32) + # ignore case on Windows + string(TOLOWER "${resolved_item}" resolved_item_compare) + string(TOLOWER "${resolved_embedded_item}" resolved_embedded_item_compare) + else() + set(resolved_item_compare "${resolved_item}") + set(resolved_embedded_item_compare "${resolved_embedded_item}") + endif() + + if ("${resolved_item_compare}" STREQUAL "${resolved_embedded_item_compare}") + # this is our only change from the original version + message(STATUS "status: resolved_item == resolved_embedded_item - not copying...") + else() + execute_process(COMMAND ${CMAKE_COMMAND} -E copy "${resolved_item}" "${resolved_embedded_item}") + if (UNIX AND NOT APPLE) + file(RPATH_REMOVE FILE "${resolved_embedded_item}") + endif() + endif() +endfunction() + function(gp_resolved_file_type_override resolved_file type_var) if( file MATCHES ".*VCRUNTIME140.*" ) set(type "system" PARENT_SCOPE) diff --git a/cmake/templates/NSIS.template.in b/cmake/templates/NSIS.template.in index fc9b9ab03d..64e3fbe889 100644 --- a/cmake/templates/NSIS.template.in +++ b/cmake/templates/NSIS.template.in @@ -905,6 +905,8 @@ Function HandlePostInstallOptions ${If} $DesktopServerState == ${BST_CHECKED} CreateShortCut "$DESKTOP\@CONSOLE_HF_SHORTCUT_NAME@.lnk" "$INSTDIR\@CONSOLE_INSTALL_SUBDIR@\@CONSOLE_WIN_EXEC_NAME@" !insertmacro WriteInstallOption @CONSOLE_DESKTOP_SHORTCUT_REG_KEY@ YES + ; Set appUserModelId + ApplicationID::Set "$DESKTOP\@CONSOLE_HF_SHORTCUT_NAME@.lnk" "@APP_USER_MODEL_ID@" ${Else} !insertmacro WriteInstallOption @CONSOLE_DESKTOP_SHORTCUT_REG_KEY@ NO ${EndIf} @@ -1162,6 +1164,8 @@ Section "-Core installation" ${If} @SERVER_COMPONENT_CONDITIONAL@ CreateShortCut "$SMPROGRAMS\$STARTMENU_FOLDER\@CONSOLE_SHORTCUT_NAME@.lnk" \ "$INSTDIR\@CONSOLE_INSTALL_SUBDIR@\@CONSOLE_WIN_EXEC_NAME@" + ; Set appUserModelId + ApplicationID::Set "$SMPROGRAMS\$STARTMENU_FOLDER\@CONSOLE_SHORTCUT_NAME@.lnk" "@APP_USER_MODEL_ID@" ${EndIf} CreateShortCut "$SMPROGRAMS\$STARTMENU_FOLDER\Uninstall.lnk" "$INSTDIR\@UNINSTALLER_NAME@" diff --git a/cmake/templates/console-build-info.json.in b/cmake/templates/console-build-info.json.in index c1ef010e08..a68df664c6 100644 --- a/cmake/templates/console-build-info.json.in +++ b/cmake/templates/console-build-info.json.in @@ -1,4 +1,8 @@ { "releaseType": "@RELEASE_TYPE@", - "buildIdentifier": "@BUILD_VERSION@" + "buildNumber": "@BUILD_NUMBER@", + "stableBuild": "@STABLE_BUILD@", + "buildIdentifier": "@BUILD_VERSION@", + "organization": "@BUILD_ORGANIZATION@", + "appUserModelId": "@APP_USER_MODEL_ID@" } diff --git a/domain-server/src/AssetsBackupHandler.cpp b/domain-server/src/AssetsBackupHandler.cpp index 2369b01690..6bcabc0bf1 100644 --- a/domain-server/src/AssetsBackupHandler.cpp +++ b/domain-server/src/AssetsBackupHandler.cpp @@ -34,8 +34,9 @@ static const chrono::minutes MAX_REFRESH_TIME { 5 }; Q_DECLARE_LOGGING_CATEGORY(asset_backup) Q_LOGGING_CATEGORY(asset_backup, "hifi.asset-backup"); -AssetsBackupHandler::AssetsBackupHandler(const QString& backupDirectory) : - _assetsDirectory(backupDirectory + ASSETS_DIR) +AssetsBackupHandler::AssetsBackupHandler(const QString& backupDirectory, bool assetServerEnabled) : + _assetsDirectory(backupDirectory + ASSETS_DIR), + _assetServerEnabled(assetServerEnabled) { // Make sure the asset directory exists. QDir(_assetsDirectory).mkpath("."); @@ -53,6 +54,7 @@ void AssetsBackupHandler::setupRefreshTimer() { auto nodeList = DependencyManager::get(); QObject::connect(nodeList.data(), &LimitedNodeList::nodeActivated, this, [this](SharedNodePointer node) { if (node->getType() == NodeType::AssetServer) { + assert(_assetServerEnabled); // run immediately for the first time. _mappingsRefreshTimer.start(0); } @@ -233,12 +235,12 @@ void AssetsBackupHandler::createBackup(const QString& backupName, QuaZip& zip) { return; } - if (_lastMappingsRefresh.time_since_epoch().count() == 0) { + if (_assetServerEnabled && _lastMappingsRefresh.time_since_epoch().count() == 0) { qCWarning(asset_backup) << "Current mappings not yet loaded."; return; } - if ((p_high_resolution_clock::now() - _lastMappingsRefresh) > MAX_REFRESH_TIME) { + if (_assetServerEnabled && (p_high_resolution_clock::now() - _lastMappingsRefresh) > MAX_REFRESH_TIME) { qCWarning(asset_backup) << "Backing up asset mappings that might be stale."; } diff --git a/domain-server/src/AssetsBackupHandler.h b/domain-server/src/AssetsBackupHandler.h index 82d684c2c3..427dc6831a 100644 --- a/domain-server/src/AssetsBackupHandler.h +++ b/domain-server/src/AssetsBackupHandler.h @@ -30,7 +30,7 @@ class AssetsBackupHandler : public QObject, public BackupHandlerInterface { Q_OBJECT public: - AssetsBackupHandler(const QString& backupDirectory); + AssetsBackupHandler(const QString& backupDirectory, bool assetServerEnabled); std::pair isAvailable(const QString& backupName) override; std::pair getRecoveryStatus() override; @@ -65,6 +65,7 @@ private: void updateMappings(); QString _assetsDirectory; + bool _assetServerEnabled { false }; QTimer _mappingsRefreshTimer; p_high_resolution_clock::time_point _lastMappingsRefresh; diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index 47b55bb5c2..b4685b907f 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -16,6 +16,8 @@ #include #include +#include + #include #include diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 4e65df495c..a34deebc95 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -149,7 +149,6 @@ DomainServer::DomainServer(int argc, char* argv[]) : QCoreApplication(argc, argv), _gatekeeper(this), _httpManager(QHostAddress::AnyIPv4, DOMAIN_SERVER_HTTP_PORT, QString("%1/resources/web/").arg(QCoreApplication::applicationDirPath()), this), - _httpsManager(NULL), _allAssignments(), _unfulfilledAssignments(), _isUsingDTLS(false), @@ -177,7 +176,7 @@ DomainServer::DomainServer(int argc, char* argv[]) : qDebug() << "[VERSION] Build sequence:" << qPrintable(applicationVersion()); qDebug() << "[VERSION] MODIFIED_ORGANIZATION:" << BuildInfo::MODIFIED_ORGANIZATION; qDebug() << "[VERSION] VERSION:" << BuildInfo::VERSION; - qDebug() << "[VERSION] BUILD_BRANCH:" << BuildInfo::BUILD_BRANCH; + qDebug() << "[VERSION] BUILD_TYPE_STRING:" << BuildInfo::BUILD_TYPE_STRING; qDebug() << "[VERSION] BUILD_GLOBAL_SERVICES:" << BuildInfo::BUILD_GLOBAL_SERVICES; qDebug() << "[VERSION] We will be using this name to find ICE servers:" << _iceServerAddr; @@ -308,7 +307,7 @@ DomainServer::DomainServer(int argc, char* argv[]) : connect(_contentManager.get(), &DomainContentBackupManager::started, _contentManager.get(), [this](){ _contentManager->addBackupHandler(BackupHandlerPointer(new EntitiesBackupHandler(getEntitiesFilePath(), getEntitiesReplacementFilePath()))); - _contentManager->addBackupHandler(BackupHandlerPointer(new AssetsBackupHandler(getContentBackupDir()))); + _contentManager->addBackupHandler(BackupHandlerPointer(new AssetsBackupHandler(getContentBackupDir(), isAssetServerEnabled()))); _contentManager->addBackupHandler(BackupHandlerPointer(new ContentSettingsBackupHandler(_settingsManager))); }); @@ -385,6 +384,8 @@ DomainServer::~DomainServer() { _contentManager->terminate(); } + DependencyManager::destroy(); + // cleanup the AssetClient thread DependencyManager::destroy(); _assetClientThread.quit(); @@ -439,7 +440,7 @@ bool DomainServer::optionallyReadX509KeyAndCertificate() { QSslCertificate sslCertificate(&certFile); QSslKey privateKey(&keyFile, QSsl::Rsa, QSsl::Pem, QSsl::PrivateKey, keyPassphraseString.toUtf8()); - _httpsManager = new HTTPSManager(QHostAddress::AnyIPv4, DOMAIN_SERVER_HTTPS_PORT, sslCertificate, privateKey, QString(), this, this); + _httpsManager.reset(new HTTPSManager(QHostAddress::AnyIPv4, DOMAIN_SERVER_HTTPS_PORT, sslCertificate, privateKey, QString(), this)); qDebug() << "TCP server listening for HTTPS connections on" << DOMAIN_SERVER_HTTPS_PORT; @@ -990,15 +991,11 @@ void DomainServer::populateDefaultStaticAssignmentsExcludingTypes(const QSet(static_cast(defaultedType) + 1)) { if (!excludedTypes.contains(defaultedType) && defaultedType != Assignment::AgentType) { - if (defaultedType == Assignment::AssetServerType) { - // Make sure the asset-server is enabled before adding it here. - // Initially we do not assign it by default so we can test it in HF domains first - static const QString ASSET_SERVER_ENABLED_KEYPATH = "asset_server.enabled"; - - if (!_settingsManager.valueOrDefaultValueForKeyPath(ASSET_SERVER_ENABLED_KEYPATH).toBool()) { - // skip to the next iteration if asset-server isn't enabled - continue; - } + // Make sure the asset-server is enabled before adding it here. + // Initially we do not assign it by default so we can test it in HF domains first + if (defaultedType == Assignment::AssetServerType && !isAssetServerEnabled()) { + // skip to the next iteraion if asset-server isn't enabled + continue; } // type has not been set from a command line or config file config, use the default @@ -1121,7 +1118,7 @@ void DomainServer::handleConnectedNode(SharedNodePointer newNode) { } void DomainServer::sendDomainListToNode(const SharedNodePointer& node, const HifiSockAddr &senderSockAddr) { - const int NUM_DOMAIN_LIST_EXTENDED_HEADER_BYTES = NUM_BYTES_RFC4122_UUID + NLPacket::NUM_BYTES_LOCALID + + const int NUM_DOMAIN_LIST_EXTENDED_HEADER_BYTES = NUM_BYTES_RFC4122_UUID + NLPacket::NUM_BYTES_LOCALID + NUM_BYTES_RFC4122_UUID + NLPacket::NUM_BYTES_LOCALID + 4; // setup the extended header for the domain list packets @@ -2683,7 +2680,7 @@ bool DomainServer::isAuthenticatedRequest(HTTPConnection* connection, const QUrl QString settingsPassword = settingsPasswordVariant.isValid() ? settingsPasswordVariant.toString() : ""; QString hexHeaderPassword = headerPassword.isEmpty() ? "" : QCryptographicHash::hash(headerPassword.toUtf8(), QCryptographicHash::Sha256).toHex(); - + if (settingsUsername == headerUsername && hexHeaderPassword == settingsPassword) { return true; } @@ -2945,6 +2942,12 @@ bool DomainServer::shouldReplicateNode(const Node& node) { } }; + +bool DomainServer::isAssetServerEnabled() { + static const QString ASSET_SERVER_ENABLED_KEYPATH = "asset_server.enabled"; + return _settingsManager.valueOrDefaultValueForKeyPath(ASSET_SERVER_ENABLED_KEYPATH).toBool(); +} + void DomainServer::nodeAdded(SharedNodePointer node) { // we don't use updateNodeWithData, so add the DomainServerNodeData to the node here node->setLinkedData(std::unique_ptr { new DomainServerNodeData() }); diff --git a/domain-server/src/DomainServer.h b/domain-server/src/DomainServer.h index 01adbd99a9..3703877fa1 100644 --- a/domain-server/src/DomainServer.h +++ b/domain-server/src/DomainServer.h @@ -72,6 +72,8 @@ public: static const QString REPLACEMENT_FILE_EXTENSION; + bool isAssetServerEnabled(); + public slots: /// Called by NodeList to inform us a node has been added void nodeAdded(SharedNodePointer node); @@ -220,7 +222,7 @@ private: DomainGatekeeper _gatekeeper; HTTPManager _httpManager; - HTTPSManager* _httpsManager; + std::unique_ptr _httpsManager; QHash _allAssignments; QQueue _unfulfilledAssignments; diff --git a/ice-server/src/IceServer.cpp b/ice-server/src/IceServer.cpp index 3cf1e1450e..6896c7a9c9 100644 --- a/ice-server/src/IceServer.cpp +++ b/ice-server/src/IceServer.cpp @@ -13,6 +13,7 @@ #include +#include #include #include #include diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index ac9441319b..4204718976 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -133,13 +133,7 @@ if (APPLE) # set where in the bundle to put the resources file set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/icon/${INTERFACE_ICON_FILENAME} PROPERTIES MACOSX_PACKAGE_LOCATION Resources) - set(DISCOVERED_RESOURCES "") - - # use the add_resources_to_os_x_bundle macro to recurse into resources - add_resources_to_os_x_bundle("${CMAKE_CURRENT_SOURCE_DIR}/resources") - # append the discovered resources to our list of interface sources - list(APPEND INTERFACE_SRCS ${DISCOVERED_RESOURCES}) list(APPEND INTERFACE_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/icon/${INTERFACE_ICON_FILENAME}) endif() @@ -316,18 +310,27 @@ if (APPLE) set(SCRIPTS_INSTALL_DIR "${INTERFACE_INSTALL_APP_PATH}/Contents/Resources") set(RESOURCES_DEV_DIR "$/../Resources") - # copy script files beside the executable add_custom_command(TARGET ${TARGET_NAME} POST_BUILD + # copy script files beside the executable COMMAND "${CMAKE_COMMAND}" -E copy_directory - "${CMAKE_SOURCE_DIR}/scripts" - "${RESOURCES_DEV_DIR}/scripts" - ) - - # copy JSDoc files beside the executable - add_custom_command(TARGET ${TARGET_NAME} POST_BUILD + "${CMAKE_SOURCE_DIR}/scripts" + "${RESOURCES_DEV_DIR}/scripts" + # copy JSDoc files beside the executable COMMAND "${CMAKE_COMMAND}" -E copy_directory - "${CMAKE_SOURCE_DIR}/tools/jsdoc/out" - "${RESOURCES_DEV_DIR}/jsdoc" + "${CMAKE_SOURCE_DIR}/tools/jsdoc/out" + "${RESOURCES_DEV_DIR}/jsdoc" + # copy the resources files beside the executable + COMMAND "${CMAKE_COMMAND}" -E copy_if_different + "${RESOURCES_RCC}" + "${RESOURCES_DEV_DIR}" + # FIXME, the edit script code loads HTML from the scripts folder + # which in turn relies on CSS that refers to the fonts. In theory + # we should be able to modify the CSS to reference the QRC path to + # the ttf files, but doing so generates a CORS policy violation, + # so we have to retain a copy of the fonts outside of the resources binary + COMMAND "${CMAKE_COMMAND}" -E copy_directory + "${PROJECT_SOURCE_DIR}/resources/fonts" + "${RESOURCES_DEV_DIR}/fonts" ) # call the fixup_interface macro to add required bundling commands for installation @@ -356,13 +359,10 @@ else() COMMAND "${CMAKE_COMMAND}" -E copy_if_different "${PROJECT_SOURCE_DIR}/resources/serverless/tutorial.json" "${RESOURCES_DEV_DIR}/serverless/tutorial.json" - ) - - # copy JSDoc files beside the executable - add_custom_command(TARGET ${TARGET_NAME} POST_BUILD + # copy JSDoc files beside the executable COMMAND "${CMAKE_COMMAND}" -E copy_directory - "${CMAKE_SOURCE_DIR}/tools/jsdoc/out" - "${INTERFACE_EXEC_DIR}/jsdoc" + "${CMAKE_SOURCE_DIR}/tools/jsdoc/out" + "${INTERFACE_EXEC_DIR}/jsdoc" ) # link target to external libraries diff --git a/interface/resources/images/buttonBezel.png b/interface/resources/images/buttonBezel.png new file mode 100644 index 0000000000..fe55855462 Binary files /dev/null and b/interface/resources/images/buttonBezel.png differ diff --git a/interface/resources/images/buttonBezel_highlight.png b/interface/resources/images/buttonBezel_highlight.png new file mode 100644 index 0000000000..ab0a99e4c5 Binary files /dev/null and b/interface/resources/images/buttonBezel_highlight.png differ diff --git a/interface/resources/meshes/defaultAvatar_full.fst b/interface/resources/meshes/defaultAvatar_full.fst index aa1c17fc40..b47faeb8a8 100644 --- a/interface/resources/meshes/defaultAvatar_full.fst +++ b/interface/resources/meshes/defaultAvatar_full.fst @@ -2,88 +2,88 @@ name = mannequin type = body+head scale = 1 filename = mannequin/mannequin.baked.fbx -joint = jointRoot = Hips +joint = jointNeck = Neck joint = jointLean = Spine -joint = jointLeftHand = LeftHand -joint = jointHead = Head joint = jointEyeLeft = LeftEye joint = jointEyeRight = RightEye +joint = jointRoot = Hips +joint = jointLeftHand = LeftHand joint = jointRightHand = RightHand -joint = jointNeck = Neck +joint = jointHead = Head freeJoint = LeftArm freeJoint = LeftForeArm freeJoint = RightArm freeJoint = RightForeArm -bs = EyeBlink_L = blink = 1 bs = JawOpen = mouth_Open = 1 bs = LipsFunnel = Oo = 1 bs = BrowsU_L = brow_Up = 1 -jointIndex = RightHandPinky2 = 19 -jointIndex = LeftHandMiddle4 = 61 -jointIndex = LeftHand = 41 -jointIndex = LeftHandRing4 = 49 -jointIndex = RightHandMiddle3 = 36 -jointIndex = LeftHandThumb4 = 57 -jointIndex = RightToe_End = 10 -jointIndex = LeftHandRing1 = 46 -jointIndex = LeftForeArm = 40 -jointIndex = RightHandIndex4 = 29 -jointIndex = LeftShoulder = 38 -jointIndex = RightHandMiddle4 = 37 -jointIndex = RightShoulder = 14 -jointIndex = LeftLeg = 2 -jointIndex = LeftToe_End = 5 -jointIndex = Hips = 0 -jointIndex = RightFoot = 8 -jointIndex = RightHandThumb2 = 31 -jointIndex = LeftHandMiddle3 = 60 -jointIndex = RightHandThumb1 = 30 -jointIndex = Neck = 62 -jointIndex = Spine = 11 -jointIndex = RightHandThumb4 = 33 -jointIndex = RightHandMiddle1 = 34 -jointIndex = LeftHandIndex4 = 53 -jointIndex = face = 68 -jointIndex = RightHandRing3 = 24 -jointIndex = LeftHandPinky4 = 45 -jointIndex = LeftHandMiddle2 = 59 -jointIndex = RightHandThumb3 = 32 +bs = EyeBlink_L = blink = 1 jointIndex = LeftHandPinky3 = 44 -jointIndex = HeadTop_End = 66 -jointIndex = Spine1 = 12 -jointIndex = LeftHandRing3 = 48 -jointIndex = mannequin1 = 67 -jointIndex = RightEye = 65 -jointIndex = RightHandRing4 = 25 -jointIndex = RightHandPinky4 = 21 -jointIndex = LeftHandRing2 = 47 -jointIndex = RightHandIndex3 = 28 -jointIndex = RightUpLeg = 6 -jointIndex = LeftArm = 39 -jointIndex = LeftHandThumb3 = 56 -jointIndex = RightHandIndex2 = 27 -jointIndex = RightForeArm = 16 -jointIndex = RightArm = 15 -jointIndex = RightHandRing2 = 23 -jointIndex = LeftHandMiddle1 = 58 -jointIndex = Spine2 = 13 -jointIndex = LeftHandThumb2 = 55 -jointIndex = RightHandMiddle2 = 35 -jointIndex = RightHandPinky1 = 18 -jointIndex = LeftUpLeg = 1 -jointIndex = RightLeg = 7 -jointIndex = LeftHandIndex2 = 51 +jointIndex = LeftHand = 41 +jointIndex = RightHandMiddle1 = 34 +jointIndex = LeftHandPinky4 = 45 jointIndex = RightHand = 17 -jointIndex = LeftHandIndex3 = 52 -jointIndex = LeftFoot = 3 jointIndex = RightHandPinky3 = 20 -jointIndex = RightHandIndex1 = 26 -jointIndex = LeftHandPinky1 = 42 -jointIndex = RightToeBase = 9 -jointIndex = LeftHandIndex1 = 50 -jointIndex = LeftToeBase = 4 -jointIndex = LeftHandPinky2 = 43 -jointIndex = RightHandRing1 = 22 -jointIndex = LeftHandThumb1 = 54 -jointIndex = LeftEye = 64 +jointIndex = LeftFoot = 3 jointIndex = Head = 63 +jointIndex = Spine1 = 12 +jointIndex = RightHandRing4 = 25 +jointIndex = RightHandPinky1 = 18 +jointIndex = LeftHandIndex1 = 50 +jointIndex = RightHandIndex3 = 28 +jointIndex = LeftHandIndex3 = 52 +jointIndex = LeftToe_End = 5 +jointIndex = RightArm = 15 +jointIndex = RightHandRing3 = 24 +jointIndex = RightHandThumb2 = 31 +jointIndex = Spine2 = 13 +jointIndex = HeadTop_End = 66 +jointIndex = LeftToeBase = 4 +jointIndex = RightUpLeg = 6 +jointIndex = RightForeArm = 16 +jointIndex = LeftHandMiddle1 = 58 +jointIndex = LeftHandRing3 = 48 +jointIndex = RightHandPinky4 = 21 +jointIndex = RightHandIndex1 = 26 +jointIndex = Hips = 0 +jointIndex = RightEye = 65 +jointIndex = RightHandPinky2 = 19 +jointIndex = LeftHandMiddle2 = 59 +jointIndex = LeftHandPinky1 = 42 +jointIndex = LeftHandRing4 = 49 +jointIndex = RightFoot = 8 +jointIndex = RightHandIndex2 = 27 +jointIndex = RightToe_End = 10 +jointIndex = RightHandThumb3 = 32 +jointIndex = LeftHandMiddle3 = 60 +jointIndex = LeftHandThumb4 = 57 +jointIndex = LeftHandMiddle4 = 61 +jointIndex = LeftHandThumb1 = 54 +jointIndex = LeftHandThumb3 = 56 +jointIndex = body = 67 +jointIndex = LeftArm = 39 +jointIndex = RightToeBase = 9 +jointIndex = LeftEye = 64 +jointIndex = RightLeg = 7 +jointIndex = face = 68 +jointIndex = LeftForeArm = 40 +jointIndex = RightHandThumb4 = 33 +jointIndex = RightHandRing1 = 22 +jointIndex = LeftUpLeg = 1 +jointIndex = LeftHandPinky2 = 43 +jointIndex = LeftLeg = 2 +jointIndex = LeftHandIndex4 = 53 +jointIndex = RightHandThumb1 = 30 +jointIndex = LeftHandRing2 = 47 +jointIndex = RightHandMiddle2 = 35 +jointIndex = RightHandMiddle3 = 36 +jointIndex = Spine = 11 +jointIndex = RightHandMiddle4 = 37 +jointIndex = LeftHandIndex2 = 51 +jointIndex = RightHandRing2 = 23 +jointIndex = LeftHandThumb2 = 55 +jointIndex = LeftShoulder = 38 +jointIndex = Neck = 62 +jointIndex = RightHandIndex4 = 29 +jointIndex = LeftHandRing1 = 46 +jointIndex = RightShoulder = 14 diff --git a/interface/resources/meshes/mannequin/Eyes.png b/interface/resources/meshes/mannequin/Eyes.png new file mode 100644 index 0000000000..df35dc9fee Binary files /dev/null and b/interface/resources/meshes/mannequin/Eyes.png differ diff --git a/interface/resources/meshes/mannequin/Eyes.ktx b/interface/resources/meshes/mannequin/Eyes_bcn.ktx similarity index 99% rename from interface/resources/meshes/mannequin/Eyes.ktx rename to interface/resources/meshes/mannequin/Eyes_bcn.ktx index ada45776a0..934cba3180 100644 Binary files a/interface/resources/meshes/mannequin/Eyes.ktx and b/interface/resources/meshes/mannequin/Eyes_bcn.ktx differ diff --git a/interface/resources/meshes/mannequin/StingrayPBS10_Base_Color.png b/interface/resources/meshes/mannequin/StingrayPBS10_Base_Color.png new file mode 100644 index 0000000000..941955916a Binary files /dev/null and b/interface/resources/meshes/mannequin/StingrayPBS10_Base_Color.png differ diff --git a/interface/resources/meshes/mannequin/StingrayPBS10_Base_Color_bcn.ktx b/interface/resources/meshes/mannequin/StingrayPBS10_Base_Color_bcn.ktx new file mode 100644 index 0000000000..cbb6cfa992 Binary files /dev/null and b/interface/resources/meshes/mannequin/StingrayPBS10_Base_Color_bcn.ktx differ diff --git a/interface/resources/meshes/mannequin/StingrayPBS10_Normal_OpenGL.png b/interface/resources/meshes/mannequin/StingrayPBS10_Normal_OpenGL.png new file mode 100644 index 0000000000..fc86048656 Binary files /dev/null and b/interface/resources/meshes/mannequin/StingrayPBS10_Normal_OpenGL.png differ diff --git a/interface/resources/meshes/mannequin/StingrayPBS10_Normal_OpenGL_bcn.ktx b/interface/resources/meshes/mannequin/StingrayPBS10_Normal_OpenGL_bcn.ktx new file mode 100644 index 0000000000..aad0b108b3 Binary files /dev/null and b/interface/resources/meshes/mannequin/StingrayPBS10_Normal_OpenGL_bcn.ktx differ diff --git a/interface/resources/meshes/mannequin/lambert1_Base_Color.ktx b/interface/resources/meshes/mannequin/lambert1_Base_Color.ktx deleted file mode 100644 index f151352592..0000000000 Binary files a/interface/resources/meshes/mannequin/lambert1_Base_Color.ktx and /dev/null differ diff --git a/interface/resources/meshes/mannequin/lambert1_Normal_OpenGL.ktx b/interface/resources/meshes/mannequin/lambert1_Normal_OpenGL.ktx deleted file mode 100644 index fb738063ae..0000000000 Binary files a/interface/resources/meshes/mannequin/lambert1_Normal_OpenGL.ktx and /dev/null differ diff --git a/interface/resources/meshes/mannequin/lambert1_Roughness.png b/interface/resources/meshes/mannequin/lambert1_Roughness.png new file mode 100644 index 0000000000..9efa372541 Binary files /dev/null and b/interface/resources/meshes/mannequin/lambert1_Roughness.png differ diff --git a/interface/resources/meshes/mannequin/lambert1_Roughness.ktx b/interface/resources/meshes/mannequin/lambert1_Roughness_bcn.ktx similarity index 99% rename from interface/resources/meshes/mannequin/lambert1_Roughness.ktx rename to interface/resources/meshes/mannequin/lambert1_Roughness_bcn.ktx index afe96d1631..628e6ce99b 100644 Binary files a/interface/resources/meshes/mannequin/lambert1_Roughness.ktx and b/interface/resources/meshes/mannequin/lambert1_Roughness_bcn.ktx differ diff --git a/interface/resources/meshes/mannequin/mannequin.baked.fbx b/interface/resources/meshes/mannequin/mannequin.baked.fbx index 2b0e1cf04b..63611b0b6c 100644 Binary files a/interface/resources/meshes/mannequin/mannequin.baked.fbx and b/interface/resources/meshes/mannequin/mannequin.baked.fbx differ diff --git a/interface/resources/meshes/tablet-with-home-button-small-bezel.fbx b/interface/resources/meshes/tablet-with-home-button-small-bezel.fbx index d94ce30429..9d50c36c15 100644 Binary files a/interface/resources/meshes/tablet-with-home-button-small-bezel.fbx and b/interface/resources/meshes/tablet-with-home-button-small-bezel.fbx differ diff --git a/interface/resources/qml/controls-uit/ComboBox.qml b/interface/resources/qml/controls-uit/ComboBox.qml index ab8a6c2344..be8c9f6740 100644 --- a/interface/resources/qml/controls-uit/ComboBox.qml +++ b/interface/resources/qml/controls-uit/ComboBox.qml @@ -171,6 +171,10 @@ FocusScope { } } + function textAt(index) { + return comboBox.textAt(index); + } + HifiControls.Label { id: comboBoxLabel text: root.label diff --git a/interface/resources/qml/controls-uit/SpinBox.qml b/interface/resources/qml/controls-uit/SpinBox.qml index 9d63122dbc..52d5a2eb99 100644 --- a/interface/resources/qml/controls-uit/SpinBox.qml +++ b/interface/resources/qml/controls-uit/SpinBox.qml @@ -17,6 +17,10 @@ import "../controls-uit" as HifiControls SpinBox { id: spinBox + HifiConstants { + id: hifi + } + property int colorScheme: hifi.colorSchemes.light readonly property bool isLightColorScheme: colorScheme === hifi.colorSchemes.light property string label: "" @@ -31,8 +35,8 @@ SpinBox { property real maximumValue: 0.0 property real realValue: 0.0 - property real realFrom: 0.0 - property real realTo: 100.0 + property real realFrom: minimumValue + property real realTo: maximumValue property real realStepSize: 1.0 signal editingFinished() @@ -53,7 +57,8 @@ SpinBox { onValueChanged: realValue = value/factor stepSize: realStepSize*factor - value: realValue*factor + value: Math.round(realValue*factor) + to : realTo*factor from : realFrom*factor @@ -81,6 +86,7 @@ SpinBox { } valueFromText: function(text, locale) { + spinBox.value = 0; // Force valueChanged signal to be emitted so that validator fires. return Number.fromLocaleString(locale, text)*factor; } @@ -110,7 +116,7 @@ SpinBox { anchors.centerIn: parent text: hifi.glyphs.caratUp size: hifi.dimensions.spinnerSize - color: spinBox.down.pressed || spinBox.up.hovered ? (isLightColorScheme ? hifi.colors.black : hifi.colors.white) : hifi.colors.gray + color: spinBox.up.pressed || spinBox.up.hovered ? (isLightColorScheme ? hifi.colors.black : hifi.colors.white) : hifi.colors.gray } } @@ -149,26 +155,14 @@ SpinBox { visible: spinBox.labelInside != "" } -// MouseArea { -// anchors.fill: parent -// propagateComposedEvents: true -// onWheel: { -// if(spinBox.activeFocus) -// wheel.accepted = false -// else -// wheel.accepted = true -// } -// onPressed: { -// mouse.accepted = false -// } -// onReleased: { -// mouse.accepted = false -// } -// onClicked: { -// mouse.accepted = false -// } -// onDoubleClicked: { -// mouse.accepted = false -// } -// } + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.NoButton + onWheel: { + if (wheel.angleDelta.y > 0) + value += stepSize + else + value -= stepSize + } + } } diff --git a/interface/resources/qml/controls-uit/TextField.qml b/interface/resources/qml/controls-uit/TextField.qml index 6743d08275..917068ac01 100644 --- a/interface/resources/qml/controls-uit/TextField.qml +++ b/interface/resources/qml/controls-uit/TextField.qml @@ -165,11 +165,11 @@ TextField { anchors.left: parent.left Binding on anchors.right { - when: parent.right - value: parent.right + when: textField.right + value: textField.right } Binding on wrapMode { - when: parent.right + when: textField.right value: Text.WordWrap } diff --git a/interface/resources/qml/dialogs/+android/FileDialog.qml b/interface/resources/qml/dialogs/+android/FileDialog.qml index 86e6e1ef6c..be6524d2b8 100644 --- a/interface/resources/qml/dialogs/+android/FileDialog.qml +++ b/interface/resources/qml/dialogs/+android/FileDialog.qml @@ -331,6 +331,7 @@ ModalWindow { } onFolderChanged: { + d.clearSelection(); fileTableModel.update(); // Update once the data from the folder change is available. } @@ -450,7 +451,7 @@ ModalWindow { rows = 0, i; - var newFilesModel = filesModelBuilder.createObject(root); + filesModel = filesModelBuilder.createObject(root); comparisonFunction = sortOrder === Qt.AscendingOrder ? function(a, b) { return a < b; } @@ -472,7 +473,7 @@ ModalWindow { while (lower < upper) { middle = Math.floor((lower + upper) / 2); var lessThan; - if (comparisonFunction(sortValue, newFilesModel.get(middle)[sortField])) { + if (comparisonFunction(sortValue, filesModel.get(middle)[sortField])) { lessThan = true; upper = middle; } else { @@ -481,7 +482,7 @@ ModalWindow { } } - newFilesModel.insert(lower, { + filesModel.insert(lower, { fileName: fileName, fileModified: (fileIsDir ? new Date(0) : model.getItem(i, "fileModified")), fileSize: model.getItem(i, "fileSize"), @@ -492,9 +493,6 @@ ModalWindow { rows++; } - filesModel = newFilesModel; - - d.clearSelection(); } } diff --git a/interface/resources/qml/dialogs/FileDialog.qml b/interface/resources/qml/dialogs/FileDialog.qml index 49bfe78434..6651af0db3 100644 --- a/interface/resources/qml/dialogs/FileDialog.qml +++ b/interface/resources/qml/dialogs/FileDialog.qml @@ -332,6 +332,7 @@ ModalWindow { } onFolderChanged: { + d.clearSelection(); fileTableModel.update(); // Update once the data from the folder change is available. } @@ -451,7 +452,7 @@ ModalWindow { rows = 0, i; - var newFilesModel = filesModelBuilder.createObject(root); + filesModel = filesModelBuilder.createObject(root); comparisonFunction = sortOrder === Qt.AscendingOrder ? function(a, b) { return a < b; } @@ -473,7 +474,7 @@ ModalWindow { while (lower < upper) { middle = Math.floor((lower + upper) / 2); var lessThan; - if (comparisonFunction(sortValue, newFilesModel.get(middle)[sortField])) { + if (comparisonFunction(sortValue, filesModel.get(middle)[sortField])) { lessThan = true; upper = middle; } else { @@ -482,7 +483,7 @@ ModalWindow { } } - newFilesModel.insert(lower, { + filesModel.insert(lower, { fileName: fileName, fileModified: (fileIsDir ? new Date(0) : model.getItem(i, "fileModified")), fileSize: model.getItem(i, "fileSize"), @@ -493,9 +494,6 @@ ModalWindow { rows++; } - filesModel = newFilesModel; - - d.clearSelection(); } } diff --git a/interface/resources/qml/dialogs/TabletFileDialog.qml b/interface/resources/qml/dialogs/TabletFileDialog.qml index e7c93e6d8e..4de0460796 100644 --- a/interface/resources/qml/dialogs/TabletFileDialog.qml +++ b/interface/resources/qml/dialogs/TabletFileDialog.qml @@ -295,7 +295,8 @@ TabletModalWindow { } onFolderChanged: { - fileTableModel.update() + d.clearSelection(); + fileTableModel.update(); } function getItem(index, field) { @@ -413,7 +414,7 @@ TabletModalWindow { rows = 0, i; - var newFilesModel = filesModelBuilder.createObject(root); + filesModel = filesModelBuilder.createObject(root); comparisonFunction = sortOrder === Qt.AscendingOrder ? function(a, b) { return a < b; } @@ -435,7 +436,7 @@ TabletModalWindow { while (lower < upper) { middle = Math.floor((lower + upper) / 2); var lessThan; - if (comparisonFunction(sortValue, newFilesModel.get(middle)[sortField])) { + if (comparisonFunction(sortValue, filesModel.get(middle)[sortField])) { lessThan = true; upper = middle; } else { @@ -444,7 +445,7 @@ TabletModalWindow { } } - newFilesModel.insert(lower, { + filesModel.insert(lower, { fileName: fileName, fileModified: (fileIsDir ? new Date(0) : model.getItem(i, "fileModified")), fileSize: model.getItem(i, "fileSize"), @@ -455,9 +456,6 @@ TabletModalWindow { rows++; } - filesModel = newFilesModel; - - d.clearSelection(); } } diff --git a/interface/resources/qml/hifi/Feed.qml b/interface/resources/qml/hifi/Feed.qml index 3f3a47a297..4cd52a582d 100644 --- a/interface/resources/qml/hifi/Feed.qml +++ b/interface/resources/qml/hifi/Feed.qml @@ -16,10 +16,11 @@ import QtQuick 2.5 import QtGraphicalEffects 1.0 import "toolbars" import "../styles-uit" +import "qrc:////qml//hifi//models" as HifiModels // Absolute path so the same code works everywhere. Column { id: root; - visible: false; + visible: !!suggestions.count; property int cardWidth: 212; property int cardHeight: 152; @@ -32,21 +33,37 @@ Column { property int stackedCardShadowHeight: 4; property int labelSize: 20; - property string metaverseServerUrl: ''; property string protocol: ''; property string actions: 'snapshot'; // sendToScript doesn't get wired until after everything gets created. So we have to queue fillDestinations on nextTick. property string labelText: actions; property string filter: ''; - onFilterChanged: filterChoicesByText(); property var goFunction: null; - property var rpc: null; + property var http: null; HifiConstants { id: hifi } - ListModel { id: suggestions; } + Component.onCompleted: suggestions.getFirstPage(); + HifiModels.PSFListModel { + id: suggestions; + http: root.http; + property var options: [ + 'include_actions=' + actions, + 'restriction=' + (Account.isLoggedIn() ? 'open,hifi' : 'open'), + 'require_online=true', + 'protocol=' + encodeURIComponent(Window.protocolSignature()) + ]; + endpoint: '/api/v1/user_stories?' + options.join('&'); + itemsPerPage: 3; + processPage: function (data) { + return data.user_stories.map(makeModelData); + }; + listModelName: actions; + listView: scroll; + searchFilter: filter; + } function resolveUrl(url) { - return (url.indexOf('/') === 0) ? (metaverseServerUrl + url) : url; + return (url.indexOf('/') === 0) ? (Account.metaverseServerURL + url) : url; } function makeModelData(data) { // create a new obj from data // ListModel elements will only ever have those properties that are defined by the first obj that is added. @@ -55,16 +72,11 @@ Column { tags = data.tags || [data.action, data.username], description = data.description || "", thumbnail_url = data.thumbnail_url || ""; - if (actions === 'concurrency,snapshot') { - // A temporary hack for simulating announcements. We won't use this in production, but if requested, we'll use this data like announcements. - data.details.connections = 4; - data.action = 'announcement'; - } return { place_name: name, username: data.username || "", path: data.path || "", - created_at: data.created_at || "", + created_at: data.created_at || data.updated_at || "", // FIXME why aren't we getting created_at? action: data.action || "", thumbnail_url: resolveUrl(thumbnail_url), image_url: resolveUrl(data.details && data.details.image_url), @@ -74,125 +86,11 @@ Column { tags: tags, description: description, online_users: data.details.connections || data.details.concurrency || 0, - drillDownToPlace: false, - - searchText: [name].concat(tags, description || []).join(' ').toUpperCase() - } - } - property var allStories: []; - property var placeMap: ({}); // Used for making stacks. - property int requestId: 0; - function handleError(url, error, data, cb) { // cb(error) and answer truthy if needed, else falsey - if (!error && (data.status === 'success')) { - return; - } - if (!error) { // Create a message from the data - error = data.status + ': ' + data.error; - } - if (typeof(error) === 'string') { // Make a proper Error object - error = new Error(error); - } - error.message += ' in ' + url; // Include the url. - cb(error); - return true; - } - function getUserStoryPage(pageNumber, cb, cb1) { // cb(error) after all pages of domain data have been added to model - // If supplied, cb1 will be run after the first page IFF it is not the last, for responsiveness. - var options = [ - 'now=' + new Date().toISOString(), - 'include_actions=' + actions, - 'restriction=' + (Account.isLoggedIn() ? 'open,hifi' : 'open'), - 'require_online=true', - 'protocol=' + protocol, - 'page=' + pageNumber - ]; - var url = metaverseBase + 'user_stories?' + options.join('&'); - var thisRequestId = ++requestId; - rpc('request', url, function (error, data) { - if (thisRequestId !== requestId) { - error = 'stale'; - } - if (handleError(url, error, data, cb)) { - return; // abandon stale requests - } - allStories = allStories.concat(data.user_stories.map(makeModelData)); - if ((data.current_page < data.total_pages) && (data.current_page <= 10)) { // just 10 pages = 100 stories for now - if ((pageNumber === 1) && cb1) { - cb1(); - } - return getUserStoryPage(pageNumber + 1, cb); - } - cb(); - }); - } - function fillDestinations() { // Public - console.debug('Feed::fillDestinations()') - - function report(label, error) { - console.log(label, actions, error || 'ok', allStories.length, 'filtered to', suggestions.count); - } - var filter = makeFilteredStoryProcessor(), counter = 0; - allStories = []; - suggestions.clear(); - placeMap = {}; - getUserStoryPage(1, function (error) { - allStories.slice(counter).forEach(filter); - report('user stories update', error); - root.visible = !!suggestions.count; - }, function () { // If there's more than a page, put what we have in the model right away, keeping track of how many are processed. - allStories.forEach(function (story) { - counter++; - filter(story); - root.visible = !!suggestions.count; - }); - report('user stories'); - }); - } - function identity(x) { - return x; - } - function makeFilteredStoryProcessor() { // answer a function(storyData) that adds it to suggestions if it matches - var words = filter.toUpperCase().split(/\s+/).filter(identity); - function suggestable(story) { - // We could filter out places we don't want to suggest, such as those where (story.place_name === AddressManager.placename) or (story.username === Account.username). - return true; - } - function matches(story) { - if (!words.length) { - return suggestable(story); - } - return words.every(function (word) { - return story.searchText.indexOf(word) >= 0; - }); - } - function addToSuggestions(place) { - var collapse = ((actions === 'concurrency,snapshot') && (place.action !== 'concurrency')) || (place.action === 'announcement'); - if (collapse) { - var existing = placeMap[place.place_name]; - if (existing) { - existing.drillDownToPlace = true; - return; - } - } - suggestions.append(place); - if (collapse) { - placeMap[place.place_name] = suggestions.get(suggestions.count - 1); - } else if (place.action === 'concurrency') { - suggestions.get(suggestions.count - 1).drillDownToPlace = true; // Don't change raw place object (in allStories). - } - } - return function (story) { - if (matches(story)) { - addToSuggestions(story); - } + // Server currently doesn't give isStacked (undefined). Could give bool. + drillDownToPlace: (data.isStacked === undefined) ? (data.action !== 'concurrency') : data.isStacked, + isStacked: !!data.isStacked }; } - function filterChoicesByText() { - suggestions.clear(); - placeMap = {}; - allStories.forEach(makeFilteredStoryProcessor()); - root.visible = !!suggestions.count; - } RalewayBold { id: label; @@ -208,6 +106,7 @@ Column { highlightMoveDuration: -1; highlightMoveVelocity: -1; currentIndex: -1; + onAtXEndChanged: { if (scroll.atXEnd && !scroll.atXBeginning) { suggestions.getNextPage(); } } spacing: 12; width: parent.width; @@ -227,6 +126,7 @@ Column { onlineUsers: model.online_users; storyId: model.metaverseId; drillDownToPlace: model.drillDownToPlace; + isStacked: model.isStacked; textPadding: root.textPadding; smallMargin: root.smallMargin; @@ -239,21 +139,4 @@ Column { unhoverThunk: function () { hovered = false } } } - NumberAnimation { - id: anim; - target: scroll; - property: "contentX"; - duration: 250; - } - function scrollToIndex(index) { - anim.running = false; - var pos = scroll.contentX; - var destPos; - scroll.positionViewAtIndex(index, ListView.Contain); - destPos = scroll.contentX; - anim.from = pos; - anim.to = destPos; - scroll.currentIndex = index; - anim.running = true; - } } diff --git a/interface/resources/qml/hifi/NameCard.qml b/interface/resources/qml/hifi/NameCard.qml index c97a802f10..c7c2174e9f 100644 --- a/interface/resources/qml/hifi/NameCard.qml +++ b/interface/resources/qml/hifi/NameCard.qml @@ -319,10 +319,10 @@ Item { visible: thisNameCard.userName !== ""; // Size width: parent.width - height: usernameTextPixelSize + 4 + height: paintedHeight // Anchors - anchors.top: isMyCard ? myDisplayName.bottom : pal.activeTab == "nearbyTab" ? displayNameContainer.bottom : undefined //(parent.height - displayNameTextPixelSize/2)); - anchors.verticalCenter: pal.activeTab == "connectionsTab" && !isMyCard ? avatarImage.verticalCenter : undefined + anchors.top: isMyCard ? myDisplayName.bottom : pal.activeTab == "nearbyTab" ? displayNameContainer.bottom : avatarImage.top //(parent.height - displayNameTextPixelSize/2)); + anchors.bottom: pal.activeTab === "connectionsTab" && !isMyCard ? avatarImage.bottom : undefined anchors.left: avatarImage.right; anchors.leftMargin: avatarImage.visible ? 5 : 0; anchors.rightMargin: 5; diff --git a/interface/resources/qml/hifi/Pal.qml b/interface/resources/qml/hifi/Pal.qml index d779b4ba42..8dcb76442b 100644 --- a/interface/resources/qml/hifi/Pal.qml +++ b/interface/resources/qml/hifi/Pal.qml @@ -18,6 +18,7 @@ import Qt.labs.settings 1.0 import "../styles-uit" import "../controls-uit" as HifiControlsUit import "../controls" as HifiControls +import "qrc:////qml//hifi//models" as HifiModels // Absolute path so the same code works everywhere. // references HMD, Users, UserActivityLogger from root context @@ -37,13 +38,42 @@ Rectangle { property var myData: ({profileUrl: "", displayName: "", userName: "", audioLevel: 0.0, avgAudioLevel: 0.0, admin: true, placeName: "", connection: "", isPresent: true}); // valid dummy until set property var ignored: ({}); // Keep a local list of ignored avatars & their data. Necessary because HashMap is slow to respond after ignoring. property var nearbyUserModelData: []; // This simple list is essentially a mirror of the nearbyUserModel listModel without all the extra complexities. - property var connectionsUserModelData: []; // This simple list is essentially a mirror of the connectionsUserModel listModel without all the extra complexities. property bool iAmAdmin: false; property var activeTab: "nearbyTab"; property bool currentlyEditingDisplayName: false property bool punctuationMode: false; HifiConstants { id: hifi; } + RootHttpRequest { id: http; } + HifiModels.PSFListModel { + id: connectionsUserModel; + http: http; + endpoint: "/api/v1/users?filter=connections"; + property var sortColumn: connectionsTable.getColumn(connectionsTable.sortIndicatorColumn); + sortProperty: switch (sortColumn && sortColumn.role) { + case 'placeName': + 'location'; + break; + case 'connection': + 'is_friend'; + break; + default: + 'username'; + } + sortAscending: connectionsTable.sortIndicatorOrder === Qt.AscendingOrder; + itemsPerPage: 9; + listView: connectionsTable; + processPage: function (data) { + return data.users.map(function (user) { + return { + userName: user.username, + connection: user.connection, + profileUrl: user.images.thumbnail, + placeName: (user.location.root || user.location.domain || {}).name || '' + }; + }); + }; + } // The letterbox used for popup messages LetterboxMessage { @@ -106,16 +136,6 @@ Rectangle { }); return sessionIDs; } - function getSelectedConnectionsUserNames() { - var userNames = []; - connectionsTable.selection.forEach(function (userIndex) { - var datum = connectionsUserModelData[userIndex]; - if (datum) { - userNames.push(datum.userName); - } - }); - return userNames; - } function refreshNearbyWithFilter() { // We should just be able to set settings.filtered to inViewCheckbox.checked, but see #3249, so send to .js for saving. var userIds = getSelectedNearbySessionIDs(); @@ -232,9 +252,7 @@ Rectangle { anchors.fill: parent; onClicked: { if (activeTab != "connectionsTab") { - connectionsLoading.visible = false; - connectionsLoading.visible = true; - pal.sendToScript({method: 'refreshConnections'}); + connectionsUserModel.getFirstPage(); } activeTab = "connectionsTab"; connectionsHelpText.color = hifi.colors.blueAccent; @@ -258,11 +276,7 @@ Rectangle { id: reloadConnections; width: reloadConnections.height; glyph: hifi.glyphs.reload; - onClicked: { - connectionsLoading.visible = false; - connectionsLoading.visible = true; - pal.sendToScript({method: 'refreshConnections'}); - } + onClicked: connectionsUserModel.getFirstPage('delayRefresh'); } } // "CONNECTIONS" text @@ -472,7 +486,7 @@ Rectangle { visible: !isCheckBox && !isButton && !isAvgAudio; uuid: model ? model.sessionId : ""; selected: styleData.selected; - isReplicated: model.isReplicated; + isReplicated: model && model.isReplicated; isAdmin: model && model.admin; isPresent: model && model.isPresent; // Size @@ -702,7 +716,7 @@ Rectangle { anchors.top: parent.top; anchors.topMargin: 185; anchors.horizontalCenter: parent.horizontalCenter; - visible: true; + visible: !connectionsUserModel.retrievedAtLeastOnePage; onVisibleChanged: { if (visible) { connectionsTimeoutTimer.start(); @@ -747,14 +761,6 @@ Rectangle { headerVisible: true; sortIndicatorColumn: settings.connectionsSortIndicatorColumn; sortIndicatorOrder: settings.connectionsSortIndicatorOrder; - onSortIndicatorColumnChanged: { - settings.connectionsSortIndicatorColumn = sortIndicatorColumn; - sortConnectionsModel(); - } - onSortIndicatorOrderChanged: { - settings.connectionsSortIndicatorOrder = sortIndicatorOrder; - sortConnectionsModel(); - } TableViewColumn { id: connectionsUserNameHeader; @@ -779,8 +785,14 @@ Rectangle { resizable: false; } - model: ListModel { - id: connectionsUserModel; + model: connectionsUserModel; + Connections { + target: connectionsTable.flickableItem; + onAtYEndChanged: { + if (connectionsTable.flickableItem.atYEnd && !connectionsTable.flickableItem.atYBeginning) { + connectionsUserModel.getNextPage(); + } + } } // This Rectangle refers to each Row in the connectionsTable. @@ -859,12 +871,9 @@ Rectangle { checked: model && (model.connection === "friend"); boxSize: 24; onClicked: { - var newValue = model.connection !== "friend"; - connectionsUserModel.setProperty(model.userIndex, styleData.role, (newValue ? "friend" : "connection")); - connectionsUserModelData[model.userIndex][styleData.role] = newValue; // Defensive programming - pal.sendToScript({method: newValue ? 'addFriend' : 'removeFriend', params: model.userName}); + pal.sendToScript({method: checked ? 'addFriend' : 'removeFriend', params: model.userName}); - UserActivityLogger["palAction"](newValue ? styleData.role : "un-" + styleData.role, model.sessionId); + UserActivityLogger["palAction"](checked ? styleData.role : "un-" + styleData.role, model.sessionId); } } } @@ -1130,16 +1139,6 @@ Rectangle { sortModel(); reloadNearby.color = 0; break; - case 'connections': - var data = message.params; - if (pal.debug) { - console.log('Got connection data: ', JSON.stringify(data)); - } - connectionsUserModelData = data; - sortConnectionsModel(); - connectionsLoading.visible = false; - connectionsRefreshProblemText.visible = false; - break; case 'select': var sessionIds = message.params[0]; var selected = message.params[1]; @@ -1239,6 +1238,14 @@ Rectangle { reloadNearby.color = 2; } break; + case 'inspectionCertificate_resetCert': + // marketplaces.js sends out a signal to QML with that method when the tablet screen changes and it's not changed to a commerce-related screen. + // We want it to only be handled by the InspectionCertificate.qml, but there's not an easy way of doing that. + // As a part of a "cleanup inspectionCertificate_resetCert" ticket, we'll have to figure out less logspammy way of doing what has to be done. + break; + case 'http.response': + http.handleHttpResponse(message); + break; default: console.log('Unrecognized message:', JSON.stringify(message)); } @@ -1287,45 +1294,6 @@ Rectangle { nearbyTable.positionViewAtRow(newSelectedIndexes[0], ListView.Beginning); } } - function sortConnectionsModel() { - var column = connectionsTable.getColumn(connectionsTable.sortIndicatorColumn); - var sortProperty = column ? column.role : "userName"; - var before = (connectionsTable.sortIndicatorOrder === Qt.AscendingOrder) ? -1 : 1; - var after = -1 * before; - // get selection(s) before sorting - var selectedIDs = getSelectedConnectionsUserNames(); - connectionsUserModelData.sort(function (a, b) { - var aValue = a[sortProperty].toString().toLowerCase(), bValue = b[sortProperty].toString().toLowerCase(); - if (!aValue && !bValue) { - return 0; - } else if (!aValue) { - return after; - } else if (!bValue) { - return before; - } - switch (true) { - case (aValue < bValue): return before; - case (aValue > bValue): return after; - default: return 0; - } - }); - connectionsTable.selection.clear(); - - connectionsUserModel.clear(); - var userIndex = 0; - var newSelectedIndexes = []; - connectionsUserModelData.forEach(function (datum) { - datum.userIndex = userIndex++; - connectionsUserModel.append(datum); - if (selectedIDs.indexOf(datum.sessionId) != -1) { - newSelectedIndexes.push(datum.userIndex); - } - }); - if (newSelectedIndexes.length > 0) { - connectionsTable.selection.select(newSelectedIndexes); - connectionsTable.positionViewAtRow(newSelectedIndexes[0], ListView.Beginning); - } - } signal sendToScript(var message); function noticeSelection() { var userIds = []; diff --git a/interface/resources/qml/hifi/RootHttpRequest.qml b/interface/resources/qml/hifi/RootHttpRequest.qml new file mode 100644 index 0000000000..0355626996 --- /dev/null +++ b/interface/resources/qml/hifi/RootHttpRequest.qml @@ -0,0 +1,39 @@ +// +// RootHttpRequest.qml +// qml/hifi +// +// Create an item of this in the ROOT qml to be able to make http requests. +// Used by PSFListModel.qml +// +// Created by Howard Stearns on 5/29/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 QtQuick 2.5 + +Item { + property var httpCalls: ({}); + property var httpCounter: 0; + // Public function for initiating an http request. + // REQUIRES parent to be root to have sendToScript! + function request(options, callback) { + console.debug('HttpRequest', JSON.stringify(options)); + httpCalls[httpCounter] = callback; + var message = {method: 'http.request', params: options, id: httpCounter++, jsonrpc: "2.0"}; + parent.sendToScript(message); + } + // REQUIRES that parent/root handle http.response message.method in fromScript, by calling this function. + function handleHttpResponse(message) { + var callback = httpCalls[message.id]; // FIXME: as different top level tablet apps gets loaded, the id repeats. We should drop old app callbacks without warning. + if (!callback) { + console.warn('No callback for', JSON.stringify(message)); + return; + } + delete httpCalls[message.id]; + console.debug('HttpRequest response', JSON.stringify(message)); + callback(message.error, message.response); + } +} diff --git a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml index f25282c738..16c1b55930 100644 --- a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml +++ b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml @@ -42,7 +42,7 @@ Rectangle { property bool alreadyOwned: false; property int itemPrice: -1; property bool isCertified; - property string itemType; + property string itemType: "unknown"; property var itemTypesArray: ["entity", "wearable", "contentSet", "app", "avatar", "unknown"]; property var itemTypesText: ["entity", "wearable", "content set", "app", "avatar", "item"]; property var buttonTextNormal: ["REZ", "WEAR", "REPLACE CONTENT SET", "INSTALL", "WEAR", "REZ"]; @@ -98,9 +98,6 @@ Rectangle { } else { root.certificateId = result.data.certificate_id; root.itemHref = result.data.download_url; - if (result.data.categories.indexOf("Wearables") > -1) { - root.itemType = "wearable"; - } root.activeView = "checkoutSuccess"; UserActivityLogger.commercePurchaseSuccess(root.itemId, root.itemAuthor, root.itemPrice, !root.alreadyOwned); } @@ -170,9 +167,6 @@ Rectangle { root.activeView = "checkoutFailure"; } else { root.itemHref = result.data.download_url; - if (result.data.categories.indexOf("Wearables") > -1) { - root.itemType = "wearable"; - } root.activeView = "checkoutSuccess"; } } @@ -186,20 +180,6 @@ Rectangle { itemPreviewImage.source = "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/previews/" + itemId + "/thumbnail/hifi-mp-" + itemId + ".jpg"; } - onItemHrefChanged: { - if (root.itemHref.indexOf(".fst") > -1) { - root.itemType = "avatar"; - } else if (root.itemHref.indexOf('.json.gz') > -1 || root.itemHref.indexOf('.content.zip') > -1) { - root.itemType = "contentSet"; - } else if (root.itemHref.indexOf('.app.json') > -1) { - root.itemType = "app"; - } else if (root.itemHref.indexOf('.json') > -1) { - root.itemType = "entity"; // "wearable" type handled later - } else { - root.itemType = "unknown"; - } - } - onItemTypeChanged: { if (root.itemType === "entity" || root.itemType === "wearable" || root.itemType === "contentSet" || root.itemType === "avatar" || root.itemType === "app") { @@ -1102,6 +1082,7 @@ Rectangle { root.referrer = message.params.referrer; root.itemAuthor = message.params.itemAuthor; root.itemEdition = message.params.itemEdition || -1; + root.itemType = message.params.itemType || "unknown"; refreshBuyUI(); break; default: diff --git a/interface/resources/qml/hifi/commerce/common/sendAsset/ConnectionItem.qml b/interface/resources/qml/hifi/commerce/common/sendAsset/ConnectionItem.qml index 66a9f9a822..41eacd68d5 100644 --- a/interface/resources/qml/hifi/commerce/common/sendAsset/ConnectionItem.qml +++ b/interface/resources/qml/hifi/commerce/common/sendAsset/ConnectionItem.qml @@ -44,7 +44,7 @@ Item { Item { id: avatarImage; - visible: profileUrl !== "" && userName !== ""; + visible: profilePicUrl !== "" && userName !== ""; // Size anchors.verticalCenter: parent.verticalCenter; anchors.left: parent.left; diff --git a/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml b/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml index c7c72e5f7c..3e4bae4780 100644 --- a/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml +++ b/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml @@ -19,6 +19,7 @@ import "../../../../styles-uit" import "../../../../controls-uit" as HifiControlsUit import "../../../../controls" as HifiControls import "../" as HifiCommerceCommon +import "qrc:////qml//hifi//models" as HifiModels // Absolute path so the same code works everywhere. Item { HifiConstants { id: hifi; } @@ -36,6 +37,8 @@ Item { property string assetName: ""; property string assetCertID: ""; property string sendingPubliclyEffectImage; + property var http; + property var listModelName; // This object is always used in a popup or full-screen Wallet section. // This MouseArea is used to prevent a user from being @@ -118,9 +121,7 @@ Item { if (root.currentActiveView === 'chooseRecipientConnection') { // Refresh connections model - connectionsLoading.visible = false; - connectionsLoading.visible = true; - sendSignalToParent({method: 'refreshConnections'}); + connectionsModel.getFirstPage(); } else if (root.currentActiveView === 'sendAssetHome') { Commerce.balance(); } else if (root.currentActiveView === 'chooseRecipientNearby') { @@ -392,11 +393,17 @@ Item { hoverEnabled: true; } - ListModel { + HifiModels.PSFListModel { id: connectionsModel; - } - ListModel { - id: filteredConnectionsModel; + http: root.http; + listModelName: root.listModelName; + endpoint: "/api/v1/users?filter=connections"; + itemsPerPage: 8; + listView: connectionsList; + processPage: function (data) { + return data.users; + }; + searchFilter: filterBar.text; } Rectangle { @@ -472,10 +479,6 @@ Item { anchors.fill: parent; centerPlaceholderGlyph: hifi.glyphs.search; - onTextChanged: { - buildFilteredConnectionsModel(); - } - onAccepted: { focus = false; } @@ -495,6 +498,7 @@ Item { AnimatedImage { id: connectionsLoading; + visible: !connectionsModel.retrievedAtLeastOnePage; source: "../../../../../icons/profilePicLoading.gif" width: 120; height: width; @@ -515,14 +519,15 @@ Item { } visible: !connectionsLoading.visible; clip: true; - model: filteredConnectionsModel; + model: connectionsModel; + onAtYEndChanged: if (connectionsList.atYEnd && !connectionsList.atYBeginning) { connectionsModel.getNextPage(); } snapMode: ListView.SnapToItem; // Anchors anchors.fill: parent; delegate: ConnectionItem { isSelected: connectionsList.currentIndex === index; - userName: model.userName; - profilePicUrl: model.profileUrl; + userName: model.username; + profilePicUrl: model.images.thumbnail; anchors.topMargin: 6; anchors.bottomMargin: 6; @@ -553,7 +558,7 @@ Item { // "Make a Connection" instructions Rectangle { id: connectionInstructions; - visible: connectionsModel.count === 0 && !connectionsLoading.visible; + visible: connectionsModel.count === 0 && !connectionsModel.searchFilter && !connectionsLoading.visible; anchors.fill: parent; color: "white"; @@ -1806,22 +1811,6 @@ Item { // FUNCTION DEFINITIONS START // - function updateConnections(connections) { - connectionsModel.clear(); - connectionsModel.append(connections); - buildFilteredConnectionsModel(); - connectionsLoading.visible = false; - } - - function buildFilteredConnectionsModel() { - filteredConnectionsModel.clear(); - for (var i = 0; i < connectionsModel.count; i++) { - if (connectionsModel.get(i).userName.toLowerCase().indexOf(filterBar.text.toLowerCase()) !== -1) { - filteredConnectionsModel.append(connectionsModel.get(i)); - } - } - } - function resetSendAssetData() { amountTextField.focus = false; optionalMessage.focus = false; diff --git a/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml b/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml index 19b57354dc..b43372da5c 100644 --- a/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml +++ b/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml @@ -163,7 +163,6 @@ Item { Rectangle { id: contextCard; - z: 2; anchors.left: parent.left; anchors.leftMargin: 30; anchors.top: parent.top; @@ -337,7 +336,6 @@ Item { Rectangle { id: permissionExplanationCard; - z: 1; anchors.left: parent.left; anchors.leftMargin: 30; anchors.top: parent.top; @@ -596,8 +594,8 @@ Item { anchors.fill: parent; hoverEnabled: enabled; onClicked: { - contextCard.z = 1; - permissionExplanationCard.z = 0; + contextCard.visible = true; + permissionExplanationCard.visible = false; root.sendToPurchases({ method: 'flipCard' }); } onEntered: { @@ -779,8 +777,8 @@ Item { noPermissionGlyph.color = hifi.colors.redAccent; } onClicked: { - contextCard.z = 0; - permissionExplanationCard.z = 1; + contextCard.visible = false; + permissionExplanationCard.visible = true; root.sendToPurchases({ method: 'flipCard' }); } } diff --git a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml index 8fe1ebe6c9..0d2acf4ec3 100644 --- a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml +++ b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml @@ -16,10 +16,12 @@ import QtQuick 2.5 import "../../../styles-uit" import "../../../controls-uit" as HifiControlsUit import "../../../controls" as HifiControls +import "qrc:////qml//hifi//models" as HifiModels // Absolute path so the same code works everywhere. import "../wallet" as HifiWallet import "../common" as HifiCommerceCommon import "../inspectionCertificate" as HifiInspectionCertificate import "../common/sendAsset" as HifiSendAsset +import "../.." as HifiCommon // references XXX from root context @@ -34,12 +36,17 @@ Rectangle { property bool punctuationMode: false; property bool isShowingMyItems: false; property bool isDebuggingFirstUseTutorial: false; - property int pendingItemCount: 0; property string installedApps; property bool keyboardRaised: false; property int numUpdatesAvailable: 0; // Style color: hifi.colors.white; + function getPurchases() { + root.activeView = "purchasesMain"; + root.installedApps = Commerce.getInstalledApps(); + purchasesModel.getFirstPage(); + Commerce.getAvailableUpdates(); + } Connections { target: Commerce; @@ -62,10 +69,7 @@ Rectangle { if ((Settings.getValue("isFirstUseOfPurchases", true) || root.isDebuggingFirstUseTutorial) && root.activeView !== "firstUseTutorial") { root.activeView = "firstUseTutorial"; } else if (!Settings.getValue("isFirstUseOfPurchases", true) && root.activeView === "initialize") { - root.activeView = "purchasesMain"; - root.installedApps = Commerce.getInstalledApps(); - Commerce.inventory(); - Commerce.getAvailableUpdates(); + getPurchases(); } } else { console.log("ERROR in Purchases.qml: Unknown wallet status: " + walletStatus); @@ -81,39 +85,7 @@ Rectangle { } onInventoryResult: { - purchasesReceived = true; - - if (result.status !== 'success') { - console.log("Failed to get purchases", result.message); - } else if (!purchasesContentsList.dragging) { // Don't modify the view if the user's scrolling - var inventoryResult = processInventoryResult(result.data.assets); - - var currentIndex = purchasesContentsList.currentIndex === -1 ? 0 : purchasesContentsList.currentIndex; - purchasesModel.clear(); - purchasesModel.append(inventoryResult); - - root.pendingItemCount = 0; - for (var i = 0; i < purchasesModel.count; i++) { - if (purchasesModel.get(i).status === "pending") { - root.pendingItemCount++; - } - } - - if (previousPurchasesModel.count !== 0) { - checkIfAnyItemStatusChanged(); - } else { - // Fill statusChanged default value - // Not doing this results in the default being true... - for (var i = 0; i < purchasesModel.count; i++) { - purchasesModel.setProperty(i, "statusChanged", false); - } - } - previousPurchasesModel.append(inventoryResult); - - buildFilteredPurchasesModel(); - - purchasesContentsList.positionViewAtIndex(currentIndex, ListView.Beginning); - } + purchasesModel.handlePage(result.status !== "success" && result.message, result); } onAvailableUpdatesResult: { @@ -134,6 +106,10 @@ Rectangle { } } + onIsShowingMyItemsChanged: { + getPurchases(); + } + Timer { id: notSetUpTimer; interval: 200; @@ -172,8 +148,14 @@ Rectangle { } } + HifiCommon.RootHttpRequest { + id: http; + } + HifiSendAsset.SendAsset { id: sendAsset; + http: http; + listModelName: "Gift Connections"; z: 998; visible: root.activeView === "giftAsset"; anchors.fill: parent; @@ -183,9 +165,7 @@ Rectangle { Connections { onSendSignalToParent: { if (msg.method === 'sendAssetHome_back' || msg.method === 'closeSendAsset') { - root.activeView = "purchasesMain"; - Commerce.inventory(); - Commerce.getAvailableUpdates(); + getPurchases(); } else { sendToScript(msg); } @@ -449,10 +429,7 @@ Rectangle { case 'tutorial_skipClicked': case 'tutorial_finished': Settings.setValue("isFirstUseOfPurchases", false); - root.activeView = "purchasesMain"; - root.installedApps = Commerce.getInstalledApps(); - Commerce.inventory(); - Commerce.getAvailableUpdates(); + getPurchases(); break; } } @@ -528,7 +505,7 @@ Rectangle { }, { "displayName": "Content Set", - "filterName": "contentSet" + "filterName": "content_set" }, { "displayName": "Entity", @@ -540,7 +517,7 @@ Rectangle { }, { "displayName": "Updatable", - "filterName": "updatable" + "filterName": "updated" } ] filterBar.primaryFilterChoices.clear(); @@ -548,14 +525,12 @@ Rectangle { } onPrimaryFilter_displayNameChanged: { - buildFilteredPurchasesModel(); - purchasesContentsList.positionViewAtIndex(0, ListView.Beginning) + purchasesModel.tagsFilter = filterBar.primaryFilter_filterName; filterBar.previousPrimaryFilter = filterBar.primaryFilter_displayName; } onTextChanged: { - buildFilteredPurchasesModel(); - purchasesContentsList.positionViewAtIndex(0, ListView.Beginning) + purchasesModel.searchFilter = filterBar.text; filterBar.previousText = filterBar.text; } } @@ -574,24 +549,41 @@ Rectangle { anchors.topMargin: 16; } - ListModel { + HifiModels.PSFListModel { id: purchasesModel; - } - ListModel { - id: previousPurchasesModel; - } - HifiCommerceCommon.SortableListModel { - id: tempPurchasesModel; - } - HifiCommerceCommon.SortableListModel { - id: filteredPurchasesModel; + itemsPerPage: 6; + listModelName: 'purchases'; + getPage: function () { + console.debug('getPage', purchasesModel.listModelName, root.isShowingMyItems, filterBar.primaryFilter_filterName, purchasesModel.currentPageToRetrieve, purchasesModel.itemsPerPage); + Commerce.inventory( + root.isShowingMyItems ? "proofs" : "purchased", + filterBar.primaryFilter_filterName, + filterBar.text, + purchasesModel.currentPageToRetrieve, + purchasesModel.itemsPerPage + ); + } + processPage: function(data) { + purchasesReceived = true; // HRS FIXME? + data.assets.forEach(function (item) { + if (item.status.length > 1) { console.warn("Unrecognized inventory status", item); } + item.status = item.status[0]; + item.categories = item.categories.join(';'); + item.cardBackVisible = false; + item.isInstalled = root.installedApps.indexOf(item.id) > -1; + item.wornEntityID = ''; + }); + sendToScript({ method: 'purchases_updateWearables' }); + + return data.assets; + } } ListView { id: purchasesContentsList; - visible: (root.isShowingMyItems && filteredPurchasesModel.count !== 0) || (!root.isShowingMyItems && filteredPurchasesModel.count !== 0); + visible: purchasesModel.count !== 0; clip: true; - model: filteredPurchasesModel; + model: purchasesModel; snapMode: ListView.SnapToItem; // Anchors anchors.top: separator.bottom; @@ -608,13 +600,13 @@ Rectangle { itemEdition: model.edition_number; numberSold: model.number_sold; limitedRun: model.limited_run; - displayedItemCount: model.displayedItemCount; - cardBackVisible: model.cardBackVisible; - isInstalled: model.isInstalled; + displayedItemCount: 999; // For now (and maybe longer), we're going to display all the edition numbers. + cardBackVisible: model.cardBackVisible || false; + isInstalled: model.isInstalled || false; wornEntityID: model.wornEntityID; upgradeUrl: model.upgrade_url; upgradeTitle: model.upgrade_title; - itemType: model.itemType; + itemType: model.item_type; isShowingMyItems: root.isShowingMyItems; valid: model.valid; anchors.topMargin: 10; @@ -706,11 +698,11 @@ Rectangle { } else if (msg.method === "setFilterText") { filterBar.text = msg.filterText; } else if (msg.method === "flipCard") { - for (var i = 0; i < filteredPurchasesModel.count; i++) { + for (var i = 0; i < purchasesModel.count; i++) { if (i !== index || msg.closeAll) { - filteredPurchasesModel.setProperty(i, "cardBackVisible", false); + purchasesModel.setProperty(i, "cardBackVisible", false); } else { - filteredPurchasesModel.setProperty(i, "cardBackVisible", true); + purchasesModel.setProperty(i, "cardBackVisible", true); } } } else if (msg.method === "updateItemClicked") { @@ -761,7 +753,7 @@ Rectangle { lightboxPopup.button2text = "CONFIRM"; lightboxPopup.button2method = function() { Entities.deleteEntity(msg.wornEntityID); - filteredPurchasesModel.setProperty(index, 'wornEntityID', ''); + purchasesModel.setProperty(index, 'wornEntityID', ''); root.activeView = "giftAsset"; lightboxPopup.visible = false; }; @@ -773,6 +765,14 @@ Rectangle { } } } + + + onAtYEndChanged: { + if (purchasesContentsList.atYEnd && !purchasesContentsList.atYBeginning) { + console.log("User scrolled to the bottom of 'Purchases'."); + purchasesModel.getNextPage(); + } + } } Rectangle { @@ -953,146 +953,14 @@ Rectangle { // // FUNCTION DEFINITIONS START // - - function processInventoryResult(inventory) { - for (var i = 0; i < inventory.length; i++) { - if (inventory[i].status.length > 1) { - console.log("WARNING: Inventory result index " + i + " has a status of length >1!") - } - inventory[i].status = inventory[i].status[0]; - inventory[i].categories = inventory[i].categories.join(';'); - } - return inventory; - } - - function populateDisplayedItemCounts() { - var itemCountDictionary = {}; - var currentItemId; - for (var i = 0; i < filteredPurchasesModel.count; i++) { - currentItemId = filteredPurchasesModel.get(i).id; - if (itemCountDictionary[currentItemId] === undefined) { - itemCountDictionary[currentItemId] = 1; - } else { - itemCountDictionary[currentItemId]++; - } - } - - for (var i = 0; i < filteredPurchasesModel.count; i++) { - filteredPurchasesModel.setProperty(i, "displayedItemCount", itemCountDictionary[filteredPurchasesModel.get(i).id]); - } - } - - function sortByDate() { - filteredPurchasesModel.sortColumnName = "purchase_date"; - filteredPurchasesModel.isSortingDescending = true; - filteredPurchasesModel.valuesAreNumerical = true; - filteredPurchasesModel.quickSort(); - } - - function buildFilteredPurchasesModel() { - var sameItemCount = 0; - - tempPurchasesModel.clear(); - - for (var i = 0; i < purchasesModel.count; i++) { - if (purchasesModel.get(i).title.toLowerCase().indexOf(filterBar.text.toLowerCase()) !== -1) { - if (purchasesModel.get(i).status !== "confirmed" && !root.isShowingMyItems) { - tempPurchasesModel.insert(0, purchasesModel.get(i)); - } else if ((root.isShowingMyItems && purchasesModel.get(i).edition_number === "0") || - (!root.isShowingMyItems && purchasesModel.get(i).edition_number !== "0")) { - tempPurchasesModel.append(purchasesModel.get(i)); - } - } - } - - // primaryFilter filtering and adding of itemType property to model - var currentItemType, currentRootFileUrl, currentCategories; - for (var i = 0; i < tempPurchasesModel.count; i++) { - currentRootFileUrl = tempPurchasesModel.get(i).root_file_url; - currentCategories = tempPurchasesModel.get(i).categories; - - if (currentRootFileUrl.indexOf(".fst") > -1) { - currentItemType = "avatar"; - } else if (currentCategories.indexOf("Wearables") > -1) { - currentItemType = "wearable"; - } else if (currentRootFileUrl.endsWith('.json.gz') || currentRootFileUrl.endsWith('.content.zip')) { - currentItemType = "contentSet"; - } else if (currentRootFileUrl.endsWith('.app.json')) { - currentItemType = "app"; - } else if (currentRootFileUrl.endsWith('.json')) { - currentItemType = "entity"; - } else { - currentItemType = "unknown"; - } - if (filterBar.primaryFilter_displayName !== "" && - ((filterBar.primaryFilter_displayName === "Updatable" && tempPurchasesModel.get(i).upgrade_url === "") || - (filterBar.primaryFilter_displayName !== "Updatable" && filterBar.primaryFilter_filterName.toLowerCase() !== currentItemType.toLowerCase()))) { - tempPurchasesModel.remove(i); - i--; - } else { - tempPurchasesModel.setProperty(i, 'itemType', currentItemType); - } - } - - for (var i = 0; i < tempPurchasesModel.count; i++) { - if (!filteredPurchasesModel.get(i)) { - sameItemCount = -1; - break; - } else if (tempPurchasesModel.get(i).itemId === filteredPurchasesModel.get(i).itemId && - tempPurchasesModel.get(i).edition_number === filteredPurchasesModel.get(i).edition_number && - tempPurchasesModel.get(i).status === filteredPurchasesModel.get(i).status) { - sameItemCount++; - } - } - - if (sameItemCount !== tempPurchasesModel.count || - filterBar.text !== filterBar.previousText || - filterBar.primaryFilter !== filterBar.previousPrimaryFilter) { - filteredPurchasesModel.clear(); - var currentId; - for (var i = 0; i < tempPurchasesModel.count; i++) { - currentId = tempPurchasesModel.get(i).id; - filteredPurchasesModel.append(tempPurchasesModel.get(i)); - filteredPurchasesModel.setProperty(i, 'cardBackVisible', false); - filteredPurchasesModel.setProperty(i, 'isInstalled', ((root.installedApps).indexOf(currentId) > -1)); - filteredPurchasesModel.setProperty(i, 'wornEntityID', ''); - } - - sendToScript({ method: 'purchases_updateWearables' }); - populateDisplayedItemCounts(); - sortByDate(); - } - } - - function checkIfAnyItemStatusChanged() { - var currentPurchasesModelId, currentPurchasesModelEdition, currentPurchasesModelStatus; - var previousPurchasesModelStatus; - for (var i = 0; i < purchasesModel.count; i++) { - currentPurchasesModelId = purchasesModel.get(i).id; - currentPurchasesModelEdition = purchasesModel.get(i).edition_number; - currentPurchasesModelStatus = purchasesModel.get(i).status; - - for (var j = 0; j < previousPurchasesModel.count; j++) { - previousPurchasesModelStatus = previousPurchasesModel.get(j).status; - if (currentPurchasesModelId === previousPurchasesModel.get(j).id && - currentPurchasesModelEdition === previousPurchasesModel.get(j).edition_number && - currentPurchasesModelStatus !== previousPurchasesModelStatus) { - - purchasesModel.setProperty(i, "statusChanged", true); - } else { - purchasesModel.setProperty(i, "statusChanged", false); - } - } - } - } function updateCurrentlyWornWearables(wearables) { - for (var i = 0; i < filteredPurchasesModel.count; i++) { + for (var i = 0; i < purchasesModel.count; i++) { for (var j = 0; j < wearables.length; j++) { - if (filteredPurchasesModel.get(i).itemType === "wearable" && - wearables[j].entityCertID === filteredPurchasesModel.get(i).certificate_id && - wearables[j].entityEdition.toString() === filteredPurchasesModel.get(i).edition_number) { - filteredPurchasesModel.setProperty(i, 'wornEntityID', wearables[j].entityID); + if (purchasesModel.get(i).itemType === "wearable" && + wearables[j].entityCertID === purchasesModel.get(i).certificate_id && + wearables[j].entityEdition.toString() === purchasesModel.get(i).edition_number) { + purchasesModel.setProperty(i, 'wornEntityID', wearables[j].entityID); break; } } @@ -1149,7 +1017,7 @@ Rectangle { switch (message.method) { case 'updatePurchases': referrerURL = message.referrerURL || ""; - titleBarContainer.referrerURL = message.referrerURL; + titleBarContainer.referrerURL = message.referrerURL || ""; filterBar.text = message.filterText ? message.filterText : ""; break; case 'inspectionCertificate_setCertificateId': @@ -1168,6 +1036,9 @@ Rectangle { case 'updateWearables': updateCurrentlyWornWearables(message.wornWearables); break; + case 'http.response': + http.handleHttpResponse(message); + break; default: console.log('Unrecognized message from marketplaces.js:', JSON.stringify(message)); } diff --git a/interface/resources/qml/hifi/commerce/wallet/Wallet.qml b/interface/resources/qml/hifi/commerce/wallet/Wallet.qml index 86700f702e..603d7fb676 100644 --- a/interface/resources/qml/hifi/commerce/wallet/Wallet.qml +++ b/interface/resources/qml/hifi/commerce/wallet/Wallet.qml @@ -19,6 +19,7 @@ import "../../../controls-uit" as HifiControlsUit import "../../../controls" as HifiControls import "../common" as HifiCommerceCommon import "../common/sendAsset" +import "../.." as HifiCommon Rectangle { HifiConstants { id: hifi; } @@ -343,8 +344,14 @@ Rectangle { } } + HifiCommon.RootHttpRequest { + id: http; + } + SendAsset { id: sendMoney; + http: http; + listModelName: "Send Money Connections"; z: 997; visible: root.activeView === "sendMoney"; anchors.fill: parent; @@ -768,6 +775,13 @@ Rectangle { case 'updateSelectedRecipientUsername': sendMoney.fromScript(message); break; + case 'http.response': + http.handleHttpResponse(message); + break; + case 'palIsStale': + case 'avatarDisconnected': + // Because we don't have "channels" for sending messages to a specific QML object, the messages are broadcast to all QML Items. If an Item of yours happens to be visible when some script sends a message with a method you don't expect, you'll get "Unrecognized message..." logs. + break; default: console.log('Unrecognized message from wallet.js:', JSON.stringify(message)); } diff --git a/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml b/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml index 7a14ee060f..4cf6db7889 100644 --- a/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml +++ b/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml @@ -18,27 +18,17 @@ import QtQuick.Controls 2.2 import "../../../styles-uit" import "../../../controls-uit" as HifiControlsUit import "../../../controls" as HifiControls +import "qrc:////qml//hifi//models" as HifiModels // Absolute path so the same code works everywhere. Item { HifiConstants { id: hifi; } id: root; - property bool initialHistoryReceived: false; - property bool historyRequestPending: true; - property bool noMoreHistoryData: false; - property int pendingCount: 0; - property int currentHistoryPage: 1; - property var pagesAlreadyAdded: new Array(); onVisibleChanged: { if (visible) { - transactionHistoryModel.clear(); Commerce.balance(); - initialHistoryReceived = false; - root.currentHistoryPage = 1; - root.noMoreHistoryData = false; - root.historyRequestPending = true; - Commerce.history(root.currentHistoryPage); + transactionHistoryModel.getFirstPage(); Commerce.getAvailableUpdates(); } else { refreshTimer.stop(); @@ -53,86 +43,7 @@ Item { } onHistoryResult : { - root.initialHistoryReceived = true; - root.historyRequestPending = false; - - if (result.status === 'success') { - var currentPage = parseInt(result.current_page); - - if (result.data.history.length === 0) { - root.noMoreHistoryData = true; - console.log("No more data to retrieve from Commerce.history() endpoint.") - } else if (root.currentHistoryPage === 1) { - var sameItemCount = 0; - tempTransactionHistoryModel.clear(); - - tempTransactionHistoryModel.append(result.data.history); - - for (var i = 0; i < tempTransactionHistoryModel.count; i++) { - if (!transactionHistoryModel.get(i)) { - sameItemCount = -1; - break; - } else if (tempTransactionHistoryModel.get(i).transaction_type === transactionHistoryModel.get(i).transaction_type && - tempTransactionHistoryModel.get(i).text === transactionHistoryModel.get(i).text) { - sameItemCount++; - } - } - - if (sameItemCount !== tempTransactionHistoryModel.count) { - transactionHistoryModel.clear(); - for (var i = 0; i < tempTransactionHistoryModel.count; i++) { - transactionHistoryModel.append(tempTransactionHistoryModel.get(i)); - } - calculatePendingAndInvalidated(); - } - } else { - if (root.pagesAlreadyAdded.indexOf(currentPage) !== -1) { - console.log("Page " + currentPage + " of history has already been added to the list."); - } else { - // First, add the history result to a temporary model - tempTransactionHistoryModel.clear(); - tempTransactionHistoryModel.append(result.data.history); - - // Make a note that we've already added this page to the model... - root.pagesAlreadyAdded.push(currentPage); - - var insertionIndex = 0; - // If there's nothing in the model right now, we don't need to modify insertionIndex. - if (transactionHistoryModel.count !== 0) { - var currentIteratorPage; - // Search through the whole transactionHistoryModel and look for the insertion point. - // The insertion point is found when the result page from the server is less than - // the page that the current item came from, OR when we've reached the end of the whole model. - for (var i = 0; i < transactionHistoryModel.count; i++) { - currentIteratorPage = transactionHistoryModel.get(i).resultIsFromPage; - - if (currentPage < currentIteratorPage) { - insertionIndex = i; - break; - } else if (i === transactionHistoryModel.count - 1) { - insertionIndex = i + 1; - break; - } - } - } - - // Go through the results we just got back from the server, setting the "resultIsFromPage" - // property of those results and adding them to the main model. - for (var i = 0; i < tempTransactionHistoryModel.count; i++) { - tempTransactionHistoryModel.setProperty(i, "resultIsFromPage", currentPage); - transactionHistoryModel.insert(i + insertionIndex, tempTransactionHistoryModel.get(i)) - } - - calculatePendingAndInvalidated(); - } - } - } - - // Only auto-refresh if the user hasn't scrolled - // and there is more data to grab - if (transactionHistory.atYBeginning && !root.noMoreHistoryData) { - refreshTimer.start(); - } + transactionHistoryModel.handlePage(null, result); } onAvailableUpdatesResult: { @@ -147,7 +58,7 @@ Item { Connections { target: GlobalServices onMyUsernameChanged: { - transactionHistoryModel.clear(); + transactionHistoryModel.resetModel(); usernameText.text = Account.username; } } @@ -235,9 +146,8 @@ Item { onTriggered: { if (transactionHistory.atYBeginning) { console.log("Refreshing 1st Page of Recent Activity..."); - root.historyRequestPending = true; Commerce.balance(); - Commerce.history(1); + transactionHistoryModel.getFirstPage("delayedClear"); } } } @@ -299,11 +209,42 @@ Item { } } - ListModel { - id: tempTransactionHistoryModel; - } - ListModel { + HifiModels.PSFListModel { id: transactionHistoryModel; + listModelName: "transaction history"; // For debugging. Alternatively, we could specify endpoint for that purpose, even though it's not used directly. + itemsPerPage: 6; + getPage: function () { + console.debug('getPage', transactionHistoryModel.listModelName, transactionHistoryModel.currentPageToRetrieve); + Commerce.history(transactionHistoryModel.currentPageToRetrieve, transactionHistoryModel.itemsPerPage); + } + processPage: function (data) { + console.debug('processPage', transactionHistoryModel.listModelName, JSON.stringify(data)); + var result, pending; // Set up or get the accumulator for pending. + if (transactionHistoryModel.currentPageToRetrieve == 1) { + pending = {transaction_type: "pendingCount", count: 0}; + result = [pending]; + } else { + pending = transactionHistoryModel.get(0); + result = []; + } + + // Either add to pending, or to result. + // Note that you only see a page of pending stuff until you scroll... + data.history.forEach(function (item) { + if (item.status === 'pending') { + pending.count++; + } else { + result = result.concat(item); + } + }); + + // Only auto-refresh if the user hasn't scrolled + // and there is more data to grab + if (transactionHistory.atYBeginning && data.history.length) { + refreshTimer.start(); + } + return result; + } } Item { anchors.top: recentActivityText.bottom; @@ -312,8 +253,8 @@ Item { anchors.left: parent.left; anchors.right: parent.right; - Item { - visible: transactionHistoryModel.count === 0 && root.initialHistoryReceived; + Item { // On empty history. We don't want to flash and then replace, so don't show until we know we're zero. + visible: transactionHistoryModel.count === 0 && transactionHistoryModel.currentPageToRetrieve < 0; anchors.centerIn: parent; width: parent.width - 12; height: parent.height; @@ -385,10 +326,10 @@ Item { model: transactionHistoryModel; delegate: Item { width: parent.width; - height: (model.transaction_type === "pendingCount" && root.pendingCount !== 0) ? 40 : ((model.status === "confirmed" || model.status === "invalidated") ? transactionText.height + 30 : 0); + height: (model.transaction_type === "pendingCount" && model.count !== 0) ? 40 : ((model.status === "confirmed" || model.status === "invalidated") ? transactionText.height + 30 : 0); Item { - visible: model.transaction_type === "pendingCount" && root.pendingCount !== 0; + visible: model.transaction_type === "pendingCount" && model.count !== 0; anchors.top: parent.top; anchors.left: parent.left; width: parent.width; @@ -397,7 +338,7 @@ Item { AnonymousProRegular { id: pendingCountText; anchors.fill: parent; - text: root.pendingCount + ' Transaction' + (root.pendingCount > 1 ? 's' : '') + ' Pending'; + text: model.count + ' Transaction' + (model.count > 1 ? 's' : '') + ' Pending'; size: 18; color: hifi.colors.blueAccent; verticalAlignment: Text.AlignVCenter; @@ -460,14 +401,9 @@ Item { } } onAtYEndChanged: { - if (transactionHistory.atYEnd) { + if (transactionHistory.atYEnd && !transactionHistory.atYBeginning) { console.log("User scrolled to the bottom of 'Recent Activity'."); - if (!root.historyRequestPending && !root.noMoreHistoryData) { - // Grab next page of results and append to model - root.historyRequestPending = true; - Commerce.history(++root.currentHistoryPage); - console.log("Fetching Page " + root.currentHistoryPage + " of Recent Activity..."); - } + transactionHistoryModel.getNextPage(); } } } @@ -506,40 +442,6 @@ Item { return year + '-' + month + '-' + day + '
' + drawnHour + ':' + min + amOrPm; } - - function calculatePendingAndInvalidated(startingPendingCount) { - var pendingCount = startingPendingCount ? startingPendingCount : 0; - for (var i = 0; i < transactionHistoryModel.count; i++) { - if (transactionHistoryModel.get(i).status === "pending") { - pendingCount++; - } - } - - root.pendingCount = pendingCount; - if (pendingCount > 0) { - transactionHistoryModel.insert(0, {"transaction_type": "pendingCount"}); - } - } - - // - // Function Name: fromScript() - // - // Relevant Variables: - // None - // - // Arguments: - // message: The message sent from the JavaScript. - // Messages are in format "{method, params}", like json-rpc. - // - // Description: - // Called when a message is received from a script. - // - function fromScript(message) { - switch (message.method) { - default: - console.log('Unrecognized message from wallet.js:', JSON.stringify(message)); - } - } signal sendSignalToWallet(var msg); // diff --git a/interface/resources/qml/hifi/dialogs/attachments/Attachment.qml b/interface/resources/qml/hifi/dialogs/attachments/Attachment.qml index 30e03bd02e..54270c2d06 100644 --- a/interface/resources/qml/hifi/dialogs/attachments/Attachment.qml +++ b/interface/resources/qml/hifi/dialogs/attachments/Attachment.qml @@ -119,8 +119,8 @@ Item { colorScheme: hifi.colorSchemes.dark currentIndex: attachment ? model.indexOf(attachment.jointName) : -1 onCurrentIndexChanged: { - if (completed && attachment && currentIndex != -1 && currentText && currentText !== attachment.jointName) { - attachment.jointName = currentText; + if (completed && attachment && currentIndex != -1 && attachment.jointName !== model[currentIndex]) { + attachment.jointName = model[currentIndex]; updateAttachment(); } } diff --git a/interface/resources/qml/hifi/models/PSFListModel.qml b/interface/resources/qml/hifi/models/PSFListModel.qml new file mode 100644 index 0000000000..1bfa2f6ae0 --- /dev/null +++ b/interface/resources/qml/hifi/models/PSFListModel.qml @@ -0,0 +1,168 @@ +// +// PSFListModel.qml +// qml/hifi/commerce/common +// +// PSFListModel +// "PSF" stands for: +// - Paged +// - Sortable +// - Filterable +// +// Created by Zach Fox on 2018-05-15 +// 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 QtQuick 2.7 + +ListModel { + id: root; + // Used when printing debug statements + property string listModelName: endpoint; + + // Parameters. Even if you override getPage, below, please set these for clarity and consistency, when applicable. + // E.g., your getPage function could refer to this sortKey, etc. + property string endpoint; + property string sortProperty; // Currently only handles sorting on one column, which fits with current needs and tables. + property bool sortAscending; + property string sortKey: !sortProperty ? '' : (sortProperty + "," + (sortAscending ? "ASC" : "DESC")); + property string searchFilter: ""; + property string tagsFilter; + + // QML fires the following changed handlers even when first instantiating the Item. So we need a guard against firing them too early. + property bool initialized: false; + Component.onCompleted: initialized = true; + onEndpointChanged: if (initialized) { getFirstPage('delayClear'); } + onSortKeyChanged: if (initialized) { getFirstPage('delayClear'); } + onSearchFilterChanged: if (initialized) { getFirstPage('delayClear'); } + onTagsFilterChanged: if (initialized) { getFirstPage('delayClear'); } + + property int itemsPerPage: 100; + + // State. + property int currentPageToRetrieve: 0; // 0 = before first page. -1 = we have them all. Otherwise 1-based page number. + property bool retrievedAtLeastOnePage: false; + // We normally clear on reset. But if we want to "refresh", we can delay clearing the model until we get a result. + // Not normally set directly, but rather by giving a truthy argument to getFirstPage(true); + property bool delayedClear: false; + function resetModel() { + if (!delayedClear) { root.clear(); } + currentPageToRetrieve = 1; + retrievedAtLeastOnePage = false; + } + + // Page processing. + + // Override to return one property of data, and/or to transform the elements. Must return an array of model elements. + property var processPage: function (data) { return data; } + + property var listView; // Optional. For debugging. + // Check consistency and call processPage. + function handlePage(error, response) { + var processed; + console.debug('handlePage', listModelName, additionalFirstPageRequested, error, JSON.stringify(response)); + function fail(message) { + console.warn("Warning page fail", listModelName, JSON.stringify(message)); + currentPageToRetrieve = -1; + requestPending = false; + delayedClear = false; + } + if (error || (response.status !== 'success')) { + return fail(error || response.status); + } + if (!requestPending) { + return fail("No request in flight."); + } + requestPending = false; + if (response.current_page && response.current_page !== currentPageToRetrieve) { // Not all endpoints specify this property. + return fail("Mismatched page, expected:" + currentPageToRetrieve); + } + processed = processPage(response.data || response); + if (response.total_pages && (response.total_pages === currentPageToRetrieve)) { + currentPageToRetrieve = -1; + } + + if (delayedClear) { + root.clear(); + delayedClear = false; + } + root.append(processed); // FIXME keep index steady, and apply any post sort + retrievedAtLeastOnePage = true; + // Suppose two properties change at once, and both of their change handlers request a new first page. + // (An example is when the a filter box gets cleared with text in it, so that the search and tags are both reset.) + // Or suppose someone just types new search text quicker than the server response. + // In these cases, we would have multiple requests in flight, and signal based responses aren't generally very good + // at matching up the right handler with the right message. Rather than require all the APIs to carefully handle such, + // and also to cut down on useless requests, we take care of that case here. + if (additionalFirstPageRequested) { + console.debug('deferred getFirstPage', listModelName); + additionalFirstPageRequested = false; + getFirstPage('delayedClear'); + } + } + function debugView(label) { + if (!listView) { return; } + console.debug(label, listModelName, 'perPage:', itemsPerPage, 'count:', listView.count, + 'index:', listView.currentIndex, 'section:', listView.currentSection, + 'atYBeginning:', listView.atYBeginning, 'atYEnd:', listView.atYEnd, + 'y:', listView.y, 'contentY:', listView.contentY); + } + + // Override either http or getPage. + property var http; // An Item that has a request function. + property var getPage: function () { // Any override MUST call handlePage(), above, even if results empty. + if (!http) { return console.warn("Neither http nor getPage was set for", listModelName); } + // If it is a path starting with slash, add the metaverseServer domain. + var url = /^\//.test(endpoint) ? (Account.metaverseServerURL + endpoint) : endpoint; + var parameters = [ + 'per_page=' + itemsPerPage, + 'page=' + currentPageToRetrieve + ]; + if (searchFilter) { + parameters.splice(parameters.length, 0, 'search=' + searchFilter); + } + if (sortKey) { + parameters.splice(parameters.length, 0, 'sort=' + sortKey); + } + + var parametersSeparator = /\?/.test(url) ? '&' : '?'; + url = url + parametersSeparator + parameters.join('&'); + console.debug('getPage', listModelName, currentPageToRetrieve); + http.request({uri: url}, handlePage); + } + + // Start the show by retrieving data according to `getPage()`. + // It can be custom-defined by this item's Parent. + property var getFirstPage: function (delayClear) { + if (requestPending) { + console.debug('deferring getFirstPage', listModelName); + additionalFirstPageRequested = true; + return; + } + delayedClear = !!delayClear; + resetModel(); + requestPending = true; + console.debug("getFirstPage", listModelName, currentPageToRetrieve); + getPage(); + } + property bool additionalFirstPageRequested: false; + property bool requestPending: false; // For de-bouncing getNextPage. + // This function, will get the _next_ page of data according to `getPage()`. + // It can be custom-defined by this item's Parent. Typical usage: + // ListView { + // id: theList + // model: thisPSFListModelId + // onAtYEndChanged: if (theList.atYEnd && !theList.atYBeginning) { thisPSFListModelId.getNextPage(); } + // ...} + property var getNextPage: function () { + if (requestPending || currentPageToRetrieve < 0) { + return; + } + currentPageToRetrieve++; + console.debug("getNextPage", listModelName, currentPageToRetrieve); + requestPending = true; + getPage(); + } +} \ No newline at end of file diff --git a/interface/resources/qml/hifi/tablet/ControllerSettings.qml b/interface/resources/qml/hifi/tablet/ControllerSettings.qml index da8334f831..4ad37b7bc8 100644 --- a/interface/resources/qml/hifi/tablet/ControllerSettings.qml +++ b/interface/resources/qml/hifi/tablet/ControllerSettings.qml @@ -69,10 +69,15 @@ Item { id: stack initialItem: inputConfiguration property alias messageVisible: imageMessageBox.visible - property alias selectedPlugin: box.currentText Rectangle { id: inputConfiguration - anchors.fill: parent + anchors { + top: parent.top + left: parent.left + right: parent.right + } + + height: 230 HifiConstants { id: hifi } @@ -168,7 +173,7 @@ Item { text: "show all input devices" onClicked: { - inputPlugins(); + box.model = inputPlugins(); changeSource(); } } @@ -208,25 +213,28 @@ Item { anchors.leftMargin: 10 anchors.topMargin: 30 } + } + Rectangle { + id: loaderRectangle + z: -1 + color: hifi.colors.baseGray + width: parent.width + anchors.left: parent.left + anchors.right: parent.right + anchors.top: inputConfiguration.bottom + anchors.bottom: parent.bottom Loader { id: loader asynchronous: false - - width: inputConfiguration.width - anchors.left: inputConfiguration.left - anchors.right: inputConfiguration.right - anchors.top: configurationHeader.bottom - anchors.topMargin: 10 - anchors.bottom: inputConfiguration.bottom - - source: InputConfiguration.configurationLayout(box.currentText); + anchors.fill: parent + source: InputConfiguration.configurationLayout(box.textAt(box.currentIndex)); onLoaded: { if (loader.item.hasOwnProperty("pluginName")) { - if (box.currentText === "HTC Vive") { + if (box.textAt(box.currentIndex) === "HTC Vive") { loader.item.pluginName = "OpenVR"; } else { - loader.item.pluginName = box.currentText; + loader.item.pluginName = box.textAt(box.currentIndex); } } @@ -252,11 +260,12 @@ Item { function changeSource() { loader.source = ""; + var selectedDevice = box.textAt(box.currentIndex); var source = ""; - if (box.currentText == "Vive") { + if (selectedDevice == "HTC Vive") { source = InputConfiguration.configurationLayout("OpenVR"); } else { - source = InputConfiguration.configurationLayout(box.currentText); + source = InputConfiguration.configurationLayout(selectedDevice); } loader.source = source; diff --git a/interface/resources/qml/hifi/tablet/OpenVrConfiguration.qml b/interface/resources/qml/hifi/tablet/OpenVrConfiguration.qml index 8fb49dffc0..20682372c5 100644 --- a/interface/resources/qml/hifi/tablet/OpenVrConfiguration.qml +++ b/interface/resources/qml/hifi/tablet/OpenVrConfiguration.qml @@ -15,1092 +15,1108 @@ import "../../controls-uit" as HifiControls import "." -Rectangle { - id: openVrConfiguration - +Flickable { + id: flick width: parent.width height: parent.height anchors.fill: parent - - property int leftMargin: 75 - property int countDown: 0 + contentHeight: 550 + flickableDirection: Flickable.VerticalFlick property string pluginName: "" - property var displayInformation: null + property var page: null; - readonly property bool feetChecked: feetBox.checked - readonly property bool hipsChecked: hipBox.checked - readonly property bool chestChecked: chestBox.checked - readonly property bool shouldersChecked: shoulderBox.checked - readonly property bool hmdHead: headBox.checked - readonly property bool headPuck: headPuckBox.checked - readonly property bool handController: handBox.checked - - readonly property bool handPuck: handPuckBox.checked - readonly property bool hmdDesktop: hmdInDesktop.checked - - property int state: buttonState.disabled - property var lastConfiguration: null - - HifiConstants { id: hifi } - - Component { id: screen; CalibratingScreen {} } - QtObject { - id: buttonState - readonly property int disabled: 0 - readonly property int apply: 1 - readonly property int applyAndCalibrate: 2 - readonly property int calibrate: 3 - - } - - MouseArea { - id: mouseArea - - anchors.fill: parent - propagateComposedEvents: true - onPressed: { - parent.forceActiveFocus() - mouse.accepted = false; - } - } - - color: hifi.colors.baseGray - - RalewayBold { - id: head - - text: "Head:" - size: 12 - - color: "white" - - anchors.left: parent.left - anchors.leftMargin: leftMargin - } - - Row { - id: headConfig - anchors.top: head.bottom - anchors.topMargin: 5 - anchors.left: openVrConfiguration.left - anchors.leftMargin: leftMargin + 10 - spacing: 10 - - HifiControls.CheckBox { - id: headBox - width: 15 - height: 15 - boxRadius: 7 - - onClicked: { - if (checked) { - headPuckBox.checked = false; - hmdInDesktop.checked = false; - } else { - checked = true; - } - sendConfigurationSettings(); - } - } - - RalewayBold { - size: 12 - text: stack.selectedPlugin + " HMD" - color: hifi.colors.lightGrayText - } - - HifiControls.CheckBox { - id: headPuckBox - width: 15 - height: 15 - boxRadius: 7 - - onClicked: { - if (checked) { - headBox.checked = false; - hmdInDesktop.checked = false; - } else { - checked = true; - } - sendConfigurationSettings(); - } - } - - RalewayBold { - size: 12 - text: "Tracker" - color: hifi.colors.lightGrayText - } - - HifiControls.CheckBox { - id: hmdInDesktop - width: 15 - height: 15 - boxRadius: 7 - visible: viveInDesktop.checked - - anchors.top: viveInDesktop.bottom - anchors.topMargin: 5 - anchors.left: openVrConfiguration.left - anchors.leftMargin: leftMargin + 10 - - onClicked: { - if (checked) { - headBox.checked = false; - headPuckBox.checked = false; - } else { - checked = true; - } - sendConfigurationSettings(); - } - } - - RalewayBold { - size: 12 - visible: viveInDesktop.checked - text: "None" - color: hifi.colors.lightGrayText - } - } - - Row { - id: headOffsets - anchors.top: headConfig.bottom - anchors.topMargin: 5 - anchors.left: openVrConfiguration.left - anchors.leftMargin: leftMargin + 10 - spacing: 10 - visible: headPuckBox.checked - HifiControls.SpinBox { - id: headYOffset - decimals: 1 - width: 112 - label: "Y Offset" - suffix: " cm" - minimumValue: -10 - realStepSize: 1 - realValue: -5 - colorScheme: hifi.colorSchemes.dark - - onEditingFinished: { - sendConfigurationSettings(); - } - } - - - HifiControls.SpinBox { - id: headZOffset - width: 112 - label: "Z Offset" - minimumValue: -10 - realStepSize: 1 - decimals: 1 - suffix: " cm" - realValue: -5 - colorScheme: hifi.colorSchemes.dark - - onEditingFinished: { - sendConfigurationSettings(); - } - } - } - - RalewayBold { - id: hands - - text: "Hands:" - size: 12 - - color: "white" - - anchors.top: (headOffsets.visible ? headOffsets.bottom : headConfig.bottom) - anchors.topMargin: (headOffsets.visible ? 22 : 10) - anchors.left: parent.left - anchors.leftMargin: leftMargin - } - - Row { - id: handConfig - anchors.top: hands.bottom - anchors.topMargin: 5 - anchors.left: openVrConfiguration.left - anchors.leftMargin: leftMargin + 10 - spacing: 10 - - HifiControls.CheckBox { - id: handBox - width: 15 - height: 15 - boxRadius: 7 - - onClicked: { - if (checked) { - handPuckBox.checked = false; - } else { - checked = true; - } - sendConfigurationSettings(); - } - } - - RalewayBold { - size: 12 - text: "Controllers" - color: hifi.colors.lightGrayText - } - - HifiControls.CheckBox { - id: handPuckBox - width: 12 - height: 15 - boxRadius: 7 - - onClicked: { - if (checked) { - handBox.checked = false; - } else { - checked = true; - } - sendConfigurationSettings(); - } - } - - RalewayBold { - size: 12 - text: "Trackers" - color: hifi.colors.lightGrayText - } - } - - Row { - id: handOffset - visible: handPuckBox.checked - anchors.top: handConfig.bottom - anchors.topMargin: 5 - anchors.left: openVrConfiguration.left - anchors.leftMargin: leftMargin + 10 - spacing: 10 - - HifiControls.SpinBox { - id: handYOffset - decimals: 1 - width: 112 - suffix: " cm" - label: "Y Offset" - minimumValue: -10 - realStepSize: 1 - colorScheme: hifi.colorSchemes.dark - - onEditingFinished: { - sendConfigurationSettings(); - } - } - - - HifiControls.SpinBox { - id: handZOffset - width: 112 - label: "Z Offset" - suffix: " cm" - minimumValue: -10 - realStepSize: 1 - decimals: 1 - colorScheme: hifi.colorSchemes.dark - - onEditingFinished: { - sendConfigurationSettings(); - } - } - } - - RalewayBold { - id: additional - - text: "Additional Trackers" - size: 12 - - color: hifi.colors.white - - anchors.top: (handOffset.visible ? handOffset.bottom : handConfig.bottom) - anchors.topMargin: (handOffset.visible ? 22 : 10) - anchors.left: parent.left - anchors.leftMargin: leftMargin - } - - RalewayRegular { - id: info - - text: "See Recommended Tracker Placement" - color: hifi.colors.blueHighlight - size: 10 - anchors { - left: additional.right - leftMargin: 10 - verticalCenter: additional.verticalCenter - } - - Rectangle { - id: selected - color: hifi.colors.blueHighlight - - width: info.width - height: 1 - - anchors { - top: info.bottom - topMargin: 1 - left: info.left - right: info.right - } - - visible: false - } - - MouseArea { - anchors.fill: parent; - hoverEnabled: true - - onEntered: { - selected.visible = true; - } - - onExited: { - selected.visible = false; - } - onClicked: { - stack.messageVisible = true; - } - } - } - - Row { - id: feetConfig - anchors.top: additional.bottom - anchors.topMargin: 15 - anchors.left: openVrConfiguration.left - anchors.leftMargin: leftMargin + 10 - spacing: 10 - - HifiControls.CheckBox { - id: feetBox - width: 15 - height: 15 - boxRadius: 7 - - onClicked: { - if (!checked) { - shoulderBox.checked = false; - chestBox.checked = false; - hipBox.checked = false; - } - sendConfigurationSettings(); - } - } - - RalewayBold { - size: 12 - text: "Feet" - color: hifi.colors.lightGrayText - } - } - - Row { - id: hipConfig - anchors.top: feetConfig.bottom - anchors.topMargin: 15 - anchors.left: openVrConfiguration.left - anchors.leftMargin: leftMargin + 10 - spacing: 10 - - HifiControls.CheckBox { - id: hipBox - width: 15 - height: 15 - boxRadius: 7 - - onClicked: { - if (checked) { - feetBox.checked = true; - } - - if (chestChecked) { - checked = true; - } - sendConfigurationSettings(); - } - } - - RalewayBold { - size: 12 - text: "Hips" - color: hifi.colors.lightGrayText - } - - RalewayRegular { - size: 12 - text: "requires feet" - color: hifi.colors.lightGray - } - } - - - Row { - id: chestConfig - anchors.top: hipConfig.bottom - anchors.topMargin: 15 - anchors.left: openVrConfiguration.left - anchors.leftMargin: leftMargin + 10 - spacing: 10 - - HifiControls.CheckBox { - id: chestBox - width: 15 - height: 15 - boxRadius: 7 - - onClicked: { - if (checked) { - hipBox.checked = true; - feetBox.checked = true; - shoulderBox.checked = false; - } - sendConfigurationSettings(); - } - } - - RalewayBold { - size: 12 - text: "Chest" - color: hifi.colors.lightGrayText - } - - RalewayRegular { - size: 12 - text: "requires hips" - color: hifi.colors.lightGray - } - } - - - Row { - id: shoulderConfig - anchors.top: chestConfig.bottom - anchors.topMargin: 15 - anchors.left: openVrConfiguration.left - anchors.leftMargin: leftMargin + 10 - spacing: 10 - - HifiControls.CheckBox { - id: shoulderBox - width: 15 - height: 15 - boxRadius: 7 - - onClicked: { - if (checked) { - hipBox.checked = true; - feetBox.checked = true; - chestBox.checked = false; - } - sendConfigurationSettings(); - } - } - - RalewayBold { - size: 12 - text: "Shoulders" - color: hifi.colors.lightGrayText - } - - RalewayRegular { - size: 12 - text: "requires hips" - color: hifi.colors.lightGray - } - } - - Row { - id: shoulderAdditionalConfig - visible: shoulderBox.checked - anchors.top: shoulderConfig.bottom - anchors.topMargin: 5 - anchors.left: openVrConfiguration.left - anchors.leftMargin: leftMargin + 20 - spacing: 10 - - HifiControls.SpinBox { - id: armCircumference - decimals: 1 - width: 160 - suffix: " cm" - label: "Arm Circumference" - minimumValue: 0 - realStepSize: 1.0 - colorScheme: hifi.colorSchemes.dark - realValue: 33.0 - - onEditingFinished: { - sendConfigurationSettings(); - } - } - - HifiControls.SpinBox { - id: shoulderWidth - width: 160 - label: "Shoulder Width" - suffix: " cm" - minimumValue: 0 - realStepSize: 1.0 - decimals: 1 - colorScheme: hifi.colorSchemes.dark - realValue: 48 - - onEditingFinished: { - sendConfigurationSettings(); - } - } - } - - Separator { - id: bottomSeperator - width: parent.width - anchors.top: shoulderAdditionalConfig.visible ? shoulderAdditionalConfig.bottom : shoulderConfig.bottom - anchors.topMargin: (shoulderAdditionalConfig.visible ? 25 : 10) - } - - Rectangle { - id: calibrationButton - property int color: hifi.buttons.blue - property int colorScheme: hifi.colorSchemes.light - property string glyph: hifi.glyphs.avatar1 - property bool enabled: false - property bool pressed: false - property bool hovered: false - property int size: 32 - property string text: "apply" - property int padding: 12 - - width: glyphButton.width + calibrationText.width + padding - height: hifi.dimensions.controlLineHeight - anchors.top: bottomSeperator.bottom - anchors.topMargin: 15 - anchors.left: parent.left - anchors.leftMargin: leftMargin - - radius: hifi.buttons.radius - - gradient: Gradient { - GradientStop { - position: 0.2 - color: { - if (!calibrationButton.enabled) { - hifi.buttons.disabledColorStart[calibrationButton.colorScheme] - } else if (calibrationButton.pressed) { - hifi.buttons.pressedColor[calibrationButton.color] - } else if (calibrationButton.hovered) { - hifi.buttons.hoveredColor[calibrationButton.color] - } else { - hifi.buttons.colorStart[calibrationButton.color] - } - } - } - - GradientStop { - position: 1.0 - color: { - if (!calibrationButton.enabled) { - hifi.buttons.disabledColorFinish[calibrationButton.colorScheme] - } else if (calibrationButton.pressed) { - hifi.buttons.pressedColor[calibrationButton.color] - } else if (calibrationButton.hovered) { - hifi.buttons.hoveredColor[calibrationButton.color] - } else { - hifi.buttons.colorFinish[calibrationButton.color] - } - } - } - } - - HiFiGlyphs { - id: glyphButton - color: enabled ? hifi.buttons.textColor[calibrationButton.color] - : hifi.buttons.disabledTextColor[calibrationButton.colorScheme] - text: calibrationButton.glyph - size: calibrationButton.size - - anchors { - top: parent.top - bottom: parent.bottom - bottomMargin: 1 - } - } - - RalewayBold { - id: calibrationText - font.capitalization: Font.AllUppercase - color: enabled ? hifi.buttons.textColor[calibrationButton.color] - : hifi.buttons.disabledTextColor[calibrationButton.colorScheme] - size: hifi.fontSizes.buttonLabel - text: calibrationButton.text - - anchors { - left: glyphButton.right - top: parent.top - topMargin: 7 - } - } - - MouseArea { - anchors.fill: parent - hoverEnabled: true - onClicked: { - if (calibrationButton.enabled) { - if (openVrConfiguration.state === buttonState.apply) { - InputConfiguration.uncalibratePlugin(pluginName); - updateCalibrationButton(); - } else { - calibrationTimer.interval = timeToCalibrate.realValue * 1000 - openVrConfiguration.countDown = timeToCalibrate.realValue; - var calibratingScreen = screen.createObject(); - stack.push(calibratingScreen); - calibratingScreen.canceled.connect(cancelCalibration); - calibratingScreen.restart.connect(restartCalibration); - calibratingScreen.start(calibrationTimer.interval, timeToCalibrate.realValue); - calibrationTimer.start(); - } - } - } - - onPressed: { - calibrationButton.pressed = true; - } - - onReleased: { - calibrationButton.pressed = false; - } - - onEntered: { - calibrationButton.hovered = true; - } - - onExited: { - calibrationButton.hovered = false; - } - } - } - - Timer { - id: calibrationTimer - repeat: false - interval: 20 - onTriggered: { - InputConfiguration.calibratePlugin(pluginName) - } - } - - Timer { - id: displayTimer - repeat: false - interval: 3000 - onTriggered: { + onPluginNameChanged: { + if (page !== null) { + page.pluginName = flick.pluginName; } } Component.onCompleted: { - InputConfiguration.calibrationStatus.connect(calibrationStatusInfo); - lastConfiguration = composeConfigurationSettings(); + page = config.createObject(flick.contentItem); } + Component { + id: config + Rectangle { + id: openVrConfiguration + property int leftMargin: 75 + property int countDown: 0 + property string pluginName: "" + property var displayInformation: null - Component.onDestruction: { - var settings = InputConfiguration.configurationSettings(pluginName); - var data = { - "num_pucks": settings["puckCount"] - } - UserActivityLogger.logAction("mocap_ui_close_dialog", data); - } + readonly property bool feetChecked: feetBox.checked + readonly property bool hipsChecked: hipBox.checked + readonly property bool chestChecked: chestBox.checked + readonly property bool shouldersChecked: shoulderBox.checked + readonly property bool hmdHead: headBox.checked + readonly property bool headPuck: headPuckBox.checked + readonly property bool handController: handBox.checked - HifiControls.SpinBox { - id: timeToCalibrate - width: 70 - anchors.top: calibrationButton.bottom - anchors.topMargin: 20 - anchors.left: parent.left - anchors.leftMargin: leftMargin + readonly property bool handPuck: handPuckBox.checked + readonly property bool hmdDesktop: hmdInDesktop.checked - minimumValue: 5 - realValue: 5 - colorScheme: hifi.colorSchemes.dark + property int state: buttonState.disabled + property var lastConfiguration: null - onEditingFinished: { - calibrationTimer.interval = realValue * 1000; - openVrConfiguration.countDown = realValue; - numberAnimation.duration = calibrationTimer.interval; - } - } + HifiConstants { id: hifi } - RalewayBold { - id: delayTextInfo - size: 10 - text: "Delay Before Calibration Starts" - color: hifi.colors.white + Component { id: screen; CalibratingScreen {} } + QtObject { + id: buttonState + readonly property int disabled: 0 + readonly property int apply: 1 + readonly property int applyAndCalibrate: 2 + readonly property int calibrate: 3 - anchors { - left: timeToCalibrate.right - leftMargin: 20 - verticalCenter: timeToCalibrate.verticalCenter - } - } - - RalewayRegular { - size: 12 - text: "sec" - color: hifi.colors.lightGray - - anchors { - left: delayTextInfo.right - leftMargin: 10 - verticalCenter: delayTextInfo.verticalCenter - } - } - - Separator { - id: advanceSeperator - width: parent.width - anchors.top: timeToCalibrate.bottom - anchors.topMargin: 10 - } - - RalewayBold { - id: advanceSettings - - text: "Advanced Settings" - size: 12 - - color: hifi.colors.white - - anchors.top: advanceSeperator.bottom - anchors.topMargin: 10 - anchors.left: parent.left - anchors.leftMargin: leftMargin - } - - - HifiControls.CheckBox { - id: viveInDesktop - width: 15 - height: 15 - boxRadius: 7 - - anchors.top: advanceSettings.bottom - anchors.topMargin: 5 - anchors.left: openVrConfiguration.left - anchors.leftMargin: leftMargin + 10 - - onClicked: { - if (!checked & hmdInDesktop.checked) { - headBox.checked = true; - headPuckBox.checked = false; - hmdInDesktop.checked = false; - } - sendConfigurationSettings(); - } - } - - RalewayBold { - id: viveDesktopText - size: 10 - text: "Use " + stack.selectedPlugin + " devices in desktop mode" - color: hifi.colors.white - - anchors { - left: viveInDesktop.right - leftMargin: 5 - verticalCenter: viveInDesktop.verticalCenter - } - } - - - NumberAnimation { - id: numberAnimation - target: openVrConfiguration - property: "countDown" - to: 0 - } - - - function logAction(action, status) { - console.log("calibrated from ui"); - var data = { - "num_pucks": status["puckCount"], - "puck_configuration": status["configuration"], - "head_puck": status["head_puck"], - "hand_puck": status["hand_pucks"] - } - UserActivityLogger.logAction(action, data); - } - - function calibrationStatusInfo(status) { - var calibrationScreen = stack.currentItem; - - if (!status["UI"]) { - calibratingScreen = screen.createObject(); - stack.push(calibratingScreen); - } - - if (status["calibrated"]) { - calibrationScreen.success(); - - if (status["UI"]) { - logAction("mocap_ui_success", status); } - } else if (!status["calibrated"]) { - calibrationScreen.failure(); + MouseArea { + id: mouseArea - if (status["UI"]) { - logAction("mocap_ui_failed", status); + anchors.fill: parent + propagateComposedEvents: true + onPressed: { + parent.forceActiveFocus() + mouse.accepted = false; + } + } + + color: hifi.colors.baseGray + + RalewayBold { + id: head + + text: "Head:" + size: 12 + + color: "white" + anchors.left: openVrConfiguration.left + anchors.leftMargin: leftMargin + } + + Row { + id: headConfig + anchors.top: head.bottom + anchors.topMargin: 5 + anchors.left: openVrConfiguration.left + anchors.leftMargin: leftMargin + 10 + spacing: 10 + + HifiControls.CheckBox { + id: headBox + width: 15 + height: 15 + boxRadius: 7 + + onClicked: { + if (checked) { + headPuckBox.checked = false; + hmdInDesktop.checked = false; + } else { + checked = true; + } + sendConfigurationSettings(); + } + } + + RalewayBold { + size: 12 + text: stack.selectedPlugin + " HMD" + color: hifi.colors.lightGrayText + } + + HifiControls.CheckBox { + id: headPuckBox + width: 15 + height: 15 + boxRadius: 7 + + onClicked: { + if (checked) { + headBox.checked = false; + hmdInDesktop.checked = false; + } else { + checked = true; + } + sendConfigurationSettings(); + } + } + + RalewayBold { + size: 12 + text: "Tracker" + color: hifi.colors.lightGrayText + } + + HifiControls.CheckBox { + id: hmdInDesktop + width: 15 + height: 15 + boxRadius: 7 + visible: viveInDesktop.checked + + anchors.top: viveInDesktop.bottom + anchors.topMargin: 5 + anchors.left: openVrConfiguration.left + anchors.leftMargin: leftMargin + 10 + + onClicked: { + if (checked) { + headBox.checked = false; + headPuckBox.checked = false; + } else { + checked = true; + } + sendConfigurationSettings(); + } + } + + RalewayBold { + size: 12 + visible: viveInDesktop.checked + text: "None" + color: hifi.colors.lightGrayText + } + } + + Row { + id: headOffsets + anchors.top: headConfig.bottom + anchors.topMargin: 5 + anchors.left: openVrConfiguration.left + anchors.leftMargin: leftMargin + 10 + spacing: 10 + visible: headPuckBox.checked + HifiControls.SpinBox { + id: headYOffset + decimals: 1 + width: 112 + label: "Y Offset" + suffix: " cm" + minimumValue: -10 + realStepSize: 1 + realValue: -5 + colorScheme: hifi.colorSchemes.dark + + onEditingFinished: { + sendConfigurationSettings(); + } + } + + + HifiControls.SpinBox { + id: headZOffset + width: 112 + label: "Z Offset" + minimumValue: -10 + realStepSize: 1 + decimals: 1 + suffix: " cm" + realValue: -5 + colorScheme: hifi.colorSchemes.dark + + onEditingFinished: { + sendConfigurationSettings(); + } + } + } + + RalewayBold { + id: hands + + text: "Hands:" + size: 12 + + color: "white" + + anchors.top: (headOffsets.visible ? headOffsets.bottom : headConfig.bottom) + anchors.topMargin: (headOffsets.visible ? 22 : 10) + anchors.left: openVrConfiguration.left + anchors.leftMargin: leftMargin + } + + Row { + id: handConfig + anchors.top: hands.bottom + anchors.topMargin: 5 + anchors.left: openVrConfiguration.left + anchors.leftMargin: leftMargin + 10 + spacing: 10 + + HifiControls.CheckBox { + id: handBox + width: 15 + height: 15 + boxRadius: 7 + + onClicked: { + if (checked) { + handPuckBox.checked = false; + } else { + checked = true; + } + sendConfigurationSettings(); + } + } + + RalewayBold { + size: 12 + text: "Controllers" + color: hifi.colors.lightGrayText + } + + HifiControls.CheckBox { + id: handPuckBox + width: 12 + height: 15 + boxRadius: 7 + + onClicked: { + if (checked) { + handBox.checked = false; + } else { + checked = true; + } + sendConfigurationSettings(); + } + } + + RalewayBold { + size: 12 + text: "Trackers" + color: hifi.colors.lightGrayText + } + } + + Row { + id: handOffset + visible: handPuckBox.checked + anchors.top: handConfig.bottom + anchors.topMargin: 5 + anchors.left: openVrConfiguration.left + anchors.leftMargin: leftMargin + 10 + spacing: 10 + + HifiControls.SpinBox { + id: handYOffset + decimals: 1 + width: 112 + suffix: " cm" + label: "Y Offset" + minimumValue: -10 + realStepSize: 1 + colorScheme: hifi.colorSchemes.dark + + onEditingFinished: { + sendConfigurationSettings(); + } + } + + + HifiControls.SpinBox { + id: handZOffset + width: 112 + label: "Z Offset" + suffix: " cm" + minimumValue: -10 + realStepSize: 1 + decimals: 1 + colorScheme: hifi.colorSchemes.dark + + onEditingFinished: { + sendConfigurationSettings(); + } + } + } + + RalewayBold { + id: additional + + text: "Additional Trackers" + size: 12 + + color: hifi.colors.white + + anchors.top: (handOffset.visible ? handOffset.bottom : handConfig.bottom) + anchors.topMargin: (handOffset.visible ? 22 : 10) + anchors.left: parent.left + anchors.leftMargin: leftMargin + } + + RalewayRegular { + id: info + + text: "See Recommended Tracker Placement" + color: hifi.colors.blueHighlight + size: 10 + anchors { + left: additional.right + leftMargin: 10 + verticalCenter: additional.verticalCenter + } + + Rectangle { + id: selected + color: hifi.colors.blueHighlight + + width: info.width + height: 1 + + anchors { + top: info.bottom + topMargin: 1 + left: info.left + right: info.right + } + + visible: false + } + + MouseArea { + anchors.fill: parent; + hoverEnabled: true + + onEntered: { + selected.visible = true; + } + + onExited: { + selected.visible = false; + } + onClicked: { + stack.messageVisible = true; + } + } + } + + Row { + id: feetConfig + anchors.top: additional.bottom + anchors.topMargin: 15 + anchors.left: parent.left + anchors.leftMargin: leftMargin + 10 + spacing: 10 + + HifiControls.CheckBox { + id: feetBox + width: 15 + height: 15 + boxRadius: 7 + + onClicked: { + if (!checked) { + shoulderBox.checked = false; + chestBox.checked = false; + hipBox.checked = false; + } + sendConfigurationSettings(); + } + } + + RalewayBold { + size: 12 + text: "Feet" + color: hifi.colors.lightGrayText + } + } + + Row { + id: hipConfig + anchors.top: feetConfig.bottom + anchors.topMargin: 15 + anchors.left: openVrConfiguration.left + anchors.leftMargin: leftMargin + 10 + spacing: 10 + + HifiControls.CheckBox { + id: hipBox + width: 15 + height: 15 + boxRadius: 7 + + onClicked: { + if (checked) { + feetBox.checked = true; + } + + if (chestChecked) { + checked = true; + } + sendConfigurationSettings(); + } + } + + RalewayBold { + size: 12 + text: "Hips" + color: hifi.colors.lightGrayText + } + + RalewayRegular { + size: 12 + text: "requires feet" + color: hifi.colors.lightGray + } + } + + + Row { + id: chestConfig + anchors.top: hipConfig.bottom + anchors.topMargin: 15 + anchors.left: openVrConfiguration.left + anchors.leftMargin: leftMargin + 10 + spacing: 10 + + HifiControls.CheckBox { + id: chestBox + width: 15 + height: 15 + boxRadius: 7 + + onClicked: { + if (checked) { + hipBox.checked = true; + feetBox.checked = true; + shoulderBox.checked = false; + } + sendConfigurationSettings(); + } + } + + RalewayBold { + size: 12 + text: "Chest" + color: hifi.colors.lightGrayText + } + + RalewayRegular { + size: 12 + text: "requires hips" + color: hifi.colors.lightGray + } + } + + + Row { + id: shoulderConfig + anchors.top: chestConfig.bottom + anchors.topMargin: 15 + anchors.left: openVrConfiguration.left + anchors.leftMargin: leftMargin + 10 + spacing: 10 + + HifiControls.CheckBox { + id: shoulderBox + width: 15 + height: 15 + boxRadius: 7 + + onClicked: { + if (checked) { + hipBox.checked = true; + feetBox.checked = true; + chestBox.checked = false; + } + sendConfigurationSettings(); + } + } + + RalewayBold { + size: 12 + text: "Shoulders" + color: hifi.colors.lightGrayText + } + + RalewayRegular { + size: 12 + text: "requires hips" + color: hifi.colors.lightGray + } + } + + Row { + id: shoulderAdditionalConfig + visible: shoulderBox.checked + anchors.top: shoulderConfig.bottom + anchors.topMargin: 5 + anchors.left: openVrConfiguration.left + anchors.leftMargin: leftMargin + 20 + spacing: 10 + + HifiControls.SpinBox { + id: armCircumference + decimals: 1 + width: 160 + suffix: " cm" + label: "Arm Circumference" + minimumValue: 0 + realStepSize: 1.0 + colorScheme: hifi.colorSchemes.dark + realValue: 33.0 + + onEditingFinished: { + sendConfigurationSettings(); + } + } + + HifiControls.SpinBox { + id: shoulderWidth + width: 160 + label: "Shoulder Width" + suffix: " cm" + minimumValue: 0 + realStepSize: 1.0 + decimals: 1 + colorScheme: hifi.colorSchemes.dark + realValue: 48 + + onEditingFinished: { + sendConfigurationSettings(); + } + } + } + + Separator { + id: bottomSeperator + width: parent.width + anchors.top: shoulderAdditionalConfig.visible ? shoulderAdditionalConfig.bottom : shoulderConfig.bottom + anchors.topMargin: (shoulderAdditionalConfig.visible ? 25 : 10) + } + + Rectangle { + id: calibrationButton + property int color: hifi.buttons.blue + property int colorScheme: hifi.colorSchemes.light + property string glyph: hifi.glyphs.avatar1 + property bool enabled: false + property bool pressed: false + property bool hovered: false + property int size: 32 + property string text: "apply" + property int padding: 12 + + width: glyphButton.width + calibrationText.width + padding + height: hifi.dimensions.controlLineHeight + anchors.top: bottomSeperator.bottom + anchors.topMargin: 15 + anchors.left: parent.left + anchors.leftMargin: leftMargin + + radius: hifi.buttons.radius + + gradient: Gradient { + GradientStop { + position: 0.2 + color: { + if (!calibrationButton.enabled) { + hifi.buttons.disabledColorStart[calibrationButton.colorScheme] + } else if (calibrationButton.pressed) { + hifi.buttons.pressedColor[calibrationButton.color] + } else if (calibrationButton.hovered) { + hifi.buttons.hoveredColor[calibrationButton.color] + } else { + hifi.buttons.colorStart[calibrationButton.color] + } + } + } + + GradientStop { + position: 1.0 + color: { + if (!calibrationButton.enabled) { + hifi.buttons.disabledColorFinish[calibrationButton.colorScheme] + } else if (calibrationButton.pressed) { + hifi.buttons.pressedColor[calibrationButton.color] + } else if (calibrationButton.hovered) { + hifi.buttons.hoveredColor[calibrationButton.color] + } else { + hifi.buttons.colorFinish[calibrationButton.color] + } + } + } + } + + HiFiGlyphs { + id: glyphButton + color: enabled ? hifi.buttons.textColor[calibrationButton.color] + : hifi.buttons.disabledTextColor[calibrationButton.colorScheme] + text: calibrationButton.glyph + size: calibrationButton.size + + anchors { + top: parent.top + bottom: parent.bottom + bottomMargin: 1 + } + } + + RalewayBold { + id: calibrationText + font.capitalization: Font.AllUppercase + color: enabled ? hifi.buttons.textColor[calibrationButton.color] + : hifi.buttons.disabledTextColor[calibrationButton.colorScheme] + size: hifi.fontSizes.buttonLabel + text: calibrationButton.text + + anchors { + left: glyphButton.right + top: parent.top + topMargin: 7 + } + } + + MouseArea { + anchors.fill: parent + hoverEnabled: true + onClicked: { + if (calibrationButton.enabled) { + if (openVrConfiguration.state === buttonState.apply) { + InputConfiguration.uncalibratePlugin(openVrConfiguration.pluginName); + updateCalibrationButton(); + } else { + calibrationTimer.interval = timeToCalibrate.realValue * 1000 + openVrConfiguration.countDown = timeToCalibrate.realValue; + var calibratingScreen = screen.createObject(); + stack.push(calibratingScreen); + calibratingScreen.canceled.connect(cancelCalibration); + calibratingScreen.restart.connect(restartCalibration); + calibratingScreen.start(calibrationTimer.interval, timeToCalibrate.realValue); + calibrationTimer.start(); + } + } + } + + onPressed: { + calibrationButton.pressed = true; + } + + onReleased: { + calibrationButton.pressed = false; + } + + onEntered: { + calibrationButton.hovered = true; + } + + onExited: { + calibrationButton.hovered = false; + } + } + } + + Timer { + id: calibrationTimer + repeat: false + interval: 20 + onTriggered: { + InputConfiguration.calibratePlugin(openVrConfiguration.pluginName) + } + } + + Timer { + id: displayTimer + repeat: false + interval: 3000 + onTriggered: { + } + } + + Component.onCompleted: { + InputConfiguration.calibrationStatus.connect(calibrationStatusInfo); + lastConfiguration = composeConfigurationSettings(); + } + + Component.onDestruction: { + var settings = InputConfiguration.configurationSettings(openVrConfiguration.pluginName); + var data = { + "num_pucks": settings["puckCount"] + } + UserActivityLogger.logAction("mocap_ui_close_dialog", data); + } + + HifiControls.SpinBox { + id: timeToCalibrate + width: 70 + anchors.top: calibrationButton.bottom + anchors.topMargin: 20 + anchors.left: parent.left + anchors.leftMargin: leftMargin + + minimumValue: 5 + realValue: 5 + colorScheme: hifi.colorSchemes.dark + + onEditingFinished: { + calibrationTimer.interval = realValue * 1000; + openVrConfiguration.countDown = realValue; + numberAnimation.duration = calibrationTimer.interval; + } + } + + RalewayBold { + id: delayTextInfo + size: 10 + text: "Delay Before Calibration Starts" + color: hifi.colors.white + + anchors { + left: timeToCalibrate.right + leftMargin: 20 + verticalCenter: timeToCalibrate.verticalCenter + } + } + + RalewayRegular { + size: 12 + text: "sec" + color: hifi.colors.lightGray + + anchors { + left: delayTextInfo.right + leftMargin: 10 + verticalCenter: delayTextInfo.verticalCenter + } + } + + Separator { + id: advanceSeperator + width: parent.width + anchors.top: timeToCalibrate.bottom + anchors.topMargin: 10 + } + + RalewayBold { + id: advanceSettings + + text: "Advanced Settings" + size: 12 + + color: hifi.colors.white + + anchors.top: advanceSeperator.bottom + anchors.topMargin: 10 + anchors.left: parent.left + anchors.leftMargin: leftMargin + } + + + HifiControls.CheckBox { + id: viveInDesktop + width: 15 + height: 15 + boxRadius: 7 + + anchors.top: advanceSettings.bottom + anchors.topMargin: 5 + anchors.left: openVrConfiguration.left + anchors.leftMargin: leftMargin + 10 + + onClicked: { + if (!checked & hmdInDesktop.checked) { + headBox.checked = true; + headPuckBox.checked = false; + hmdInDesktop.checked = false; + } + sendConfigurationSettings(); + } + } + + RalewayBold { + id: viveDesktopText + size: 10 + text: "Use " + stack.selectedPlugin + " devices in desktop mode" + color: hifi.colors.white + + anchors { + left: viveInDesktop.right + leftMargin: 5 + verticalCenter: viveInDesktop.verticalCenter + } + } + + + NumberAnimation { + id: numberAnimation + target: openVrConfiguration + property: "countDown" + to: 0 + } + + + function logAction(action, status) { + var data = { + "num_pucks": status["puckCount"], + "puck_configuration": status["configuration"], + "head_puck": status["head_puck"], + "hand_puck": status["hand_pucks"] + } + UserActivityLogger.logAction(action, data); + } + + function calibrationStatusInfo(status) { + var calibrationScreen = stack.currentItem; + + if (!status["UI"]) { + calibratingScreen = screen.createObject(); + stack.push(calibratingScreen); + } + + if (status["calibrated"]) { + calibrationScreen.success(); + + if (status["UI"]) { + logAction("mocap_ui_success", status); + } + + } else if (!status["calibrated"]) { + calibrationScreen.failure(); + + if (status["UI"]) { + logAction("mocap_ui_failed", status); + } + } + updateCalibrationButton(); + } + + + function trackersForConfiguration() { + var pucksNeeded = 0; + + if (headPuckBox.checked) { + pucksNeeded++; + } + + if (feetBox.checked) { + pucksNeeded++; + } + + if (hipBox.checked) { + pucksNeeded++; + } + + if (chestBox.checked) { + pucksNeeded++; + } + + if (shoulderBox.checked) { + pucksNeeded++; + } + + return pucksNeeded; + } + + function cancelCalibration() { + calibrationTimer.stop(); + } + + function restartCalibration() { + calibrationTimer.restart(); + } + + function displayConfiguration() { + var settings = InputConfiguration.configurationSettings(openVrConfiguration.pluginName); + var configurationType = settings["trackerConfiguration"]; + displayTrackerConfiguration(configurationType); + + + var HmdHead = settings["HMDHead"]; + var viveController = settings["handController"]; + var desktopMode = settings["desktopMode"]; + var hmdDesktopPosition = settings["hmdDesktopTracking"]; + + armCircumference.realValue = settings.armCircumference; + shoulderWidth.realValue = settings.shoulderWidth; + + if (HmdHead) { + headBox.checked = true; + headPuckBox.checked = false; + } else { + headPuckBox.checked = true; + headBox.checked = false; + } + + if (viveController) { + handBox.checked = true; + handPuckBox.checked = false; + } else { + handPuckBox.checked = true; + handBox.checked = false; + } + + viveInDesktop.checked = desktopMode; + hmdInDesktop.checked = hmdDesktopPosition; + + initializeButtonState(); + updateCalibrationText(); + + var data = { + "num_pucks": settings["puckCount"] + }; + + UserActivityLogger.logAction("mocap_ui_open_dialog", data); + } + + function displayTrackerConfiguration(type) { + if (type === "Feet") { + feetBox.checked = true; + } else if (type === "FeetAndHips") { + feetBox.checked = true; + hipBox.checked = true; + } else if (type === "FeetHipsChest") { + feetBox.checked = true; + hipBox.checked = true; + chestBox.checked = true; + } else if (type === "FeetHipsAndShoulders") { + feetBox.checked = true; + hipBox.checked = true; + shoulderBox.checked = true; + } else if (type === "FeetHipsChestAndShoulders") { + feetBox.checked = true; + hipBox.checked = true; + chestBox.checked = true; + shoulderBox.checked = true; + } + } + + function updateButtonState() { + var settings = composeConfigurationSettings(); + var bodySetting = settings["bodyConfiguration"]; + var headSetting = settings["headConfiguration"]; + var headOverride = headSetting["override"]; + var handSetting = settings["handConfiguration"]; + var handOverride = handSetting["override"]; + + var settingsChanged = false; + + if (lastConfiguration["bodyConfiguration"] !== bodySetting) { + settingsChanged = true; + } + + var lastHead = lastConfiguration["headConfiguration"]; + if (lastHead["override"] !== headOverride) { + settingsChanged = true; + } + + var lastHand = lastConfiguration["handConfiguration"]; + if (lastHand["override"] !== handOverride) { + settingsChanged = true; + } + + if (settingsChanged) { + if ((!handOverride) && (!headOverride) && (bodySetting === "None")) { + state = buttonState.apply; + } else { + state = buttonState.applyAndCalibrate; + } + } else { + if (state == buttonState.apply) { + state = buttonState.disabled; + } else if (state == buttonState.applyAndCalibrate) { + state = buttonState.calibrate; + } + } + + lastConfiguration = settings; + } + + function initializeButtonState() { + var settings = composeConfigurationSettings(); + var bodySetting = settings["bodyConfiguration"]; + var headSetting = settings["headConfiguration"]; + var headOverride = headSetting["override"]; + var handSetting = settings["handConfiguration"]; + var handOverride = handSetting["override"]; + + + if ((!handOverride) && (!headOverride) && (bodySetting === "None")) { + state = buttonState.disabled; + } else { + state = buttonState.calibrate; + } + } + + function updateCalibrationButton() { + updateButtonState(); + updateCalibrationText(); + } + + function updateCalibrationText() { + if (buttonState.disabled == state) { + calibrationButton.enabled = false; + calibrationButton.text = "Apply"; + } else if (buttonState.apply == state) { + calibrationButton.enabled = true; + calibrationButton.text = "Apply"; + } else if (buttonState.applyAndCalibrate == state) { + calibrationButton.enabled = true; + calibrationButton.text = "Apply And Calibrate"; + } else if (buttonState.calibrate == state) { + calibrationButton.enabled = true; + calibrationButton.text = "Calibrate"; + } + } + + function composeConfigurationSettings() { + var trackerConfiguration = ""; + var overrideHead = false; + var overrideHandController = false; + + if (shouldersChecked && chestChecked) { + trackerConfiguration = "FeetHipsChestAndShoulders"; + } else if (shouldersChecked) { + trackerConfiguration = "FeetHipsAndShoulders"; + } else if (chestChecked) { + trackerConfiguration = "FeetHipsAndChest"; + } else if (hipsChecked) { + trackerConfiguration = "FeetAndHips"; + } else if (feetChecked) { + trackerConfiguration = "Feet"; + } else { + trackerConfiguration = "None"; + } + + if (headPuck) { + overrideHead = true; + } else if (hmdHead) { + overrideHead = false; + } + + if (handController) { + overrideHandController = false; + } else if (handPuck) { + overrideHandController = true; + } + + var headObject = { + "override": overrideHead, + "Y": headYOffset.realValue, + "Z": headZOffset.realValue + } + + var handObject = { + "override": overrideHandController, + "Y": handYOffset.realValue, + "Z": handZOffset.realValue + } + + var settingsObject = { + "bodyConfiguration": trackerConfiguration, + "headConfiguration": headObject, + "handConfiguration": handObject, + "armCircumference": armCircumference.realValue, + "shoulderWidth": shoulderWidth.realValue, + "desktopMode": viveInDesktop.checked, + "hmdDesktopTracking": hmdInDesktop.checked + } + + return settingsObject; + } + + function sendConfigurationSettings() { + var settings = composeConfigurationSettings(); + InputConfiguration.setConfigurationSettings(settings, openVrConfiguration.pluginName); + updateCalibrationButton(); } } - updateCalibrationButton(); - } - - - function trackersForConfiguration() { - var pucksNeeded = 0; - - if (headPuckBox.checked) { - pucksNeeded++; - } - - if (feetBox.checked) { - pucksNeeded++; - } - - if (hipBox.checked) { - pucksNeeded++; - } - - if (chestBox.checked) { - pucksNeeded++; - } - - if (shoulderBox.checked) { - pucksNeeded++; - } - - return pucksNeeded; - } - - function cancelCalibration() { - calibrationTimer.stop(); - } - - function restartCalibration() { - calibrationTimer.restart(); - } - - function displayConfiguration() { - var settings = InputConfiguration.configurationSettings(pluginName); - var configurationType = settings["trackerConfiguration"]; - displayTrackerConfiguration(configurationType); - - - var HmdHead = settings["HMDHead"]; - var viveController = settings["handController"]; - var desktopMode = settings["desktopMode"]; - var hmdDesktopPosition = settings["hmdDesktopTracking"]; - - armCircumference.realValue = settings.armCircumference; - shoulderWidth.realValue = settings.shoulderWidth; - - if (HmdHead) { - headBox.checked = true; - headPuckBox.checked = false; - } else { - headPuckBox.checked = true; - headBox.checked = false; - } - - if (viveController) { - handBox.checked = true; - handPuckBox.checked = false; - } else { - handPuckBox.checked = true; - handBox.checked = false; - } - - viveInDesktop.checked = desktopMode; - hmdInDesktop.checked = hmdDesktopPosition; - - initializeButtonState(); - updateCalibrationText(); - - var data = { - "num_pucks": settings["puckCount"] - }; - - UserActivityLogger.logAction("mocap_ui_open_dialog", data); - } - - function displayTrackerConfiguration(type) { - if (type === "Feet") { - feetBox.checked = true; - } else if (type === "FeetAndHips") { - feetBox.checked = true; - hipBox.checked = true; - } else if (type === "FeetHipsChest") { - feetBox.checked = true; - hipBox.checked = true; - chestBox.checked = true; - } else if (type === "FeetHipsAndShoulders") { - feetBox.checked = true; - hipBox.checked = true; - shoulderBox.checked = true; - } else if (type === "FeetHipsChestAndShoulders") { - feetBox.checked = true; - hipBox.checked = true; - chestBox.checked = true; - shoulderBox.checked = true; - } - } - - function updateButtonState() { - var settings = composeConfigurationSettings(); - var bodySetting = settings["bodyConfiguration"]; - var headSetting = settings["headConfiguration"]; - var headOverride = headSetting["override"]; - var handSetting = settings["handConfiguration"]; - var handOverride = handSetting["override"]; - - var settingsChanged = false; - - if (lastConfiguration["bodyConfiguration"] !== bodySetting) { - settingsChanged = true; - } - - var lastHead = lastConfiguration["headConfiguration"]; - if (lastHead["override"] !== headOverride) { - settingsChanged = true; - } - - var lastHand = lastConfiguration["handConfiguration"]; - if (lastHand["override"] !== handOverride) { - settingsChanged = true; - } - - if (settingsChanged) { - if ((!handOverride) && (!headOverride) && (bodySetting === "None")) { - state = buttonState.apply; - } else { - state = buttonState.applyAndCalibrate; - } - } else { - if (state == buttonState.apply) { - state = buttonState.disabled; - } else if (state == buttonState.applyAndCalibrate) { - state = buttonState.calibrate; - } - } - - lastConfiguration = settings; - } - - function initializeButtonState() { - var settings = composeConfigurationSettings(); - var bodySetting = settings["bodyConfiguration"]; - var headSetting = settings["headConfiguration"]; - var headOverride = headSetting["override"]; - var handSetting = settings["handConfiguration"]; - var handOverride = handSetting["override"]; - - - if ((!handOverride) && (!headOverride) && (bodySetting === "None")) { - state = buttonState.disabled; - } else { - state = buttonState.calibrate; - } - } - - function updateCalibrationButton() { - updateButtonState(); - updateCalibrationText(); - } - - function updateCalibrationText() { - if (buttonState.disabled == state) { - calibrationButton.enabled = false; - calibrationButton.text = "Apply"; - } else if (buttonState.apply == state) { - calibrationButton.enabled = true; - calibrationButton.text = "Apply"; - } else if (buttonState.applyAndCalibrate == state) { - calibrationButton.enabled = true; - calibrationButton.text = "Apply And Calibrate"; - } else if (buttonState.calibrate == state) { - calibrationButton.enabled = true; - calibrationButton.text = "Calibrate"; - } - } - - function composeConfigurationSettings() { - var trackerConfiguration = ""; - var overrideHead = false; - var overrideHandController = false; - - if (shouldersChecked && chestChecked) { - trackerConfiguration = "FeetHipsChestAndShoulders"; - } else if (shouldersChecked) { - trackerConfiguration = "FeetHipsAndShoulders"; - } else if (chestChecked) { - trackerConfiguration = "FeetHipsAndChest"; - } else if (hipsChecked) { - trackerConfiguration = "FeetAndHips"; - } else if (feetChecked) { - trackerConfiguration = "Feet"; - } else { - trackerConfiguration = "None"; - } - - if (headPuck) { - overrideHead = true; - } else if (hmdHead) { - overrideHead = false; - } - - if (handController) { - overrideHandController = false; - } else if (handPuck) { - overrideHandController = true; - } - - var headObject = { - "override": overrideHead, - "Y": headYOffset.realValue, - "Z": headZOffset.realValue - } - - var handObject = { - "override": overrideHandController, - "Y": handYOffset.realValue, - "Z": handZOffset.realValue - } - - var settingsObject = { - "bodyConfiguration": trackerConfiguration, - "headConfiguration": headObject, - "handConfiguration": handObject, - "armCircumference": armCircumference.realValue, - "shoulderWidth": shoulderWidth.realValue, - "desktopMode": viveInDesktop.checked, - "hmdDesktopTracking": hmdInDesktop.checked - } - - return settingsObject; - } - - function sendConfigurationSettings() { - var settings = composeConfigurationSettings(); - InputConfiguration.setConfigurationSettings(settings, pluginName); - updateCalibrationButton(); } } diff --git a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml index dc67494e27..08f86770e6 100644 --- a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml +++ b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml @@ -34,41 +34,16 @@ StackView { height: parent !== null ? parent.height : undefined property int cardWidth: 212; property int cardHeight: 152; - property string metaverseBase: addressBarDialog.metaverseServerUrl + "/api/v1/"; property var tablet: null; - // This version only implements rpc(method, parameters, callback(error, result)) calls initiated from here, not initiated from .js, nor "notifications". - property var rpcCalls: ({}); - property var rpcCounter: 0; + RootHttpRequest { id: http; } signal sendToScript(var message); - function rpc(method, parameters, callback) { - console.debug('TabletAddressDialog: rpc: method = ', method, 'parameters = ', parameters, 'callback = ', callback) - - rpcCalls[rpcCounter] = callback; - var message = {method: method, params: parameters, id: rpcCounter++, jsonrpc: "2.0"}; - sendToScript(message); - } function fromScript(message) { - if (message.method === 'refreshFeeds') { - var feeds = [happeningNow, places, snapshots]; - console.debug('TabletAddressDialog::fromScript: refreshFeeds', 'feeds = ', feeds); - - feeds.forEach(function(feed) { - feed.protocol = encodeURIComponent(message.protocolSignature); - Qt.callLater(feed.fillDestinations); - }); - - return; + switch (message.method) { + case 'http.response': + http.handleHttpResponse(message); + break; } - - var callback = rpcCalls[message.id]; - if (!callback) { - // FIXME: We often recieve very long messages here, the logging of which is drastically slowing down the main thread - //console.log('No callback for message fromScript', JSON.stringify(message)); - return; - } - delete rpcCalls[message.id]; - callback(message.error, message.result); } Component { id: tabletWebView; TabletWebView {} } @@ -346,12 +321,11 @@ StackView { width: parent.width; cardWidth: 312 + (2 * 4); cardHeight: 163 + (2 * 4); - metaverseServerUrl: addressBarDialog.metaverseServerUrl; labelText: 'HAPPENING NOW'; actions: 'announcement'; filter: addressLine.text; goFunction: goCard; - rpc: root.rpc; + http: http; } Feed { id: places; @@ -359,12 +333,11 @@ StackView { cardWidth: 210; cardHeight: 110 + messageHeight; messageHeight: 44; - metaverseServerUrl: addressBarDialog.metaverseServerUrl; labelText: 'PLACES'; actions: 'concurrency'; filter: addressLine.text; goFunction: goCard; - rpc: root.rpc; + http: http; } Feed { id: snapshots; @@ -373,12 +346,11 @@ StackView { cardHeight: 75 + messageHeight + 4; messageHeight: 32; textPadding: 6; - metaverseServerUrl: addressBarDialog.metaverseServerUrl; labelText: 'RECENT SNAPS'; actions: 'snapshot'; filter: addressLine.text; goFunction: goCard; - rpc: root.rpc; + http: http; } } } diff --git a/interface/resources/qml/hifi/tablet/TabletRoot.qml b/interface/resources/qml/hifi/tablet/TabletRoot.qml index 15db5d8f88..fa268ad6ee 100644 --- a/interface/resources/qml/hifi/tablet/TabletRoot.qml +++ b/interface/resources/qml/hifi/tablet/TabletRoot.qml @@ -65,6 +65,18 @@ Item { return false; } + function closeDialog() { + if (openMessage != null) { + openMessage.destroy(); + openMessage = null; + } + + if (openModal != null) { + openModal.destroy(); + openModal = null; + } + } + function isUrlLoaded(url) { if (currentApp >= 0) { var currentAppUrl = tabletApps.get(currentApp).appUrl; diff --git a/interface/resources/qml/hifi/tablet/tabletWindows/TabletFileDialog.qml b/interface/resources/qml/hifi/tablet/tabletWindows/TabletFileDialog.qml index f3f98f24e5..871d1c92a9 100644 --- a/interface/resources/qml/hifi/tablet/tabletWindows/TabletFileDialog.qml +++ b/interface/resources/qml/hifi/tablet/tabletWindows/TabletFileDialog.qml @@ -8,12 +8,12 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -import QtQuick 2.5 -import QtQuick.Controls 1.4 +import QtQuick 2.7 import Qt.labs.folderlistmodel 2.1 import Qt.labs.settings 1.0 -import QtQuick.Controls.Styles 1.4 import QtQuick.Dialogs 1.2 as OriginalDialogs +import QtQuick.Controls 1.4 as QQC1 +import QtQuick.Controls 2.3 import ".." import "../../../controls-uit" @@ -30,6 +30,8 @@ Rectangle { color: hifi.colors.baseGray; + property var filesModel: ListModel { } + Settings { category: "FileDialog" property alias width: root.width @@ -149,7 +151,7 @@ Rectangle { ComboBox { id: pathSelector - anchors { + anchors { top: parent.top topMargin: hifi.dimensions.contentMargin.y left: navControls.right @@ -247,7 +249,9 @@ Rectangle { } currentSelectionUrl = helper.pathToUrl(fileTableView.model.get(row).filePath); - currentSelectionIsFolder = fileTableView.model.isFolder(row); + currentSelectionIsFolder = fileTableView.model !== filesModel ? + fileTableView.model.isFolder(row) : + fileTableModel.isFolder(row); if (root.selectDirectory || !currentSelectionIsFolder) { currentSelection.text = capitalizeDrive(helper.urlToPath(currentSelectionUrl)); } else { @@ -287,6 +291,7 @@ Rectangle { } onFolderChanged: { + d.clearSelection(); fileTableModel.update(); // Update once the data from the folder change is available. } @@ -327,7 +332,12 @@ Rectangle { } } - ListModel { + Component { + id: filesModelBuilder + ListModel { } + } + + QtObject { id: fileTableModel // FolderListModel has a couple of problems: @@ -379,7 +389,11 @@ Rectangle { if (row === -1) { return false; } - return get(row).fileIsDir; + return filesModel.get(row).fileIsDir; + } + + function get(row) { + return filesModel.get(row) } function update() { @@ -397,7 +411,7 @@ Rectangle { rows = 0, i; - clear(); + filesModel = filesModelBuilder.createObject(root); comparisonFunction = sortOrder === Qt.AscendingOrder ? function(a, b) { return a < b; } @@ -419,7 +433,7 @@ Rectangle { while (lower < upper) { middle = Math.floor((lower + upper) / 2); var lessThan; - if (comparisonFunction(sortValue, get(middle)[sortField])) { + if (comparisonFunction(sortValue, filesModel.get(middle)[sortField])) { lessThan = true; upper = middle; } else { @@ -428,7 +442,7 @@ Rectangle { } } - insert(lower, { + filesModel.insert(lower, { fileName: fileName, fileModified: (fileIsDir ? new Date(0) : model.getItem(i, "fileModified")), fileSize: model.getItem(i, "fileSize"), @@ -439,8 +453,6 @@ Rectangle { rows++; } - - d.clearSelection(); } } @@ -465,12 +477,12 @@ Rectangle { sortIndicatorOrder: Qt.AscendingOrder sortIndicatorVisible: true - model: fileTableModel + model: filesModel function updateSort() { - model.sortOrder = sortIndicatorOrder; - model.sortColumn = sortIndicatorColumn; - model.update(); + fileTableModel.sortOrder = sortIndicatorOrder; + fileTableModel.sortColumn = sortIndicatorColumn; + fileTableModel.update(); } onSortIndicatorColumnChanged: { updateSort(); } @@ -522,7 +534,7 @@ Rectangle { } } - TableViewColumn { + QQC1.TableViewColumn { id: fileNameColumn role: "fileName" title: "Name" @@ -530,7 +542,7 @@ Rectangle { movable: false resizable: true } - TableViewColumn { + QQC1.TableViewColumn { id: fileMofifiedColumn role: "fileModified" title: "Date" @@ -539,7 +551,7 @@ Rectangle { resizable: true visible: !selectDirectory } - TableViewColumn { + QQC1.TableViewColumn { role: "fileSize" title: "Size" width: fileTableView.width - fileNameColumn.width - fileMofifiedColumn.width @@ -554,11 +566,12 @@ Rectangle { } function navigateToCurrentRow() { + var currentModel = fileTableView.model !== filesModel ? fileTableView.model : fileTableModel var row = fileTableView.currentRow - var isFolder = model.isFolder(row); - var file = model.get(row).filePath; + var isFolder = currentModel.isFolder(row); + var file = currentModel.get(row).filePath; if (isFolder) { - fileTableView.model.folder = helper.pathToUrl(file); + currentModel.folder = helper.pathToUrl(file); } else { okAction.trigger(); } @@ -573,7 +586,8 @@ Rectangle { var newPrefix = prefix + event.text.toLowerCase(); var matchedIndex = -1; for (var i = 0; i < model.count; ++i) { - var name = model.get(i).fileName.toLowerCase(); + var name = model !== filesModel ? model.get(i).fileName.toLowerCase() : + filesModel.get(i).fileName.toLowerCase(); if (0 === name.indexOf(newPrefix)) { matchedIndex = i; break; diff --git a/interface/src/AboutUtil.cpp b/interface/src/AboutUtil.cpp index 5179897443..634e52b481 100644 --- a/interface/src/AboutUtil.cpp +++ b/interface/src/AboutUtil.cpp @@ -8,48 +8,46 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // + +#include "AboutUtil.h" #include #include -#include "AboutUtil.h" -#include "BuildInfo.h" #include +#include + +#include "BuildInfo.h" #include "DependencyManager.h" #include "scripting/HMDScriptingInterface.h" #include "Application.h" -#include AboutUtil::AboutUtil(QObject *parent) : QObject(parent) { - QLocale locale_; - m_DateConverted = QDate::fromString(BuildInfo::BUILD_TIME, "dd/MM/yyyy"). - toString(locale_.dateFormat(QLocale::ShortFormat)); + QLocale locale; + _dateConverted = QDate::fromString(BuildInfo::BUILD_TIME, "dd/MM/yyyy"). + toString(locale.dateFormat(QLocale::ShortFormat)); } -AboutUtil *AboutUtil::getInstance() -{ +AboutUtil *AboutUtil::getInstance() { static AboutUtil instance; return &instance; } -QString AboutUtil::buildDate() const -{ - return m_DateConverted; +QString AboutUtil::getBuildDate() const { + return _dateConverted; } -QString AboutUtil::buildVersion() const -{ +QString AboutUtil::getBuildVersion() const { return BuildInfo::VERSION; } -QString AboutUtil::qtVersion() const -{ +QString AboutUtil::getQtVersion() const { return qVersion(); } void AboutUtil::openUrl(const QString& url) const { auto tabletScriptingInterface = DependencyManager::get(); - auto tablet = dynamic_cast(tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system")); + auto tablet = tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system"); auto hmd = DependencyManager::get(); auto offscreenUi = DependencyManager::get(); diff --git a/interface/src/AboutUtil.h b/interface/src/AboutUtil.h index 9b65b887b9..767e69842d 100644 --- a/interface/src/AboutUtil.h +++ b/interface/src/AboutUtil.h @@ -15,28 +15,41 @@ #include -class AboutUtil : public QObject { +/**jsdoc + * @namespace HifiAbout + * + * @hifi-interface + * @hifi-client-entity + * + * @property {string} buildDate + * @property {string} buildVersion + * @property {string} qtVersion + */ +class AboutUtil : public QObject { Q_OBJECT - Q_PROPERTY(QString buildDate READ buildDate CONSTANT) - Q_PROPERTY(QString buildVersion READ buildVersion CONSTANT) - Q_PROPERTY(QString qtVersion READ qtVersion CONSTANT) - - AboutUtil(QObject* parent = nullptr); + Q_PROPERTY(QString buildDate READ getBuildDate CONSTANT) + Q_PROPERTY(QString buildVersion READ getBuildVersion CONSTANT) + Q_PROPERTY(QString qtVersion READ getQtVersion CONSTANT) public: static AboutUtil* getInstance(); ~AboutUtil() {} - QString buildDate() const; - QString buildVersion() const; - QString qtVersion() const; + QString getBuildDate() const; + QString getBuildVersion() const; + QString getQtVersion() const; public slots: + + /**jsdoc + * @function HifiAbout.openUrl + * @param {string} url + */ void openUrl(const QString &url) const; private: - - QString m_DateConverted; + AboutUtil(QObject* parent = nullptr); + QString _dateConverted; }; #endif // hifi_AboutUtil_h diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index a43c2fdc5f..1910beecdd 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -198,7 +198,6 @@ #include #include -#include #include #include #include @@ -334,7 +333,11 @@ static const QString RENDER_FORWARD{ "HIFI_RENDER_FORWARD" }; static bool DISABLE_DEFERRED = QProcessEnvironment::systemEnvironment().contains(RENDER_FORWARD); #endif +#if !defined(Q_OS_ANDROID) static const int MAX_CONCURRENT_RESOURCE_DOWNLOADS = 16; +#else +static const int MAX_CONCURRENT_RESOURCE_DOWNLOADS = 4; +#endif // For processing on QThreadPool, we target a number of threads after reserving some // based on how many are being consumed by the application and the display plugin. However, @@ -693,8 +696,8 @@ private: }; /**jsdoc - *

The Controller.Hardware.Application object has properties representing Interface's state. The property - * values are integer IDs, uniquely identifying each output. Read-only. These can be mapped to actions or functions or + *

The Controller.Hardware.Application object has properties representing Interface's state. The property + * values are integer IDs, uniquely identifying each output. Read-only. These can be mapped to actions or functions or * Controller.Standard items in a {@link RouteObject} mapping (e.g., using the {@link RouteObject#when} method). * Each data value is either 1.0 for "true" or 0.0 for "false".

* @@ -776,7 +779,7 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) { static const auto SUPPRESS_SETTINGS_RESET = "--suppress-settings-reset"; bool suppressPrompt = cmdOptionExists(argc, const_cast(argv), SUPPRESS_SETTINGS_RESET); - // Ignore any previous crashes if running from command line with a test script. + // Ignore any previous crashes if running from command line with a test script. bool inTestMode { false }; for (int i = 0; i < argc; ++i) { QString parameter(argv[i]); @@ -798,15 +801,8 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) { qApp->setProperty(hifi::properties::APP_LOCAL_DATA_PATH, cacheDir); } - // FIXME fix the OSX installer to install the resources.rcc binary instead of resource files and remove - // this conditional exclusion -#if !defined(Q_OS_OSX) { -#if defined(Q_OS_ANDROID) - const QString resourcesBinaryFile = QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/resources.rcc"; -#else - const QString resourcesBinaryFile = QCoreApplication::applicationDirPath() + "/resources.rcc"; -#endif + const QString resourcesBinaryFile = PathUtils::getRccPath(); if (!QFile::exists(resourcesBinaryFile)) { throw std::runtime_error("Unable to find primary resources"); } @@ -814,7 +810,6 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) { throw std::runtime_error("Unable to load primary resources"); } } -#endif // Tell the plugin manager about our statically linked plugins auto pluginManager = PluginManager::getInstance(); @@ -909,7 +904,6 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) { DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); - DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); @@ -926,7 +920,6 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) { DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); - DependencyManager::set(); DependencyManager::set(); DependencyManager::set(nullptr, qApp->getOcteeSceneStats()); DependencyManager::set(); @@ -1112,7 +1105,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo qCDebug(interfaceapp) << "[VERSION] Build sequence:" << qPrintable(applicationVersion()); qCDebug(interfaceapp) << "[VERSION] MODIFIED_ORGANIZATION:" << BuildInfo::MODIFIED_ORGANIZATION; qCDebug(interfaceapp) << "[VERSION] VERSION:" << BuildInfo::VERSION; - qCDebug(interfaceapp) << "[VERSION] BUILD_BRANCH:" << BuildInfo::BUILD_BRANCH; + qCDebug(interfaceapp) << "[VERSION] BUILD_TYPE_STRING:" << BuildInfo::BUILD_TYPE_STRING; qCDebug(interfaceapp) << "[VERSION] BUILD_GLOBAL_SERVICES:" << BuildInfo::BUILD_GLOBAL_SERVICES; #if USE_STABLE_GLOBAL_SERVICES qCDebug(interfaceapp) << "[VERSION] We will use STABLE global services."; @@ -1369,11 +1362,11 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo initializeGL(); qCDebug(interfaceapp, "Initialized GL"); - // Initialize the display plugin architecture + // Initialize the display plugin architecture initializeDisplayPlugins(); qCDebug(interfaceapp, "Initialized Display"); - // Create the rendering engine. This can be slow on some machines due to lots of + // Create the rendering engine. This can be slow on some machines due to lots of // GPU pipeline creation. initializeRenderEngine(); qCDebug(interfaceapp, "Initialized Render Engine."); @@ -1417,7 +1410,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo // In practice we shouldn't run across installs that don't have a known installer type. // Client or Client+Server installs should always have the installer.ini next to their // respective interface.exe, and Steam installs will be detected as such. If a user were - // to delete the installer.ini, though, and as an example, we won't know the context of the + // to delete the installer.ini, though, and as an example, we won't know the context of the // original install. constexpr auto INSTALLER_KEY_TYPE = "type"; constexpr auto INSTALLER_KEY_CAMPAIGN = "campaign"; @@ -1443,17 +1436,9 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo // add firstRun flag from settings to launch event Setting::Handle firstRun { Settings::firstRun, true }; - // once the settings have been loaded, check if we need to flip the default for UserActivityLogger - auto& userActivityLogger = UserActivityLogger::getInstance(); - if (!userActivityLogger.isDisabledSettingSet()) { - // the user activity logger is opt-out for Interface - // but it's defaulted to disabled for other targets - // so we need to enable it here if it has never been disabled by the user - userActivityLogger.disable(false); - } - QString machineFingerPrint = uuidStringWithoutCurlyBraces(FingerprintUtils::getMachineFingerprint()); + auto& userActivityLogger = UserActivityLogger::getInstance(); if (userActivityLogger.isEnabled()) { // sessionRunTime will be reset soon by loadSettings. Grab it now to get previous session value. // The value will be 0 if the user blew away settings this session, which is both a feature and a bug. @@ -1465,6 +1450,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo { "tester", QProcessEnvironment::systemEnvironment().contains(TESTER) }, { "installer_campaign", installerCampaign }, { "installer_type", installerType }, + { "build_type", BuildInfo::BUILD_TYPE_STRING }, { "previousSessionCrashed", _previousSessionCrashed }, { "previousSessionRuntime", sessionRunTime.get() }, { "cpu_architecture", QSysInfo::currentCpuArchitecture() }, @@ -1787,10 +1773,12 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo // If launched from Steam, let it handle updates const QString HIFI_NO_UPDATER_COMMAND_LINE_KEY = "--no-updater"; bool noUpdater = arguments().indexOf(HIFI_NO_UPDATER_COMMAND_LINE_KEY) != -1; - if (!noUpdater) { + bool buildCanUpdate = BuildInfo::BUILD_TYPE == BuildInfo::BuildType::Stable + || BuildInfo::BUILD_TYPE == BuildInfo::BuildType::Master; + if (!noUpdater && buildCanUpdate) { constexpr auto INSTALLER_TYPE_CLIENT_ONLY = "client_only"; - auto applicationUpdater = DependencyManager::get(); + auto applicationUpdater = DependencyManager::set(); AutoUpdater::InstallerType type = installerType == INSTALLER_TYPE_CLIENT_ONLY ? AutoUpdater::InstallerType::CLIENT_ONLY : AutoUpdater::InstallerType::FULL; @@ -2182,7 +2170,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo if (testProperty.isValid()) { auto scriptEngines = DependencyManager::get(); const auto testScript = property(hifi::properties::TEST).toUrl(); - + // Set last parameter to exit interface when the test script finishes, if so requested scriptEngines->loadScript(testScript, false, false, false, false, quitWhenFinished); @@ -2253,9 +2241,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo DependencyManager::get()->setSetPrecisionPickingOperator([&](unsigned int rayPickID, bool value) { DependencyManager::get()->setPrecisionPicking(rayPickID, value); }); - EntityTreeRenderer::setRenderDebugHullsOperator([] { - return Menu::getInstance()->isOptionChecked(MenuOption::PhysicsShowHulls); - }); // Preload Tablet sounds DependencyManager::get()->preloadSounds(); @@ -2399,7 +2384,7 @@ void Application::onAboutToQuit() { } } - // The active display plugin needs to be loaded before the menu system is active, + // The active display plugin needs to be loaded before the menu system is active, // so its persisted explicitly here Setting::Handle{ ACTIVE_DISPLAY_PLUGIN_SETTING_NAME }.set(getActiveDisplayPlugin()->getName()); @@ -2610,10 +2595,55 @@ void Application::initializeGL() { _isGLInitialized = true; } - _glWidget->makeCurrent(); - glClearColor(0.2f, 0.2f, 0.2f, 1); - glClear(GL_COLOR_BUFFER_BIT); - _glWidget->swapBuffers(); + if (!_glWidget->makeCurrent()) { + qCWarning(interfaceapp, "Unable to make window context current"); + } + +#if !defined(DISABLE_QML) + // Build a shared canvas / context for the Chromium processes + { + // Disable signed distance field font rendering on ATI/AMD GPUs, due to + // https://highfidelity.manuscript.com/f/cases/13677/Text-showing-up-white-on-Marketplace-app + std::string vendor{ (const char*)glGetString(GL_VENDOR) }; + if ((vendor.find("AMD") != std::string::npos) || (vendor.find("ATI") != std::string::npos)) { + qputenv("QTWEBENGINE_CHROMIUM_FLAGS", QByteArray("--disable-distance-field-text")); + } + + // Chromium rendering uses some GL functions that prevent nSight from capturing + // frames, so we only create the shared context if nsight is NOT active. + if (!nsightActive()) { + _chromiumShareContext = new OffscreenGLCanvas(); + _chromiumShareContext->setObjectName("ChromiumShareContext"); + _chromiumShareContext->create(_glWidget->qglContext()); + if (!_chromiumShareContext->makeCurrent()) { + qCWarning(interfaceapp, "Unable to make chromium shared context current"); + } + qt_gl_set_global_share_context(_chromiumShareContext->getContext()); + _chromiumShareContext->doneCurrent(); + // Restore the GL widget context + if (!_glWidget->makeCurrent()) { + qCWarning(interfaceapp, "Unable to make window context current"); + } + } else { + qCWarning(interfaceapp) << "nSight detected, disabling chrome rendering"; + } + } +#endif + + // Build a shared canvas / context for the QML rendering + { + _qmlShareContext = new OffscreenGLCanvas(); + _qmlShareContext->setObjectName("QmlShareContext"); + _qmlShareContext->create(_glWidget->qglContext()); + if (!_qmlShareContext->makeCurrent()) { + qCWarning(interfaceapp, "Unable to make QML shared context current"); + } + OffscreenQmlSurface::setSharedContext(_qmlShareContext->getContext()); + _qmlShareContext->doneCurrent(); + if (!_glWidget->makeCurrent()) { + qCWarning(interfaceapp, "Unable to make window context current"); + } + } // Build an offscreen GL context for the main thread. _offscreenContext = new OffscreenGLCanvas(); @@ -2625,6 +2655,11 @@ void Application::initializeGL() { _offscreenContext->doneCurrent(); _offscreenContext->setThreadContext(); + _glWidget->makeCurrent(); + glClearColor(0.2f, 0.2f, 0.2f, 1); + glClear(GL_COLOR_BUFFER_BIT); + _glWidget->swapBuffers(); + // Move the GL widget context to the render event handler thread _renderEventHandler = new RenderEventHandler(_glWidget->qglContext()); if (!_offscreenContext->makeCurrent()) { @@ -2634,7 +2669,7 @@ void Application::initializeGL() { // Create the GPU backend // Requires the window context, because that's what's used in the actual rendering - // and the GPU backend will make things like the VAO which cannot be shared across + // and the GPU backend will make things like the VAO which cannot be shared across // contexts _glWidget->makeCurrent(); gpu::Context::init(); @@ -2657,7 +2692,7 @@ void Application::initializeDisplayPlugins() { auto lastActiveDisplayPluginName = activeDisplayPluginSetting.get(); auto defaultDisplayPlugin = displayPlugins.at(0); - // Once time initialization code + // Once time initialization code DisplayPluginPointer targetDisplayPlugin; foreach(auto displayPlugin, displayPlugins) { displayPlugin->setContext(_gpuContext); @@ -2670,7 +2705,7 @@ void Application::initializeDisplayPlugins() { } // The default display plugin needs to be activated first, otherwise the display plugin thread - // may be launched by an external plugin, which is bad + // may be launched by an external plugin, which is bad setDisplayPlugin(defaultDisplayPlugin); // Now set the desired plugin if it's not the same as the default plugin @@ -2747,40 +2782,6 @@ extern void setupPreferences(); static void addDisplayPluginToMenu(const DisplayPluginPointer& displayPlugin, int index, bool active = false); void Application::initializeUi() { - // Build a shared canvas / context for the Chromium processes -#if !defined(DISABLE_QML) - // Chromium rendering uses some GL functions that prevent nSight from capturing - // frames, so we only create the shared context if nsight is NOT active. - if (!nsightActive()) { - _chromiumShareContext = new OffscreenGLCanvas(); - _chromiumShareContext->setObjectName("ChromiumShareContext"); - _chromiumShareContext->create(_offscreenContext->getContext()); - if (!_chromiumShareContext->makeCurrent()) { - qCWarning(interfaceapp, "Unable to make chromium shared context current"); - } - qt_gl_set_global_share_context(_chromiumShareContext->getContext()); - _chromiumShareContext->doneCurrent(); - // Restore the GL widget context - _offscreenContext->makeCurrent(); - } else { - qCWarning(interfaceapp) << "nSIGHT detected, disabling chrome rendering"; - } -#endif - - // Build a shared canvas / context for the QML rendering - _qmlShareContext = new OffscreenGLCanvas(); - _qmlShareContext->setObjectName("QmlShareContext"); - _qmlShareContext->create(_offscreenContext->getContext()); - if (!_qmlShareContext->makeCurrent()) { - qCWarning(interfaceapp, "Unable to make QML shared context current"); - } - OffscreenQmlSurface::setSharedContext(_qmlShareContext->getContext()); - _qmlShareContext->doneCurrent(); - // Restore the GL widget context - _offscreenContext->makeCurrent(); - // Make sure all QML surfaces share the main thread GL context - OffscreenQmlSurface::setSharedContext(_offscreenContext->getContext()); - AddressBarDialog::registerType(); ErrorDialog::registerType(); LoginDialog::registerType(); @@ -3645,7 +3646,6 @@ void Application::keyPressEvent(QKeyEvent* event) { _keysPressed.insert(event->key()); _controllerScriptingInterface->emitKeyPressEvent(event); // send events to any registered scripts - // if one of our scripts have asked to capture this event, then stop processing it if (_controllerScriptingInterface->isKeyCaptured(event)) { return; @@ -3671,9 +3671,21 @@ void Application::keyPressEvent(QKeyEvent* event) { } break; - case Qt::Key_1: - case Qt::Key_2: - case Qt::Key_3: + case Qt::Key_1: { + Menu* menu = Menu::getInstance(); + menu->triggerOption(MenuOption::FirstPerson); + break; + } + case Qt::Key_2: { + Menu* menu = Menu::getInstance(); + menu->triggerOption(MenuOption::FullscreenMirror); + break; + } + case Qt::Key_3: { + Menu* menu = Menu::getInstance(); + menu->triggerOption(MenuOption::ThirdPerson); + break; + } case Qt::Key_4: case Qt::Key_5: case Qt::Key_6: @@ -3730,6 +3742,13 @@ void Application::keyPressEvent(QKeyEvent* event) { } break; + case Qt::Key_R: + if (isMeta && !event->isAutoRepeat()) { + DependencyManager::get()->reloadAllScripts(); + DependencyManager::get()->clearCache(); + } + break; + case Qt::Key_Asterisk: Menu::getInstance()->triggerOption(MenuOption::DefaultSkybox); break; @@ -3795,68 +3814,6 @@ void Application::keyPressEvent(QKeyEvent* event) { Menu::getInstance()->triggerOption(MenuOption::Chat); break; -#if 0 - case Qt::Key_I: - if (isShifted) { - _myCamera.setEyeOffsetOrientation(glm::normalize( - glm::quat(glm::vec3(0.002f, 0, 0)) * _myCamera.getEyeOffsetOrientation())); - } else { - _myCamera.setEyeOffsetPosition(_myCamera.getEyeOffsetPosition() + glm::vec3(0, 0.001, 0)); - } - updateProjectionMatrix(); - break; - - case Qt::Key_K: - if (isShifted) { - _myCamera.setEyeOffsetOrientation(glm::normalize( - glm::quat(glm::vec3(-0.002f, 0, 0)) * _myCamera.getEyeOffsetOrientation())); - } else { - _myCamera.setEyeOffsetPosition(_myCamera.getEyeOffsetPosition() + glm::vec3(0, -0.001, 0)); - } - updateProjectionMatrix(); - break; - - case Qt::Key_J: - if (isShifted) { - QMutexLocker viewLocker(&_viewMutex); - _viewFrustum.setFocalLength(_viewFrustum.getFocalLength() - 0.1f); - } else { - _myCamera.setEyeOffsetPosition(_myCamera.getEyeOffsetPosition() + glm::vec3(-0.001, 0, 0)); - } - updateProjectionMatrix(); - break; - - case Qt::Key_M: - if (isShifted) { - QMutexLocker viewLocker(&_viewMutex); - _viewFrustum.setFocalLength(_viewFrustum.getFocalLength() + 0.1f); - } else { - _myCamera.setEyeOffsetPosition(_myCamera.getEyeOffsetPosition() + glm::vec3(0.001, 0, 0)); - } - updateProjectionMatrix(); - break; - - case Qt::Key_U: - if (isShifted) { - _myCamera.setEyeOffsetOrientation(glm::normalize( - glm::quat(glm::vec3(0, 0, -0.002f)) * _myCamera.getEyeOffsetOrientation())); - } else { - _myCamera.setEyeOffsetPosition(_myCamera.getEyeOffsetPosition() + glm::vec3(0, 0, -0.001)); - } - updateProjectionMatrix(); - break; - - case Qt::Key_Y: - if (isShifted) { - _myCamera.setEyeOffsetOrientation(glm::normalize( - glm::quat(glm::vec3(0, 0, 0.002f)) * _myCamera.getEyeOffsetOrientation())); - } else { - _myCamera.setEyeOffsetPosition(_myCamera.getEyeOffsetPosition() + glm::vec3(0, 0, 0.001)); - } - updateProjectionMatrix(); - break; -#endif - case Qt::Key_Slash: Menu::getInstance()->triggerOption(MenuOption::Stats); break; @@ -4239,7 +4196,7 @@ bool Application::acceptSnapshot(const QString& urlString) { static uint32_t _renderedFrameIndex { INVALID_FRAME }; bool Application::shouldPaint() const { - if (_aboutToQuit) { + if (_aboutToQuit || _window->isMinimized()) { return false; } @@ -4799,12 +4756,15 @@ void Application::loadSettings() { // DONT CHECK IN //DependencyManager::get()->setAutomaticLODAdjust(false); - Menu::getInstance()->loadSettings(); + auto menu = Menu::getInstance(); + menu->loadSettings(); + + // override the menu option show overlays to always be true on startup + menu->setIsOptionChecked(MenuOption::Overlays, true); // If there is a preferred plugin, we probably messed it up with the menu settings, so fix it. auto pluginManager = PluginManager::getInstance(); auto plugins = pluginManager->getPreferredDisplayPlugins(); - auto menu = Menu::getInstance(); if (plugins.size() > 0) { for (auto plugin : plugins) { if (auto action = menu->getActionForOption(plugin->getName())) { @@ -5474,7 +5434,7 @@ void Application::update(float deltaTime) { // process octree stats packets are sent in between full sends of a scene (this isn't currently true). // We keep physics disabled until we've received a full scene and everything near the avatar in that // scene is ready to compute its collision shape. - if (nearbyEntitiesAreReadyForPhysics()) { + if (nearbyEntitiesAreReadyForPhysics() && getMyAvatar()->isReadyForPhysics()) { _physicsEnabled = true; getMyAvatar()->updateMotionBehaviorFromMenu(); } @@ -5832,7 +5792,7 @@ void Application::update(float deltaTime) { viewIsDifferentEnough = true; } - + // if it's been a while since our last query or the view has significantly changed then send a query, otherwise suppress it static const std::chrono::seconds MIN_PERIOD_BETWEEN_QUERIES { 3 }; auto now = SteadyClock::now(); @@ -5893,14 +5853,10 @@ void Application::update(float deltaTime) { { - PerformanceTimer perfTimer("limitless"); + PerformanceTimer perfTimer("AnimDebugDraw"); AnimDebugDraw::getInstance().update(); } - { - PerformanceTimer perfTimer("limitless"); - DependencyManager::get()->update(); - } { // Game loop is done, mark the end of the frame for the scene transactions and the render loop to take over PerformanceTimer perfTimer("enqueueFrame"); @@ -6200,7 +6156,9 @@ void Application::updateWindowTitle() const { auto nodeList = DependencyManager::get(); auto accountManager = DependencyManager::get(); - QString buildVersion = " (build " + applicationVersion() + ")"; + QString buildVersion = " - " + + (BuildInfo::BUILD_TYPE == BuildInfo::BuildType::Stable ? QString("Version") : QString("Build")) + + " " + applicationVersion(); QString loginStatus = accountManager->isLoggedIn() ? "" : " (NOT LOGGED IN)"; @@ -6607,7 +6565,6 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEnginePointe scriptEngine->registerGlobalObject("UserActivityLogger", DependencyManager::get().data()); scriptEngine->registerGlobalObject("Users", DependencyManager::get().data()); - scriptEngine->registerGlobalObject("LimitlessSpeechRecognition", DependencyManager::get().data()); scriptEngine->registerGlobalObject("GooglePoly", DependencyManager::get().data()); if (auto steamClient = PluginManager::getInstance()->getSteamClientPlugin()) { @@ -7646,18 +7603,18 @@ void Application::takeSnapshot(bool notify, bool includeAnimated, float aspectRa }); } -void Application::takeSecondaryCameraSnapshot(const QString& filename) { - postLambdaEvent([filename, this] { +void Application::takeSecondaryCameraSnapshot(const bool& notify, const QString& filename) { + postLambdaEvent([notify, filename, this] { QString snapshotPath = DependencyManager::get()->saveSnapshot(getActiveDisplayPlugin()->getSecondaryCameraScreenshot(), filename, TestScriptingInterface::getInstance()->getTestResultsLocation()); - emit DependencyManager::get()->stillSnapshotTaken(snapshotPath, true); + emit DependencyManager::get()->stillSnapshotTaken(snapshotPath, notify); }); } -void Application::takeSecondaryCamera360Snapshot(const glm::vec3& cameraPosition, const bool& cubemapOutputFormat, const QString& filename) { - postLambdaEvent([filename, cubemapOutputFormat, cameraPosition] { - DependencyManager::get()->save360Snapshot(cameraPosition, cubemapOutputFormat, filename); +void Application::takeSecondaryCamera360Snapshot(const glm::vec3& cameraPosition, const bool& cubemapOutputFormat, const bool& notify, const QString& filename) { + postLambdaEvent([notify, filename, cubemapOutputFormat, cameraPosition] { + DependencyManager::get()->save360Snapshot(cameraPosition, cubemapOutputFormat, notify, filename); }); } @@ -7761,7 +7718,7 @@ void Application::sendLambdaEvent(const std::function& f) { } else { LambdaEvent event(f); QCoreApplication::sendEvent(this, &event); - } + } } void Application::initPlugins(const QStringList& arguments) { @@ -7984,7 +7941,7 @@ void Application::setDisplayPlugin(DisplayPluginPointer newDisplayPlugin) { } // FIXME don't have the application directly set the state of the UI, - // instead emit a signal that the display plugin is changing and let + // instead emit a signal that the display plugin is changing and let // the desktop lock itself. Reduces coupling between the UI and display // plugins auto offscreenUi = DependencyManager::get(); @@ -8093,7 +8050,6 @@ void Application::switchDisplayMode() { setActiveDisplayPlugin(DESKTOP_DISPLAY_PLUGIN_NAME); startHMDStandBySession(); } - emit activeDisplayPluginChanged(); } _previousHMDWornStatus = currentHMDWornStatus; } diff --git a/interface/src/Application.h b/interface/src/Application.h index 0fea476c07..236edf8bb0 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -281,8 +281,11 @@ public: float getGameLoopRate() const { return _gameLoopCounter.rate(); } void takeSnapshot(bool notify, bool includeAnimated = false, float aspectRatio = 0.0f, const QString& filename = QString()); - void takeSecondaryCameraSnapshot(const QString& filename = QString()); - void takeSecondaryCamera360Snapshot(const glm::vec3& cameraPosition, const bool& cubemapOutputFormat, const QString& filename = QString()); + void takeSecondaryCameraSnapshot(const bool& notify, const QString& filename = QString()); + void takeSecondaryCamera360Snapshot(const glm::vec3& cameraPosition, + const bool& cubemapOutputFormat, + const bool& notify, + const QString& filename = QString()); void shareSnapshot(const QString& filename, const QUrl& href = QUrl("")); diff --git a/interface/src/Application_render.cpp b/interface/src/Application_render.cpp index 2a16e8c33c..2daa49dcf7 100644 --- a/interface/src/Application_render.cpp +++ b/interface/src/Application_render.cpp @@ -30,9 +30,6 @@ void Application::editRenderArgs(RenderArgsEditor editor) { void Application::paintGL() { // Some plugins process message events, allowing paintGL to be called reentrantly. - if (_aboutToQuit || _window->isMinimized()) { - return; - } _renderFrameCount++; _lastTimeRendered.start(); @@ -208,10 +205,6 @@ void Application::runRenderFrame(RenderArgs* renderArgs) { RenderArgs::DebugFlags renderDebugFlags = RenderArgs::RENDER_DEBUG_NONE; - if (Menu::getInstance()->isOptionChecked(MenuOption::PhysicsShowHulls)) { - renderDebugFlags = static_cast(renderDebugFlags | - static_cast(RenderArgs::RENDER_DEBUG_HULLS)); - } renderArgs->_debugFlags = renderDebugFlags; } diff --git a/interface/src/Crashpad.cpp b/interface/src/Crashpad.cpp index 45f1d0778f..88651925d5 100644 --- a/interface/src/Crashpad.cpp +++ b/interface/src/Crashpad.cpp @@ -18,6 +18,7 @@ #if HAS_CRASHPAD #include +#include #include #include @@ -69,6 +70,8 @@ bool startCrashHandler() { annotations["token"] = BACKTRACE_TOKEN; annotations["format"] = "minidump"; annotations["version"] = BuildInfo::VERSION.toStdString(); + annotations["build_number"] = BuildInfo::BUILD_NUMBER.toStdString(); + annotations["build_type"] = BuildInfo::BUILD_TYPE_STRING.toStdString(); arguments.push_back("--no-rate-limit"); diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index f55c389a1f..17997acab3 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -103,16 +103,32 @@ Menu::Menu() { editMenu->addSeparator(); // Edit > Cut - addActionToQMenuAndActionHash(editMenu, "Cut", Qt::CTRL | Qt::Key_X); + auto cutAction = addActionToQMenuAndActionHash(editMenu, "Cut", QKeySequence::Cut); + connect(cutAction, &QAction::triggered, [] { + QKeyEvent* keyEvent = new QKeyEvent(QEvent::KeyPress, Qt::Key_X, Qt::ControlModifier); + QCoreApplication::postEvent(QCoreApplication::instance(), keyEvent); + }); // Edit > Copy - addActionToQMenuAndActionHash(editMenu, "Copy", Qt::CTRL | Qt::Key_C); + auto copyAction = addActionToQMenuAndActionHash(editMenu, "Copy", QKeySequence::Copy); + connect(copyAction, &QAction::triggered, [] { + QKeyEvent* keyEvent = new QKeyEvent(QEvent::KeyPress, Qt::Key_C, Qt::ControlModifier); + QCoreApplication::postEvent(QCoreApplication::instance(), keyEvent); + }); // Edit > Paste - addActionToQMenuAndActionHash(editMenu, "Paste", Qt::CTRL | Qt::Key_V); + auto pasteAction = addActionToQMenuAndActionHash(editMenu, "Paste", QKeySequence::Paste); + connect(pasteAction, &QAction::triggered, [] { + QKeyEvent* keyEvent = new QKeyEvent(QEvent::KeyPress, Qt::Key_V, Qt::ControlModifier); + QCoreApplication::postEvent(QCoreApplication::instance(), keyEvent); + }); // Edit > Delete - addActionToQMenuAndActionHash(editMenu, "Delete", Qt::Key_Delete); + auto deleteAction = addActionToQMenuAndActionHash(editMenu, "Delete", QKeySequence::Delete); + connect(deleteAction, &QAction::triggered, [] { + QKeyEvent* keyEvent = new QKeyEvent(QEvent::KeyPress, Qt::Key_Delete, Qt::ControlModifier); + QCoreApplication::postEvent(QCoreApplication::instance(), keyEvent); + }); editMenu->addSeparator(); @@ -201,21 +217,21 @@ Menu::Menu() { // View > First Person auto firstPersonAction = cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash( - viewMenu, MenuOption::FirstPerson, Qt::Key_1, + viewMenu, MenuOption::FirstPerson, 0, true, qApp, SLOT(cameraMenuChanged()))); firstPersonAction->setProperty(EXCLUSION_GROUP_KEY, QVariant::fromValue(cameraModeGroup)); // View > Third Person auto thirdPersonAction = cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash( - viewMenu, MenuOption::ThirdPerson, Qt::Key_3, + viewMenu, MenuOption::ThirdPerson, 0, false, qApp, SLOT(cameraMenuChanged()))); thirdPersonAction->setProperty(EXCLUSION_GROUP_KEY, QVariant::fromValue(cameraModeGroup)); // View > Mirror auto viewMirrorAction = cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash( - viewMenu, MenuOption::FullscreenMirror, Qt::Key_2, + viewMenu, MenuOption::FullscreenMirror, 0, false, qApp, SLOT(cameraMenuChanged()))); viewMirrorAction->setProperty(EXCLUSION_GROUP_KEY, QVariant::fromValue(cameraModeGroup)); @@ -572,6 +588,10 @@ Menu::Menu() { }); addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::FixGaze, 0, false); + addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::ToggleHipsFollowing, 0, false, + avatar.get(), SLOT(setToggleHips(bool))); + addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::AnimDebugDrawBaseOfSupport, 0, false, + avatar.get(), SLOT(setEnableDebugDrawBaseOfSupport(bool))); addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::AnimDebugDrawDefaultPose, 0, false, avatar.get(), SLOT(setEnableDebugDrawDefaultPose(bool))); addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::AnimDebugDrawAnimPose, 0, false, @@ -705,7 +725,6 @@ Menu::Menu() { addCheckableActionToQMenuAndActionHash(physicsOptionsMenu, MenuOption::PhysicsShowOwned, 0, false, drawStatusConfig, SLOT(setShowNetwork(bool))); } - 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))); diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 8569911cbd..1ab7faa82b 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -30,6 +30,7 @@ namespace MenuOption { const QString AddressBar = "Show Address Bar"; const QString Animations = "Animations..."; const QString AnimDebugDrawAnimPose = "Debug Draw Animation"; + const QString AnimDebugDrawBaseOfSupport = "Debug Draw Base of Support"; const QString AnimDebugDrawDefaultPose = "Debug Draw Default Pose"; const QString AnimDebugDrawPosition= "Debug Draw Position"; const QString AskToResetSettings = "Ask To Reset Settings on Start"; @@ -140,7 +141,6 @@ namespace MenuOption { const QString Overlays = "Show Overlays"; const QString PackageModel = "Package Model as .fst..."; const QString Pair = "Pair"; - const QString PhysicsShowHulls = "Draw Collision Shapes"; const QString PhysicsShowOwned = "Highlight Simulation Ownership"; const QString VerboseLogging = "Verbose Logging"; const QString PhysicsShowBulletWireframe = "Show Bullet Collision"; @@ -202,6 +202,7 @@ namespace MenuOption { const QString ThirdPerson = "Third Person"; const QString ThreePointCalibration = "3 Point Calibration"; const QString ThrottleFPSIfNotFocus = "Throttle FPS If Not Focus"; // FIXME - this value duplicated in Basic2DWindowOpenGLDisplayPlugin.cpp + const QString ToggleHipsFollowing = "Toggle Hips Following"; const QString ToolWindow = "Tool Window"; const QString TransmitterDrive = "Transmitter Drive"; const QString TurnWithHead = "Turn using Head"; diff --git a/interface/src/SecondaryCamera.cpp b/interface/src/SecondaryCamera.cpp index db51cf99c8..b9a767f700 100644 --- a/interface/src/SecondaryCamera.cpp +++ b/interface/src/SecondaryCamera.cpp @@ -9,11 +9,13 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include "Application.h" #include "SecondaryCamera.h" -#include -#include + #include +#include +#include + +#include "Application.h" using RenderArgsPointer = std::shared_ptr; diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index 094b3bb67b..4d133706e6 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -104,6 +104,9 @@ void AvatarManager::updateMyAvatar(float deltaTime) { PerformanceWarning warn(showWarnings, "AvatarManager::updateMyAvatar()"); _myAvatar->update(deltaTime); + render::Transaction transaction; + _myAvatar->updateRenderItem(transaction); + qApp->getMain3DScene()->enqueueTransaction(transaction); quint64 now = usecTimestampNow(); quint64 dt = now - _lastSendAvatarDataTime; @@ -465,13 +468,14 @@ void AvatarManager::updateAvatarRenderStatus(bool shouldRenderAvatars) { _shouldRender = shouldRenderAvatars; const render::ScenePointer& scene = qApp->getMain3DScene(); render::Transaction transaction; + auto avatarHashCopy = getHashCopy(); if (_shouldRender) { - for (auto avatarData : _avatarHash) { + for (auto avatarData : avatarHashCopy) { auto avatar = std::static_pointer_cast(avatarData); avatar->addToScene(avatar, scene, transaction); } } else { - for (auto avatarData : _avatarHash) { + for (auto avatarData : avatarHashCopy) { auto avatar = std::static_pointer_cast(avatarData); avatar->removeFromScene(avatar, scene, transaction); } @@ -511,7 +515,8 @@ RayToAvatarIntersectionResult AvatarManager::findRayIntersectionVector(const Pic glm::vec3 normDirection = glm::normalize(ray.direction); - for (auto avatarData : _avatarHash) { + auto avatarHashCopy = getHashCopy(); + for (auto avatarData : avatarHashCopy) { auto avatar = std::static_pointer_cast(avatarData); if ((avatarsToInclude.size() > 0 && !avatarsToInclude.contains(avatar->getID())) || (avatarsToDiscard.size() > 0 && avatarsToDiscard.contains(avatar->getID()))) { diff --git a/interface/src/avatar/AvatarMotionState.cpp b/interface/src/avatar/AvatarMotionState.cpp index 6fc1bd8196..4c5aaacb95 100644 --- a/interface/src/avatar/AvatarMotionState.cpp +++ b/interface/src/avatar/AvatarMotionState.cpp @@ -21,17 +21,6 @@ AvatarMotionState::AvatarMotionState(AvatarSharedPointer avatar, const btCollisi _type = MOTIONSTATE_TYPE_AVATAR; } -void AvatarMotionState::handleEasyChanges(uint32_t& flags) { - ObjectMotionState::handleEasyChanges(flags); - if (flags & Simulation::DIRTY_PHYSICS_ACTIVATION && !_body->isActive()) { - _body->activate(); - } -} - -bool AvatarMotionState::handleHardAndEasyChanges(uint32_t& flags, PhysicsEngine* engine) { - return ObjectMotionState::handleHardAndEasyChanges(flags, engine); -} - AvatarMotionState::~AvatarMotionState() { assert(_avatar); _avatar = nullptr; @@ -57,9 +46,6 @@ PhysicsMotionType AvatarMotionState::computePhysicsMotionType() const { const btCollisionShape* AvatarMotionState::computeNewShape() { ShapeInfo shapeInfo; std::static_pointer_cast(_avatar)->computeShapeInfo(shapeInfo); - glm::vec3 halfExtents = shapeInfo.getHalfExtents(); - halfExtents.y = 0.0f; - _diameter = 2.0f * glm::length(halfExtents); return getShapeManager()->getShape(shapeInfo); } @@ -74,31 +60,25 @@ void AvatarMotionState::getWorldTransform(btTransform& worldTrans) const { worldTrans.setRotation(glmToBullet(getObjectRotation())); if (_body) { _body->setLinearVelocity(glmToBullet(getObjectLinearVelocity())); - _body->setAngularVelocity(glmToBullet(getObjectAngularVelocity())); + _body->setAngularVelocity(glmToBullet(getObjectLinearVelocity())); } } // virtual void AvatarMotionState::setWorldTransform(const btTransform& worldTrans) { + // HACK: The PhysicsEngine does not actually move OTHER avatars -- instead it slaves their local RigidBody to the transform + // as specified by a remote simulation. However, to give the remote simulation time to respond to our own objects we tie + // the other avatar's body to its true position with a simple spring. This is a HACK that will have to be improved later. const float SPRING_TIMESCALE = 0.5f; float tau = PHYSICS_ENGINE_FIXED_SUBSTEP / SPRING_TIMESCALE; btVector3 currentPosition = worldTrans.getOrigin(); - btVector3 offsetToTarget = glmToBullet(getObjectPosition()) - currentPosition; - float distance = offsetToTarget.length(); - if ((1.0f - tau) * distance > _diameter) { - // the avatar body is far from its target --> slam position - btTransform newTransform; - newTransform.setOrigin(currentPosition + offsetToTarget); - newTransform.setRotation(glmToBullet(getObjectRotation())); - _body->setWorldTransform(newTransform); - _body->setLinearVelocity(glmToBullet(getObjectLinearVelocity())); - _body->setAngularVelocity(glmToBullet(getObjectAngularVelocity())); - } else { - // the avatar body is near its target --> slam velocity - btVector3 velocity = glmToBullet(getObjectLinearVelocity()) + (1.0f / SPRING_TIMESCALE) * offsetToTarget; - _body->setLinearVelocity(velocity); - _body->setAngularVelocity(glmToBullet(getObjectAngularVelocity())); - } + btVector3 targetPosition = glmToBullet(getObjectPosition()); + btTransform newTransform; + newTransform.setOrigin((1.0f - tau) * currentPosition + tau * targetPosition); + newTransform.setRotation(glmToBullet(getObjectRotation())); + _body->setWorldTransform(newTransform); + _body->setLinearVelocity(glmToBullet(getObjectLinearVelocity())); + _body->setAngularVelocity(glmToBullet(getObjectLinearVelocity())); } // These pure virtual methods must be implemented for each MotionState type @@ -165,8 +145,3 @@ void AvatarMotionState::computeCollisionGroupAndMask(int32_t& group, int32_t& ma mask = Physics::getDefaultCollisionMask(group); } -// virtual -float AvatarMotionState::getMass() const { - return std::static_pointer_cast(_avatar)->computeMass(); -} - diff --git a/interface/src/avatar/AvatarMotionState.h b/interface/src/avatar/AvatarMotionState.h index 2738aba8ee..07e8102752 100644 --- a/interface/src/avatar/AvatarMotionState.h +++ b/interface/src/avatar/AvatarMotionState.h @@ -23,9 +23,6 @@ class AvatarMotionState : public ObjectMotionState { public: AvatarMotionState(AvatarSharedPointer avatar, const btCollisionShape* shape); - virtual void handleEasyChanges(uint32_t& flags) override; - virtual bool handleHardAndEasyChanges(uint32_t& flags, PhysicsEngine* engine) override; - virtual PhysicsMotionType getMotionType() const override { return _motionType; } virtual uint32_t getIncomingDirtyFlags() override; @@ -67,8 +64,6 @@ public: virtual void computeCollisionGroupAndMask(int32_t& group, int32_t& mask) const override; - virtual float getMass() const override; - friend class AvatarManager; friend class Avatar; @@ -81,7 +76,6 @@ protected: virtual const btCollisionShape* computeNewShape() override; AvatarSharedPointer _avatar; - float _diameter { 0.0f }; uint32_t _dirtyFlags; }; diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index d3ab739649..d57905ee33 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -52,6 +52,7 @@ #include "MyHead.h" #include "MySkeletonModel.h" +#include "AnimUtil.h" #include "Application.h" #include "AvatarManager.h" #include "AvatarActionHold.h" @@ -422,12 +423,12 @@ void MyAvatar::update(float deltaTime) { } #ifdef DEBUG_DRAW_HMD_MOVING_AVERAGE - glm::vec3 p = transformPoint(getSensorToWorldMatrix(), getControllerPoseInAvatarFrame(controller::Pose::HEAD) * - glm::vec3(_headControllerFacingMovingAverage.x, 0.0f, _headControllerFacingMovingAverage.y)); - DebugDraw::getInstance().addMarker("facing-avg", getOrientation(), p, glm::vec4(1.0f)); - p = transformPoint(getSensorToWorldMatrix(), getHMDSensorPosition() + - glm::vec3(_headControllerFacing.x, 0.0f, _headControllerFacing.y)); - DebugDraw::getInstance().addMarker("facing", getOrientation(), p, glm::vec4(1.0f)); + auto sensorHeadPose = getControllerPoseInSensorFrame(controller::Action::HEAD); + glm::vec3 worldHeadPos = transformPoint(getSensorToWorldMatrix(), sensorHeadPose.getTranslation()); + glm::vec3 worldFacingAverage = transformVectorFast(getSensorToWorldMatrix(), glm::vec3(_headControllerFacingMovingAverage.x, 0.0f, _headControllerFacingMovingAverage.y)); + glm::vec3 worldFacing = transformVectorFast(getSensorToWorldMatrix(), glm::vec3(_headControllerFacing.x, 0.0f, _headControllerFacing.y)); + DebugDraw::getInstance().drawRay(worldHeadPos, worldHeadPos + worldFacing, glm::vec4(0.0f, 1.0f, 0.0f, 1.0f)); + DebugDraw::getInstance().drawRay(worldHeadPos, worldHeadPos + worldFacingAverage, glm::vec4(0.0f, 0.0f, 1.0f, 1.0f)); #endif if (_goToPending) { @@ -654,8 +655,8 @@ void MyAvatar::simulate(float deltaTime) { if (success) { moveOperator.addEntityToMoveList(entity, newCube); } - // send an edit packet to update the entity-server about the queryAABox. If it's an - // avatar-entity, don't. + // send an edit packet to update the entity-server about the queryAABox + // unless it is client-only if (packetSender && !entity->getClientOnly()) { EntityItemProperties properties = entity->getProperties(); properties.setQueryAACubeDirty(); @@ -663,6 +664,17 @@ void MyAvatar::simulate(float deltaTime) { packetSender->queueEditEntityMessage(PacketType::EntityEdit, entityTree, entity->getID(), properties); entity->setLastBroadcast(usecTimestampNow()); + + entity->forEachDescendant([&](SpatiallyNestablePointer descendant) { + EntityItemPointer entityDescendant = std::static_pointer_cast(descendant); + if (!entityDescendant->getClientOnly() && descendant->updateQueryAACube()) { + EntityItemProperties descendantProperties; + descendantProperties.setQueryAACube(descendant->getQueryAACube()); + descendantProperties.setLastEdited(now); + packetSender->queueEditEntityMessage(PacketType::EntityEdit, entityTree, entityDescendant->getID(), descendantProperties); + entityDescendant->setLastBroadcast(now); // for debug/physics status icons + } + }); } } }); @@ -701,7 +713,8 @@ void MyAvatar::updateFromHMDSensorMatrix(const glm::mat4& hmdSensorMatrix) { _hmdSensorOrientation = glmExtractRotation(hmdSensorMatrix); auto headPose = getControllerPoseInSensorFrame(controller::Action::HEAD); if (headPose.isValid()) { - _headControllerFacing = getFacingDir2D(headPose.rotation); + glm::quat bodyOrientation = computeBodyFacingFromHead(headPose.rotation, Vectors::UNIT_Y); + _headControllerFacing = getFacingDir2D(bodyOrientation); } else { _headControllerFacing = glm::vec2(1.0f, 0.0f); } @@ -1068,6 +1081,22 @@ float loadSetting(Settings& settings, const QString& name, float defaultValue) { return value; } +void MyAvatar::setToggleHips(bool followHead) { + _follow.setToggleHipsFollowing(followHead); +} + +void MyAvatar::FollowHelper::setToggleHipsFollowing(bool followHead) { + _toggleHipsFollowing = followHead; +} + +bool MyAvatar::FollowHelper::getToggleHipsFollowing() const { + return _toggleHipsFollowing; +} + +void MyAvatar::setEnableDebugDrawBaseOfSupport(bool isEnabled) { + _enableDebugDrawBaseOfSupport = isEnabled; +} + void MyAvatar::setEnableDebugDrawDefaultPose(bool isEnabled) { _enableDebugDrawDefaultPose = isEnabled; @@ -1127,7 +1156,11 @@ void MyAvatar::setEnableDebugDrawIKChains(bool isEnabled) { } void MyAvatar::setEnableMeshVisible(bool isEnabled) { - _skeletonModel->setVisibleInScene(isEnabled, qApp->getMain3DScene(), render::ItemKey::TAG_BITS_NONE, true); + return Avatar::setEnableMeshVisible(isEnabled); +} + +bool MyAvatar::getEnableMeshVisible() const { + return Avatar::getEnableMeshVisible(); } void MyAvatar::setEnableInverseKinematics(bool isEnabled) { @@ -1195,6 +1228,8 @@ void MyAvatar::loadData() { settings.endGroup(); setEnableMeshVisible(Menu::getInstance()->isOptionChecked(MenuOption::MeshVisible)); + _follow.setToggleHipsFollowing (Menu::getInstance()->isOptionChecked(MenuOption::ToggleHipsFollowing)); + setEnableDebugDrawBaseOfSupport(Menu::getInstance()->isOptionChecked(MenuOption::AnimDebugDrawBaseOfSupport)); setEnableDebugDrawDefaultPose(Menu::getInstance()->isOptionChecked(MenuOption::AnimDebugDrawDefaultPose)); setEnableDebugDrawAnimPose(Menu::getInstance()->isOptionChecked(MenuOption::AnimDebugDrawAnimPose)); setEnableDebugDrawPosition(Menu::getInstance()->isOptionChecked(MenuOption::AnimDebugDrawPosition)); @@ -1479,7 +1514,10 @@ void MyAvatar::setSkeletonModelURL(const QUrl& skeletonModelURL) { _skeletonModelChangeCount++; int skeletonModelChangeCount = _skeletonModelChangeCount; Avatar::setSkeletonModelURL(skeletonModelURL); - _skeletonModel->setVisibleInScene(true, qApp->getMain3DScene(), render::ItemKey::TAG_BITS_NONE, true); + _skeletonModel->setTagMask(render::hifi::TAG_NONE); + _skeletonModel->setGroupCulled(true); + _skeletonModel->setVisibleInScene(true, qApp->getMain3DScene()); + _headBoneSet.clear(); _cauterizationNeedsUpdate = true; @@ -2054,14 +2092,12 @@ void MyAvatar::preDisplaySide(const 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); + uint8_t modelRenderTagBits = shouldDrawHead ? render::hifi::TAG_ALL_VIEWS : render::hifi::TAG_SECONDARY_VIEW; - uint8_t castShadowRenderTagBits = render::ItemKey::TAG_BITS_0 | render::ItemKey::TAG_BITS_1; - _attachmentModels[i]->setCanCastShadow(true, qApp->getMain3DScene(), - castShadowRenderTagBits, false); + _attachmentModels[i]->setTagMask(modelRenderTagBits); + _attachmentModels[i]->setGroupCulled(false); + _attachmentModels[i]->setCanCastShadow(true); + _attachmentModels[i]->setVisibleInScene(true, qApp->getMain3DScene()); } } } @@ -2081,6 +2117,31 @@ bool MyAvatar::shouldRenderHead(const RenderArgs* renderArgs) const { return !defaultMode || !firstPerson || !insideHead; } +void MyAvatar::setHasScriptedBlendshapes(bool hasScriptedBlendshapes) { + if (hasScriptedBlendshapes == _hasScriptedBlendShapes) { + return; + } + if (!hasScriptedBlendshapes) { + // send a forced avatarData update to make sure the script can send neutal blendshapes on unload + // without having to wait for the update loop, make sure _hasScriptedBlendShapes is still true + // before sending the update, or else it won't send the neutal blendshapes to the receiving clients + sendAvatarDataPacket(true); + } + _hasScriptedBlendShapes = hasScriptedBlendshapes; +} + +void MyAvatar::setHasProceduralBlinkFaceMovement(bool hasProceduralBlinkFaceMovement) { + _headData->setHasProceduralBlinkFaceMovement(hasProceduralBlinkFaceMovement); +} + +void MyAvatar::setHasProceduralEyeFaceMovement(bool hasProceduralEyeFaceMovement) { + _headData->setHasProceduralEyeFaceMovement(hasProceduralEyeFaceMovement); +} + +void MyAvatar::setHasAudioEnabledFaceMovement(bool hasAudioEnabledFaceMovement) { + _headData->setHasAudioEnabledFaceMovement(hasAudioEnabledFaceMovement); +} + void MyAvatar::updateOrientation(float deltaTime) { // Smoothly rotate body with arrow keys @@ -2394,11 +2455,16 @@ void MyAvatar::restrictScaleFromDomainSettings(const QJsonObject& domainSettings if (_domainMinimumHeight > _domainMaximumHeight) { std::swap(_domainMinimumHeight, _domainMaximumHeight); } + // Set avatar current scale Settings settings; settings.beginGroup("Avatar"); _targetScale = loadSetting(settings, "scale", 1.0f); + // clamp the desired _targetScale by the domain limits NOW, don't try to gracefully animate. Because + // this might cause our avatar to become embedded in the terrain. + _targetScale = getDomainLimitedScale(); + qCDebug(interfaceapp) << "This domain requires a minimum avatar scale of " << _domainMinimumHeight << " and a maximum avatar scale of " << _domainMaximumHeight; @@ -2407,6 +2473,8 @@ void MyAvatar::restrictScaleFromDomainSettings(const QJsonObject& domainSettings setModelScale(_targetScale); rebuildCollisionShape(); settings.endGroup(); + + _haveReceivedHeightLimitsFromDomain = true; } void MyAvatar::leaveDomain() { @@ -2424,6 +2492,7 @@ void MyAvatar::saveAvatarScale() { void MyAvatar::clearScaleRestriction() { _domainMinimumHeight = MIN_AVATAR_HEIGHT; _domainMaximumHeight = MAX_AVATAR_HEIGHT; + _haveReceivedHeightLimitsFromDomain = false; } void MyAvatar::goToLocation(const QVariant& propertiesVar) { @@ -2541,8 +2610,12 @@ bool MyAvatar::safeLanding(const glm::vec3& position) { // If position is not reliably safe from being stuck by physics, answer true and place a candidate better position in betterPositionOut. bool MyAvatar::requiresSafeLanding(const glm::vec3& positionIn, glm::vec3& betterPositionOut) { + // We begin with utilities and tests. The Algorithm in four parts is below. - auto halfHeight = _characterController.getCapsuleHalfHeight() + _characterController.getCapsuleRadius(); + // NOTE: we use estimated avatar height here instead of the bullet capsule halfHeight, because + // the domain avatar height limiting might not have taken effect yet on the actual bullet shape. + auto halfHeight = 0.5f * getHeight(); + if (halfHeight == 0) { return false; // zero height avatar } @@ -2551,14 +2624,13 @@ bool MyAvatar::requiresSafeLanding(const glm::vec3& positionIn, glm::vec3& bette return false; // no entity tree } // More utilities. - const auto offset = getWorldOrientation() *_characterController.getCapsuleLocalOffset(); - const auto capsuleCenter = positionIn + offset; + const auto capsuleCenter = positionIn; const auto up = _worldUpDirection, down = -up; glm::vec3 upperIntersection, upperNormal, lowerIntersection, lowerNormal; EntityItemID upperId, lowerId; QVector include{}, ignore{}; auto mustMove = [&] { // Place bottom of capsule at the upperIntersection, and check again based on the capsule center. - betterPositionOut = upperIntersection + (up * halfHeight) - offset; + betterPositionOut = upperIntersection + (up * halfHeight); return true; }; auto findIntersection = [&](const glm::vec3& startPointIn, const glm::vec3& directionIn, glm::vec3& intersectionOut, EntityItemID& entityIdOut, glm::vec3& normalOut) { @@ -2578,7 +2650,7 @@ bool MyAvatar::requiresSafeLanding(const glm::vec3& positionIn, glm::vec3& bette EntityItemID entityID = entityTree->findRayIntersection(startPointIn, directionIn, include, ignore, visibleOnly, collidableOnly, precisionPicking, element, distance, face, normalOut, extraInfo, lockType, accurateResult); if (entityID.isNull()) { - return false; + return false; } intersectionOut = startPointIn + (directionIn * distance); entityIdOut = entityID; @@ -2604,7 +2676,7 @@ bool MyAvatar::requiresSafeLanding(const glm::vec3& positionIn, glm::vec3& bette // I.e., we are in a clearing between two objects. if (isDown(upperNormal) && isUp(lowerNormal)) { auto spaceBetween = glm::distance(upperIntersection, lowerIntersection); - const float halfHeightFactor = 2.5f; // Until case 5003 is fixed (and maybe after?), we need a fudge factor. Also account for content modelers not being precise. + const float halfHeightFactor = 2.25f; // Until case 5003 is fixed (and maybe after?), we need a fudge factor. Also account for content modelers not being precise. if (spaceBetween > (halfHeightFactor * halfHeight)) { // There is room for us to fit in that clearing. If there wasn't, physics would oscilate us between the objects above and below. // We're now going to iterate upwards through successive upperIntersections, testing to see if we're contained within the top surface of some entity. @@ -2803,6 +2875,7 @@ glm::mat4 MyAvatar::deriveBodyFromHMDSensor() const { auto headPose = getControllerPoseInSensorFrame(controller::Action::HEAD); if (headPose.isValid()) { headPosition = headPose.translation; + // AJT: TODO: can remove this Y_180 headOrientation = headPose.rotation * Quaternions::Y_180; } const glm::quat headOrientationYawOnly = cancelOutRollAndPitch(headOrientation); @@ -2825,6 +2898,8 @@ glm::mat4 MyAvatar::deriveBodyFromHMDSensor() const { // eyeToNeck offset is relative full HMD orientation. // while neckToRoot offset is only relative to HMDs yaw. // Y_180 is necessary because rig is z forward and hmdOrientation is -z forward + + // AJT: TODO: can remove this Y_180, if we remove the higher level one. glm::vec3 headToNeck = headOrientation * Quaternions::Y_180 * (localNeck - localHead); glm::vec3 neckToRoot = headOrientationYawOnly * Quaternions::Y_180 * -localNeck; @@ -2834,6 +2909,202 @@ glm::mat4 MyAvatar::deriveBodyFromHMDSensor() const { return createMatFromQuatAndPos(headOrientationYawOnly, bodyPos); } +// ease in function for dampening cg movement +static float slope(float num) { + const float CURVE_CONSTANT = 1.0f; + float ret = 1.0f; + if (num > 0.0f) { + ret = 1.0f - (1.0f / (1.0f + CURVE_CONSTANT * num)); + } + return ret; +} + +// This function gives a soft clamp at the edge of the base of support +// dampenCgMovement returns the damped cg value in Avatar space. +// cgUnderHeadHandsAvatarSpace is also in Avatar space +// baseOfSupportScale is based on the height of the user +static glm::vec3 dampenCgMovement(glm::vec3 cgUnderHeadHandsAvatarSpace, float baseOfSupportScale) { + float distanceFromCenterZ = cgUnderHeadHandsAvatarSpace.z; + float distanceFromCenterX = cgUnderHeadHandsAvatarSpace.x; + + // In the forward direction we need a different scale because forward is in + // the direction of the hip extensor joint, which means bending usually happens + // well before reaching the edge of the base of support. + const float clampFront = DEFAULT_AVATAR_SUPPORT_BASE_FRONT * DEFAULT_AVATAR_FORWARD_DAMPENING_FACTOR * baseOfSupportScale; + float clampBack = DEFAULT_AVATAR_SUPPORT_BASE_BACK * DEFAULT_AVATAR_LATERAL_DAMPENING_FACTOR * baseOfSupportScale; + float clampLeft = DEFAULT_AVATAR_SUPPORT_BASE_LEFT * DEFAULT_AVATAR_LATERAL_DAMPENING_FACTOR * baseOfSupportScale; + float clampRight = DEFAULT_AVATAR_SUPPORT_BASE_RIGHT * DEFAULT_AVATAR_LATERAL_DAMPENING_FACTOR * baseOfSupportScale; + glm::vec3 dampedCg(0.0f, 0.0f, 0.0f); + + // find the damped z coord of the cg + if (cgUnderHeadHandsAvatarSpace.z < 0.0f) { + // forward displacement + dampedCg.z = slope(fabs(distanceFromCenterZ / clampFront)) * clampFront; + } else { + // backwards displacement + dampedCg.z = slope(fabs(distanceFromCenterZ / clampBack)) * clampBack; + } + + // find the damped x coord of the cg + if (cgUnderHeadHandsAvatarSpace.x > 0.0f) { + // right of center + dampedCg.x = slope(fabs(distanceFromCenterX / clampRight)) * clampRight; + } else { + // left of center + dampedCg.x = slope(fabs(distanceFromCenterX / clampLeft)) * clampLeft; + } + return dampedCg; +} + +// computeCounterBalance returns the center of gravity in Avatar space +glm::vec3 MyAvatar::computeCounterBalance() const { + struct JointMass { + QString name; + float weight; + glm::vec3 position; + JointMass() {}; + JointMass(QString n, float w, glm::vec3 p) { + name = n; + weight = w; + position = p; + } + }; + + // init the body part weights + JointMass cgHeadMass(QString("Head"), DEFAULT_AVATAR_HEAD_MASS, glm::vec3(0.0f, 0.0f, 0.0f)); + JointMass cgLeftHandMass(QString("LeftHand"), DEFAULT_AVATAR_LEFTHAND_MASS, glm::vec3(0.0f, 0.0f, 0.0f)); + JointMass cgRightHandMass(QString("RightHand"), DEFAULT_AVATAR_RIGHTHAND_MASS, glm::vec3(0.0f, 0.0f, 0.0f)); + glm::vec3 tposeHead = DEFAULT_AVATAR_HEAD_POS; + glm::vec3 tposeHips = glm::vec3(0.0f, 0.0f, 0.0f); + + if (_skeletonModel->getRig().indexOfJoint(cgHeadMass.name) != -1) { + cgHeadMass.position = getAbsoluteJointTranslationInObjectFrame(_skeletonModel->getRig().indexOfJoint(cgHeadMass.name)); + tposeHead = getAbsoluteDefaultJointTranslationInObjectFrame(_skeletonModel->getRig().indexOfJoint(cgHeadMass.name)); + } + if (_skeletonModel->getRig().indexOfJoint(cgLeftHandMass.name) != -1) { + cgLeftHandMass.position = getAbsoluteJointTranslationInObjectFrame(_skeletonModel->getRig().indexOfJoint(cgLeftHandMass.name)); + } else { + cgLeftHandMass.position = DEFAULT_AVATAR_LEFTHAND_POS; + } + if (_skeletonModel->getRig().indexOfJoint(cgRightHandMass.name) != -1) { + cgRightHandMass.position = getAbsoluteJointTranslationInObjectFrame(_skeletonModel->getRig().indexOfJoint(cgRightHandMass.name)); + } else { + cgRightHandMass.position = DEFAULT_AVATAR_RIGHTHAND_POS; + } + if (_skeletonModel->getRig().indexOfJoint("Hips") != -1) { + tposeHips = getAbsoluteDefaultJointTranslationInObjectFrame(_skeletonModel->getRig().indexOfJoint("Hips")); + } + + // find the current center of gravity position based on head and hand moments + glm::vec3 sumOfMoments = (cgHeadMass.weight * cgHeadMass.position) + (cgLeftHandMass.weight * cgLeftHandMass.position) + (cgRightHandMass.weight * cgRightHandMass.position); + float totalMass = cgHeadMass.weight + cgLeftHandMass.weight + cgRightHandMass.weight; + + glm::vec3 currentCg = (1.0f / totalMass) * sumOfMoments; + currentCg.y = 0.0f; + // dampening the center of gravity, in effect, limits the value to the perimeter of the base of support + float baseScale = 1.0f; + if (getUserEyeHeight() > 0.0f) { + baseScale = getUserEyeHeight() / DEFAULT_AVATAR_EYE_HEIGHT; + } + glm::vec3 desiredCg = dampenCgMovement(currentCg, baseScale); + + // compute hips position to maintain desiredCg + glm::vec3 counterBalancedForHead = (totalMass + DEFAULT_AVATAR_HIPS_MASS) * desiredCg; + counterBalancedForHead -= sumOfMoments; + glm::vec3 counterBalancedCg = (1.0f / DEFAULT_AVATAR_HIPS_MASS) * counterBalancedForHead; + + // find the height of the hips + glm::vec3 xzDiff((cgHeadMass.position.x - counterBalancedCg.x), 0.0f, (cgHeadMass.position.z - counterBalancedCg.z)); + float headMinusHipXz = glm::length(xzDiff); + float headHipDefault = glm::length(tposeHead - tposeHips); + float hipHeight = 0.0f; + if (headHipDefault > headMinusHipXz) { + hipHeight = sqrtf((headHipDefault * headHipDefault) - (headMinusHipXz * headMinusHipXz)); + } + counterBalancedCg.y = (cgHeadMass.position.y - hipHeight); + + // this is to be sure that the feet don't lift off the floor. + // add 5 centimeters to allow for going up on the toes. + if (counterBalancedCg.y > (tposeHips.y + 0.05f)) { + // if the height is higher than default hips, clamp to default hips + counterBalancedCg.y = tposeHips.y + 0.05f; + } + return counterBalancedCg; +} + +// this function matches the hips rotation to the new cghips-head axis +// headOrientation, headPosition and hipsPosition are in avatar space +// returns the matrix of the hips in Avatar space +static glm::mat4 computeNewHipsMatrix(glm::quat headOrientation, glm::vec3 headPosition, glm::vec3 hipsPosition) { + + glm::quat bodyOrientation = computeBodyFacingFromHead(headOrientation, Vectors::UNIT_Y); + + const float MIX_RATIO = 0.3f; + glm::quat hipsRot = safeLerp(Quaternions::IDENTITY, bodyOrientation, MIX_RATIO); + glm::vec3 hipsFacing = hipsRot * Vectors::UNIT_Z; + + glm::vec3 spineVec = headPosition - hipsPosition; + glm::vec3 u, v, w; + generateBasisVectors(glm::normalize(spineVec), hipsFacing, u, v, w); + return glm::mat4(glm::vec4(w, 0.0f), + glm::vec4(u, 0.0f), + glm::vec4(v, 0.0f), + glm::vec4(hipsPosition, 1.0f)); +} + +static void drawBaseOfSupport(float baseOfSupportScale, float footLocal, glm::mat4 avatarToWorld) { + // scale the base of support based on user height + float clampFront = DEFAULT_AVATAR_SUPPORT_BASE_FRONT * baseOfSupportScale; + float clampBack = DEFAULT_AVATAR_SUPPORT_BASE_BACK * baseOfSupportScale; + float clampLeft = DEFAULT_AVATAR_SUPPORT_BASE_LEFT * baseOfSupportScale; + float clampRight = DEFAULT_AVATAR_SUPPORT_BASE_RIGHT * baseOfSupportScale; + float floor = footLocal + 0.05f; + + // transform the base of support corners to world space + glm::vec3 frontRight = transformPoint(avatarToWorld, { clampRight, floor, clampFront }); + glm::vec3 frontLeft = transformPoint(avatarToWorld, { clampLeft, floor, clampFront }); + glm::vec3 backRight = transformPoint(avatarToWorld, { clampRight, floor, clampBack }); + glm::vec3 backLeft = transformPoint(avatarToWorld, { clampLeft, floor, clampBack }); + + // draw the borders + const glm::vec4 rayColor = { 1.0f, 0.0f, 0.0f, 1.0f }; + DebugDraw::getInstance().drawRay(backLeft, frontLeft, rayColor); + DebugDraw::getInstance().drawRay(backLeft, backRight, rayColor); + DebugDraw::getInstance().drawRay(backRight, frontRight, rayColor); + DebugDraw::getInstance().drawRay(frontLeft, frontRight, rayColor); +} + +// this function finds the hips position using a center of gravity model that +// balances the head and hands with the hips over the base of support +// returns the rotation (-z forward) and position of the Avatar in Sensor space +glm::mat4 MyAvatar::deriveBodyUsingCgModel() const { + glm::mat4 sensorToWorldMat = getSensorToWorldMatrix(); + glm::mat4 worldToSensorMat = glm::inverse(sensorToWorldMat); + auto headPose = getControllerPoseInSensorFrame(controller::Action::HEAD); + + glm::mat4 sensorHeadMat = createMatFromQuatAndPos(headPose.rotation * Quaternions::Y_180, headPose.translation); + + // convert into avatar space + glm::mat4 avatarToWorldMat = getTransform().getMatrix(); + glm::mat4 avatarHeadMat = glm::inverse(avatarToWorldMat) * sensorToWorldMat * sensorHeadMat; + + if (_enableDebugDrawBaseOfSupport) { + float scaleBaseOfSupport = getUserEyeHeight() / DEFAULT_AVATAR_EYE_HEIGHT; + glm::vec3 rightFootPositionLocal = getAbsoluteJointTranslationInObjectFrame(_skeletonModel->getRig().indexOfJoint("RightFoot")); + drawBaseOfSupport(scaleBaseOfSupport, rightFootPositionLocal.y, avatarToWorldMat); + } + + // get the new center of gravity + const glm::vec3 cgHipsPosition = computeCounterBalance(); + + // find the new hips rotation using the new head-hips axis as the up axis + glm::mat4 avatarHipsMat = computeNewHipsMatrix(glmExtractRotation(avatarHeadMat), extractTranslation(avatarHeadMat), cgHipsPosition); + + // convert hips from avatar to sensor space + // The Y_180 is to convert from z forward to -z forward. + return worldToSensorMat * avatarToWorldMat * avatarHipsMat; +} + float MyAvatar::getUserHeight() const { return _userHeight.get(); } @@ -2855,6 +3126,10 @@ float MyAvatar::getWalkSpeed() const { return _walkSpeed.get() * _walkSpeedScalar; } +bool MyAvatar::isReadyForPhysics() const { + return qApp->isServerlessMode() || _haveReceivedHeightLimitsFromDomain; +} + void MyAvatar::setSprintMode(bool sprint) { _walkSpeedScalar = sprint ? AVATAR_SPRINT_SPEED_SCALAR : AVATAR_WALK_SPEED_SCALAR; } @@ -2998,9 +3273,7 @@ void MyAvatar::FollowHelper::decrementTimeRemaining(float dt) { bool MyAvatar::FollowHelper::shouldActivateRotation(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const { const float FOLLOW_ROTATION_THRESHOLD = cosf(PI / 6.0f); // 30 degrees glm::vec2 bodyFacing = getFacingDir2D(currentBodyMatrix); - return glm::dot(-myAvatar.getHeadControllerFacingMovingAverage(), bodyFacing) < FOLLOW_ROTATION_THRESHOLD; - } bool MyAvatar::FollowHelper::shouldActivateHorizontal(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const { @@ -3071,11 +3344,19 @@ void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat AnimPose followWorldPose(currentWorldMatrix); + glm::quat currentHipsLocal = myAvatar.getAbsoluteJointRotationInObjectFrame(myAvatar.getJointIndex("Hips")); + const glm::quat hipsinWorldSpace = followWorldPose.rot() * (Quaternions::Y_180 * (currentHipsLocal)); + const glm::vec3 avatarUpWorld = glm::normalize(followWorldPose.rot()*(Vectors::UP)); + glm::quat resultingSwingInWorld; + glm::quat resultingTwistInWorld; + swingTwistDecomposition(hipsinWorldSpace, avatarUpWorld, resultingSwingInWorld, resultingTwistInWorld); + // remove scale present from sensorToWorldMatrix followWorldPose.scale() = glm::vec3(1.0f); if (isActive(Rotation)) { - followWorldPose.rot() = glmExtractRotation(desiredWorldMatrix); + //use the hmd reading for the hips follow + followWorldPose.rot() = glmExtractRotation(desiredWorldMatrix); } if (isActive(Horizontal)) { glm::vec3 desiredTranslation = extractTranslation(desiredWorldMatrix); @@ -3471,6 +3752,10 @@ void MyAvatar::updateHoldActions(const AnimPose& prePhysicsPose, const AnimPose& } } +bool MyAvatar::isRecenteringHorizontally() const { + return _follow.isActive(FollowHelper::Horizontal); +} + const MyHead* MyAvatar::getMyHead() const { return static_cast(getHead()); } diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index bba840d185..1a6feb142a 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -86,6 +86,10 @@ class MyAvatar : public Avatar { * @property {number} audioListenerModeCamera=1 - The audio listening position is at the camera. Read-only. * @property {number} audioListenerModeCustom=2 - The audio listening position is at a the position specified by set by the * customListenPosition and customListenOrientation property values. Read-only. + * @property {boolean} hasScriptedBlendshapes=false - Blendshapes will be transmitted over the network if set to true. + * @property {boolean} hasProceduralBlinkFaceMovement=true - procedural blinking will be turned on if set to true. + * @property {boolean} hasProceduralEyeFaceMovement=true - procedural eye movement will be turned on if set to true. + * @property {boolean} hasAudioEnabledFaceMovement=true - If set to true, voice audio will move the mouth Blendshapes while MyAvatar.hasScriptedBlendshapes is enabled. * @property {Vec3} customListenPosition=Vec3.ZERO - The listening position used when the audioListenerMode * property value is audioListenerModeCustom. * @property {Quat} customListenOrientation=Quat.IDENTITY - The listening orientation used when the @@ -105,6 +109,9 @@ class MyAvatar : public Avatar { * by 30cm. Read-only. * @property {Pose} rightHandTipPose - The pose of the right hand as determined by the hand controllers, with the position * by 30cm. Read-only. + * @property {boolean} centerOfGravityModelEnabled=true - If true then the avatar hips are placed according to the center of + * gravity model that balance the center of gravity over the base of support of the feet. Setting the value false + * will result in the default behaviour where the hips are placed under the head. * @property {boolean} hmdLeanRecenterEnabled=true - If true then the avatar is re-centered to be under the * head's position. In room-scale VR, this behavior is what causes your avatar to follow your HMD as you walk around * the room. Setting the value false is useful if you want to pin the avatar to a fixed position. @@ -184,6 +191,10 @@ class MyAvatar : public Avatar { Q_PROPERTY(AudioListenerMode audioListenerModeHead READ getAudioListenerModeHead) Q_PROPERTY(AudioListenerMode audioListenerModeCamera READ getAudioListenerModeCamera) Q_PROPERTY(AudioListenerMode audioListenerModeCustom READ getAudioListenerModeCustom) + Q_PROPERTY(bool hasScriptedBlendshapes READ getHasScriptedBlendshapes WRITE setHasScriptedBlendshapes) + Q_PROPERTY(bool hasProceduralBlinkFaceMovement READ getHasProceduralBlinkFaceMovement WRITE setHasProceduralBlinkFaceMovement) + Q_PROPERTY(bool hasProceduralEyeFaceMovement READ getHasProceduralEyeFaceMovement WRITE setHasProceduralEyeFaceMovement) + Q_PROPERTY(bool hasAudioEnabledFaceMovement READ getHasAudioEnabledFaceMovement WRITE setHasAudioEnabledFaceMovement) //TODO: make gravity feature work Q_PROPERTY(glm::vec3 gravity READ getGravity WRITE setGravity) Q_PROPERTY(glm::vec3 leftHandPosition READ getLeftHandPosition) @@ -199,6 +210,7 @@ class MyAvatar : public Avatar { Q_PROPERTY(float energy READ getEnergy WRITE setEnergy) Q_PROPERTY(bool isAway READ getIsAway WRITE setAway) + Q_PROPERTY(bool centerOfGravityModelEnabled READ getCenterOfGravityModelEnabled WRITE setCenterOfGravityModelEnabled) Q_PROPERTY(bool hmdLeanRecenterEnabled READ getHMDLeanRecenterEnabled WRITE setHMDLeanRecenterEnabled) Q_PROPERTY(bool collisionsEnabled READ getCollisionsEnabled WRITE setCollisionsEnabled) Q_PROPERTY(bool characterControllerEnabled READ getCharacterControllerEnabled WRITE setCharacterControllerEnabled) @@ -464,7 +476,7 @@ public: Q_INVOKABLE bool getClearOverlayWhenMoving() const { return _clearOverlayWhenMoving; } /**jsdoc * @function MyAvatar.setClearOverlayWhenMoving - * @returns {boolean} + * @param {boolean} on */ Q_INVOKABLE void setClearOverlayWhenMoving(bool on) { _clearOverlayWhenMoving = on; } @@ -480,7 +492,16 @@ public: */ Q_INVOKABLE QString getDominantHand() const { return _dominantHand; } - + /**jsdoc + * @function MyAvatar.setCenterOfGravityModelEnabled + * @param {boolean} enabled + */ + Q_INVOKABLE void setCenterOfGravityModelEnabled(bool value) { _centerOfGravityModelEnabled = value; } + /**jsdoc + * @function MyAvatar.getCenterOfGravityModelEnabled + * @returns {boolean} + */ + Q_INVOKABLE bool getCenterOfGravityModelEnabled() const { return _centerOfGravityModelEnabled; } /**jsdoc * @function MyAvatar.setHMDLeanRecenterEnabled * @param {boolean} enabled @@ -564,6 +585,13 @@ public: */ Q_INVOKABLE void triggerRotationRecenter(); + /**jsdoc + *The isRecenteringHorizontally function returns true if MyAvatar + *is translating the root of the Avatar to keep the center of gravity under the head. + *isActive(Horizontal) is returned. + *@function MyAvatar.isRecenteringHorizontally + */ + Q_INVOKABLE bool isRecenteringHorizontally() const; eyeContactTarget getEyeContactTarget(); @@ -956,10 +984,18 @@ public: void removeHoldAction(AvatarActionHold* holdAction); // thread-safe void updateHoldActions(const AnimPose& prePhysicsPose, const AnimPose& postUpdatePose); + // derive avatar body position and orientation from the current HMD Sensor location. - // results are in HMD frame + // results are in sensor frame (-z forward) glm::mat4 deriveBodyFromHMDSensor() const; + glm::vec3 computeCounterBalance() const; + + // derive avatar body position and orientation from using the current HMD Sensor location in relation to the previous + // location of the base of support of the avatar. + // results are in sensor frame (-z foward) + glm::mat4 deriveBodyUsingCgModel() const; + /**jsdoc * @function MyAvatar.isUp * @param {Vec3} direction @@ -987,6 +1023,8 @@ public: QVector getScriptUrls(); + bool isReadyForPhysics() const; + public slots: /**jsdoc @@ -1107,7 +1145,16 @@ public slots: */ Q_INVOKABLE void updateMotionBehaviorFromMenu(); - + /**jsdoc + * @function MyAvatar.setToggleHips + * @param {boolean} enabled + */ + void setToggleHips(bool followHead); + /**jsdoc + * @function MyAvatar.setEnableDebugDrawBaseOfSupport + * @param {boolean} enabled + */ + void setEnableDebugDrawBaseOfSupport(bool isEnabled); /**jsdoc * @function MyAvatar.setEnableDebugDrawDefaultPose * @param {boolean} enabled @@ -1159,7 +1206,7 @@ public slots: * @function MyAvatar.getEnableMeshVisible * @returns {boolean} true if your avatar's mesh is visible, otherwise false. */ - bool getEnableMeshVisible() const { return _skeletonModel->isVisible(); } + bool getEnableMeshVisible() const override; /**jsdoc * Set whether or not your avatar mesh is visible. @@ -1171,7 +1218,7 @@ public slots: * MyAvatar.setEnableMeshVisible(true); * }, 10000); */ - void setEnableMeshVisible(bool isEnabled); + virtual void setEnableMeshVisible(bool isEnabled) override; /**jsdoc * @function MyAvatar.setEnableInverseKinematics @@ -1341,6 +1388,14 @@ private: virtual bool shouldRenderHead(const RenderArgs* renderArgs) const override; void setShouldRenderLocally(bool shouldRender) { _shouldRender = shouldRender; setEnableMeshVisible(shouldRender); } bool getShouldRenderLocally() const { return _shouldRender; } + void setHasScriptedBlendshapes(bool hasScriptedBlendshapes); + bool getHasScriptedBlendshapes() const override { return _hasScriptedBlendShapes; } + void setHasProceduralBlinkFaceMovement(bool hasProceduralBlinkFaceMovement); + bool getHasProceduralBlinkFaceMovement() const override { return _headData->getHasProceduralBlinkFaceMovement(); } + void setHasProceduralEyeFaceMovement(bool hasProceduralEyeFaceMovement); + bool getHasProceduralEyeFaceMovement() const override { return _headData->getHasProceduralEyeFaceMovement(); } + void setHasAudioEnabledFaceMovement(bool hasAudioEnabledFaceMovement); + bool getHasAudioEnabledFaceMovement() const override { return _headData->getHasAudioEnabledFaceMovement(); } bool isMyAvatar() const override { return true; } virtual int parseDataFromBuffer(const QByteArray& buffer) override; virtual glm::vec3 getSkeletonPosition() const override; @@ -1449,6 +1504,7 @@ private: bool _hmdRollControlEnabled { true }; float _hmdRollControlDeadZone { ROLL_CONTROL_DEAD_ZONE_DEFAULT }; float _hmdRollControlRate { ROLL_CONTROL_RATE_DEFAULT }; + std::atomic _hasScriptedBlendShapes { false }; // working copy -- see AvatarData for thread-safe _sensorToWorldMatrixCache, used for outward facing access glm::mat4 _sensorToWorldMatrix { glm::mat4() }; @@ -1458,8 +1514,8 @@ private: glm::quat _hmdSensorOrientation; glm::vec3 _hmdSensorPosition; // cache head controller pose in sensor space - glm::vec2 _headControllerFacing; // facing vector in xz plane - glm::vec2 _headControllerFacingMovingAverage { 0, 0 }; // facing vector in xz plane + glm::vec2 _headControllerFacing; // facing vector in xz plane (sensor space) + glm::vec2 _headControllerFacingMovingAverage { 0.0f, 0.0f }; // facing vector in xz plane (sensor space) // cache of the current body position and orientation of the avatar's body, // in sensor space. @@ -1495,9 +1551,12 @@ private: void setForceActivateVertical(bool val); bool getForceActivateHorizontal() const; void setForceActivateHorizontal(bool val); - std::atomic _forceActivateRotation{ false }; - std::atomic _forceActivateVertical{ false }; - std::atomic _forceActivateHorizontal{ false }; + bool getToggleHipsFollowing() const; + void setToggleHipsFollowing(bool followHead); + std::atomic _forceActivateRotation { false }; + std::atomic _forceActivateVertical { false }; + std::atomic _forceActivateHorizontal { false }; + std::atomic _toggleHipsFollowing { true }; }; FollowHelper _follow; @@ -1510,6 +1569,7 @@ private: bool _prevShouldDrawHead; bool _rigEnabled { true }; + bool _enableDebugDrawBaseOfSupport { false }; bool _enableDebugDrawDefaultPose { false }; bool _enableDebugDrawAnimPose { false }; bool _enableDebugDrawHandControllers { false }; @@ -1532,6 +1592,7 @@ private: std::map _controllerPoseMap; mutable std::mutex _controllerPoseMapMutex; + bool _centerOfGravityModelEnabled { true }; bool _hmdLeanRecenterEnabled { true }; bool _sprint { false }; @@ -1568,6 +1629,8 @@ private: // load avatar scripts once when rig is ready bool _shouldLoadScripts { false }; + + bool _haveReceivedHeightLimitsFromDomain = { false }; }; QScriptValue audioListenModeToScriptValue(QScriptEngine* engine, const AudioListenerMode& audioListenerMode); diff --git a/interface/src/avatar/MyHead.cpp b/interface/src/avatar/MyHead.cpp index cad2f9e5d0..9b05a26c76 100644 --- a/interface/src/avatar/MyHead.cpp +++ b/interface/src/avatar/MyHead.cpp @@ -46,32 +46,18 @@ void MyHead::simulate(float deltaTime) { auto player = DependencyManager::get(); // Only use face trackers when not playing back a recording. if (!player->isPlaying()) { - FaceTracker* faceTracker = qApp->getActiveFaceTracker(); - _isFaceTrackerConnected = faceTracker != nullptr && !faceTracker->isMuted(); + auto faceTracker = qApp->getActiveFaceTracker(); + const bool hasActualFaceTrackerConnected = faceTracker && !faceTracker->isMuted(); + _isFaceTrackerConnected = hasActualFaceTrackerConnected || _owningAvatar->getHasScriptedBlendshapes(); if (_isFaceTrackerConnected) { - _transientBlendshapeCoefficients = faceTracker->getBlendshapeCoefficients(); - - if (typeid(*faceTracker) == typeid(DdeFaceTracker)) { - - if (Menu::getInstance()->isOptionChecked(MenuOption::UseAudioForMouth)) { - calculateMouthShapes(deltaTime); - - const int JAW_OPEN_BLENDSHAPE = 21; - const int MMMM_BLENDSHAPE = 34; - const int FUNNEL_BLENDSHAPE = 40; - const int SMILE_LEFT_BLENDSHAPE = 28; - const int SMILE_RIGHT_BLENDSHAPE = 29; - _transientBlendshapeCoefficients[JAW_OPEN_BLENDSHAPE] += _audioJawOpen; - _transientBlendshapeCoefficients[SMILE_LEFT_BLENDSHAPE] += _mouth4; - _transientBlendshapeCoefficients[SMILE_RIGHT_BLENDSHAPE] += _mouth4; - _transientBlendshapeCoefficients[MMMM_BLENDSHAPE] += _mouth2; - _transientBlendshapeCoefficients[FUNNEL_BLENDSHAPE] += _mouth3; - } - applyEyelidOffset(getFinalOrientationInWorldFrame()); + if (hasActualFaceTrackerConnected) { + _blendshapeCoefficients = faceTracker->getBlendshapeCoefficients(); } } + auto eyeTracker = DependencyManager::get(); _isEyeTrackerConnected = eyeTracker->isTracking(); + // if eye tracker is connected we should get the data here. } Parent::simulate(deltaTime); } diff --git a/interface/src/avatar/MySkeletonModel.cpp b/interface/src/avatar/MySkeletonModel.cpp index f317f6b2c1..c15b00ca19 100644 --- a/interface/src/avatar/MySkeletonModel.cpp +++ b/interface/src/avatar/MySkeletonModel.cpp @@ -45,7 +45,14 @@ static AnimPose computeHipsInSensorFrame(MyAvatar* myAvatar, bool isFlying) { return result; } - glm::mat4 hipsMat = myAvatar->deriveBodyFromHMDSensor(); + glm::mat4 hipsMat; + if (myAvatar->getCenterOfGravityModelEnabled()) { + // then we use center of gravity model + hipsMat = myAvatar->deriveBodyUsingCgModel(); + } else { + // otherwise use the default of putting the hips under the head + hipsMat = myAvatar->deriveBodyFromHMDSensor(); + } glm::vec3 hipsPos = extractTranslation(hipsMat); glm::quat hipsRot = glmExtractRotation(hipsMat); @@ -53,8 +60,11 @@ static AnimPose computeHipsInSensorFrame(MyAvatar* myAvatar, bool isFlying) { glm::mat4 avatarToSensorMat = worldToSensorMat * avatarToWorldMat; // dampen hips rotation, by mixing it with the avatar orientation in sensor space - const float MIX_RATIO = 0.5f; - hipsRot = safeLerp(glmExtractRotation(avatarToSensorMat), hipsRot, MIX_RATIO); + // turning this off for center of gravity model because it is already mixed in there + if (!(myAvatar->getCenterOfGravityModelEnabled())) { + const float MIX_RATIO = 0.5f; + hipsRot = safeLerp(glmExtractRotation(avatarToSensorMat), hipsRot, MIX_RATIO); + } if (isFlying) { // rotate the hips back to match the flying animation. @@ -73,6 +83,7 @@ static AnimPose computeHipsInSensorFrame(MyAvatar* myAvatar, bool isFlying) { hipsPos = headPos + tiltRot * (hipsPos - headPos); } + // AJT: TODO can we remove this? return AnimPose(hipsRot * Quaternions::Y_180, hipsPos); } @@ -170,6 +181,15 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { } } + bool isFlying = (myAvatar->getCharacterController()->getState() == CharacterController::State::Hover || myAvatar->getCharacterController()->computeCollisionGroup() == BULLET_COLLISION_GROUP_COLLISIONLESS); + if (isFlying != _prevIsFlying) { + const float FLY_TO_IDLE_HIPS_TRANSITION_TIME = 0.5f; + _flyIdleTimer = FLY_TO_IDLE_HIPS_TRANSITION_TIME; + } else { + _flyIdleTimer -= deltaTime; + } + _prevIsFlying = isFlying; + // if hips are not under direct control, estimate the hips position. if (avatarHeadPose.isValid() && !(params.primaryControllerFlags[Rig::PrimaryControllerType_Hips] & (uint8_t)Rig::ControllerFlags::Enabled)) { bool isFlying = (myAvatar->getCharacterController()->getState() == CharacterController::State::Hover || myAvatar->getCharacterController()->computeCollisionGroup() == BULLET_COLLISION_GROUP_COLLISIONLESS); @@ -181,14 +201,28 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { AnimPose hips = computeHipsInSensorFrame(myAvatar, isFlying); + // timescale in seconds + const float TRANS_HORIZ_TIMESCALE = 0.15f; + const float TRANS_VERT_TIMESCALE = 0.01f; // We want the vertical component of the hips to follow quickly to prevent spine squash/stretch. + const float ROT_TIMESCALE = 0.15f; + const float FLY_IDLE_TRANSITION_TIMESCALE = 0.25f; + + float transHorizAlpha, transVertAlpha, rotAlpha; + if (_flyIdleTimer < 0.0f) { + transHorizAlpha = glm::min(deltaTime / TRANS_HORIZ_TIMESCALE, 1.0f); + transVertAlpha = glm::min(deltaTime / TRANS_VERT_TIMESCALE, 1.0f); + rotAlpha = glm::min(deltaTime / ROT_TIMESCALE, 1.0f); + } else { + transHorizAlpha = glm::min(deltaTime / FLY_IDLE_TRANSITION_TIMESCALE, 1.0f); + transVertAlpha = glm::min(deltaTime / FLY_IDLE_TRANSITION_TIMESCALE, 1.0f); + rotAlpha = glm::min(deltaTime / FLY_IDLE_TRANSITION_TIMESCALE, 1.0f); + } + // smootly lerp hips, in sensorframe, with different coeff for horiz and vertical translation. - const float ROT_ALPHA = 0.9f; - const float TRANS_HORIZ_ALPHA = 0.9f; - const float TRANS_VERT_ALPHA = 0.1f; float hipsY = hips.trans().y; - hips.trans() = lerp(hips.trans(), _prevHips.trans(), TRANS_HORIZ_ALPHA); - hips.trans().y = lerp(hipsY, _prevHips.trans().y, TRANS_VERT_ALPHA); - hips.rot() = safeLerp(hips.rot(), _prevHips.rot(), ROT_ALPHA); + hips.trans() = lerp(_prevHips.trans(), hips.trans(), transHorizAlpha); + hips.trans().y = lerp(_prevHips.trans().y, hipsY, transVertAlpha); + hips.rot() = safeLerp(_prevHips.rot(), hips.rot(), rotAlpha); _prevHips = hips; _prevHipsValid = true; diff --git a/interface/src/avatar/MySkeletonModel.h b/interface/src/avatar/MySkeletonModel.h index 252b6c293b..ebef9796a4 100644 --- a/interface/src/avatar/MySkeletonModel.h +++ b/interface/src/avatar/MySkeletonModel.h @@ -28,6 +28,8 @@ private: AnimPose _prevHips; // sensor frame bool _prevHipsValid { false }; + bool _prevIsFlying { false }; + float _flyIdleTimer { 0.0f }; std::map _jointRotationFrameOffsetMap; }; diff --git a/interface/src/commerce/Ledger.cpp b/interface/src/commerce/Ledger.cpp index f791ea25bc..69698e82a6 100644 --- a/interface/src/commerce/Ledger.cpp +++ b/interface/src/commerce/Ledger.cpp @@ -134,8 +134,14 @@ void Ledger::balance(const QStringList& keys) { keysQuery("balance", "balanceSuccess", "balanceFailure"); } -void Ledger::inventory(const QStringList& keys) { - keysQuery("inventory", "inventorySuccess", "inventoryFailure"); +void Ledger::inventory(const QString& editionFilter, const QString& typeFilter, const QString& titleFilter, const int& page, const int& perPage) { + QJsonObject params; + params["edition_filter"] = editionFilter; + params["type_filter"] = typeFilter; + params["title_filter"] = titleFilter; + params["page"] = page; + params["per_page"] = perPage; + keysQuery("inventory", "inventorySuccess", "inventoryFailure", params); } QString hfcString(const QJsonValue& sentValue, const QJsonValue& receivedValue) { @@ -260,9 +266,9 @@ void Ledger::historyFailure(QNetworkReply& reply) { failResponse("history", reply); } -void Ledger::history(const QStringList& keys, const int& pageNumber) { +void Ledger::history(const QStringList& keys, const int& pageNumber, const int& itemsPerPage) { QJsonObject params; - params["per_page"] = 100; + params["per_page"] = itemsPerPage; params["page"] = pageNumber; keysQuery("history", "historySuccess", "historyFailure", params); } diff --git a/interface/src/commerce/Ledger.h b/interface/src/commerce/Ledger.h index abc97bfe72..8a8fd2630a 100644 --- a/interface/src/commerce/Ledger.h +++ b/interface/src/commerce/Ledger.h @@ -28,8 +28,8 @@ public: void buy(const QString& hfc_key, int cost, const QString& asset_id, const QString& inventory_key, const bool controlled_failure = false); bool receiveAt(const QString& hfc_key, const QString& signing_key); void balance(const QStringList& keys); - void inventory(const QStringList& keys); - void history(const QStringList& keys, const int& pageNumber); + void inventory(const QString& editionFilter, const QString& typeFilter, const QString& titleFilter, const int& page, const int& perPage); + void history(const QStringList& keys, const int& pageNumber, const int& itemsPerPage); void account(); void updateLocation(const QString& asset_id, const QString& location, const bool& alsoUpdateSiblings = false, const bool controlledFailure = false); void certificateInfo(const QString& certificateId); diff --git a/interface/src/commerce/QmlCommerce.cpp b/interface/src/commerce/QmlCommerce.cpp index 722f29ba2f..b960c0b703 100644 --- a/interface/src/commerce/QmlCommerce.cpp +++ b/interface/src/commerce/QmlCommerce.cpp @@ -105,21 +105,21 @@ void QmlCommerce::balance() { } } -void QmlCommerce::inventory() { +void QmlCommerce::inventory(const QString& editionFilter, const QString& typeFilter, const QString& titleFilter, const int& page, const int& perPage) { auto ledger = DependencyManager::get(); auto wallet = DependencyManager::get(); QStringList cachedPublicKeys = wallet->listPublicKeys(); if (!cachedPublicKeys.isEmpty()) { - ledger->inventory(cachedPublicKeys); + ledger->inventory(editionFilter, typeFilter, titleFilter, page, perPage); } } -void QmlCommerce::history(const int& pageNumber) { +void QmlCommerce::history(const int& pageNumber, const int& itemsPerPage) { auto ledger = DependencyManager::get(); auto wallet = DependencyManager::get(); QStringList cachedPublicKeys = wallet->listPublicKeys(); if (!cachedPublicKeys.isEmpty()) { - ledger->history(cachedPublicKeys, pageNumber); + ledger->history(cachedPublicKeys, pageNumber, itemsPerPage); } } @@ -227,10 +227,13 @@ QString QmlCommerce::getInstalledApps() { QString scriptURL = appFileJsonObject["scriptURL"].toString(); // If the script .app.json is on the user's local disk but the associated script isn't running - // for some reason, start that script again. + // for some reason (i.e. the user stopped it from Running Scripts), + // delete the .app.json from the user's local disk. if (!runningScripts.contains(scriptURL)) { - if ((DependencyManager::get()->loadScript(scriptURL.trimmed())).isNull()) { - qCDebug(commerce) << "Couldn't start script while checking installed apps."; + if (!appFile.remove()) { + qCWarning(commerce) + << "Couldn't delete local .app.json file (app's script isn't running). App filename is:" + << appFileName; } } } else { diff --git a/interface/src/commerce/QmlCommerce.h b/interface/src/commerce/QmlCommerce.h index 27e97fe7db..a0c6916799 100644 --- a/interface/src/commerce/QmlCommerce.h +++ b/interface/src/commerce/QmlCommerce.h @@ -73,8 +73,8 @@ protected: Q_INVOKABLE void buy(const QString& assetId, int cost, const bool controlledFailure = false); Q_INVOKABLE void balance(); - Q_INVOKABLE void inventory(); - Q_INVOKABLE void history(const int& pageNumber); + Q_INVOKABLE void inventory(const QString& editionFilter = QString(), const QString& typeFilter = QString(), const QString& titleFilter = QString(), const int& page = 1, const int& perPage = 20); + Q_INVOKABLE void history(const int& pageNumber, const int& itemsPerPage = 100); Q_INVOKABLE void generateKeyPair(); Q_INVOKABLE void account(); diff --git a/interface/src/commerce/Wallet.cpp b/interface/src/commerce/Wallet.cpp index 982adb4b5e..e003ae88a0 100644 --- a/interface/src/commerce/Wallet.cpp +++ b/interface/src/commerce/Wallet.cpp @@ -314,6 +314,7 @@ Wallet::Wallet() { auto nodeList = DependencyManager::get(); auto ledger = DependencyManager::get(); auto& packetReceiver = nodeList->getPacketReceiver(); + _passphrase = new QString(""); packetReceiver.registerListener(PacketType::ChallengeOwnership, this, "handleChallengeOwnershipPacket"); packetReceiver.registerListener(PacketType::ChallengeOwnershipRequest, this, "handleChallengeOwnershipPacket"); @@ -365,6 +366,10 @@ Wallet::~Wallet() { if (_securityImage) { delete _securityImage; } + + if (_passphrase) { + delete _passphrase; + } } bool Wallet::setPassphrase(const QString& passphrase) { @@ -531,7 +536,6 @@ bool Wallet::walletIsAuthenticatedWithPassphrase() { // be sure to add the public key so we don't do this over and over _publicKeys.push_back(publicKey.toBase64()); - DependencyManager::get()->setWalletStatus((uint)WalletStatus::WALLET_STATUS_READY); return true; } } @@ -610,7 +614,11 @@ void Wallet::updateImageProvider() { SecurityImageProvider* securityImageProvider; // inform offscreenUI security image provider - QQmlEngine* engine = DependencyManager::get()->getSurfaceContext()->engine(); + auto offscreenUI = DependencyManager::get(); + if (!offscreenUI) { + return; + } + QQmlEngine* engine = offscreenUI->getSurfaceContext()->engine(); securityImageProvider = reinterpret_cast(engine->imageProvider(SecurityImageProvider::PROVIDER_NAME)); securityImageProvider->setSecurityImage(_securityImage); diff --git a/interface/src/commerce/Wallet.h b/interface/src/commerce/Wallet.h index 8a7d6b8c07..665afd9a23 100644 --- a/interface/src/commerce/Wallet.h +++ b/interface/src/commerce/Wallet.h @@ -78,7 +78,7 @@ private: QByteArray _salt; QByteArray _iv; QByteArray _ckey; - QString* _passphrase { new QString("") }; + QString* _passphrase { nullptr }; bool _isOverridingServer { false }; bool writeWallet(const QString& newPassphrase = QString("")); diff --git a/interface/src/main.cpp b/interface/src/main.cpp index 22db128f7e..cd8c052d63 100644 --- a/interface/src/main.cpp +++ b/interface/src/main.cpp @@ -81,6 +81,13 @@ int main(int argc, const char* argv[]) { // Instance UserActivityLogger now that the settings are loaded auto& ual = UserActivityLogger::getInstance(); + // once the settings have been loaded, check if we need to flip the default for UserActivityLogger + if (!ual.isDisabledSettingSet()) { + // the user activity logger is opt-out for Interface + // but it's defaulted to disabled for other targets + // so we need to enable it here if it has never been disabled by the user + ual.disable(false); + } qDebug() << "UserActivityLogger is enabled:" << ual.isEnabled(); if (ual.isEnabled()) { diff --git a/interface/src/raypick/PickScriptingInterface.cpp b/interface/src/raypick/PickScriptingInterface.cpp index 8da6e7c615..74459ca624 100644 --- a/interface/src/raypick/PickScriptingInterface.cpp +++ b/interface/src/raypick/PickScriptingInterface.cpp @@ -36,7 +36,7 @@ unsigned int PickScriptingInterface::createPick(const PickQuery::PickType type, * @typedef {object} Picks.RayPickProperties * @property {boolean} [enabled=false] If this Pick should start enabled or not. Disabled Picks do not updated their pick results. * @property {number} [filter=Picks.PICK_NOTHING] The filter for this Pick to use, constructed using filter flags combined using bitwise OR. - * @property {float} [maxDistance=0.0] The max distance at which this Pick will intersect. 0.0 = no max. < 0.0 is invalid. + * @property {number} [maxDistance=0.0] The max distance at which this Pick will intersect. 0.0 = no max. < 0.0 is invalid. * @property {string} [joint] Only for Joint or Mouse Ray Picks. If "Mouse", it will create a Ray Pick that follows the system mouse, in desktop or HMD. * If "Avatar", it will create a Joint Ray Pick that follows your avatar's head. Otherwise, it will create a Joint Ray Pick that follows the given joint, if it * exists on your current avatar. @@ -103,7 +103,7 @@ unsigned int PickScriptingInterface::createRayPick(const QVariant& properties) { * @property {number} [hand=-1] An integer. 0 == left, 1 == right. Invalid otherwise. * @property {boolean} [enabled=false] If this Pick should start enabled or not. Disabled Picks do not updated their pick results. * @property {number} [filter=Picks.PICK_NOTHING] The filter for this Pick to use, constructed using filter flags combined using bitwise OR. - * @property {float} [maxDistance=0.0] The max distance at which this Pick will intersect. 0.0 = no max. < 0.0 is invalid. + * @property {number} [maxDistance=0.0] The max distance at which this Pick will intersect. 0.0 = no max. < 0.0 is invalid. */ unsigned int PickScriptingInterface::createStylusPick(const QVariant& properties) { QVariantMap propMap = properties.toMap(); diff --git a/interface/src/raypick/PickScriptingInterface.h b/interface/src/raypick/PickScriptingInterface.h index 5ef5d27d74..0ee091716d 100644 --- a/interface/src/raypick/PickScriptingInterface.h +++ b/interface/src/raypick/PickScriptingInterface.h @@ -104,7 +104,7 @@ public: * @property {number} type The intersection type. * @property {boolean} intersects If there was a valid intersection (type != INTERSECTED_NONE) * @property {Uuid} objectID The ID of the intersected object. Uuid.NULL for the HUD or invalid intersections. - * @property {float} distance The distance to the intersection point from the origin of the ray. + * @property {number} distance The distance to the intersection point from the origin of the ray. * @property {Vec3} intersection The intersection point in world-space. * @property {Vec3} surfaceNormal The surface normal at the intersected point. All NANs if type == INTERSECTED_HUD. * @property {Variant} extraInfo Additional intersection details when available for Model objects. @@ -118,7 +118,7 @@ public: * @property {number} type The intersection type. * @property {boolean} intersects If there was a valid intersection (type != INTERSECTED_NONE) * @property {Uuid} objectID The ID of the intersected object. Uuid.NULL for the HUD or invalid intersections. - * @property {float} distance The distance to the intersection point from the origin of the ray. + * @property {number} distance The distance to the intersection point from the origin of the ray. * @property {Vec3} intersection The intersection point in world-space. * @property {Vec3} surfaceNormal The surface normal at the intersected point. All NANs if type == INTERSECTED_HUD. * @property {Variant} extraInfo Additional intersection details when available for Model objects. diff --git a/interface/src/raypick/PointerScriptingInterface.h b/interface/src/raypick/PointerScriptingInterface.h index 49eb40504d..628af84790 100644 --- a/interface/src/raypick/PointerScriptingInterface.h +++ b/interface/src/raypick/PointerScriptingInterface.h @@ -136,7 +136,7 @@ public: * Sets the length of this Pointer. No effect on Stylus Pointers. * @function Pointers.setLength * @param {number} uid The ID of the Pointer, as returned by {@link Pointers.createPointer}. - * @param {float} length The desired length of the Pointer. + * @param {number} length The desired length of the Pointer. */ Q_INVOKABLE void setLength(unsigned int uid, float length) const { DependencyManager::get()->setLength(uid, length); } diff --git a/interface/src/scripting/ControllerScriptingInterface.h b/interface/src/scripting/ControllerScriptingInterface.h index e16383e234..051a372aad 100644 --- a/interface/src/scripting/ControllerScriptingInterface.h +++ b/interface/src/scripting/ControllerScriptingInterface.h @@ -105,9 +105,6 @@ class ScriptEngine; *
  • {@link Controller.getValue|getValue}
  • *
  • {@link Controller.getAxisValue|getAxisValue}
  • *
  • {@link Controller.getPoseValue|getgetPoseValue}
  • - *
  • {@link Controller.getButtonValue|getButtonValue} for a particular device
  • - *
  • {@link Controller.getAxisValue(0)|getAxisValue} for a particular device
  • - *
  • {@link Controller.getPoseValue(0)|getPoseValue} for a particular device
  • *
  • {@link Controller.getActionValue|getActionValue}
  • * * diff --git a/interface/src/scripting/HMDScriptingInterface.h b/interface/src/scripting/HMDScriptingInterface.h index d2a272851f..d4dba2f0f5 100644 --- a/interface/src/scripting/HMDScriptingInterface.h +++ b/interface/src/scripting/HMDScriptingInterface.h @@ -56,7 +56,8 @@ class QScriptEngine; * @property {Uuid} tabletID - The UUID of the tablet body model overlay. * @property {Uuid} tabletScreenID - The UUID of the tablet's screen overlay. * @property {Uuid} homeButtonID - The UUID of the tablet's "home" button overlay. - * @property {Uuid} homeButtonHighlightID - The UUID of the tablet's "home" button highlight overlay. + * @property {Uuid} homeButtonHighlightMaterialID - The UUID of the material entity used to highlight tablet button + * @property {Uuid} homeButtonUnhighlightMaterialID - The UUID of the material entity use to unhighlight the entity */ class HMDScriptingInterface : public AbstractHMDScriptingInterface, public Dependency { Q_OBJECT @@ -67,8 +68,9 @@ class HMDScriptingInterface : public AbstractHMDScriptingInterface, public Depen Q_PROPERTY(bool tabletContextualMode READ getTabletContextualMode) Q_PROPERTY(QUuid tabletID READ getCurrentTabletFrameID WRITE setCurrentTabletFrameID) Q_PROPERTY(QUuid homeButtonID READ getCurrentHomeButtonID WRITE setCurrentHomeButtonID) - Q_PROPERTY(QUuid homeButtonHighlightID READ getCurrentHomeButtonHightlightID WRITE setCurrentHomeButtonHightlightID) Q_PROPERTY(QUuid tabletScreenID READ getCurrentTabletScreenID WRITE setCurrentTabletScreenID) + Q_PROPERTY(QUuid homeButtonHighlightMaterialID READ getCurrentHomeButtonHighlightMaterialID WRITE setCurrentHomeButtonHighlightMaterialID) + Q_PROPERTY(QUuid homeButtonUnhighlightMaterialID READ getCurrentHomeButtonUnhighlightMaterialID WRITE setCurrentHomeButtonUnhighlightMaterialID) public: @@ -372,20 +374,24 @@ public: void setCurrentHomeButtonID(QUuid homeButtonID) { _homeButtonID = homeButtonID; } QUuid getCurrentHomeButtonID() const { return _homeButtonID; } - void setCurrentHomeButtonHightlightID(QUuid homeButtonHightlightID) { _homeButtonHightlightID = homeButtonHightlightID; } - QUuid getCurrentHomeButtonHightlightID() const { return _homeButtonHightlightID; } - void setCurrentTabletScreenID(QUuid tabletID) { _tabletScreenID = tabletID; } QUuid getCurrentTabletScreenID() const { return _tabletScreenID; } + void setCurrentHomeButtonHighlightMaterialID(QUuid homeButtonHighlightMaterialID) { _homeButtonHighlightMaterialID = homeButtonHighlightMaterialID; } + QUuid getCurrentHomeButtonHighlightMaterialID() { return _homeButtonHighlightMaterialID; } + + void setCurrentHomeButtonUnhighlightMaterialID(QUuid homeButtonUnhighlightMaterialID) { _homeButtonUnhighlightMaterialID = homeButtonUnhighlightMaterialID; } + QUuid getCurrentHomeButtonUnhighlightMaterialID() { return _homeButtonUnhighlightMaterialID; } + private: bool _showTablet { false }; bool _tabletContextualMode { false }; QUuid _tabletUIID; // this is the entityID of the tablet frame QUuid _tabletScreenID; // this is the overlayID which is part of (a child of) the tablet-ui. QUuid _homeButtonID; - QUuid _homeButtonHightlightID; QUuid _tabletEntityID; + QUuid _homeButtonHighlightMaterialID; + QUuid _homeButtonUnhighlightMaterialID; // Get the position of the HMD glm::vec3 getPosition() const; diff --git a/interface/src/scripting/LimitlessConnection.cpp b/interface/src/scripting/LimitlessConnection.cpp deleted file mode 100644 index f504136489..0000000000 --- a/interface/src/scripting/LimitlessConnection.cpp +++ /dev/null @@ -1,93 +0,0 @@ -#include "LimitlessConnection.h" - -#include -#include -#include -#include -#include -#include "LimitlessVoiceRecognitionScriptingInterface.h" - -LimitlessConnection::LimitlessConnection() : - _streamingAudioForTranscription(false) -{ -} - -void LimitlessConnection::startListening(QString authCode) { - _transcribeServerSocket.reset(new QTcpSocket(this)); - connect(_transcribeServerSocket.get(), &QTcpSocket::readyRead, this, - &LimitlessConnection::transcriptionReceived); - connect(_transcribeServerSocket.get(), &QTcpSocket::disconnected, this, [this](){stopListening();}); - - static const auto host = "gserv_devel.studiolimitless.com"; - _transcribeServerSocket->connectToHost(host, 1407); - _transcribeServerSocket->waitForConnected(); - QString requestHeader = QString::asprintf("Authorization: %s\r\nfs: %i\r\n", - authCode.toLocal8Bit().data(), AudioConstants::SAMPLE_RATE); - qCDebug(interfaceapp) << "Sending Limitless Audio Stream Request: " << requestHeader; - _transcribeServerSocket->write(requestHeader.toLocal8Bit()); - _transcribeServerSocket->waitForBytesWritten(); -} - -void LimitlessConnection::stopListening() { - emit onFinishedSpeaking(_currentTranscription); - _streamingAudioForTranscription = false; - _currentTranscription = ""; - if (!isConnected()) - return; - _transcribeServerSocket->close(); - disconnect(_transcribeServerSocket.get(), &QTcpSocket::readyRead, this, - &LimitlessConnection::transcriptionReceived); - _transcribeServerSocket.release()->deleteLater(); - disconnect(DependencyManager::get().data(), &AudioClient::inputReceived, this, - &LimitlessConnection::audioInputReceived); - qCDebug(interfaceapp) << "Connection to Limitless Voice Server closed."; -} - -void LimitlessConnection::audioInputReceived(const QByteArray& inputSamples) { - if (isConnected()) { - _transcribeServerSocket->write(inputSamples.data(), inputSamples.size()); - _transcribeServerSocket->waitForBytesWritten(); - } -} - -void LimitlessConnection::transcriptionReceived() { - while (_transcribeServerSocket && _transcribeServerSocket->bytesAvailable() > 0) { - const QByteArray data = _transcribeServerSocket->readAll(); - _serverDataBuffer.append(data); - int begin = _serverDataBuffer.indexOf('<'); - int end = _serverDataBuffer.indexOf('>'); - while (begin > -1 && end > -1) { - const int len = end - begin; - const QByteArray serverMessage = _serverDataBuffer.mid(begin+1, len-1); - if (serverMessage.contains("1407")) { - qCDebug(interfaceapp) << "Limitless Speech Server denied the request."; - // Don't spam the server with further false requests please. - DependencyManager::get()->setListeningToVoice(true); - stopListening(); - return; - } else if (serverMessage.contains("1408")) { - qCDebug(interfaceapp) << "Limitless Audio request authenticated!"; - _serverDataBuffer.clear(); - connect(DependencyManager::get().data(), &AudioClient::inputReceived, this, - &LimitlessConnection::audioInputReceived); - return; - } - QJsonObject json = QJsonDocument::fromJson(serverMessage.data()).object(); - _serverDataBuffer.remove(begin, len+1); - _currentTranscription = json["alternatives"].toArray()[0].toObject()["transcript"].toString(); - emit onReceivedTranscription(_currentTranscription); - if (json["isFinal"] == true) { - qCDebug(interfaceapp) << "Final transcription: " << _currentTranscription; - stopListening(); - return; - } - begin = _serverDataBuffer.indexOf('<'); - end = _serverDataBuffer.indexOf('>'); - } - } -} - -bool LimitlessConnection::isConnected() const { - return _transcribeServerSocket.get() && _transcribeServerSocket->isWritable() - && _transcribeServerSocket->state() != QAbstractSocket::SocketState::UnconnectedState; -} diff --git a/interface/src/scripting/LimitlessConnection.h b/interface/src/scripting/LimitlessConnection.h deleted file mode 100644 index 9ed39bd653..0000000000 --- a/interface/src/scripting/LimitlessConnection.h +++ /dev/null @@ -1,46 +0,0 @@ -// -// SpeechRecognitionScriptingInterface.h -// interface/src/scripting -// -// Created by Trevor Berninger on 3/24/17. -// Copyright 2017 Limitless ltd. -// -// 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_LimitlessConnection_h -#define hifi_LimitlessConnection_h - -#include -#include -#include - -#include - -class LimitlessConnection : public QObject { - Q_OBJECT -public: - LimitlessConnection(); - - Q_INVOKABLE void startListening(QString authCode); - Q_INVOKABLE void stopListening(); - - std::atomic _streamingAudioForTranscription; - -signals: - void onReceivedTranscription(QString speech); - void onFinishedSpeaking(QString speech); - -private: - void transcriptionReceived(); - void audioInputReceived(const QByteArray& inputSamples); - - bool isConnected() const; - - std::unique_ptr _transcribeServerSocket; - QByteArray _serverDataBuffer; - QString _currentTranscription; -}; - -#endif //hifi_LimitlessConnection_h diff --git a/interface/src/scripting/LimitlessVoiceRecognitionScriptingInterface.cpp b/interface/src/scripting/LimitlessVoiceRecognitionScriptingInterface.cpp deleted file mode 100644 index 2692608106..0000000000 --- a/interface/src/scripting/LimitlessVoiceRecognitionScriptingInterface.cpp +++ /dev/null @@ -1,66 +0,0 @@ -// -// SpeechRecognitionScriptingInterface.h -// interface/src/scripting -// -// Created by Trevor Berninger on 3/20/17. -// Copyright 2017 Limitless ltd. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#include "LimitlessVoiceRecognitionScriptingInterface.h" - -#include - -#include - -#include "InterfaceLogging.h" -#include "ui/AvatarInputs.h" - -const float LimitlessVoiceRecognitionScriptingInterface::_audioLevelThreshold = 0.33f; -const int LimitlessVoiceRecognitionScriptingInterface::_voiceTimeoutDuration = 2000; - -LimitlessVoiceRecognitionScriptingInterface::LimitlessVoiceRecognitionScriptingInterface() : - _shouldStartListeningForVoice(false) -{ - _voiceTimer.setSingleShot(true); - connect(&_voiceTimer, &QTimer::timeout, this, &LimitlessVoiceRecognitionScriptingInterface::voiceTimeout); - connect(&_connection, &LimitlessConnection::onReceivedTranscription, this, [this](QString transcription){emit onReceivedTranscription(transcription);}); - connect(&_connection, &LimitlessConnection::onFinishedSpeaking, this, [this](QString transcription){emit onFinishedSpeaking(transcription);}); - moveToNewNamedThread(&_connection, "Limitless Connection"); -} - -void LimitlessVoiceRecognitionScriptingInterface::update() { - const float audioLevel = AvatarInputs::getInstance()->loudnessToAudioLevel(DependencyManager::get()->getAudioAverageInputLoudness()); - - if (_shouldStartListeningForVoice) { - if (_connection._streamingAudioForTranscription) { - if (audioLevel > _audioLevelThreshold) { - if (_voiceTimer.isActive()) { - _voiceTimer.stop(); - } - } else if (!_voiceTimer.isActive()){ - _voiceTimer.start(_voiceTimeoutDuration); - } - } else if (audioLevel > _audioLevelThreshold) { - // to make sure invoke doesn't get called twice before the method actually gets called - _connection._streamingAudioForTranscription = true; - QMetaObject::invokeMethod(&_connection, "startListening", Q_ARG(QString, authCode)); - } - } -} - -void LimitlessVoiceRecognitionScriptingInterface::setListeningToVoice(bool listening) { - _shouldStartListeningForVoice = listening; -} - -void LimitlessVoiceRecognitionScriptingInterface::setAuthKey(QString key) { - authCode = key; -} - -void LimitlessVoiceRecognitionScriptingInterface::voiceTimeout() { - if (_connection._streamingAudioForTranscription) { - QMetaObject::invokeMethod(&_connection, "stopListening"); - } -} diff --git a/interface/src/scripting/LimitlessVoiceRecognitionScriptingInterface.h b/interface/src/scripting/LimitlessVoiceRecognitionScriptingInterface.h deleted file mode 100644 index 2a35c37ab0..0000000000 --- a/interface/src/scripting/LimitlessVoiceRecognitionScriptingInterface.h +++ /dev/null @@ -1,49 +0,0 @@ -// -// SpeechRecognitionScriptingInterface.h -// interface/src/scripting -// -// Created by Trevor Berninger on 3/20/17. -// Copyright 2017 Limitless ltd. -// -// 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_SpeechRecognitionScriptingInterface_h -#define hifi_SpeechRecognitionScriptingInterface_h - -#include -#include -#include -#include "LimitlessConnection.h" - -class LimitlessVoiceRecognitionScriptingInterface : public QObject, public Dependency { - Q_OBJECT -public: - LimitlessVoiceRecognitionScriptingInterface(); - - void update(); - - QString authCode; - -public slots: - void setListeningToVoice(bool listening); - void setAuthKey(QString key); - -signals: - void onReceivedTranscription(QString speech); - void onFinishedSpeaking(QString speech); - -private: - - bool _shouldStartListeningForVoice; - static const float _audioLevelThreshold; - static const int _voiceTimeoutDuration; - - QTimer _voiceTimer; - LimitlessConnection _connection; - - void voiceTimeout(); -}; - -#endif //hifi_SpeechRecognitionScriptingInterface_h diff --git a/interface/src/scripting/SelectionScriptingInterface.h b/interface/src/scripting/SelectionScriptingInterface.h index 8b5c12e3dc..83aec63ee2 100644 --- a/interface/src/scripting/SelectionScriptingInterface.h +++ b/interface/src/scripting/SelectionScriptingInterface.h @@ -182,7 +182,7 @@ public: /**jsdoc * Get the list of avatars, entities, and overlays stored in a selection list. - * @function Selection.getList + * @function Selection.getSelectedItemsList * @param {string} listName - The name of the selection list. * @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. @@ -257,7 +257,7 @@ public: void onSelectedItemsListChanged(const QString& listName); signals: - /**jsoc + /**jsdoc * Triggered when a list's content changes. * @function Selection.selectedItemsListChanged * @param {string} listName - The name of the selection list that changed. diff --git a/interface/src/scripting/TestScriptingInterface.cpp b/interface/src/scripting/TestScriptingInterface.cpp index 700994c517..430441226f 100644 --- a/interface/src/scripting/TestScriptingInterface.cpp +++ b/interface/src/scripting/TestScriptingInterface.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -186,3 +187,7 @@ void TestScriptingInterface::saveObject(QVariant variant, const QString& filenam file.write(jsonData); file.close(); } + +void TestScriptingInterface::showMaximized() { + qApp->getWindow()->showMaximized(); +} \ No newline at end of file diff --git a/interface/src/scripting/TestScriptingInterface.h b/interface/src/scripting/TestScriptingInterface.h index 5666417727..c47e39d1f3 100644 --- a/interface/src/scripting/TestScriptingInterface.h +++ b/interface/src/scripting/TestScriptingInterface.h @@ -27,70 +27,128 @@ public slots: /**jsdoc * Exits the application + * @function Test.quit */ void quit(); /**jsdoc * Waits for all texture transfers to be complete + * @function Test.waitForTextureIdle */ void waitForTextureIdle(); /**jsdoc * Waits for all pending downloads to be complete + * @function Test.waitForDownloadIdle */ void waitForDownloadIdle(); /**jsdoc * Waits for all file parsing operations to be complete + * @function Test.waitForProcessingIdle */ void waitForProcessingIdle(); /**jsdoc * Waits for all pending downloads, parsing and texture transfers to be complete + * @function Test.waitIdle */ void waitIdle(); + /**jsdoc + * Waits for establishment of connection to server + * @function Test.waitForConnection + * @param {int} maxWaitMs [default=10000] - Number of milliseconds to wait + */ bool waitForConnection(qint64 maxWaitMs = 10000); + /**jsdoc + * Waits a specific number of milliseconds + * @function Test.wait + * @param {int} milliseconds - Number of milliseconds to wait + */ void wait(int milliseconds); + /**jsdoc + * Waits for all pending downloads, parsing and texture transfers to be complete + * @function Test.loadTestScene + * @param {string} sceneFile - URL of scene to load + */ bool loadTestScene(QString sceneFile); + /**jsdoc + * Clears all caches + * @function Test.clear + */ void clear(); /**jsdoc * Start recording Chrome compatible tracing events * logRules can be used to specify a set of logging category rules to limit what gets captured + * @function Test.startTracing + * @param {string} logrules [defaultValue=""] - See implementation for explanation */ bool startTracing(QString logrules = ""); /**jsdoc * Stop recording Chrome compatible tracing events and serialize recorded events to a file * Using a filename with a .gz extension will automatically compress the output file + * @function Test.stopTracing + * @param {string} filename - Name of file to save to + * @returns {bool} True if successful. */ bool stopTracing(QString filename); + /**jsdoc + * Starts a specific trace event + * @function Test.startTraceEvent + * @param {string} name - Name of event + */ void startTraceEvent(QString name); + /**jsdoc + * Stop a specific name event + * Using a filename with a .gz extension will automatically compress the output file + * @function Test.endTraceEvent + * @param {string} filename - Name of event + */ void endTraceEvent(QString name); /**jsdoc * Write detailed timing stats of next physics stepSimulation() to filename + * @function Test.savePhysicsSimulationStats + * @param {string} filename - Name of file to save to */ void savePhysicsSimulationStats(QString filename); + /**jsdoc + * Profiles a specific function + * @function Test.savePhysicsSimulationStats + * @param {string} name - Name used to reference the function + * @param {function} function - Function to profile + */ Q_INVOKABLE void profileRange(const QString& name, QScriptValue function); /**jsdoc * Clear all caches (menu command Reload Content) + * @function Test.clearCaches */ void clearCaches(); /**jsdoc * Save a JSON object to a file in the test results location + * @function Test.saveObject + * @param {string} name - Name of the object + * @param {string} filename - Name of file to save to */ void saveObject(QVariant v, const QString& filename); + /**jsdoc + * Maximizes the window + * @function Test.showMaximized + */ + void showMaximized(); + private: bool waitForCondition(qint64 maxWaitMs, std::function condition); QString _testResultsLocation; diff --git a/interface/src/scripting/WindowScriptingInterface.cpp b/interface/src/scripting/WindowScriptingInterface.cpp index af9b5c8a46..0aea7a02c5 100644 --- a/interface/src/scripting/WindowScriptingInterface.cpp +++ b/interface/src/scripting/WindowScriptingInterface.cpp @@ -446,12 +446,12 @@ void WindowScriptingInterface::takeSnapshot(bool notify, bool includeAnimated, f qApp->takeSnapshot(notify, includeAnimated, aspectRatio, filename); } -void WindowScriptingInterface::takeSecondaryCameraSnapshot(const QString& filename) { - qApp->takeSecondaryCameraSnapshot(filename); +void WindowScriptingInterface::takeSecondaryCameraSnapshot(const bool& notify, const QString& filename) { + qApp->takeSecondaryCameraSnapshot(notify, filename); } -void WindowScriptingInterface::takeSecondaryCamera360Snapshot(const glm::vec3& cameraPosition, const bool& cubemapOutputFormat, const QString& filename) { - qApp->takeSecondaryCamera360Snapshot(cameraPosition, cubemapOutputFormat, filename); +void WindowScriptingInterface::takeSecondaryCamera360Snapshot(const glm::vec3& cameraPosition, const bool& cubemapOutputFormat, const bool& notify, const QString& filename) { + qApp->takeSecondaryCamera360Snapshot(cameraPosition, cubemapOutputFormat, notify, filename); } void WindowScriptingInterface::shareSnapshot(const QString& path, const QUrl& href) { diff --git a/interface/src/scripting/WindowScriptingInterface.h b/interface/src/scripting/WindowScriptingInterface.h index 5ddbc30dd3..29eb6421e1 100644 --- a/interface/src/scripting/WindowScriptingInterface.h +++ b/interface/src/scripting/WindowScriptingInterface.h @@ -318,7 +318,6 @@ public slots: * {@link Window.stillSnapshotTaken|stillSnapshotTaken} is emitted; when a still image plus moving images are captured, * {@link Window.processingGifStarted|processingGifStarted} and {@link Window.processingGifCompleted|processingGifCompleted} * are emitted. The path to store the snapshots and the length of the animated GIF to capture are specified in Settings > - * NOTE: to provide a non-default value - all previous parameters must be provided. * General > Snapshots. * @function Window.takeSnapshot * @param {boolean} [notify=true] - This value is passed on through the {@link Window.stillSnapshotTaken|stillSnapshotTaken} @@ -328,7 +327,7 @@ public slots: * @param {number} [aspectRatio=0] - The width/height ratio of the snapshot required. If the value is 0 the * full resolution is used (window dimensions in desktop mode; HMD display dimensions in HMD mode), otherwise one of the * dimensions is adjusted in order to match the aspect ratio. - * @param {string} [filename=""] - If this parameter is not given, the image will be saved as 'hifi-snap-by--YYYY-MM-DD_HH-MM-SS'. + * @param {string} [filename=""] - If this parameter is not given, the image will be saved as "hifi-snap-by-<user name>-YYYY-MM-DD_HH-MM-SS". * If this parameter is "" then the image will be saved as ".jpg". * Otherwise, the image will be saved to this filename, with an appended ".jpg". * @@ -360,29 +359,29 @@ public slots: /**jsdoc * Takes a still snapshot of the current view from the secondary camera that can be set up through the {@link Render} API. - * NOTE: to provide a non-default value - all previous parameters must be provided. * @function Window.takeSecondaryCameraSnapshot - * @param {string} [filename=""] - If this parameter is not given, the image will be saved as 'hifi-snap-by--YYYY-MM-DD_HH-MM-SS'. + * @param {boolean} [notify=true] - This value is passed on through the {@link Window.stillSnapshotTaken|stillSnapshotTaken} + * signal. + * @param {string} [filename=""] - If this parameter is not given, the image will be saved as "hifi-snap-by-<user name>-YYYY-MM-DD_HH-MM-SS". * If this parameter is "" then the image will be saved as ".jpg". * Otherwise, the image will be saved to this filename, with an appended ".jpg". - * - * var filename = QString(); */ - void takeSecondaryCameraSnapshot(const QString& filename = QString()); + void takeSecondaryCameraSnapshot(const bool& notify = true, const QString& filename = QString()); /**jsdoc - * Takes a 360 snapshot given a position of the secondary camera (which does not need to have been previously set up). - * @function Window.takeSecondaryCameraSnapshot - * @param {vec3} [cameraPosition] - The (x, y, z) position of the camera for the 360 snapshot - * @param {boolean} [cubemapOutputFormat=false] - If true then the snapshot is saved as a cube map image, - * otherwise is saved as an equirectangular image. - * @param {string} [filename=""] - If this parameter is not given, the image will be saved as 'hifi-snap-by--YYYY-MM-DD_HH-MM-SS'. - * If this parameter is "" then the image will be saved as ".jpg". - * Otherwise, the image will be saved to this filename, with an appended ".jpg". - * - * var filename = QString(); - */ - void takeSecondaryCamera360Snapshot(const glm::vec3& cameraPosition, const bool& cubemapOutputFormat = false, const QString& filename = QString()); + * Takes a 360° snapshot at a given position for the secondary camera. The secondary camera does not need to have been + * set up. + * @function Window.takeSecondaryCamera360Snapshot + * @param {Vec3} cameraPosition - The position of the camera for the snapshot. + * @param {boolean} [cubemapOutputFormat=false] - If true then the snapshot is saved as a cube map image, + * otherwise is saved as an equirectangular image. + * @param {boolean} [notify=true] - This value is passed on through the {@link Window.stillSnapshotTaken|stillSnapshotTaken} + * signal. + * @param {string} [filename=""] - If this parameter is not supplied, the image will be saved as "hifi-snap-by-<user name>-YYYY-MM-DD_HH-MM-SS". + * If this parameter is "" then the image will be saved as ".jpg". + * Otherwise, the image will be saved to this filename, with an appended ".jpg". + */ + void takeSecondaryCamera360Snapshot(const glm::vec3& cameraPosition, const bool& cubemapOutputFormat = false, const bool& notify = true, const QString& filename = QString()); /**jsdoc * Emit a {@link Window.connectionAdded|connectionAdded} or a {@link Window.connectionError|connectionError} signal that @@ -612,7 +611,8 @@ signals: void stillSnapshotTaken(const QString& pathStillSnapshot, bool notify); /**jsdoc - * Triggered when a still equirectangular snapshot has been taken by calling {@link Window.takeSecondaryCamera360Snapshot|takeSecondaryCamera360Snapshot} + * Triggered when a still 360° snapshot has been taken by calling + * {@link Window.takeSecondaryCamera360Snapshot|takeSecondaryCamera360Snapshot}. * @function Window.snapshot360Taken * @param {string} pathStillSnapshot - The path and name of the snapshot image file. * @param {boolean} notify - The value of the notify parameter that {@link Window.takeSecondaryCamera360Snapshot|takeSecondaryCamera360Snapshot} diff --git a/interface/src/ui/DialogsManager.cpp b/interface/src/ui/DialogsManager.cpp index d01e7d6671..83601a2797 100644 --- a/interface/src/ui/DialogsManager.cpp +++ b/interface/src/ui/DialogsManager.cpp @@ -93,10 +93,18 @@ void DialogsManager::setDomainConnectionFailureVisibility(bool visible) { static const QUrl url("dialogs/TabletConnectionFailureDialog.qml"); auto hmd = DependencyManager::get(); if (visible) { + _dialogCreatedWhileShown = tablet->property("tabletShown").toBool(); tablet->initialScreen(url); if (!hmd->getShouldShowTablet()) { hmd->openTablet(); } + } else if (tablet->isPathLoaded(url)) { + tablet->closeDialog(); + tablet->gotoHomeScreen(); + if (!_dialogCreatedWhileShown) { + hmd->closeTablet(); + } + _dialogCreatedWhileShown = false; } } } diff --git a/interface/src/ui/DialogsManager.h b/interface/src/ui/DialogsManager.h index f17ac39a7e..0633dec573 100644 --- a/interface/src/ui/DialogsManager.h +++ b/interface/src/ui/DialogsManager.h @@ -80,6 +80,7 @@ private: QPointer _octreeStatsDialog; QPointer _testingDialog; QPointer _domainConnectionDialog; + bool _dialogCreatedWhileShown { false }; bool _addressBarVisible { false }; }; diff --git a/interface/src/ui/OverlayConductor.cpp b/interface/src/ui/OverlayConductor.cpp index e7e3c91d13..d131bb3467 100644 --- a/interface/src/ui/OverlayConductor.cpp +++ b/interface/src/ui/OverlayConductor.cpp @@ -18,7 +18,6 @@ #include "InterfaceLogging.h" OverlayConductor::OverlayConductor() { - } OverlayConductor::~OverlayConductor() { @@ -33,8 +32,8 @@ bool OverlayConductor::headOutsideOverlay() const { glm::vec3 uiPos = uiTransform.getTranslation(); glm::vec3 uiForward = uiTransform.getRotation() * glm::vec3(0.0f, 0.0f, -1.0f); - const float MAX_COMPOSITOR_DISTANCE = 0.99f; // If you're 1m from center of ui sphere, you're at the surface. - const float MAX_COMPOSITOR_ANGLE = 180.0f; // rotation check is effectively disabled + const float MAX_COMPOSITOR_DISTANCE = 0.99f; // If you're 1m from center of ui sphere, you're at the surface. + const float MAX_COMPOSITOR_ANGLE = 180.0f; // rotation check is effectively disabled if (glm::distance(uiPos, hmdPos) > MAX_COMPOSITOR_DISTANCE || glm::dot(uiForward, hmdForward) < cosf(glm::radians(MAX_COMPOSITOR_ANGLE))) { return true; @@ -43,10 +42,9 @@ bool OverlayConductor::headOutsideOverlay() const { } bool OverlayConductor::updateAvatarIsAtRest() { - auto myAvatar = DependencyManager::get()->getMyAvatar(); - const quint64 REST_ENABLE_TIME_USECS = 1000 * 1000; // 1 s + const quint64 REST_ENABLE_TIME_USECS = 1000 * 1000; // 1 s const quint64 REST_DISABLE_TIME_USECS = 200 * 1000; // 200 ms const float AT_REST_THRESHOLD = 0.01f; @@ -69,31 +67,6 @@ bool OverlayConductor::updateAvatarIsAtRest() { return _currentAtRest; } -bool OverlayConductor::updateAvatarHasDriveInput() { - auto myAvatar = DependencyManager::get()->getMyAvatar(); - - const quint64 DRIVE_ENABLE_TIME_USECS = 200 * 1000; // 200 ms - const quint64 DRIVE_DISABLE_TIME_USECS = 1000 * 1000; // 1 s - - bool desiredDriving = myAvatar->hasDriveInput(); - if (desiredDriving != _desiredDriving) { - // start timer - _desiredDrivingTimer = usecTimestampNow() + (desiredDriving ? DRIVE_ENABLE_TIME_USECS : DRIVE_DISABLE_TIME_USECS); - } - - _desiredDriving = desiredDriving; - - if (_desiredDrivingTimer != 0 && usecTimestampNow() > _desiredDrivingTimer) { - // timer expired - // change state! - _currentDriving = _desiredDriving; - // disable timer - _desiredDrivingTimer = 0; - } - - return _currentDriving; -} - void OverlayConductor::centerUI() { // place the overlay at the current hmd position in sensor space auto camMat = cancelOutRollAndPitch(qApp->getHMDSensorPose()); @@ -115,20 +88,19 @@ void OverlayConductor::update(float dt) { _hmdMode = false; } - bool prevDriving = _currentDriving; - bool isDriving = updateAvatarHasDriveInput(); - bool drivingChanged = prevDriving != isDriving; bool isAtRest = updateAvatarIsAtRest(); + bool isMoving = !isAtRest; + bool shouldRecenter = false; - if (_flags & SuppressedByDrive) { - if (!isDriving) { - _flags &= ~SuppressedByDrive; - shouldRecenter = true; + if (_flags & SuppressedByMove) { + if (!isMoving) { + _flags &= ~SuppressedByMove; + shouldRecenter = true; } } else { - if (myAvatar->getClearOverlayWhenMoving() && drivingChanged && isDriving) { - _flags |= SuppressedByDrive; + if (myAvatar->getClearOverlayWhenMoving() && isMoving) { + _flags |= SuppressedByMove; } } @@ -143,7 +115,6 @@ void OverlayConductor::update(float dt) { } } - bool targetVisible = Menu::getInstance()->isOptionChecked(MenuOption::Overlays) && (0 == (_flags & SuppressMask)); if (targetVisible != currentVisible) { offscreenUi->setPinned(!targetVisible); diff --git a/interface/src/ui/OverlayConductor.h b/interface/src/ui/OverlayConductor.h index cdd596a7bc..cf69c32fc5 100644 --- a/interface/src/ui/OverlayConductor.h +++ b/interface/src/ui/OverlayConductor.h @@ -23,23 +23,17 @@ public: private: bool headOutsideOverlay() const; - bool updateAvatarHasDriveInput(); bool updateAvatarIsAtRest(); enum SupressionFlags { - SuppressedByDrive = 0x01, + SuppressedByMove = 0x01, SuppressedByHead = 0x02, SuppressMask = 0x03, }; - uint8_t _flags { SuppressedByDrive }; + uint8_t _flags { SuppressedByMove }; bool _hmdMode { false }; - // used by updateAvatarHasDriveInput - uint64_t _desiredDrivingTimer { 0 }; - bool _desiredDriving { false }; - bool _currentDriving { false }; - // used by updateAvatarIsAtRest uint64_t _desiredAtRestTimer { 0 }; bool _desiredAtRest { true }; diff --git a/interface/src/ui/Snapshot.cpp b/interface/src/ui/Snapshot.cpp index b2c0ee7ce7..2b306ace91 100644 --- a/interface/src/ui/Snapshot.cpp +++ b/interface/src/ui/Snapshot.cpp @@ -122,8 +122,9 @@ static const glm::quat CAMERA_ORIENTATION_LEFT(glm::quat(glm::radians(glm::vec3( static const glm::quat CAMERA_ORIENTATION_BACK(glm::quat(glm::radians(glm::vec3(0.0f, 180.0f, 0.0f)))); static const glm::quat CAMERA_ORIENTATION_RIGHT(glm::quat(glm::radians(glm::vec3(0.0f, 270.0f, 0.0f)))); static const glm::quat CAMERA_ORIENTATION_UP(glm::quat(glm::radians(glm::vec3(90.0f, 0.0f, 0.0f)))); -void Snapshot::save360Snapshot(const glm::vec3& cameraPosition, const bool& cubemapOutputFormat, const QString& filename) { +void Snapshot::save360Snapshot(const glm::vec3& cameraPosition, const bool& cubemapOutputFormat, const bool& notify, const QString& filename) { _snapshotFilename = filename; + _notify360 = notify; _cubemapOutputFormat = cubemapOutputFormat; SecondaryCameraJobConfig* secondaryCameraRenderConfig = static_cast(qApp->getRenderEngine()->getConfiguration()->getConfig("SecondaryCamera")); @@ -247,7 +248,8 @@ void Snapshot::convertToCubemap() { painter.end(); - emit DependencyManager::get()->snapshot360Taken(saveSnapshot(outputImage, _snapshotFilename), true); + emit DependencyManager::get()->snapshot360Taken(saveSnapshot(outputImage, _snapshotFilename), + _notify360); } void Snapshot::convertToEquirectangular() { @@ -327,7 +329,8 @@ void Snapshot::convertToEquirectangular() { } } - emit DependencyManager::get()->snapshot360Taken(saveSnapshot(outputImage, _snapshotFilename), true); + emit DependencyManager::get()->snapshot360Taken(saveSnapshot(outputImage, _snapshotFilename), + _notify360); } QTemporaryFile* Snapshot::saveTempSnapshot(QImage image) { diff --git a/interface/src/ui/Snapshot.h b/interface/src/ui/Snapshot.h index 2bac857a97..8fc05775bd 100644 --- a/interface/src/ui/Snapshot.h +++ b/interface/src/ui/Snapshot.h @@ -50,7 +50,10 @@ class Snapshot : public QObject, public Dependency { public: Snapshot(); QString saveSnapshot(QImage image, const QString& filename, const QString& pathname = QString()); - void save360Snapshot(const glm::vec3& cameraPosition, const bool& cubemapOutputFormat, const QString& filename); + void save360Snapshot(const glm::vec3& cameraPosition, + const bool& cubemapOutputFormat, + const bool& notify, + const QString& filename); QTemporaryFile* saveTempSnapshot(QImage image); SnapshotMetaData* parseSnapshotData(QString snapshotPath); @@ -89,6 +92,7 @@ private: const QString& userSelectedFilename = QString(), const QString& userSelectedPathname = QString()); QString _snapshotFilename; + bool _notify360; bool _cubemapOutputFormat; QTimer _snapshotTimer; qint16 _snapshotIndex; diff --git a/interface/src/ui/SnapshotUploader.cpp b/interface/src/ui/SnapshotUploader.cpp index 67902c1a35..4613871d25 100644 --- a/interface/src/ui/SnapshotUploader.cpp +++ b/interface/src/ui/SnapshotUploader.cpp @@ -35,6 +35,7 @@ void SnapshotUploader::uploadSuccess(QNetworkReply& reply) { QString thumbnailUrl = dataObject.value("thumbnail_url").toString(); QString imageUrl = dataObject.value("image_url").toString(); QString snapshotID = dataObject.value("id").toString(); + QString originalImageFileName = dataObject.value("original_image_file_name").toString(); auto addressManager = DependencyManager::get(); QString placeName = _inWorldLocation.authority(); // We currently only upload shareable places, in which case this is just host. QString currentPath = _inWorldLocation.path(); @@ -48,6 +49,7 @@ void SnapshotUploader::uploadSuccess(QNetworkReply& reply) { detailsObject.insert("shareable_url", dataObject.value("shareable_url").toString()); } detailsObject.insert("snapshot_id", snapshotID); + detailsObject.insert("original_image_file_name", originalImageFileName); QString pickledDetails = QJsonDocument(detailsObject).toJson(); userStoryObject.insert("details", pickledDetails); userStoryObject.insert("thumbnail_url", thumbnailUrl); diff --git a/interface/src/ui/UpdateDialog.cpp b/interface/src/ui/UpdateDialog.cpp index 2dcc0c07eb..7ff2132ab9 100644 --- a/interface/src/ui/UpdateDialog.cpp +++ b/interface/src/ui/UpdateDialog.cpp @@ -21,19 +21,31 @@ UpdateDialog::UpdateDialog(QQuickItem* parent) : OffscreenQmlDialog(parent) { auto applicationUpdater = DependencyManager::get(); - int currentVersion = QCoreApplication::applicationVersion().toInt(); - int latestVersion = applicationUpdater.data()->getBuildData().lastKey(); - _updateAvailableDetails = "v" + QString::number(latestVersion) + " released on " - + QString(applicationUpdater.data()->getBuildData()[latestVersion]["releaseTime"]).replace(" ", " "); + if (applicationUpdater) { - _releaseNotes = ""; - for (int i = latestVersion; i > currentVersion; i--) { - if (applicationUpdater.data()->getBuildData().contains(i)) { - QString releaseNotes = applicationUpdater.data()->getBuildData()[i]["releaseNotes"]; - releaseNotes.remove("
    "); - releaseNotes.remove(QRegExp("^\n+")); - _releaseNotes += "\n" + QString().sprintf("%d", i) + "\n" + releaseNotes + "\n"; + auto buildData = applicationUpdater.data()->getBuildData(); + ApplicationVersion latestVersion = buildData.lastKey(); + _updateAvailableDetails = "v" + latestVersion.versionString + " released on " + + QString(buildData[latestVersion]["releaseTime"]).replace(" ", " "); + + _releaseNotes = ""; + + auto it = buildData.end(); + while (it != buildData.begin()) { + --it; + + if (applicationUpdater->getCurrentVersion() < it.key()) { + // grab the release notes for this later version + QString releaseNotes = it.value()["releaseNotes"]; + releaseNotes.remove("
    "); + releaseNotes.remove(QRegExp("^\n+")); + _releaseNotes += "\n" + it.key().versionString + "\n" + releaseNotes + "\n"; + } else { + break; + } } + + } } @@ -47,5 +59,5 @@ const QString& UpdateDialog::releaseNotes() const { void UpdateDialog::triggerUpgrade() { auto applicationUpdater = DependencyManager::get(); - applicationUpdater.data()->performAutoUpdate(applicationUpdater.data()->getBuildData().lastKey()); + applicationUpdater.data()->openLatestUpdateURL(); } diff --git a/interface/src/ui/overlays/Base3DOverlay.cpp b/interface/src/ui/overlays/Base3DOverlay.cpp index f4efd1301d..551b352952 100644 --- a/interface/src/ui/overlays/Base3DOverlay.cpp +++ b/interface/src/ui/overlays/Base3DOverlay.cpp @@ -283,7 +283,7 @@ QVariant Base3DOverlay::getProperty(const QString& property) { } bool Base3DOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - float& distance, BoxFace& face, glm::vec3& surfaceNormal) { + float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking) { return false; } @@ -349,3 +349,23 @@ void Base3DOverlay::setVisible(bool visible) { Parent::setVisible(visible); notifyRenderVariableChange(); } + +render::ItemKey Base3DOverlay::getKey() { + auto builder = render::ItemKey::Builder(Overlay::getKey()); + + if (getDrawInFront()) { + builder.withLayer(render::hifi::LAYER_3D_FRONT); + } else if (getDrawHUDLayer()) { + builder.withLayer(render::hifi::LAYER_3D_HUD); + } else { + builder.withoutLayer(); + } + + builder.withoutViewSpace(); + + if (isTransparent()) { + builder.withTransparent(); + } + + return builder.build(); +} \ No newline at end of file diff --git a/interface/src/ui/overlays/Base3DOverlay.h b/interface/src/ui/overlays/Base3DOverlay.h index ab83a64273..7c5f551e6a 100644 --- a/interface/src/ui/overlays/Base3DOverlay.h +++ b/interface/src/ui/overlays/Base3DOverlay.h @@ -35,6 +35,7 @@ public: // getters virtual bool is3D() const override { return true; } + virtual render::ItemKey getKey() override; virtual uint32_t fetchMetaSubItems(render::ItemIDs& subItems) const override { subItems.push_back(getRenderItemID()); return (uint32_t) subItems.size(); } virtual scriptable::ScriptableModelBase getScriptableModel() override { return scriptable::ScriptableModelBase(); } @@ -68,11 +69,11 @@ public: virtual QVariant getProperty(const QString& property) override; virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, - BoxFace& face, glm::vec3& surfaceNormal); + BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking = false); virtual bool findRayIntersectionExtraInfo(const glm::vec3& origin, const glm::vec3& direction, - float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo) { - return findRayIntersection(origin, direction, distance, face, surfaceNormal); + float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking = false) { + return findRayIntersection(origin, direction, distance, face, surfaceNormal, precisionPicking); } virtual SpatialParentTree* getParentTree() const override; diff --git a/interface/src/ui/overlays/Circle3DOverlay.cpp b/interface/src/ui/overlays/Circle3DOverlay.cpp index 33f40f7c63..ef89213d68 100644 --- a/interface/src/ui/overlays/Circle3DOverlay.cpp +++ b/interface/src/ui/overlays/Circle3DOverlay.cpp @@ -521,7 +521,7 @@ QVariant Circle3DOverlay::getProperty(const QString& property) { } bool Circle3DOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, - BoxFace& face, glm::vec3& surfaceNormal) { + BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking) { // Scale the dimensions by the diameter glm::vec2 dimensions = getOuterRadius() * 2.0f * getDimensions(); diff --git a/interface/src/ui/overlays/Circle3DOverlay.h b/interface/src/ui/overlays/Circle3DOverlay.h index ef491b7f46..0dc0f8b138 100644 --- a/interface/src/ui/overlays/Circle3DOverlay.h +++ b/interface/src/ui/overlays/Circle3DOverlay.h @@ -55,7 +55,7 @@ public: void setMinorTickMarksColor(const xColor& value) { _minorTickMarksColor = value; } virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, - BoxFace& face, glm::vec3& surfaceNormal) override; + BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking = false) override; virtual Circle3DOverlay* createClone() const override; diff --git a/interface/src/ui/overlays/Grid3DOverlay.h b/interface/src/ui/overlays/Grid3DOverlay.h index 5a67b21e07..34fe4dbbb6 100644 --- a/interface/src/ui/overlays/Grid3DOverlay.h +++ b/interface/src/ui/overlays/Grid3DOverlay.h @@ -35,7 +35,7 @@ public: virtual Grid3DOverlay* createClone() const override; // Grids are UI tools, and may not be intersected (pickable) - virtual bool findRayIntersection(const glm::vec3&, const glm::vec3&, float&, BoxFace&, glm::vec3&) override { return false; } + virtual bool findRayIntersection(const glm::vec3&, const glm::vec3&, float&, BoxFace&, glm::vec3&, bool precisionPicking = false) override { return false; } protected: Transform evalRenderTransform() override; diff --git a/interface/src/ui/overlays/Image3DOverlay.cpp b/interface/src/ui/overlays/Image3DOverlay.cpp index 6e9946e935..a4ce7f9e0d 100644 --- a/interface/src/ui/overlays/Image3DOverlay.cpp +++ b/interface/src/ui/overlays/Image3DOverlay.cpp @@ -258,7 +258,7 @@ void Image3DOverlay::setURL(const QString& url) { } bool Image3DOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - float& distance, BoxFace& face, glm::vec3& surfaceNormal) { + float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking) { if (_texture && _texture->isLoaded()) { // Make sure position and rotation is updated. Transform transform = getTransform(); diff --git a/interface/src/ui/overlays/Image3DOverlay.h b/interface/src/ui/overlays/Image3DOverlay.h index aa802a82a9..4432e3b07c 100644 --- a/interface/src/ui/overlays/Image3DOverlay.h +++ b/interface/src/ui/overlays/Image3DOverlay.h @@ -43,7 +43,7 @@ public: bool isTransparent() override { return Base3DOverlay::isTransparent() || _alphaTexture; } virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, - BoxFace& face, glm::vec3& surfaceNormal) override; + BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking = false) override; virtual Image3DOverlay* createClone() const override; diff --git a/interface/src/ui/overlays/ModelOverlay.cpp b/interface/src/ui/overlays/ModelOverlay.cpp index 27e3bd0e2d..f4289b1bf5 100644 --- a/interface/src/ui/overlays/ModelOverlay.cpp +++ b/interface/src/ui/overlays/ModelOverlay.cpp @@ -100,26 +100,40 @@ void ModelOverlay::update(float deltatime) { processMaterials(); emit DependencyManager::get()->modelAddedToScene(getID(), NestableType::Overlay, _model); } + bool metaDirty = false; if (_visibleDirty) { _visibleDirty = false; // don't show overlays in mirrors or spectator-cam unless _isVisibleInSecondaryCamera is true - _model->setVisibleInScene(getVisible(), scene, - render::ItemKey::TAG_BITS_0 | - (_isVisibleInSecondaryCamera ? render::ItemKey::TAG_BITS_1 : render::ItemKey::TAG_BITS_NONE), - false); + uint8_t modelRenderTagMask = (_isVisibleInSecondaryCamera ? render::hifi::TAG_ALL_VIEWS : render::hifi::TAG_MAIN_VIEW); + _model->setTagMask(modelRenderTagMask, scene); + _model->setVisibleInScene(getVisible(), scene); + metaDirty = true; } if (_drawInFrontDirty) { _drawInFrontDirty = false; _model->setLayeredInFront(getDrawInFront(), scene); + metaDirty = true; } if (_drawInHUDDirty) { _drawInHUDDirty = false; _model->setLayeredInHUD(getDrawHUDLayer(), scene); + metaDirty = true; + } + if (_groupCulledDirty) { + _groupCulledDirty = false; + _model->setGroupCulled(_isGroupCulled, scene); + metaDirty = true; + } + if (metaDirty) { + transaction.updateItem(getRenderItemID(), [](Overlay& data) {}); } scene->enqueueTransaction(transaction); if (!_texturesLoaded && _model->getGeometry() && _model->getGeometry()->areTexturesLoaded()) { _texturesLoaded = true; + if (!_modelTextures.isEmpty()) { + _model->setTextures(_modelTextures); + } _model->updateRenderItems(); } } @@ -150,13 +164,24 @@ void ModelOverlay::setVisible(bool visible) { } void ModelOverlay::setDrawInFront(bool drawInFront) { - Base3DOverlay::setDrawInFront(drawInFront); - _drawInFrontDirty = true; + if (drawInFront != getDrawInFront()) { + Base3DOverlay::setDrawInFront(drawInFront); + _drawInFrontDirty = true; + } } void ModelOverlay::setDrawHUDLayer(bool drawHUDLayer) { - Base3DOverlay::setDrawHUDLayer(drawHUDLayer); - _drawInHUDDirty = true; + if (drawHUDLayer != getDrawHUDLayer()) { + Base3DOverlay::setDrawHUDLayer(drawHUDLayer); + _drawInHUDDirty = true; + } +} + +void ModelOverlay::setGroupCulled(bool groupCulled) { + if (groupCulled != _isGroupCulled) { + _isGroupCulled = groupCulled; + _groupCulledDirty = true; + } } void ModelOverlay::setProperties(const QVariantMap& properties) { @@ -207,8 +232,12 @@ void ModelOverlay::setProperties(const QVariantMap& properties) { if (texturesValue.isValid() && texturesValue.canConvert(QVariant::Map)) { _texturesLoaded = false; QVariantMap textureMap = texturesValue.toMap(); - QMetaObject::invokeMethod(_model.get(), "setTextures", Qt::AutoConnection, - Q_ARG(const QVariantMap&, textureMap)); + _modelTextures = textureMap; + } + + auto groupCulledValue = properties["isGroupCulled"]; + if (groupCulledValue.isValid() && groupCulledValue.canConvert(QVariant::Bool)) { + setGroupCulled(groupCulledValue.toBool()); } // jointNames is read-only. @@ -348,6 +377,8 @@ vectorType ModelOverlay::mapJoints(mapFunction function) const { * {@link Overlays.findRayIntersection|findRayIntersection} ignores the overlay. * @property {boolean} drawInFront=false - If true, the overlay is rendered in front of other overlays that don't * have drawInFront set to true, and in front of entities. + * @property {boolean} isGroupCulled=false - If true, the mesh parts of the model are LOD culled as a group. + * If false, separate mesh parts will be LOD culled individually. * @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed. * @property {Uuid} parentID=null - The avatar, entity, or overlay that the overlay is parented to. * @property {number} parentJointIndex=65535 - Integer value specifying the skeleton joint that the overlay is attached to if @@ -478,16 +509,16 @@ QVariant ModelOverlay::getProperty(const QString& property) { } bool ModelOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - float& distance, BoxFace& face, glm::vec3& surfaceNormal) { + float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking) { QVariantMap extraInfo; - return _model->findRayIntersectionAgainstSubMeshes(origin, direction, distance, face, surfaceNormal, extraInfo); + return _model->findRayIntersectionAgainstSubMeshes(origin, direction, distance, face, surfaceNormal, extraInfo, precisionPicking); } bool ModelOverlay::findRayIntersectionExtraInfo(const glm::vec3& origin, const glm::vec3& direction, - float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo) { + float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) { - return _model->findRayIntersectionAgainstSubMeshes(origin, direction, distance, face, surfaceNormal, extraInfo); + return _model->findRayIntersectionAgainstSubMeshes(origin, direction, distance, face, surfaceNormal, extraInfo, precisionPicking); } ModelOverlay* ModelOverlay::createClone() const { @@ -712,3 +743,11 @@ scriptable::ScriptableModelBase ModelOverlay::getScriptableModel() { } return result; } + +render::ItemKey ModelOverlay::getKey() { + auto builder = render::ItemKey::Builder(Base3DOverlay::getKey()); + if (_isGroupCulled) { + builder.withMetaCullGroup(); + } + return builder.build(); +} \ No newline at end of file diff --git a/interface/src/ui/overlays/ModelOverlay.h b/interface/src/ui/overlays/ModelOverlay.h index 3ef3f23fec..f7a79c5615 100644 --- a/interface/src/ui/overlays/ModelOverlay.h +++ b/interface/src/ui/overlays/ModelOverlay.h @@ -33,6 +33,7 @@ public: virtual uint32_t fetchMetaSubItems(render::ItemIDs& subItems) const override; + render::ItemKey getKey() override; void clearSubRenderItemIDs(); void setSubRenderItemIDs(const render::ItemIDs& ids); @@ -44,9 +45,9 @@ public: void setProperties(const QVariantMap& properties) override; QVariant getProperty(const QString& property) override; virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, - BoxFace& face, glm::vec3& surfaceNormal) override; + BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking = false) override; virtual bool findRayIntersectionExtraInfo(const glm::vec3& origin, const glm::vec3& direction, - float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo) override; + float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking = false) override; virtual ModelOverlay* createClone() const override; @@ -63,6 +64,7 @@ public: void setVisible(bool visible) override; void setDrawInFront(bool drawInFront) override; void setDrawHUDLayer(bool drawHUDLayer) override; + void setGroupCulled(bool groupCulled); void addMaterial(graphics::MaterialLayer material, const std::string& parentMaterialName) override; void removeMaterial(graphics::MaterialPointer material, const std::string& parentMaterialName) override; @@ -121,6 +123,8 @@ private: bool _visibleDirty { true }; bool _drawInFrontDirty { false }; bool _drawInHUDDirty { false }; + bool _isGroupCulled { false }; + bool _groupCulledDirty { false }; void processMaterials(); diff --git a/interface/src/ui/overlays/Overlay.cpp b/interface/src/ui/overlays/Overlay.cpp index 2c0c7c71b6..faa15ee2b4 100644 --- a/interface/src/ui/overlays/Overlay.cpp +++ b/interface/src/ui/overlays/Overlay.cpp @@ -244,4 +244,21 @@ void Overlay::addMaterial(graphics::MaterialLayer material, const std::string& p void Overlay::removeMaterial(graphics::MaterialPointer material, const std::string& parentMaterialName) { std::lock_guard lock(_materialsLock); _materials[parentMaterialName].remove(material); +} + +render::ItemKey Overlay::getKey() { + auto builder = render::ItemKey::Builder().withTypeShape(); + + builder.withViewSpace(); + builder.withLayer(render::hifi::LAYER_2D); + + if (!getVisible()) { + builder.withInvisible(); + } + + // always visible in primary view. if isVisibleInSecondaryCamera, also draw in secondary view + render::hifi::Tag viewTagBits = getIsVisibleInSecondaryCamera() ? render::hifi::TAG_ALL_VIEWS : render::hifi::TAG_MAIN_VIEW; + builder.withTagBits(viewTagBits); + + return builder.build(); } \ No newline at end of file diff --git a/interface/src/ui/overlays/Overlay.h b/interface/src/ui/overlays/Overlay.h index 2f27d50f7e..45fc77a452 100644 --- a/interface/src/ui/overlays/Overlay.h +++ b/interface/src/ui/overlays/Overlay.h @@ -40,6 +40,7 @@ public: virtual void update(float deltatime) {} virtual void render(RenderArgs* args) = 0; + virtual render::ItemKey getKey(); virtual AABox getBounds() const = 0; virtual bool supportsGetProperty() const { return true; } @@ -132,7 +133,6 @@ private: namespace render { template <> const ItemKey payloadGetKey(const Overlay::Pointer& overlay); template <> const Item::Bound payloadGetBound(const Overlay::Pointer& overlay); - template <> int payloadGetLayer(const Overlay::Pointer& overlay); template <> void payloadRender(const Overlay::Pointer& overlay, RenderArgs* args); template <> const ShapeKey shapeGetShapeKey(const Overlay::Pointer& overlay); template <> uint32_t metaFetchMetaSubItems(const Overlay::Pointer& overlay, ItemIDs& subItems); diff --git a/interface/src/ui/overlays/Overlays.cpp b/interface/src/ui/overlays/Overlays.cpp index 85041aad4e..be7b0d42fc 100644 --- a/interface/src/ui/overlays/Overlays.cpp +++ b/interface/src/ui/overlays/Overlays.cpp @@ -554,7 +554,7 @@ RayToOverlayIntersectionResult Overlays::findRayIntersectionVector(const PickRay glm::vec3 thisSurfaceNormal; QVariantMap thisExtraInfo; if (thisOverlay->findRayIntersectionExtraInfo(ray.origin, ray.direction, thisDistance, - thisFace, thisSurfaceNormal, thisExtraInfo)) { + thisFace, thisSurfaceNormal, thisExtraInfo, precisionPicking)) { bool isDrawInFront = thisOverlay->getDrawInFront(); if ((bestIsFront && isDrawInFront && thisDistance < bestDistance) || (!bestIsFront && (isDrawInFront || thisDistance < bestDistance))) { diff --git a/interface/src/ui/overlays/OverlaysPayload.cpp b/interface/src/ui/overlays/OverlaysPayload.cpp index 185547a333..37fadef0b4 100644 --- a/interface/src/ui/overlays/OverlaysPayload.cpp +++ b/interface/src/ui/overlays/OverlaysPayload.cpp @@ -32,48 +32,11 @@ namespace render { template <> const ItemKey payloadGetKey(const Overlay::Pointer& overlay) { - auto builder = ItemKey::Builder().withTypeShape(); - if (overlay->is3D()) { - auto overlay3D = std::static_pointer_cast(overlay); - if (overlay3D->getDrawInFront() || overlay3D->getDrawHUDLayer()) { - builder.withLayered(); - } - if (overlay->isTransparent()) { - builder.withTransparent(); - } - } else { - builder.withViewSpace(); - } - - if (!overlay->getVisible()) { - builder.withInvisible(); - } - - // always visible in primary view. if isVisibleInSecondaryCamera, also draw in secondary view - uint32_t viewTaskBits = render::ItemKey::TAG_BITS_0 | - (overlay->getIsVisibleInSecondaryCamera() ? render::ItemKey::TAG_BITS_1 : render::ItemKey::TAG_BITS_NONE); - - builder.withTagBits(viewTaskBits); - - return builder.build(); + return overlay->getKey(); } template <> const Item::Bound payloadGetBound(const Overlay::Pointer& overlay) { return overlay->getBounds(); } - template <> int payloadGetLayer(const Overlay::Pointer& overlay) { - if (overlay->is3D()) { - auto overlay3D = std::dynamic_pointer_cast(overlay); - if (overlay3D->getDrawInFront()) { - return Item::LAYER_3D_FRONT; - } else if (overlay3D->getDrawHUDLayer()) { - return Item::LAYER_3D_HUD; - } else { - return Item::LAYER_3D; - } - } else { - return Item::LAYER_2D; - } - } template <> void payloadRender(const Overlay::Pointer& overlay, RenderArgs* args) { if (args) { overlay->render(args); @@ -83,7 +46,6 @@ namespace render { return overlay->getShapeKey(); } - template <> uint32_t metaFetchMetaSubItems(const Overlay::Pointer& overlay, ItemIDs& subItems) { return overlay->fetchMetaSubItems(subItems); } diff --git a/interface/src/ui/overlays/Planar3DOverlay.cpp b/interface/src/ui/overlays/Planar3DOverlay.cpp index f53d06a239..9a436c7564 100644 --- a/interface/src/ui/overlays/Planar3DOverlay.cpp +++ b/interface/src/ui/overlays/Planar3DOverlay.cpp @@ -71,7 +71,7 @@ QVariant Planar3DOverlay::getProperty(const QString& property) { } bool Planar3DOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - float& distance, BoxFace& face, glm::vec3& surfaceNormal) { + float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking) { // FIXME - face and surfaceNormal not being returned return findRayRectangleIntersection(origin, direction, getWorldOrientation(), getWorldPosition(), getDimensions(), distance); } diff --git a/interface/src/ui/overlays/Planar3DOverlay.h b/interface/src/ui/overlays/Planar3DOverlay.h index 0a0e75696e..e2a0e1f896 100644 --- a/interface/src/ui/overlays/Planar3DOverlay.h +++ b/interface/src/ui/overlays/Planar3DOverlay.h @@ -31,7 +31,7 @@ public: virtual QVariant getProperty(const QString& property) override; virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, - BoxFace& face, glm::vec3& surfaceNormal) override; + BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking = false) override; protected: glm::vec2 _dimensions; diff --git a/interface/src/ui/overlays/Volume3DOverlay.cpp b/interface/src/ui/overlays/Volume3DOverlay.cpp index 3aed2a5b42..cf1f7f7fcb 100644 --- a/interface/src/ui/overlays/Volume3DOverlay.cpp +++ b/interface/src/ui/overlays/Volume3DOverlay.cpp @@ -76,7 +76,7 @@ QVariant Volume3DOverlay::getProperty(const QString& property) { } bool Volume3DOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - float& distance, BoxFace& face, glm::vec3& surfaceNormal) { + float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking) { // extents is the entity relative, scaled, centered extents of the entity glm::mat4 worldToEntityMatrix; Transform transform = getTransform(); diff --git a/interface/src/ui/overlays/Volume3DOverlay.h b/interface/src/ui/overlays/Volume3DOverlay.h index bde8c71aef..e9b996a6dd 100644 --- a/interface/src/ui/overlays/Volume3DOverlay.h +++ b/interface/src/ui/overlays/Volume3DOverlay.h @@ -31,7 +31,7 @@ public: QVariant getProperty(const QString& property) override; virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, - BoxFace& face, glm::vec3& surfaceNormal) override; + BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking = false) override; protected: // Centered local bounding box diff --git a/interface/src/ui/overlays/Web3DOverlay.cpp b/interface/src/ui/overlays/Web3DOverlay.cpp index 8af818edc6..ade478347c 100644 --- a/interface/src/ui/overlays/Web3DOverlay.cpp +++ b/interface/src/ui/overlays/Web3DOverlay.cpp @@ -622,7 +622,7 @@ void Web3DOverlay::setScriptURL(const QString& scriptURL) { } } -bool Web3DOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face, glm::vec3& surfaceNormal) { +bool Web3DOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking) { glm::vec2 dimensions = getDimensions(); glm::quat rotation = getWorldOrientation(); glm::vec3 position = getWorldPosition(); diff --git a/interface/src/ui/overlays/Web3DOverlay.h b/interface/src/ui/overlays/Web3DOverlay.h index d888424cbc..2cf35c0172 100644 --- a/interface/src/ui/overlays/Web3DOverlay.h +++ b/interface/src/ui/overlays/Web3DOverlay.h @@ -53,7 +53,7 @@ public: QVariant getProperty(const QString& property) override; virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, - BoxFace& face, glm::vec3& surfaceNormal) override; + BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking = false) override; virtual Web3DOverlay* createClone() const override; diff --git a/libraries/animation/src/AnimUtil.cpp b/libraries/animation/src/AnimUtil.cpp index 65c605b5ba..acb90126fc 100644 --- a/libraries/animation/src/AnimUtil.cpp +++ b/libraries/animation/src/AnimUtil.cpp @@ -9,7 +9,9 @@ // #include "AnimUtil.h" -#include "GLMHelpers.h" +#include +#include +#include // TODO: use restrict keyword // TODO: excellent candidate for simd vectorization. @@ -107,3 +109,44 @@ AnimPose boneLookAt(const glm::vec3& target, const AnimPose& bone) { glm::vec4(bone.trans(), 1.0f)); return AnimPose(lookAt); } + +// This will attempt to determine the proper body facing of a characters body +// assumes headRot is z-forward and y-up. +// and returns a bodyRot that is also z-forward and y-up +glm::quat computeBodyFacingFromHead(const glm::quat& headRot, const glm::vec3& up) { + + glm::vec3 bodyUp = glm::normalize(up); + + // initially take the body facing from the head. + glm::vec3 headUp = headRot * Vectors::UNIT_Y; + glm::vec3 headForward = headRot * Vectors::UNIT_Z; + glm::vec3 headLeft = headRot * Vectors::UNIT_X; + const float NOD_THRESHOLD = cosf(glm::radians(45.0f)); + const float TILT_THRESHOLD = cosf(glm::radians(30.0f)); + + glm::vec3 bodyForward = headForward; + + float nodDot = glm::dot(headForward, bodyUp); + float tiltDot = glm::dot(headLeft, bodyUp); + + if (fabsf(tiltDot) < TILT_THRESHOLD) { // if we are not tilting too much + if (nodDot < -NOD_THRESHOLD) { // head is looking downward + // the body should face in the same direction as the top the head. + bodyForward = headUp; + } else if (nodDot > NOD_THRESHOLD) { // head is looking upward + // the body should face away from the top of the head. + bodyForward = -headUp; + } + } + + // cancel out upward component + bodyForward = glm::normalize(bodyForward - nodDot * bodyUp); + + glm::vec3 u, v, w; + generateBasisVectors(bodyForward, bodyUp, u, v, w); + + // create matrix from orthogonal basis vectors + glm::mat4 bodyMat(glm::vec4(w, 0.0f), glm::vec4(v, 0.0f), glm::vec4(u, 0.0f), glm::vec4(0.0f, 0.0f, 0.0f, 1.0f)); + + return glmExtractRotation(bodyMat); +} diff --git a/libraries/animation/src/AnimUtil.h b/libraries/animation/src/AnimUtil.h index f2cceb361b..3cd7f4b6fb 100644 --- a/libraries/animation/src/AnimUtil.h +++ b/libraries/animation/src/AnimUtil.h @@ -33,4 +33,9 @@ inline glm::quat safeLerp(const glm::quat& a, const glm::quat& b, float alpha) { AnimPose boneLookAt(const glm::vec3& target, const AnimPose& bone); +// This will attempt to determine the proper body facing of a characters body +// assumes headRot is z-forward and y-up. +// and returns a bodyRot that is also z-forward and y-up +glm::quat computeBodyFacingFromHead(const glm::quat& headRot, const glm::vec3& up); + #endif diff --git a/libraries/audio/src/Sound.cpp b/libraries/audio/src/Sound.cpp index cd93f7b52e..67f9952771 100644 --- a/libraries/audio/src/Sound.cpp +++ b/libraries/audio/src/Sound.cpp @@ -31,6 +31,8 @@ #include "AudioLogging.h" #include "AudioSRC.h" +#include "flump3dec.h" + QScriptValue soundSharedPointerToScriptValue(QScriptEngine* engine, const SharedSoundPointer& in) { return engine->newQObject(new SoundScriptingInterface(in), QScriptEngine::ScriptOwnership); } @@ -90,19 +92,35 @@ void SoundProcessor::run() { QString fileName = _url.fileName().toLower(); static const QString WAV_EXTENSION = ".wav"; + static const QString MP3_EXTENSION = ".mp3"; static const QString RAW_EXTENSION = ".raw"; + if (fileName.endsWith(WAV_EXTENSION)) { QByteArray outputAudioByteArray; int sampleRate = interpretAsWav(rawAudioByteArray, outputAudioByteArray); if (sampleRate == 0) { - qCDebug(audio) << "Unsupported WAV file type"; + qCWarning(audio) << "Unsupported WAV file type"; emit onError(300, "Failed to load sound file, reason: unsupported WAV file type"); return; } downSample(outputAudioByteArray, sampleRate); + + } else if (fileName.endsWith(MP3_EXTENSION)) { + + QByteArray outputAudioByteArray; + + int sampleRate = interpretAsMP3(rawAudioByteArray, outputAudioByteArray); + if (sampleRate == 0) { + qCWarning(audio) << "Unsupported MP3 file type"; + emit onError(300, "Failed to load sound file, reason: unsupported MP3 file type"); + return; + } + + downSample(outputAudioByteArray, sampleRate); + } else if (fileName.endsWith(RAW_EXTENSION)) { // check if this was a stereo raw file // since it's raw the only way for us to know that is if the file was called .stereo.raw @@ -113,8 +131,9 @@ void SoundProcessor::run() { // Process as 48khz RAW file downSample(rawAudioByteArray, 48000); + } else { - qCDebug(audio) << "Unknown sound file type"; + qCWarning(audio) << "Unknown sound file type"; emit onError(300, "Failed to load sound file, reason: unknown sound file type"); return; } @@ -204,7 +223,7 @@ int SoundProcessor::interpretAsWav(const QByteArray& inputAudioByteArray, QByteA // Read the "RIFF" chunk RIFFHeader riff; if (waveStream.readRawData((char*)&riff, sizeof(RIFFHeader)) != sizeof(RIFFHeader)) { - qCDebug(audio) << "Not a valid WAVE file."; + qCWarning(audio) << "Not a valid WAVE file."; return 0; } @@ -212,11 +231,11 @@ int SoundProcessor::interpretAsWav(const QByteArray& inputAudioByteArray, QByteA if (strncmp(riff.descriptor.id, "RIFF", 4) == 0) { waveStream.setByteOrder(QDataStream::LittleEndian); } else { - qCDebug(audio) << "Currently not supporting big-endian audio files."; + qCWarning(audio) << "Currently not supporting big-endian audio files."; return 0; } if (strncmp(riff.type, "WAVE", 4) != 0) { - qCDebug(audio) << "Not a valid WAVE file."; + qCWarning(audio) << "Not a valid WAVE file."; return 0; } @@ -224,7 +243,7 @@ int SoundProcessor::interpretAsWav(const QByteArray& inputAudioByteArray, QByteA chunk fmt; while (true) { if (waveStream.readRawData((char*)&fmt, sizeof(chunk)) != sizeof(chunk)) { - qCDebug(audio) << "Not a valid WAVE file."; + qCWarning(audio) << "Not a valid WAVE file."; return 0; } if (strncmp(fmt.id, "fmt ", 4) == 0) { @@ -236,14 +255,14 @@ int SoundProcessor::interpretAsWav(const QByteArray& inputAudioByteArray, QByteA // Read the "fmt " chunk WAVEFormat wave; if (waveStream.readRawData((char*)&wave, sizeof(WAVEFormat)) != sizeof(WAVEFormat)) { - qCDebug(audio) << "Not a valid WAVE file."; + qCWarning(audio) << "Not a valid WAVE file."; return 0; } // Parse the "fmt " chunk if (qFromLittleEndian(wave.audioFormat) != WAVEFORMAT_PCM && qFromLittleEndian(wave.audioFormat) != WAVEFORMAT_EXTENSIBLE) { - qCDebug(audio) << "Currently not supporting non PCM audio files."; + qCWarning(audio) << "Currently not supporting non PCM audio files."; return 0; } if (qFromLittleEndian(wave.numChannels) == 2) { @@ -251,11 +270,11 @@ int SoundProcessor::interpretAsWav(const QByteArray& inputAudioByteArray, QByteA } else if (qFromLittleEndian(wave.numChannels) == 4) { _isAmbisonic = true; } else if (qFromLittleEndian(wave.numChannels) != 1) { - qCDebug(audio) << "Currently not supporting audio files with other than 1/2/4 channels."; + qCWarning(audio) << "Currently not supporting audio files with other than 1/2/4 channels."; return 0; } if (qFromLittleEndian(wave.bitsPerSample) != 16) { - qCDebug(audio) << "Currently not supporting non 16bit audio files."; + qCWarning(audio) << "Currently not supporting non 16bit audio files."; return 0; } @@ -266,7 +285,7 @@ int SoundProcessor::interpretAsWav(const QByteArray& inputAudioByteArray, QByteA chunk data; while (true) { if (waveStream.readRawData((char*)&data, sizeof(chunk)) != sizeof(chunk)) { - qCDebug(audio) << "Not a valid WAVE file."; + qCWarning(audio) << "Not a valid WAVE file."; return 0; } if (strncmp(data.id, "data", 4) == 0) { @@ -279,10 +298,101 @@ int SoundProcessor::interpretAsWav(const QByteArray& inputAudioByteArray, QByteA quint32 outputAudioByteArraySize = qFromLittleEndian(data.size); outputAudioByteArray.resize(outputAudioByteArraySize); if (waveStream.readRawData(outputAudioByteArray.data(), outputAudioByteArraySize) != (int)outputAudioByteArraySize) { - qCDebug(audio) << "Error reading WAV file"; + qCWarning(audio) << "Error reading WAV file"; return 0; } _duration = (float)(outputAudioByteArraySize / (wave.sampleRate * wave.numChannels * wave.bitsPerSample / 8.0f)); return wave.sampleRate; } + +// returns MP3 sample rate, used for resampling +int SoundProcessor::interpretAsMP3(const QByteArray& inputAudioByteArray, QByteArray& outputAudioByteArray) { + using namespace flump3dec; + + static const int MP3_SAMPLES_MAX = 1152; + static const int MP3_CHANNELS_MAX = 2; + static const int MP3_BUFFER_SIZE = MP3_SAMPLES_MAX * MP3_CHANNELS_MAX * sizeof(int16_t); + uint8_t mp3Buffer[MP3_BUFFER_SIZE]; + + // create bitstream + Bit_stream_struc *bitstream = bs_new(); + if (bitstream == nullptr) { + return 0; + } + + // create decoder + mp3tl *decoder = mp3tl_new(bitstream, MP3TL_MODE_16BIT); + if (decoder == nullptr) { + bs_free(bitstream); + return 0; + } + + // initialize + bs_set_data(bitstream, (uint8_t*)inputAudioByteArray.data(), inputAudioByteArray.size()); + int frameCount = 0; + int sampleRate = 0; + int numChannels = 0; + + // skip ID3 tag, if present + Mp3TlRetcode result = mp3tl_skip_id3(decoder); + + while (!(result == MP3TL_ERR_NO_SYNC || result == MP3TL_ERR_NEED_DATA)) { + + mp3tl_sync(decoder); + + // find MP3 header + const fr_header *header = nullptr; + result = mp3tl_decode_header(decoder, &header); + + if (result == MP3TL_ERR_OK) { + + if (frameCount++ == 0) { + + qCDebug(audio) << "Decoding MP3 with bitrate =" << header->bitrate + << "sample rate =" << header->sample_rate + << "channels =" << header->channels; + + // save header info + sampleRate = header->sample_rate; + numChannels = header->channels; + + // skip Xing header, if present + result = mp3tl_skip_xing(decoder, header); + } + + // decode MP3 frame + if (result == MP3TL_ERR_OK) { + + result = mp3tl_decode_frame(decoder, mp3Buffer, MP3_BUFFER_SIZE); + + // fill bad frames with silence + int len = header->frame_samples * header->channels * sizeof(int16_t); + if (result == MP3TL_ERR_BAD_FRAME) { + memset(mp3Buffer, 0, len); + } + + if (result == MP3TL_ERR_OK || result == MP3TL_ERR_BAD_FRAME) { + outputAudioByteArray.append((char*)mp3Buffer, len); + } + } + } + } + + // free decoder + mp3tl_free(decoder); + + // free bitstream + bs_free(bitstream); + + int outputAudioByteArraySize = outputAudioByteArray.size(); + if (outputAudioByteArraySize == 0) { + qCWarning(audio) << "Error decoding MP3 file"; + return 0; + } + + _isStereo = (numChannels == 2); + _isAmbisonic = false; + _duration = (float)outputAudioByteArraySize / (sampleRate * numChannels * sizeof(int16_t)); + return sampleRate; +} diff --git a/libraries/audio/src/Sound.h b/libraries/audio/src/Sound.h index 4cfdac7792..061c4a2417 100644 --- a/libraries/audio/src/Sound.h +++ b/libraries/audio/src/Sound.h @@ -62,6 +62,7 @@ public: void downSample(const QByteArray& rawAudioByteArray, int sampleRate); int interpretAsWav(const QByteArray& inputAudioByteArray, QByteArray& outputAudioByteArray); + int interpretAsMP3(const QByteArray& inputAudioByteArray, QByteArray& outputAudioByteArray); signals: void onSuccess(QByteArray data, bool stereo, bool ambisonic, float duration); diff --git a/libraries/audio/src/flump3dec.cpp b/libraries/audio/src/flump3dec.cpp new file mode 100644 index 0000000000..935ddecabb --- /dev/null +++ b/libraries/audio/src/flump3dec.cpp @@ -0,0 +1,8205 @@ +/* + * FLUENDO S.A. + * Copyright (C) <2005 - 2011> + * + * This Source Code is licensed under MIT license and the explanations attached + * in MIT License Statements. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is furnished to do + * so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * MIT license Statements for Fluendo's mp3 plug-in Source Code + * ------------------------------------------------------------ + * + * Fluendo's mp3 software Source Code (the "Source Code") is licensed under the + * MIT license provisions. + * + * The MIT license is an open source license that permits the User to operate and + * use in many forms the Source Code, which would be governed under its + * regulations. + * + * The purpose of this note is to clarify the intellectual property rights granted + * over the Source Code by Fluendo, as well as other legal issues that concern + * your use of it. + * + * MIT license contents and provisions + * ----------------------------------- + * + * The MIT license allows you to do the following things with the Source Code: + * + * - Copy and use the Source Code alone or jointly with other code for any + * purposes. + * Copy of the Source Code is not limited and is royalty-free. + * + * - Merge the Source Code with other code for developing new applications with no + * limits. + * + * - Modifying the Source Code for developing the plug-in or for implementing the + * plug-in in other applications for any purposes. The MIT License does not + * require you to share these modifications with anyone. + * + * - Publish, distribute, sublicense and sell copies of the Source Code to third + * parties. + * + * - Permit anyone to whom the Source Code is licensed to enjoy the rights above + * subject to the MIT license provisions. + * + * By licensing this Source Code under the MIT License, Fluendo is offering to the + * community the rights set out above without restriction and without any + * obligation for the User of the Source Code to release his/her modifications + * back to the community. Anyone operating with the Source Code released from + * Fluendo must grant the same MIT license rights to the community, except for any + * modifications operated on the Source Code which can be granted under a + * different license (even a proprietary license). + * + * All these rights granted to the User for the Source Code hold a limitation + * which is to include MIT permission notice and the following copyright notice: + * "Copyright 2005 Fluendo, S.L. This Source Code is licensed under MIT license + * and the explanations attached in MIT License Statements". These notices shall + * be included in all copies of the Source Code or in substantial parts of the + * Source Code which may be released separately or with modifications. + * + * Patents over the plug-in and/or Source Code + * ------------------------------------------- + * + * The binaries that can be created by compiling this Source Code released by + * Fluendo might be covered by patents in various parts of the world. Fluendo + * does not own or claim to own any patents on the techniques used in the code. + * (Such patents are owned or claimed to be owned by Thompson Licensing, S.A. and + * some other entities as the case may be). + * + * Fluendo has got the relevant licenses to cover its own activities with the + * Source Code but it is not authorized to sublicense nor to grant the rights + * which it has acquired over the patents. In this sense, you can work and deal + * freely with the Source Code under MIT provisions set out above, bearing in mind + * that some activities might not be allowed under applicable patent regulations + * and that Fluendo is not granting any rights in relation to such patents. + * + * The patent license granted to Fluendo only covers Fluendo's own Software and + * Source Code activities. In any case, this software license does not allow you + * to redistribute or copy complete, ready to use mp3 software decoder binaries + * made from the Source Code as made available by Fluendo. You can of course + * distribute binaries you make yourself under any terms allowed by the MIT + * license and whatever necessary rights you have or have acquired according to + * applicable patent regulations. + * + * As Fluendo can not assure that any of the activities you undertake do not + * infringe any patents or other industrial or intellectual property rights, + * Fluendo hereby disclaims any liability for any patent infringement that may be + * claimed to you or to any other person from any legitimate right’s owner, as + * stated in MIT license. So it is your responsibility to get information and to + * acquire the necessary patent licenses to undertake your activities legally. + */ + +// +// Modifications and bug fixes copyright 2018 High Fidelity, Inc. +// Now passes ISO/IEC 11172-4 "full accuracy" compliance testing. +// + +#include "flump3dec.h" + +#include +#include + +namespace flump3dec { + +/*********************************************************************** +* +* Global Definitions +* +***********************************************************************/ + +#ifdef __GNUC__ +#define G_LIKELY(expr) (__builtin_expect ((expr), 1)) +#define G_UNLIKELY(expr) (__builtin_expect ((expr), 0)) +#else +#define G_LIKELY +#define G_UNLIKELY +#endif + +#ifdef __GNUC__ +#define ATTR_UNUSED __attribute__ ((unused)) +#else +#define ATTR_UNUSED +#endif + +#if defined(_MSC_VER) +#define STATIC_INLINE static __inline +#elif defined(__GNUC__) +#define STATIC_INLINE static __inline__ +#else +#define STATIC_INLINE static inline +#endif + +#define __CACHE_LINE_BYTES 16 +#define __CACHE_LINE_ALIGN(a) ((((gsize)a) + __CACHE_LINE_BYTES - 1) & ~(__CACHE_LINE_BYTES - 1)) + +#if defined(_MSC_VER) +#define __CACHE_LINE_DECL_ALIGN(x) __declspec (align(__CACHE_LINE_BYTES)) x +#elif defined(__GNUC__) +#define __CACHE_LINE_DECL_ALIGN(x) x __attribute__ ((aligned (__CACHE_LINE_BYTES))) +#else +#define __CACHE_LINE_DECL_ALIGN(x) x +#endif + +#if defined(_MSC_VER) +#define BYTESWAP32(p) _byteswap_ulong(*(uint32_t *)(p)) +#elif defined(__GNUC__) +#define BYTESWAP32(p) __builtin_bswap32(*(uint32_t *)(p)) +#else +#define BYTESWAP32(p) (uint32_t)(((p)[0] << 24) | ((p)[1] << 16) | ((p)[2] << 8) | ((p)[3] << 0)) +#endif + +#define SYNC_WORD_LNGTH 11 +#define HEADER_LNGTH 21 +#define SYNC_WORD ((guint32)(0x7ff)) + +/* General Definitions */ +#ifndef PI +#define PI 3.141592653589793115997963468544185161590576171875 +#endif + +#define PI4 PI/4 +#define PI64 PI/64 + +/* Maximum samples per sub-band */ +#define SSLIMIT 18 +/* Maximum number of sub-bands */ +#define SBLIMIT 32 + +#define HAN_SIZE 512 +#define SCALE_BLOCK 12 +#define SCALE_RANGE 64 +#define SCALE 32768 +#define CRC16_POLYNOMIAL 0x8005 + +/* MPEG Header Definitions - ID Bit Values */ +#define MPEG_VERSION_1 0x03 +#define MPEG_VERSION_2 0x02 +#define MPEG_VERSION_2_5 0x00 + +/* MPEG Header Definitions - Mode Values */ +#define MPG_MD_STEREO 0 +#define MPG_MD_JOINT_STEREO 1 +#define MPG_MD_DUAL_CHANNEL 2 +#define MPG_MD_MONO 3 + +/*********************************************************************** +* +* Global Type Definitions +* +***********************************************************************/ + +/* Structure for Reading Layer II Allocation Tables from File */ +typedef struct +{ + unsigned int steps; + unsigned int bits; + unsigned int group; + unsigned int quant; +} sb_alloc; + +typedef sb_alloc al_table[SBLIMIT][16]; + +/* Layer III side information. */ +typedef struct +{ + guint part2_3_length; + guint big_values; + guint global_gain; + guint scalefac_compress; + guint window_switching_flag; + guint block_type; + guint mixed_block_flag; + guint table_select[3]; + guint subblock_gain[3]; + guint region0_count; + guint region1_count; + guint preflag; + guint scalefac_scale; + guint count1table_select; +} gr_info_t; + +typedef struct +{ + guint main_data_begin; + guint private_bits; + guint scfsi[4][2]; /* [band?][channel] */ + gr_info_t gr[2][2]; /* [group][channel] */ +} III_side_info_t; + +/* Layer III scale factors. */ +typedef struct +{ + int l[22]; /* [cb] */ + int s[3][13]; /* [window][cb] */ +} III_scalefac_t[2]; /* [ch] */ + +/* top level structure for frame decoding */ +typedef struct +{ + fr_header header; /* raw header information */ + int actual_mode; /* when writing IS, may forget if 0 chs */ + int stereo; /* 1 for mono, 2 for stereo */ + int jsbound; /* first band of joint stereo coding */ + int sblimit; /* total number of sub bands */ + const al_table *alloc; /* bit allocation table for the frame */ + + /* Synthesis workspace */ + __CACHE_LINE_DECL_ALIGN(float filter[64][32]); + __CACHE_LINE_DECL_ALIGN(float synbuf[2][2 * HAN_SIZE]); + gint bufOffset[2]; + + /* scale data */ + guint scalefac_buffer[54]; +} frame_params; + +/* Huffman decoder bit buffer decls */ +#define HDBB_BUFSIZE 4096 + +typedef struct +{ + guint avail; + guint buf_byte_idx; + guint buf_bit_idx; +#if ENABLE_OPT_BS + guint remaining; + guint32 accumulator; +#endif + guint8 *buf; +} huffdec_bitbuf; + +/* Huffman Decoder bit buffer functions */ +static void h_setbuf (huffdec_bitbuf * bb, guint8 * buf, guint size); +static void h_reset (huffdec_bitbuf * bb); + +#if ENABLE_OPT_BS +static inline guint32 h_get1bit (huffdec_bitbuf * bb); +static inline void h_flushbits (huffdec_bitbuf * bb, guint N); +#else +#define h_get1bit(bb) (h_getbits ((bb), 1)) +#define h_flushbits(bb,bits) (h_getbits ((bb), (bits))) +#endif + +/* read N bits from the bit stream */ +static inline guint32 h_getbits (huffdec_bitbuf * bb, guint N); + +static void h_rewindNbits (huffdec_bitbuf * bb, guint N); + +/* Return the current bit stream position (in bits) */ +#if ENABLE_OPT_BS +#define h_sstell(bb) ((bb->buf_byte_idx * 8) - bb->buf_bit_idx) +#else +#define h_sstell(bb) ((bb->buf_byte_idx * 8) + (BS_ACUM_SIZE - bb->buf_bit_idx)) +#endif + +#if ENABLE_OPT_BS +/* This optimizazion assumes that N will be lesser than 32 */ +static inline guint32 +h_getbits (huffdec_bitbuf * bb, guint N) +{ + guint32 val = 0; + static const guint32 h_mask_table[] = { + 0x00000000, + 0x00000001, 0x00000003, 0x00000007, 0x0000000f, + 0x0000001f, 0x0000003f, 0x0000007f, 0x000000ff, + 0x000001ff, 0x000003ff, 0x000007ff, 0x00000fff, + 0x00001fff, 0x00003fff, 0x00007fff, 0x0000ffff, + 0x0001ffff, 0x0003ffff, 0x0007ffff, 0x000fffff, + 0x001fffff, 0x003fffff, 0x007fffff, 0x00ffffff, + 0x01ffffff, 0x03ffffff, 0x07ffffff, 0x0fffffff, + 0x1fffffff, 0x3fffffff, 0x7fffffff, 0xffffffff + }; + + if (N == 0) + return 0; + + /* Most common case will be when accumulator has enough bits */ + if (G_LIKELY (N <= bb->buf_bit_idx)) { + /* first reduce buf_bit_idx by the number of bits that are taken */ + bb->buf_bit_idx -= N; + /* Displace to right and mask to extract the desired number of bits */ + val = (bb->accumulator >> bb->buf_bit_idx) & h_mask_table[N]; + return (val); + } + + /* Next cases will be when there's not enough data on the accumulator + and there's atleast 4 bytes in the */ + if (bb->remaining >= 4) { + /* First take all remaining bits */ + if (bb->buf_bit_idx > 0) + val = bb->accumulator & h_mask_table[bb->buf_bit_idx]; + /* calculate how many more bits are required */ + N -= bb->buf_bit_idx; + /* reload the accumulator */ + bb->buf_bit_idx = 32 - N; /* subtract the remaining required bits */ + bb->remaining -= 4; + + /* we need reverse the byte order */ + bb->accumulator = BYTESWAP32(bb->buf + bb->buf_byte_idx); + bb->buf_byte_idx += 4; + + val <<= N; + val |= (bb->accumulator >> bb->buf_bit_idx) & h_mask_table[N]; + return (val); + } + + /* Next case when remains less that one word on the buffer */ + if (bb->remaining > 0) { + /* First take all remaining bits */ + if (bb->buf_bit_idx > 0) + val = bb->accumulator & h_mask_table[bb->buf_bit_idx]; + /* calculate how many more bits are required */ + N -= bb->buf_bit_idx; + /* reload the accumulator */ + bb->buf_bit_idx = (bb->remaining * 8) - N; /* subtract the remaining required bits */ + + bb->accumulator = 0; + /* load remaining bytes into the accumulator in the right order */ + for (; bb->remaining > 0; bb->remaining--) { + bb->accumulator <<= 8; + bb->accumulator |= (guint32) bb->buf[bb->buf_byte_idx++]; + } + val <<= N; + val |= (bb->accumulator >> bb->buf_bit_idx) & h_mask_table[N]; + return (val); + } + + return 0; +} + +static inline guint32 +h_get1bit (huffdec_bitbuf * bb) +{ + guint32 val = 0; + + /* Most common case will be when accumulator has enough bits */ + if (G_LIKELY (bb->buf_bit_idx > 0)) { + /* first reduce buf_bit_idx by the number of bits that are taken */ + bb->buf_bit_idx--; + /* Displace to right and mask to extract the desired number of bits */ + val = (bb->accumulator >> bb->buf_bit_idx) & 0x1; + return (val); + } + + /* Next cases will be when there's not enough data on the accumulator + and there's atleast 4 bytes in the */ + if (bb->remaining >= 4) { + /* reload the accumulator */ + bb->buf_bit_idx = 31; /* subtract 1 bit */ + bb->remaining -= 4; + + /* we need reverse the byte order */ + bb->accumulator = BYTESWAP32(bb->buf + bb->buf_byte_idx); + bb->buf_byte_idx += 4; + + val = (bb->accumulator >> bb->buf_bit_idx) & 0x1; + return (val); + } + + /* Next case when remains less that one word on the buffer */ + if (bb->remaining > 0) { + /* reload the accumulator */ + bb->buf_bit_idx = (bb->remaining * 8) - 1; /* subtract 1 bit */ + + bb->accumulator = 0; + /* load remaining bytes into the accumulator in the right order */ + for (; bb->remaining > 0; bb->remaining--) { + bb->accumulator <<= 8; + bb->accumulator |= (guint32) bb->buf[bb->buf_byte_idx++]; + } + + val = (bb->accumulator >> bb->buf_bit_idx) & 0x1; + return (val); + } + + return 0; +} + +static inline void +h_flushbits (huffdec_bitbuf * bb, guint N) +{ + guint bits; + guint bytes; + + if (N < 32) { + bits = N; + } else { + N -= bb->buf_bit_idx; + bytes = N >> 3; + bits = N & 0x7; + bb->buf_byte_idx += bytes; + bb->remaining -= bytes; + bb->buf_bit_idx = 0; + + if (bb->remaining >= 4) { + /* reload the accumulator */ + bb->buf_bit_idx = 32; /* subtract 1 bit */ + bb->remaining -= 4; + + /* we need reverse the byte order */ + bb->accumulator = BYTESWAP32(bb->buf + bb->buf_byte_idx); + bb->buf_byte_idx += 4; + } else if (bb->remaining > 0) { + /* reload the accumulator */ + bb->buf_bit_idx = bb->remaining * 8; + + bb->accumulator = 0; + /* load remaining bytes into the accumulator in the right order */ + for (; bb->remaining > 0; bb->remaining--) { + bb->accumulator <<= 8; + bb->accumulator |= (guint32) bb->buf[bb->buf_byte_idx++]; + } + } + } + + if (bits) + h_getbits (bb, bits); +} + +#else +static inline guint32 +h_getbits (huffdec_bitbuf * bb, guint N) +{ + guint32 val = 0; + guint j = N; + guint k, tmp; + guint mask; + + while (j > 0) { + if (!bb->buf_bit_idx) { + bb->buf_bit_idx = 8; + bb->buf_byte_idx++; + if (bb->buf_byte_idx > bb->avail) { + return 0; + } + } + k = MIN (j, bb->buf_bit_idx); + + mask = (1 << (bb->buf_bit_idx)) - 1; + tmp = bb->buf[bb->buf_byte_idx % HDBB_BUFSIZE] & mask; + tmp = tmp >> (bb->buf_bit_idx - k); + val |= tmp << (j - k); + bb->buf_bit_idx -= k; + j -= k; + } + return (val); +} +#endif + +typedef struct mp3cimpl_info mp3cimpl_info; + +struct mp3cimpl_info +{ + huffdec_bitbuf bb; /* huffman decoder bit buffer */ + guint8 hb_buf[HDBB_BUFSIZE]; /* Huffman decoder work buffer */ + guint main_data_end; /* Number of bytes in the bit reservoir at the + * end of the last frame */ + + /* Hybrid */ + gfloat prevblck[2][SBLIMIT][SSLIMIT]; + + /* scale data */ + guint scalefac_buffer[54]; +}; + +typedef short PCM[2][SSLIMIT][SBLIMIT]; +typedef unsigned int SAM[2][3][SBLIMIT]; +typedef float FRA[2][3][SBLIMIT]; + +/* Size of the temp buffer for output samples, max 2 channels * + * sub-bands * samples-per-sub-band * 2 buffers + */ +#define SAMPLE_BUF_SIZE (4 * 2 * SBLIMIT * SSLIMIT) + +struct mp3tl +{ + void * alloc_memory; + gboolean need_sync; + gboolean need_header; + gboolean at_eos; + gboolean lost_sync; + + /* Bit stream to read the data from */ + Bit_stream_struc *bs; + + /* Layer number we're decoding, 0 if we don't know yet */ + guint8 stream_layer; + + guint64 frame_num; + + /* Bits consumed from the stream so far */ + gint64 bits_used; + + /* Number of samples output so far */ + guint32 sample_frames; + + /* Total number of errors encountered */ + guint error_count; + + /* Sample size configure for. Always 16-bits, for now */ + guint sample_size; + + /* Frame decoding info */ + frame_params fr_ps; + + /* Number of granules in this frame (1 or 2) */ + guint n_granules; + /* CRC value read from the mpeg data */ + guint old_crc; + + __CACHE_LINE_DECL_ALIGN(PCM pcm_sample); + __CACHE_LINE_DECL_ALIGN(SAM sample); + __CACHE_LINE_DECL_ALIGN(FRA fraction); + + /* Output samples circular buffer and read and write ptrs */ + gint16 *sample_buf; + guint32 sample_w; + + char *reason; /* Reason why an error was returned, if known */ + + mp3cimpl_info c_impl; + + /*Free format bitrate */ + guint32 free_bitrate; + + /*Used for one time calculation of free bitrate */ + gboolean free_first; +}; + +/* Sample rates table, index by MPEG version, samplerate index */ +static const gint s_rates[4][4] = { + {11025, 12000, 8000, 0}, /* MPEG_VERSION_2_5 */ + {0, 0, 0, 0}, /* Invalid MPEG version */ + {22050, 24000, 16000, 0}, /* MPEG_VERSION_2 */ + {44100, 48000, 32000, 0} /* MPEG_VERSION_1 */ +}; + +/* MPEG version 1 bitrates. indexed by layer, bitrate index */ +static const gint bitrates_v1[3][15] = { + {0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448}, + {0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384}, + {0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320} +}; + +/* MPEG version 2 (LSF) and 2.5 bitrates. indexed by layer, bitrate index */ +static const gint bitrates_v2[3][15] = { + {0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256}, + {0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160}, + {0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160} +}; + +typedef struct +{ + gint sub_bands; /* Number of sub-bands in the frame */ + + const al_table alloc; /* The quantisation table itself */ +} bitalloc_table; + +/* There are 5 allocation tables available based on + * the bitrate and sample rate */ +static const bitalloc_table ba_tables[] = +{ + { + /* alloc_0 */ + 27, + { + { + { 0, 4, 0, 0 }, { 3, 5, 1, 0 }, { 7, 3, 3, 2 }, { 15, 4, 3, 4 }, + { 31, 5, 3, 5 }, { 63, 6, 3, 6 }, { 127, 7, 3, 7 }, { 255, 8, 3, 8 }, + { 511, 9, 3, 9 }, { 1023, 10, 3, 10 }, { 2047, 11, 3, 11 }, + { 4095, 12, 3, 12 }, { 8191, 13, 3, 13 }, { 16383, 14, 3, 14 }, + { 32767, 15, 3, 15 }, { 65535, 16, 3, 16 }, + }, { + { 0, 4, 0, 0 }, { 3, 5, 1, 0 }, { 7, 3, 3, 2 }, { 15, 4, 3, 4 }, + { 31, 5, 3, 5 }, { 63, 6, 3, 6 }, { 127, 7, 3, 7 }, { 255, 8, 3, 8 }, + { 511, 9, 3, 9 }, { 1023, 10, 3, 10 }, { 2047, 11, 3, 11 }, + { 4095, 12, 3, 12 }, { 8191, 13, 3, 13 }, { 16383, 14, 3, 14 }, + { 32767, 15, 3, 15 }, { 65535, 16, 3, 16 } + }, { + { 0, 4, 0, 0 }, { 3, 5, 1, 0 }, { 7, 3, 3, 2 }, { 15, 4, 3, 4 }, + { 31, 5, 3, 5 }, { 63, 6, 3, 6 }, { 127, 7, 3, 7 }, { 255, 8, 3, 8 }, + { 511, 9, 3, 9 }, { 1023, 10, 3, 10 }, { 2047, 11, 3, 11 }, + { 4095, 12, 3, 12 }, { 8191, 13, 3, 13 }, { 16383, 14, 3, 14 }, + { 32767, 15, 3, 15 }, { 65535, 16, 3, 16 } + }, { + { 0, 4, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 7, 3, 3, 2 }, + { 9, 10, 1, 3 }, { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 63, 6, 3, 6 }, + { 127, 7, 3, 7 }, { 255, 8, 3, 8 }, { 511, 9, 3, 9 }, + { 1023, 10, 3, 10 }, { 2047, 11, 3, 11 }, { 4095, 12, 3, 12 }, + { 8191, 13, 3, 13 }, { 65535, 16, 3, 16 } + }, { + { 0, 4, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 7, 3, 3, 2 }, + { 9, 10, 1, 3 }, { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 63, 6, 3, 6 }, + { 127, 7, 3, 7 }, { 255, 8, 3, 8 }, { 511, 9, 3, 9 }, + { 1023, 10, 3, 10 }, { 2047, 11, 3, 11 }, { 4095, 12, 3, 12 }, + { 8191, 13, 3, 13 }, { 65535, 16, 3, 16 } + }, { + { 0, 4, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 7, 3, 3, 2 }, + { 9, 10, 1, 3 }, { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 63, 6, 3, 6 }, + { 127, 7, 3, 7 }, { 255, 8, 3, 8 }, { 511, 9, 3, 9 }, + { 1023, 10, 3, 10 }, { 2047, 11, 3, 11 }, { 4095, 12, 3, 12 }, + { 8191, 13, 3, 13 }, { 65535, 16, 3, 16 } + }, { + { 0, 4, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 7, 3, 3, 2 }, + { 9, 10, 1, 3 }, { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 63, 6, 3, 6 }, + { 127, 7, 3, 7 }, { 255, 8, 3, 8 }, { 511, 9, 3, 9 }, + { 1023, 10, 3, 10 }, { 2047, 11, 3, 11 }, { 4095, 12, 3, 12 }, + { 8191, 13, 3, 13 }, { 65535, 16, 3, 16 } + }, { + { 0, 4, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 7, 3, 3, 2 }, + { 9, 10, 1, 3 }, { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 63, 6, 3, 6 }, + { 127, 7, 3, 7 }, { 255, 8, 3, 8 }, { 511, 9, 3, 9 }, + { 1023, 10, 3, 10 }, { 2047, 11, 3, 11 }, { 4095, 12, 3, 12 }, + { 8191, 13, 3, 13 }, { 65535, 16, 3, 16 } + }, { + { 0, 4, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 7, 3, 3, 2 }, + { 9, 10, 1, 3 }, { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 63, 6, 3, 6 }, + { 127, 7, 3, 7 }, { 255, 8, 3, 8 }, { 511, 9, 3, 9 }, + { 1023, 10, 3, 10 }, { 2047, 11, 3, 11 }, { 4095, 12, 3, 12 }, + { 8191, 13, 3, 13 }, { 65535, 16, 3, 16 } + }, { + { 0, 4, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 7, 3, 3, 2 }, + { 9, 10, 1, 3 }, { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 63, 6, 3, 6 }, + { 127, 7, 3, 7 }, { 255, 8, 3, 8 }, { 511, 9, 3, 9 }, + { 1023, 10, 3, 10 }, { 2047, 11, 3, 11 }, { 4095, 12, 3, 12 }, + { 8191, 13, 3, 13 }, { 65535, 16, 3, 16 } + }, { + { 0, 4, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 7, 3, 3, 2 }, + { 9, 10, 1, 3 }, { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 63, 6, 3, 6 }, + { 127, 7, 3, 7 }, { 255, 8, 3, 8 }, { 511, 9, 3, 9 }, + { 1023, 10, 3, 10 }, { 2047, 11, 3, 11 }, { 4095, 12, 3, 12 }, + { 8191, 13, 3, 13 }, { 65535, 16, 3, 16 } + }, { + { 0, 3, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 7, 3, 3, 2 }, + { 9, 10, 1, 3 }, { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 65535, 16, 3, 16 } + }, { + { 0, 3, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 7, 3, 3, 2 }, + { 9, 10, 1, 3 }, { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, + { 65535, 16, 3, 16 } + }, { + { 0, 3, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 7, 3, 3, 2 }, + { 9, 10, 1, 3 }, { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, + { 65535, 16, 3, 16 } + }, { + { 0, 3, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 7, 3, 3, 2 }, + { 9, 10, 1, 3 }, { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, + { 65535, 16, 3, 16 } + }, { + { 0, 3, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 7, 3, 3, 2 }, + { 9, 10, 1, 3 }, { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, + { 65535, 16, 3, 16 } + }, { + { 0, 3, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 7, 3, 3, 2 }, + { 9, 10, 1, 3 }, { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, + { 65535, 16, 3, 16 } + }, { + { 0, 3, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 7, 3, 3, 2 }, + { 9, 10, 1, 3 }, { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, + { 65535, 16, 3, 16 } + }, { + { 0, 3, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 7, 3, 3, 2 }, + { 9, 10, 1, 3 }, { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 65535, 16, 3, 16 } + }, { + { 0, 3, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 7, 3, 3, 2 }, + { 9, 10, 1, 3 }, { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 65535, 16, 3, 16 } + }, { + { 0, 3, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 7, 3, 3, 2 }, + { 9, 10, 1, 3 }, { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 65535, 16, 3, 16 } + }, { + { 0, 3, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 7, 3, 3, 2 }, + { 9, 10, 1, 3 }, { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 65535, 16, 3, 16 } + }, { + { 0, 3, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 7, 3, 3, 2 }, + { 9, 10, 1, 3 }, { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 65535, 16, 3, 16 } + }, { + { 0, 2, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 65535, 16, 3, 16 } + }, { + { 0, 2, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 65535, 16, 3, 16 } + }, { + { 0, 2, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 65535, 16, 3, 16 } + }, { + { 0, 2, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 65535, 16, 3, 16 } + } + } + }, + { + /* alloc_1 */ + 30, + { + { + { 0, 4, 0, 0 }, { 3, 5, 1, 0 }, { 7, 3, 3, 2 }, { 15, 4, 3, 4 }, + { 31, 5, 3, 5 }, { 63, 6, 3, 6 }, { 127, 7, 3, 7 }, { 255, 8, 3, 8 }, + { 511, 9, 3, 9 }, { 1023, 10, 3, 10 }, { 2047, 11, 3, 11 }, + { 4095, 12, 3, 12 }, { 8191, 13, 3, 13 }, { 16383, 14, 3, 14 }, + { 32767, 15, 3, 15 }, { 65535, 16, 3, 16 } + }, { + { 0, 4, 0, 0 }, { 3, 5, 1, 0 }, { 7, 3, 3, 2 }, { 15, 4, 3, 4 }, + { 31, 5, 3, 5 }, { 63, 6, 3, 6 }, { 127, 7, 3, 7 }, { 255, 8, 3, 8 }, + { 511, 9, 3, 9 }, { 1023, 10, 3, 10 }, { 2047, 11, 3, 11 }, + { 4095, 12, 3, 12 }, { 8191, 13, 3, 13 }, { 16383, 14, 3, 14 }, + { 32767, 15, 3, 15 }, { 65535, 16, 3, 16 } + }, { + { 0, 4, 0, 0 }, { 3, 5, 1, 0 }, { 7, 3, 3, 2 }, { 15, 4, 3, 4 }, + { 31, 5, 3, 5 }, { 63, 6, 3, 6 }, { 127, 7, 3, 7 }, { 255, 8, 3, 8 }, + { 511, 9, 3, 9 }, { 1023, 10, 3, 10 }, { 2047, 11, 3, 11 }, + { 4095, 12, 3, 12 }, { 8191, 13, 3, 13 }, { 16383, 14, 3, 14 }, + { 32767, 15, 3, 15 }, { 65535, 16, 3, 16 } + }, { + { 0, 4, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 7, 3, 3, 2 }, + { 9, 10, 1, 3 }, { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 63, 6, 3, 6 }, + { 127, 7, 3, 7 }, { 255, 8, 3, 8 }, { 511, 9, 3, 9 }, + { 1023, 10, 3, 10 }, { 2047, 11, 3, 11 }, { 4095, 12, 3, 12 }, + { 8191, 13, 3, 13 }, { 65535, 16, 3, 16 } + }, { + { 0, 4, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 7, 3, 3, 2 }, + { 9, 10, 1, 3 }, { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 63, 6, 3, 6 }, + { 127, 7, 3, 7 }, { 255, 8, 3, 8 }, { 511, 9, 3, 9 }, + { 1023, 10, 3, 10 }, { 2047, 11, 3, 11 }, { 4095, 12, 3, 12 }, + { 8191, 13, 3, 13 }, { 65535, 16, 3, 16 } + }, { + { 0, 4, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 7, 3, 3, 2 }, + { 9, 10, 1, 3 }, { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 63, 6, 3, 6 }, + { 127, 7, 3, 7 }, { 255, 8, 3, 8 }, { 511, 9, 3, 9 }, + { 1023, 10, 3, 10 }, { 2047, 11, 3, 11 }, { 4095, 12, 3, 12 }, + { 8191, 13, 3, 13 }, { 65535, 16, 3, 16 } + }, { + { 0, 4, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 7, 3, 3, 2 }, + { 9, 10, 1, 3 }, { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 63, 6, 3, 6 }, + { 127, 7, 3, 7 }, { 255, 8, 3, 8 }, { 511, 9, 3, 9 }, + { 1023, 10, 3, 10 }, { 2047, 11, 3, 11 }, { 4095, 12, 3, 12 }, + { 8191, 13, 3, 13 }, { 65535, 16, 3, 16 } + }, { + { 0, 4, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 7, 3, 3, 2 }, + { 9, 10, 1, 3 }, { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 63, 6, 3, 6 }, + { 127, 7, 3, 7 }, { 255, 8, 3, 8 }, { 511, 9, 3, 9 }, + { 1023, 10, 3, 10 }, { 2047, 11, 3, 11 }, { 4095, 12, 3, 12 }, + { 8191, 13, 3, 13 }, { 65535, 16, 3, 16 } + }, { + { 0, 4, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 7, 3, 3, 2 }, + { 9, 10, 1, 3 }, { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 63, 6, 3, 6 }, + { 127, 7, 3, 7 }, { 255, 8, 3, 8 }, { 511, 9, 3, 9 }, + { 1023, 10, 3, 10 }, { 2047, 11, 3, 11 }, { 4095, 12, 3, 12 }, + { 8191, 13, 3, 13 }, { 65535, 16, 3, 16 } + }, { + { 0, 4, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 7, 3, 3, 2 }, + { 9, 10, 1, 3 }, { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 63, 6, 3, 6 }, + { 127, 7, 3, 7 }, { 255, 8, 3, 8 }, { 511, 9, 3, 9 }, + { 1023, 10, 3, 10 }, { 2047, 11, 3, 11 }, { 4095, 12, 3, 12 }, + { 8191, 13, 3, 13 }, { 65535, 16, 3, 16 } + }, { + { 0, 4, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 7, 3, 3, 2 }, + { 9, 10, 1, 3 }, { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 63, 6, 3, 6 }, + { 127, 7, 3, 7 }, { 255, 8, 3, 8 }, { 511, 9, 3, 9 }, + { 1023, 10, 3, 10 }, { 2047, 11, 3, 11 }, { 4095, 12, 3, 12 }, + { 8191, 13, 3, 13 }, { 65535, 16, 3, 16 } + }, { + { 0, 3, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 7, 3, 3, 2 }, + { 9, 10, 1, 3 }, { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 65535, 16, 3, 16 } + }, { + { 0, 3, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 7, 3, 3, 2 }, + { 9, 10, 1, 3 }, { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 65535, 16, 3, 16 } + }, { + { 0, 3, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 7, 3, 3, 2 }, + { 9, 10, 1, 3 }, { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 65535, 16, 3, 16 } + }, { + { 0, 3, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 7, 3, 3, 2 }, + { 9, 10, 1, 3 }, { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 65535, 16, 3, 16 } + }, { + { 0, 3, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 7, 3, 3, 2 }, + { 9, 10, 1, 3 }, { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 65535, 16, 3, 16 } + }, { + { 0, 3, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 7, 3, 3, 2 }, + { 9, 10, 1, 3 }, { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 65535, 16, 3, 16 } + }, { + { 0, 3, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 7, 3, 3, 2 }, + { 9, 10, 1, 3 }, { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 65535, 16, 3, 16 } + }, { + { 0, 3, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 7, 3, 3, 2 }, + { 9, 10, 1, 3 }, { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 65535, 16, 3, 16 } + }, { + { 0, 3, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 7, 3, 3, 2 }, + { 9, 10, 1, 3 }, { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 65535, 16, 3, 16 } + }, { + { 0, 3, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 7, 3, 3, 2 }, + { 9, 10, 1, 3 }, { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 65535, 16, 3, 16 } + }, { + { 0, 3, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 7, 3, 3, 2 }, + { 9, 10, 1, 3 }, { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 65535, 16, 3, 16 } + }, { + { 0, 3, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 7, 3, 3, 2 }, + { 9, 10, 1, 3 }, { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 65535, 16, 3, 16 } + }, { + { 0, 2, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 65535, 16, 3, 16 } + }, { + { 0, 2, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 65535, 16, 3, 16 } + }, { + { 0, 2, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 65535, 16, 3, 16 } + }, { + { 0, 2, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 65535, 16, 3, 16 } + }, { + { 0, 2, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 65535, 16, 3, 16 } + }, { + { 0, 2, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 65535, 16, 3, 16 } + }, { + { 0, 2, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 65535, 16, 3, 16 } + } + } + }, + { + /* alloc_2 */ + 8, + { + { + { 0, 4, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 9, 10, 1, 3 }, + { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 63, 6, 3, 6 }, { 127, 7, 3, 7 }, + { 255, 8, 3, 8 }, { 511, 9, 3, 9 }, { 1023, 10, 3, 10 }, + { 2047, 11, 3, 11 }, { 4095, 12, 3, 12 }, { 8191, 13, 3, 13 }, + { 16383, 14, 3, 14 }, { 32767, 15, 3, 15 } + }, { + { 0, 4, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 9, 10, 1, 3 }, + { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 63, 6, 3, 6 }, { 127, 7, 3, 7 }, + { 255, 8, 3, 8 }, { 511, 9, 3, 9 }, { 1023, 10, 3, 10 }, + { 2047, 11, 3, 11 }, { 4095, 12, 3, 12 }, { 8191, 13, 3, 13 }, + { 16383, 14, 3, 14 }, { 32767, 15, 3, 15 } + }, { + { 0, 3, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 9, 10, 1, 3 }, + { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 63, 6, 3, 6 }, { 127, 7, 3, 7 } + }, { + { 0, 3, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 9, 10, 1, 3 }, + { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 63, 6, 3, 6 }, { 127, 7, 3, 7 } + }, { + { 0, 3, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 9, 10, 1, 3 }, + { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 63, 6, 3, 6 }, { 127, 7, 3, 7 } + }, { + { 0, 3, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 9, 10, 1, 3 }, + { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 63, 6, 3, 6 }, { 127, 7, 3, 7 } + }, { + { 0, 3, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 9, 10, 1, 3 }, + { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 63, 6, 3, 6 }, { 127, 7, 3, 7 } + }, { + { 0, 3, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 9, 10, 1, 3 }, + { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 63, 6, 3, 6 }, { 127, 7, 3, 7 } + } + } + }, + { + /* alloc_3 */ + 12, + { + { + { 0, 4, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 9, 10, 1, 3 }, + { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 63, 6, 3, 6 }, { 127, 7, 3, 7 }, + { 255, 8, 3, 8 }, { 511, 9, 3, 9 }, { 1023, 10, 3, 10 }, + { 2047, 11, 3, 11 }, { 4095, 12, 3, 12 }, { 8191, 13, 3, 13 }, + { 16383, 14, 3, 14 }, { 32767, 15, 3, 15 } + }, { + { 0, 4, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 9, 10, 1, 3 }, + { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 63, 6, 3, 6 }, { 127, 7, 3, 7 }, + { 255, 8, 3, 8 }, { 511, 9, 3, 9 }, { 1023, 10, 3, 10 }, + { 2047, 11, 3, 11 }, { 4095, 12, 3, 12 }, { 8191, 13, 3, 13 }, + { 16383, 14, 3, 14 }, { 32767, 15, 3, 15 } + }, { + { 0, 3, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 9, 10, 1, 3 }, + { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 63, 6, 3, 6 }, { 127, 7, 3, 7 } + }, { + { 0, 3, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 9, 10, 1, 3 }, + { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 63, 6, 3, 6 }, { 127, 7, 3, 7 } + }, { + { 0, 3, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 9, 10, 1, 3 }, + { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 63, 6, 3, 6 }, { 127, 7, 3, 7 } + }, { + { 0, 3, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 9, 10, 1, 3 }, + { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 63, 6, 3, 6 }, { 127, 7, 3, 7 } + }, { + { 0, 3, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 9, 10, 1, 3 }, + { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 63, 6, 3, 6 }, { 127, 7, 3, 7 } + }, { + { 0, 3, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 9, 10, 1, 3 }, + { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 63, 6, 3, 6 }, { 127, 7, 3, 7 } + }, { + { 0, 3, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 9, 10, 1, 3 }, + { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 63, 6, 3, 6 }, { 127, 7, 3, 7 } + }, { + { 0, 3, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 9, 10, 1, 3 }, + { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 63, 6, 3, 6 }, { 127, 7, 3, 7 } + }, { + { 0, 3, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 9, 10, 1, 3 }, + { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 63, 6, 3, 6 }, { 127, 7, 3, 7 } + }, { + { 0, 3, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 9, 10, 1, 3 }, + { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 63, 6, 3, 6 }, { 127, 7, 3, 7 } + } + } +}, +{ + /* alloc_4 */ + 30, + { + { + { 0, 4, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 7, 3, 3, 2 }, + { 9, 10, 1, 3 }, { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 63, 6, 3, 6 }, + { 127, 7, 3, 7 }, { 255, 8, 3, 8 }, { 511, 9, 3, 9 }, + { 1023, 10, 3, 10 }, { 2047, 11, 3, 11 }, { 4095, 12, 3, 12 }, + { 8191, 13, 3, 13 }, { 16383, 14, 3, 14 } + }, { + { 0, 4, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 7, 3, 3, 2 }, + { 9, 10, 1, 3 }, { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 63, 6, 3, 6 }, + { 127, 7, 3, 7 }, { 255, 8, 3, 8 }, { 511, 9, 3, 9 }, + { 1023, 10, 3, 10 }, { 2047, 11, 3, 11 }, { 4095, 12, 3, 12 }, + { 8191, 13, 3, 13 }, { 16383, 14, 3, 14 } + }, { + { 0, 4, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 7, 3, 3, 2 }, + { 9, 10, 1, 3 }, { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 63, 6, 3, 6 }, + { 127, 7, 3, 7 }, { 255, 8, 3, 8 }, { 511, 9, 3, 9 }, + { 1023, 10, 3, 10 }, { 2047, 11, 3, 11 }, { 4095, 12, 3, 12 }, + { 8191, 13, 3, 13 }, { 16383, 14, 3, 14 } + }, { + { 0, 4, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 7, 3, 3, 2 }, + { 9, 10, 1, 3 }, { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 63, 6, 3, 6 }, + { 127, 7, 3, 7 }, { 255, 8, 3, 8 }, { 511, 9, 3, 9 }, + { 1023, 10, 3, 10 }, { 2047, 11, 3, 11 }, { 4095, 12, 3, 12 }, + { 8191, 13, 3, 13 }, { 16383, 14, 3, 14 } + }, { + { 0, 3, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 9, 10, 1, 3 }, + { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 63, 6, 3, 6 }, { 127, 7, 3, 7 } + }, { + { 0, 3, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 9, 10, 1, 3 }, + { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 63, 6, 3, 6 }, { 127, 7, 3, 7 } + }, { + { 0, 3, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 9, 10, 1, 3 }, + { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 63, 6, 3, 6 }, { 127, 7, 3, 7 } + }, { + { 0, 3, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 9, 10, 1, 3 }, + { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 63, 6, 3, 6 }, { 127, 7, 3, 7 } + }, { + { 0, 3, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 9, 10, 1, 3 }, + { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 63, 6, 3, 6 }, { 127, 7, 3, 7 } + }, { + { 0, 3, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 9, 10, 1, 3 }, + { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 63, 6, 3, 6 }, { 127, 7, 3, 7 } + }, { + { 0, 3, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 9, 10, 1, 3 }, + { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 63, 6, 3, 6 }, { 127, 7, 3, 7 } + }, { + { 0, 2, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 9, 10, 1, 3 } + }, { + { 0, 2, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 9, 10, 1, 3 } + }, { + { 0, 2, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 9, 10, 1, 3 } + }, { + { 0, 2, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 9, 10, 1, 3 } + }, { + { 0, 2, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 9, 10, 1, 3 } + }, { + { 0, 2, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 9, 10, 1, 3 } + }, { + { 0, 2, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 9, 10, 1, 3 } + }, { + { 0, 2, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 9, 10, 1, 3 } + }, { + { 0, 2, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 9, 10, 1, 3 } + }, { + { 0, 2, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 9, 10, 1, 3 } + }, { + { 0, 2, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 9, 10, 1, 3 } + }, { + { 0, 2, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 9, 10, 1, 3 } + }, { + { 0, 2, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 9, 10, 1, 3 } + }, { + { 0, 2, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 9, 10, 1, 3 } + }, { + { 0, 2, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 9, 10, 1, 3 } + }, { + { 0, 2, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 9, 10, 1, 3 } + }, { + { 0, 2, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 9, 10, 1, 3 } + }, { + { 0, 2, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 9, 10, 1, 3 } + }, { + { 0, 2, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 9, 10, 1, 3 } + } + } + } +}; + +struct huffcodetab +{ + guint treelen; /* length of decoder tree */ + gint xlen; /* max. x-index+ */ + gint ylen; /* max. y-index+ */ + guint linbits; /* number of linbits */ + gboolean quad_table; /* TRUE for quadruple tables (32 & 33) */ + const guchar (*val)[2]; /* decoder tree data */ +}; + +/* Arrays of the table constants */ +static const guchar huffbits_1[7][2] = { + {0x02, 0x01}, {0x00, 0x00}, {0x02, 0x01}, {0x00, 0x10}, + {0x02, 0x01}, {0x00, 0x01}, {0x00, 0x11} +}; + +static const guchar huffbits_2[17][2] = { + {0x02, 0x01}, {0x00, 0x00}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0x10}, {0x00, 0x01}, {0x02, 0x01}, {0x00, 0x11}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x20}, {0x00, 0x21}, + {0x02, 0x01}, {0x00, 0x12}, {0x02, 0x01}, {0x00, 0x02}, + {0x00, 0x22} +}; + +static const guchar huffbits_3[17][2] = { + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x00}, {0x00, 0x01}, + {0x02, 0x01}, {0x00, 0x11}, {0x02, 0x01}, {0x00, 0x10}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x20}, {0x00, 0x21}, + {0x02, 0x01}, {0x00, 0x12}, {0x02, 0x01}, {0x00, 0x02}, + {0x00, 0x22} +}; + +static const guchar huffbits_5[31][2] = { + {0x02, 0x01}, {0x00, 0x00}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0x10}, {0x00, 0x01}, {0x02, 0x01}, {0x00, 0x11}, + {0x08, 0x01}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x20}, + {0x00, 0x02}, {0x02, 0x01}, {0x00, 0x21}, {0x00, 0x12}, + {0x08, 0x01}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x22}, + {0x00, 0x30}, {0x02, 0x01}, {0x00, 0x03}, {0x00, 0x13}, + {0x02, 0x01}, {0x00, 0x31}, {0x02, 0x01}, {0x00, 0x32}, + {0x02, 0x01}, {0x00, 0x23}, {0x00, 0x33} +}; + +static const guchar huffbits_6[31][2] = { + {0x06, 0x01}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x00}, + {0x00, 0x10}, {0x00, 0x11}, {0x06, 0x01}, {0x02, 0x01}, + {0x00, 0x01}, {0x02, 0x01}, {0x00, 0x20}, {0x00, 0x21}, + {0x06, 0x01}, {0x02, 0x01}, {0x00, 0x12}, {0x02, 0x01}, + {0x00, 0x02}, {0x00, 0x22}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0x31}, {0x00, 0x13}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0x30}, {0x00, 0x32}, {0x02, 0x01}, {0x00, 0x23}, + {0x02, 0x01}, {0x00, 0x03}, {0x00, 0x33} +}; + +static const guchar huffbits_7[71][2] = { + {0x02, 0x01}, {0x00, 0x00}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0x10}, {0x00, 0x01}, {0x08, 0x01}, {0x02, 0x01}, + {0x00, 0x11}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x20}, + {0x00, 0x02}, {0x00, 0x21}, {0x12, 0x01}, {0x06, 0x01}, + {0x02, 0x01}, {0x00, 0x12}, {0x02, 0x01}, {0x00, 0x22}, + {0x00, 0x30}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x31}, + {0x00, 0x13}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x03}, + {0x00, 0x32}, {0x02, 0x01}, {0x00, 0x23}, {0x00, 0x04}, + {0x0a, 0x01}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x40}, + {0x00, 0x41}, {0x02, 0x01}, {0x00, 0x14}, {0x02, 0x01}, + {0x00, 0x42}, {0x00, 0x24}, {0x0c, 0x01}, {0x06, 0x01}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x33}, {0x00, 0x43}, + {0x00, 0x50}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x34}, + {0x00, 0x05}, {0x00, 0x51}, {0x06, 0x01}, {0x02, 0x01}, + {0x00, 0x15}, {0x02, 0x01}, {0x00, 0x52}, {0x00, 0x25}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x44}, {0x00, 0x35}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x53}, {0x00, 0x54}, + {0x02, 0x01}, {0x00, 0x45}, {0x00, 0x55} +}; + +static const guchar huffbits_8[71][2] = { + {0x06, 0x01}, {0x02, 0x01}, {0x00, 0x00}, {0x02, 0x01}, + {0x00, 0x10}, {0x00, 0x01}, {0x02, 0x01}, {0x00, 0x11}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x21}, {0x00, 0x12}, + {0x0e, 0x01}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x20}, + {0x00, 0x02}, {0x02, 0x01}, {0x00, 0x22}, {0x04, 0x01}, + {0x02, 0x01}, {0x00, 0x30}, {0x00, 0x03}, {0x02, 0x01}, + {0x00, 0x31}, {0x00, 0x13}, {0x0e, 0x01}, {0x08, 0x01}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x32}, {0x00, 0x23}, + {0x02, 0x01}, {0x00, 0x40}, {0x00, 0x04}, {0x02, 0x01}, + {0x00, 0x41}, {0x02, 0x01}, {0x00, 0x14}, {0x00, 0x42}, + {0x0c, 0x01}, {0x06, 0x01}, {0x02, 0x01}, {0x00, 0x24}, + {0x02, 0x01}, {0x00, 0x33}, {0x00, 0x50}, {0x04, 0x01}, + {0x02, 0x01}, {0x00, 0x43}, {0x00, 0x34}, {0x00, 0x51}, + {0x06, 0x01}, {0x02, 0x01}, {0x00, 0x15}, {0x02, 0x01}, + {0x00, 0x05}, {0x00, 0x52}, {0x06, 0x01}, {0x02, 0x01}, + {0x00, 0x25}, {0x02, 0x01}, {0x00, 0x44}, {0x00, 0x35}, + {0x02, 0x01}, {0x00, 0x53}, {0x02, 0x01}, {0x00, 0x45}, + {0x02, 0x01}, {0x00, 0x54}, {0x00, 0x55} +}; + +static const guchar huffbits_9[71][2] = { + {0x08, 0x01}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x00}, + {0x00, 0x10}, {0x02, 0x01}, {0x00, 0x01}, {0x00, 0x11}, + {0x0a, 0x01}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x20}, + {0x00, 0x21}, {0x02, 0x01}, {0x00, 0x12}, {0x02, 0x01}, + {0x00, 0x02}, {0x00, 0x22}, {0x0c, 0x01}, {0x06, 0x01}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x30}, {0x00, 0x03}, + {0x00, 0x31}, {0x02, 0x01}, {0x00, 0x13}, {0x02, 0x01}, + {0x00, 0x32}, {0x00, 0x23}, {0x0c, 0x01}, {0x04, 0x01}, + {0x02, 0x01}, {0x00, 0x41}, {0x00, 0x14}, {0x04, 0x01}, + {0x02, 0x01}, {0x00, 0x40}, {0x00, 0x33}, {0x02, 0x01}, + {0x00, 0x42}, {0x00, 0x24}, {0x0a, 0x01}, {0x06, 0x01}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x04}, {0x00, 0x50}, + {0x00, 0x43}, {0x02, 0x01}, {0x00, 0x34}, {0x00, 0x51}, + {0x08, 0x01}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x15}, + {0x00, 0x52}, {0x02, 0x01}, {0x00, 0x25}, {0x00, 0x44}, + {0x06, 0x01}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x05}, + {0x00, 0x54}, {0x00, 0x53}, {0x02, 0x01}, {0x00, 0x35}, + {0x02, 0x01}, {0x00, 0x45}, {0x00, 0x55} +}; + +static const guchar huffbits_10[127][2] = { + {0x02, 0x01}, {0x00, 0x00}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0x10}, {0x00, 0x01}, {0x0a, 0x01}, {0x02, 0x01}, + {0x00, 0x11}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x20}, + {0x00, 0x02}, {0x02, 0x01}, {0x00, 0x21}, {0x00, 0x12}, + {0x1c, 0x01}, {0x08, 0x01}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0x22}, {0x00, 0x30}, {0x02, 0x01}, {0x00, 0x31}, + {0x00, 0x13}, {0x08, 0x01}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0x03}, {0x00, 0x32}, {0x02, 0x01}, {0x00, 0x23}, + {0x00, 0x40}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x41}, + {0x00, 0x14}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x04}, + {0x00, 0x33}, {0x02, 0x01}, {0x00, 0x42}, {0x00, 0x24}, + {0x1c, 0x01}, {0x0a, 0x01}, {0x06, 0x01}, {0x04, 0x01}, + {0x02, 0x01}, {0x00, 0x50}, {0x00, 0x05}, {0x00, 0x60}, + {0x02, 0x01}, {0x00, 0x61}, {0x00, 0x16}, {0x0c, 0x01}, + {0x06, 0x01}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x43}, + {0x00, 0x34}, {0x00, 0x51}, {0x02, 0x01}, {0x00, 0x15}, + {0x02, 0x01}, {0x00, 0x52}, {0x00, 0x25}, {0x04, 0x01}, + {0x02, 0x01}, {0x00, 0x26}, {0x00, 0x36}, {0x00, 0x71}, + {0x14, 0x01}, {0x08, 0x01}, {0x02, 0x01}, {0x00, 0x17}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x44}, {0x00, 0x53}, + {0x00, 0x06}, {0x06, 0x01}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0x35}, {0x00, 0x45}, {0x00, 0x62}, {0x02, 0x01}, + {0x00, 0x70}, {0x02, 0x01}, {0x00, 0x07}, {0x00, 0x64}, + {0x0e, 0x01}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x72}, + {0x00, 0x27}, {0x06, 0x01}, {0x02, 0x01}, {0x00, 0x63}, + {0x02, 0x01}, {0x00, 0x54}, {0x00, 0x55}, {0x02, 0x01}, + {0x00, 0x46}, {0x00, 0x73}, {0x08, 0x01}, {0x04, 0x01}, + {0x02, 0x01}, {0x00, 0x37}, {0x00, 0x65}, {0x02, 0x01}, + {0x00, 0x56}, {0x00, 0x74}, {0x06, 0x01}, {0x02, 0x01}, + {0x00, 0x47}, {0x02, 0x01}, {0x00, 0x66}, {0x00, 0x75}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x57}, {0x00, 0x76}, + {0x02, 0x01}, {0x00, 0x67}, {0x00, 0x77} +}; + +static const guchar huffbits_11[127][2] = { + {0x06, 0x01}, {0x02, 0x01}, {0x00, 0x00}, {0x02, 0x01}, + {0x00, 0x10}, {0x00, 0x01}, {0x08, 0x01}, {0x02, 0x01}, + {0x00, 0x11}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x20}, + {0x00, 0x02}, {0x00, 0x12}, {0x18, 0x01}, {0x08, 0x01}, + {0x02, 0x01}, {0x00, 0x21}, {0x02, 0x01}, {0x00, 0x22}, + {0x02, 0x01}, {0x00, 0x30}, {0x00, 0x03}, {0x04, 0x01}, + {0x02, 0x01}, {0x00, 0x31}, {0x00, 0x13}, {0x04, 0x01}, + {0x02, 0x01}, {0x00, 0x32}, {0x00, 0x23}, {0x04, 0x01}, + {0x02, 0x01}, {0x00, 0x40}, {0x00, 0x04}, {0x02, 0x01}, + {0x00, 0x41}, {0x00, 0x14}, {0x1e, 0x01}, {0x10, 0x01}, + {0x0a, 0x01}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x42}, + {0x00, 0x24}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x33}, + {0x00, 0x43}, {0x00, 0x50}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0x34}, {0x00, 0x51}, {0x00, 0x61}, {0x06, 0x01}, + {0x02, 0x01}, {0x00, 0x16}, {0x02, 0x01}, {0x00, 0x06}, + {0x00, 0x26}, {0x02, 0x01}, {0x00, 0x62}, {0x02, 0x01}, + {0x00, 0x15}, {0x02, 0x01}, {0x00, 0x05}, {0x00, 0x52}, + {0x10, 0x01}, {0x0a, 0x01}, {0x06, 0x01}, {0x04, 0x01}, + {0x02, 0x01}, {0x00, 0x25}, {0x00, 0x44}, {0x00, 0x60}, + {0x02, 0x01}, {0x00, 0x63}, {0x00, 0x36}, {0x04, 0x01}, + {0x02, 0x01}, {0x00, 0x70}, {0x00, 0x17}, {0x00, 0x71}, + {0x10, 0x01}, {0x06, 0x01}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0x07}, {0x00, 0x64}, {0x00, 0x72}, {0x02, 0x01}, + {0x00, 0x27}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x53}, + {0x00, 0x35}, {0x02, 0x01}, {0x00, 0x54}, {0x00, 0x45}, + {0x0a, 0x01}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x46}, + {0x00, 0x73}, {0x02, 0x01}, {0x00, 0x37}, {0x02, 0x01}, + {0x00, 0x65}, {0x00, 0x56}, {0x0a, 0x01}, {0x06, 0x01}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x55}, {0x00, 0x57}, + {0x00, 0x74}, {0x02, 0x01}, {0x00, 0x47}, {0x00, 0x66}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x75}, {0x00, 0x76}, + {0x02, 0x01}, {0x00, 0x67}, {0x00, 0x77} +}; + +static const guchar huffbits_12[127][2] = { + {0x0c, 0x01}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x10}, + {0x00, 0x01}, {0x02, 0x01}, {0x00, 0x11}, {0x02, 0x01}, + {0x00, 0x00}, {0x02, 0x01}, {0x00, 0x20}, {0x00, 0x02}, + {0x10, 0x01}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x21}, + {0x00, 0x12}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x22}, + {0x00, 0x31}, {0x02, 0x01}, {0x00, 0x13}, {0x02, 0x01}, + {0x00, 0x30}, {0x02, 0x01}, {0x00, 0x03}, {0x00, 0x40}, + {0x1a, 0x01}, {0x08, 0x01}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0x32}, {0x00, 0x23}, {0x02, 0x01}, {0x00, 0x41}, + {0x00, 0x33}, {0x0a, 0x01}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0x14}, {0x00, 0x42}, {0x02, 0x01}, {0x00, 0x24}, + {0x02, 0x01}, {0x00, 0x04}, {0x00, 0x50}, {0x04, 0x01}, + {0x02, 0x01}, {0x00, 0x43}, {0x00, 0x34}, {0x02, 0x01}, + {0x00, 0x51}, {0x00, 0x15}, {0x1c, 0x01}, {0x0e, 0x01}, + {0x08, 0x01}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x52}, + {0x00, 0x25}, {0x02, 0x01}, {0x00, 0x53}, {0x00, 0x35}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x60}, {0x00, 0x16}, + {0x00, 0x61}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x62}, + {0x00, 0x26}, {0x06, 0x01}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0x05}, {0x00, 0x06}, {0x00, 0x44}, {0x02, 0x01}, + {0x00, 0x54}, {0x00, 0x45}, {0x12, 0x01}, {0x0a, 0x01}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x63}, {0x00, 0x36}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x70}, {0x00, 0x07}, + {0x00, 0x71}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x17}, + {0x00, 0x64}, {0x02, 0x01}, {0x00, 0x46}, {0x00, 0x72}, + {0x0a, 0x01}, {0x06, 0x01}, {0x02, 0x01}, {0x00, 0x27}, + {0x02, 0x01}, {0x00, 0x55}, {0x00, 0x73}, {0x02, 0x01}, + {0x00, 0x37}, {0x00, 0x56}, {0x08, 0x01}, {0x04, 0x01}, + {0x02, 0x01}, {0x00, 0x65}, {0x00, 0x74}, {0x02, 0x01}, + {0x00, 0x47}, {0x00, 0x66}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0x75}, {0x00, 0x57}, {0x02, 0x01}, {0x00, 0x76}, + {0x02, 0x01}, {0x00, 0x67}, {0x00, 0x77} +}; + +static const guchar huffbits_13[511][2] = { + {0x02, 0x01}, {0x00, 0x00}, {0x06, 0x01}, {0x02, 0x01}, + {0x00, 0x10}, {0x02, 0x01}, {0x00, 0x01}, {0x00, 0x11}, + {0x1c, 0x01}, {0x08, 0x01}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0x20}, {0x00, 0x02}, {0x02, 0x01}, {0x00, 0x21}, + {0x00, 0x12}, {0x08, 0x01}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0x22}, {0x00, 0x30}, {0x02, 0x01}, {0x00, 0x03}, + {0x00, 0x31}, {0x06, 0x01}, {0x02, 0x01}, {0x00, 0x13}, + {0x02, 0x01}, {0x00, 0x32}, {0x00, 0x23}, {0x04, 0x01}, + {0x02, 0x01}, {0x00, 0x40}, {0x00, 0x04}, {0x00, 0x41}, + {0x46, 0x01}, {0x1c, 0x01}, {0x0e, 0x01}, {0x06, 0x01}, + {0x02, 0x01}, {0x00, 0x14}, {0x02, 0x01}, {0x00, 0x33}, + {0x00, 0x42}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x24}, + {0x00, 0x50}, {0x02, 0x01}, {0x00, 0x43}, {0x00, 0x34}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x51}, {0x00, 0x15}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x05}, {0x00, 0x52}, + {0x02, 0x01}, {0x00, 0x25}, {0x02, 0x01}, {0x00, 0x44}, + {0x00, 0x53}, {0x0e, 0x01}, {0x08, 0x01}, {0x04, 0x01}, + {0x02, 0x01}, {0x00, 0x60}, {0x00, 0x06}, {0x02, 0x01}, + {0x00, 0x61}, {0x00, 0x16}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0x80}, {0x00, 0x08}, {0x00, 0x81}, {0x10, 0x01}, + {0x08, 0x01}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x35}, + {0x00, 0x62}, {0x02, 0x01}, {0x00, 0x26}, {0x00, 0x54}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x45}, {0x00, 0x63}, + {0x02, 0x01}, {0x00, 0x36}, {0x00, 0x70}, {0x06, 0x01}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x07}, {0x00, 0x55}, + {0x00, 0x71}, {0x02, 0x01}, {0x00, 0x17}, {0x02, 0x01}, + {0x00, 0x27}, {0x00, 0x37}, {0x48, 0x01}, {0x18, 0x01}, + {0x0c, 0x01}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x18}, + {0x00, 0x82}, {0x02, 0x01}, {0x00, 0x28}, {0x04, 0x01}, + {0x02, 0x01}, {0x00, 0x64}, {0x00, 0x46}, {0x00, 0x72}, + {0x08, 0x01}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x84}, + {0x00, 0x48}, {0x02, 0x01}, {0x00, 0x90}, {0x00, 0x09}, + {0x02, 0x01}, {0x00, 0x91}, {0x00, 0x19}, {0x18, 0x01}, + {0x0e, 0x01}, {0x08, 0x01}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0x73}, {0x00, 0x65}, {0x02, 0x01}, {0x00, 0x56}, + {0x00, 0x74}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x47}, + {0x00, 0x66}, {0x00, 0x83}, {0x06, 0x01}, {0x02, 0x01}, + {0x00, 0x38}, {0x02, 0x01}, {0x00, 0x75}, {0x00, 0x57}, + {0x02, 0x01}, {0x00, 0x92}, {0x00, 0x29}, {0x0e, 0x01}, + {0x08, 0x01}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x67}, + {0x00, 0x85}, {0x02, 0x01}, {0x00, 0x58}, {0x00, 0x39}, + {0x02, 0x01}, {0x00, 0x93}, {0x02, 0x01}, {0x00, 0x49}, + {0x00, 0x86}, {0x06, 0x01}, {0x02, 0x01}, {0x00, 0xa0}, + {0x02, 0x01}, {0x00, 0x68}, {0x00, 0x0a}, {0x02, 0x01}, + {0x00, 0xa1}, {0x00, 0x1a}, {0x44, 0x01}, {0x18, 0x01}, + {0x0c, 0x01}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0xa2}, + {0x00, 0x2a}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x95}, + {0x00, 0x59}, {0x02, 0x01}, {0x00, 0xa3}, {0x00, 0x3a}, + {0x08, 0x01}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x4a}, + {0x00, 0x96}, {0x02, 0x01}, {0x00, 0xb0}, {0x00, 0x0b}, + {0x02, 0x01}, {0x00, 0xb1}, {0x00, 0x1b}, {0x14, 0x01}, + {0x08, 0x01}, {0x02, 0x01}, {0x00, 0xb2}, {0x04, 0x01}, + {0x02, 0x01}, {0x00, 0x76}, {0x00, 0x77}, {0x00, 0x94}, + {0x06, 0x01}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x87}, + {0x00, 0x78}, {0x00, 0xa4}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0x69}, {0x00, 0xa5}, {0x00, 0x2b}, {0x0c, 0x01}, + {0x06, 0x01}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x5a}, + {0x00, 0x88}, {0x00, 0xb3}, {0x02, 0x01}, {0x00, 0x3b}, + {0x02, 0x01}, {0x00, 0x79}, {0x00, 0xa6}, {0x06, 0x01}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x6a}, {0x00, 0xb4}, + {0x00, 0xc0}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x0c}, + {0x00, 0x98}, {0x00, 0xc1}, {0x3c, 0x01}, {0x16, 0x01}, + {0x0a, 0x01}, {0x06, 0x01}, {0x02, 0x01}, {0x00, 0x1c}, + {0x02, 0x01}, {0x00, 0x89}, {0x00, 0xb5}, {0x02, 0x01}, + {0x00, 0x5b}, {0x00, 0xc2}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0x2c}, {0x00, 0x3c}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0xb6}, {0x00, 0x6b}, {0x02, 0x01}, {0x00, 0xc4}, + {0x00, 0x4c}, {0x10, 0x01}, {0x08, 0x01}, {0x04, 0x01}, + {0x02, 0x01}, {0x00, 0xa8}, {0x00, 0x8a}, {0x02, 0x01}, + {0x00, 0xd0}, {0x00, 0x0d}, {0x02, 0x01}, {0x00, 0xd1}, + {0x02, 0x01}, {0x00, 0x4b}, {0x02, 0x01}, {0x00, 0x97}, + {0x00, 0xa7}, {0x0c, 0x01}, {0x06, 0x01}, {0x02, 0x01}, + {0x00, 0xc3}, {0x02, 0x01}, {0x00, 0x7a}, {0x00, 0x99}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0xc5}, {0x00, 0x5c}, + {0x00, 0xb7}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x1d}, + {0x00, 0xd2}, {0x02, 0x01}, {0x00, 0x2d}, {0x02, 0x01}, + {0x00, 0x7b}, {0x00, 0xd3}, {0x34, 0x01}, {0x1c, 0x01}, + {0x0c, 0x01}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x3d}, + {0x00, 0xc6}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x6c}, + {0x00, 0xa9}, {0x02, 0x01}, {0x00, 0x9a}, {0x00, 0xd4}, + {0x08, 0x01}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0xb8}, + {0x00, 0x8b}, {0x02, 0x01}, {0x00, 0x4d}, {0x00, 0xc7}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x7c}, {0x00, 0xd5}, + {0x02, 0x01}, {0x00, 0x5d}, {0x00, 0xe0}, {0x0a, 0x01}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0xe1}, {0x00, 0x1e}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x0e}, {0x00, 0x2e}, + {0x00, 0xe2}, {0x08, 0x01}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0xe3}, {0x00, 0x6d}, {0x02, 0x01}, {0x00, 0x8c}, + {0x00, 0xe4}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0xe5}, + {0x00, 0xba}, {0x00, 0xf0}, {0x26, 0x01}, {0x10, 0x01}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0xf1}, {0x00, 0x1f}, + {0x06, 0x01}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0xaa}, + {0x00, 0x9b}, {0x00, 0xb9}, {0x02, 0x01}, {0x00, 0x3e}, + {0x02, 0x01}, {0x00, 0xd6}, {0x00, 0xc8}, {0x0c, 0x01}, + {0x06, 0x01}, {0x02, 0x01}, {0x00, 0x4e}, {0x02, 0x01}, + {0x00, 0xd7}, {0x00, 0x7d}, {0x02, 0x01}, {0x00, 0xab}, + {0x02, 0x01}, {0x00, 0x5e}, {0x00, 0xc9}, {0x06, 0x01}, + {0x02, 0x01}, {0x00, 0x0f}, {0x02, 0x01}, {0x00, 0x9c}, + {0x00, 0x6e}, {0x02, 0x01}, {0x00, 0xf2}, {0x00, 0x2f}, + {0x20, 0x01}, {0x10, 0x01}, {0x06, 0x01}, {0x04, 0x01}, + {0x02, 0x01}, {0x00, 0xd8}, {0x00, 0x8d}, {0x00, 0x3f}, + {0x06, 0x01}, {0x02, 0x01}, {0x00, 0xf3}, {0x02, 0x01}, + {0x00, 0xe6}, {0x00, 0xca}, {0x02, 0x01}, {0x00, 0xf4}, + {0x00, 0x4f}, {0x08, 0x01}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0xbb}, {0x00, 0xac}, {0x02, 0x01}, {0x00, 0xe7}, + {0x00, 0xf5}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0xd9}, + {0x00, 0x9d}, {0x02, 0x01}, {0x00, 0x5f}, {0x00, 0xe8}, + {0x1e, 0x01}, {0x0c, 0x01}, {0x06, 0x01}, {0x02, 0x01}, + {0x00, 0x6f}, {0x02, 0x01}, {0x00, 0xf6}, {0x00, 0xcb}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0xbc}, {0x00, 0xad}, + {0x00, 0xda}, {0x08, 0x01}, {0x02, 0x01}, {0x00, 0xf7}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x7e}, {0x00, 0x7f}, + {0x00, 0x8e}, {0x06, 0x01}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0x9e}, {0x00, 0xae}, {0x00, 0xcc}, {0x02, 0x01}, + {0x00, 0xf8}, {0x00, 0x8f}, {0x12, 0x01}, {0x08, 0x01}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0xdb}, {0x00, 0xbd}, + {0x02, 0x01}, {0x00, 0xea}, {0x00, 0xf9}, {0x04, 0x01}, + {0x02, 0x01}, {0x00, 0x9f}, {0x00, 0xeb}, {0x02, 0x01}, + {0x00, 0xbe}, {0x02, 0x01}, {0x00, 0xcd}, {0x00, 0xfa}, + {0x0e, 0x01}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0xdd}, + {0x00, 0xec}, {0x06, 0x01}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0xe9}, {0x00, 0xaf}, {0x00, 0xdc}, {0x02, 0x01}, + {0x00, 0xce}, {0x00, 0xfb}, {0x08, 0x01}, {0x04, 0x01}, + {0x02, 0x01}, {0x00, 0xbf}, {0x00, 0xde}, {0x02, 0x01}, + {0x00, 0xcf}, {0x00, 0xee}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0xdf}, {0x00, 0xef}, {0x02, 0x01}, {0x00, 0xff}, + {0x02, 0x01}, {0x00, 0xed}, {0x02, 0x01}, {0x00, 0xfd}, + {0x02, 0x01}, {0x00, 0xfc}, {0x00, 0xfe} +}; + +static const guchar huffbits_15[511][2] = { + {0x10, 0x01}, {0x06, 0x01}, {0x02, 0x01}, {0x00, 0x00}, + {0x02, 0x01}, {0x00, 0x10}, {0x00, 0x01}, {0x02, 0x01}, + {0x00, 0x11}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x20}, + {0x00, 0x02}, {0x02, 0x01}, {0x00, 0x21}, {0x00, 0x12}, + {0x32, 0x01}, {0x10, 0x01}, {0x06, 0x01}, {0x02, 0x01}, + {0x00, 0x22}, {0x02, 0x01}, {0x00, 0x30}, {0x00, 0x31}, + {0x06, 0x01}, {0x02, 0x01}, {0x00, 0x13}, {0x02, 0x01}, + {0x00, 0x03}, {0x00, 0x40}, {0x02, 0x01}, {0x00, 0x32}, + {0x00, 0x23}, {0x0e, 0x01}, {0x06, 0x01}, {0x04, 0x01}, + {0x02, 0x01}, {0x00, 0x04}, {0x00, 0x14}, {0x00, 0x41}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x33}, {0x00, 0x42}, + {0x02, 0x01}, {0x00, 0x24}, {0x00, 0x43}, {0x0a, 0x01}, + {0x06, 0x01}, {0x02, 0x01}, {0x00, 0x34}, {0x02, 0x01}, + {0x00, 0x50}, {0x00, 0x05}, {0x02, 0x01}, {0x00, 0x51}, + {0x00, 0x15}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x52}, + {0x00, 0x25}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x44}, + {0x00, 0x53}, {0x00, 0x61}, {0x5a, 0x01}, {0x24, 0x01}, + {0x12, 0x01}, {0x0a, 0x01}, {0x06, 0x01}, {0x02, 0x01}, + {0x00, 0x35}, {0x02, 0x01}, {0x00, 0x60}, {0x00, 0x06}, + {0x02, 0x01}, {0x00, 0x16}, {0x00, 0x62}, {0x04, 0x01}, + {0x02, 0x01}, {0x00, 0x26}, {0x00, 0x54}, {0x02, 0x01}, + {0x00, 0x45}, {0x00, 0x63}, {0x0a, 0x01}, {0x06, 0x01}, + {0x02, 0x01}, {0x00, 0x36}, {0x02, 0x01}, {0x00, 0x70}, + {0x00, 0x07}, {0x02, 0x01}, {0x00, 0x71}, {0x00, 0x55}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x17}, {0x00, 0x64}, + {0x02, 0x01}, {0x00, 0x72}, {0x00, 0x27}, {0x18, 0x01}, + {0x10, 0x01}, {0x08, 0x01}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0x46}, {0x00, 0x73}, {0x02, 0x01}, {0x00, 0x37}, + {0x00, 0x65}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x56}, + {0x00, 0x80}, {0x02, 0x01}, {0x00, 0x08}, {0x00, 0x74}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x81}, {0x00, 0x18}, + {0x02, 0x01}, {0x00, 0x82}, {0x00, 0x28}, {0x10, 0x01}, + {0x08, 0x01}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x47}, + {0x00, 0x66}, {0x02, 0x01}, {0x00, 0x83}, {0x00, 0x38}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x75}, {0x00, 0x57}, + {0x02, 0x01}, {0x00, 0x84}, {0x00, 0x48}, {0x06, 0x01}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x90}, {0x00, 0x19}, + {0x00, 0x91}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x92}, + {0x00, 0x76}, {0x02, 0x01}, {0x00, 0x67}, {0x00, 0x29}, + {0x5c, 0x01}, {0x24, 0x01}, {0x12, 0x01}, {0x0a, 0x01}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x85}, {0x00, 0x58}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x09}, {0x00, 0x77}, + {0x00, 0x93}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x39}, + {0x00, 0x94}, {0x02, 0x01}, {0x00, 0x49}, {0x00, 0x86}, + {0x0a, 0x01}, {0x06, 0x01}, {0x02, 0x01}, {0x00, 0x68}, + {0x02, 0x01}, {0x00, 0xa0}, {0x00, 0x0a}, {0x02, 0x01}, + {0x00, 0xa1}, {0x00, 0x1a}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0xa2}, {0x00, 0x2a}, {0x02, 0x01}, {0x00, 0x95}, + {0x00, 0x59}, {0x1a, 0x01}, {0x0e, 0x01}, {0x06, 0x01}, + {0x02, 0x01}, {0x00, 0xa3}, {0x02, 0x01}, {0x00, 0x3a}, + {0x00, 0x87}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x78}, + {0x00, 0xa4}, {0x02, 0x01}, {0x00, 0x4a}, {0x00, 0x96}, + {0x06, 0x01}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x69}, + {0x00, 0xb0}, {0x00, 0xb1}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0x1b}, {0x00, 0xa5}, {0x00, 0xb2}, {0x0e, 0x01}, + {0x08, 0x01}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x5a}, + {0x00, 0x2b}, {0x02, 0x01}, {0x00, 0x88}, {0x00, 0x97}, + {0x02, 0x01}, {0x00, 0xb3}, {0x02, 0x01}, {0x00, 0x79}, + {0x00, 0x3b}, {0x08, 0x01}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0x6a}, {0x00, 0xb4}, {0x02, 0x01}, {0x00, 0x4b}, + {0x00, 0xc1}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x98}, + {0x00, 0x89}, {0x02, 0x01}, {0x00, 0x1c}, {0x00, 0xb5}, + {0x50, 0x01}, {0x22, 0x01}, {0x10, 0x01}, {0x06, 0x01}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x5b}, {0x00, 0x2c}, + {0x00, 0xc2}, {0x06, 0x01}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0x0b}, {0x00, 0xc0}, {0x00, 0xa6}, {0x02, 0x01}, + {0x00, 0xa7}, {0x00, 0x7a}, {0x0a, 0x01}, {0x04, 0x01}, + {0x02, 0x01}, {0x00, 0xc3}, {0x00, 0x3c}, {0x04, 0x01}, + {0x02, 0x01}, {0x00, 0x0c}, {0x00, 0x99}, {0x00, 0xb6}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x6b}, {0x00, 0xc4}, + {0x02, 0x01}, {0x00, 0x4c}, {0x00, 0xa8}, {0x14, 0x01}, + {0x0a, 0x01}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x8a}, + {0x00, 0xc5}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0xd0}, + {0x00, 0x5c}, {0x00, 0xd1}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0xb7}, {0x00, 0x7b}, {0x02, 0x01}, {0x00, 0x1d}, + {0x02, 0x01}, {0x00, 0x0d}, {0x00, 0x2d}, {0x0c, 0x01}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0xd2}, {0x00, 0xd3}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x3d}, {0x00, 0xc6}, + {0x02, 0x01}, {0x00, 0x6c}, {0x00, 0xa9}, {0x06, 0x01}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x9a}, {0x00, 0xb8}, + {0x00, 0xd4}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x8b}, + {0x00, 0x4d}, {0x02, 0x01}, {0x00, 0xc7}, {0x00, 0x7c}, + {0x44, 0x01}, {0x22, 0x01}, {0x12, 0x01}, {0x0a, 0x01}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0xd5}, {0x00, 0x5d}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0xe0}, {0x00, 0x0e}, + {0x00, 0xe1}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x1e}, + {0x00, 0xe2}, {0x02, 0x01}, {0x00, 0xaa}, {0x00, 0x2e}, + {0x08, 0x01}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0xb9}, + {0x00, 0x9b}, {0x02, 0x01}, {0x00, 0xe3}, {0x00, 0xd6}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x6d}, {0x00, 0x3e}, + {0x02, 0x01}, {0x00, 0xc8}, {0x00, 0x8c}, {0x10, 0x01}, + {0x08, 0x01}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0xe4}, + {0x00, 0x4e}, {0x02, 0x01}, {0x00, 0xd7}, {0x00, 0x7d}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0xe5}, {0x00, 0xba}, + {0x02, 0x01}, {0x00, 0xab}, {0x00, 0x5e}, {0x08, 0x01}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0xc9}, {0x00, 0x9c}, + {0x02, 0x01}, {0x00, 0xf1}, {0x00, 0x1f}, {0x06, 0x01}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0xf0}, {0x00, 0x6e}, + {0x00, 0xf2}, {0x02, 0x01}, {0x00, 0x2f}, {0x00, 0xe6}, + {0x26, 0x01}, {0x12, 0x01}, {0x08, 0x01}, {0x04, 0x01}, + {0x02, 0x01}, {0x00, 0xd8}, {0x00, 0xf3}, {0x02, 0x01}, + {0x00, 0x3f}, {0x00, 0xf4}, {0x06, 0x01}, {0x02, 0x01}, + {0x00, 0x4f}, {0x02, 0x01}, {0x00, 0x8d}, {0x00, 0xd9}, + {0x02, 0x01}, {0x00, 0xbb}, {0x00, 0xca}, {0x08, 0x01}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0xac}, {0x00, 0xe7}, + {0x02, 0x01}, {0x00, 0x7e}, {0x00, 0xf5}, {0x08, 0x01}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x9d}, {0x00, 0x5f}, + {0x02, 0x01}, {0x00, 0xe8}, {0x00, 0x8e}, {0x02, 0x01}, + {0x00, 0xf6}, {0x00, 0xcb}, {0x22, 0x01}, {0x12, 0x01}, + {0x0a, 0x01}, {0x06, 0x01}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0x0f}, {0x00, 0xae}, {0x00, 0x6f}, {0x02, 0x01}, + {0x00, 0xbc}, {0x00, 0xda}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0xad}, {0x00, 0xf7}, {0x02, 0x01}, {0x00, 0x7f}, + {0x00, 0xe9}, {0x08, 0x01}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0x9e}, {0x00, 0xcc}, {0x02, 0x01}, {0x00, 0xf8}, + {0x00, 0x8f}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0xdb}, + {0x00, 0xbd}, {0x02, 0x01}, {0x00, 0xea}, {0x00, 0xf9}, + {0x10, 0x01}, {0x08, 0x01}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0x9f}, {0x00, 0xdc}, {0x02, 0x01}, {0x00, 0xcd}, + {0x00, 0xeb}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0xbe}, + {0x00, 0xfa}, {0x02, 0x01}, {0x00, 0xaf}, {0x00, 0xdd}, + {0x0e, 0x01}, {0x06, 0x01}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0xec}, {0x00, 0xce}, {0x00, 0xfb}, {0x04, 0x01}, + {0x02, 0x01}, {0x00, 0xbf}, {0x00, 0xed}, {0x02, 0x01}, + {0x00, 0xde}, {0x00, 0xfc}, {0x06, 0x01}, {0x04, 0x01}, + {0x02, 0x01}, {0x00, 0xcf}, {0x00, 0xfd}, {0x00, 0xee}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0xdf}, {0x00, 0xfe}, + {0x02, 0x01}, {0x00, 0xef}, {0x00, 0xff} +}; + +static const guchar huffbits_16[511][2] = { + {0x02, 0x01}, {0x00, 0x00}, {0x06, 0x01}, {0x02, 0x01}, + {0x00, 0x10}, {0x02, 0x01}, {0x00, 0x01}, {0x00, 0x11}, + {0x2a, 0x01}, {0x08, 0x01}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0x20}, {0x00, 0x02}, {0x02, 0x01}, {0x00, 0x21}, + {0x00, 0x12}, {0x0a, 0x01}, {0x06, 0x01}, {0x02, 0x01}, + {0x00, 0x22}, {0x02, 0x01}, {0x00, 0x30}, {0x00, 0x03}, + {0x02, 0x01}, {0x00, 0x31}, {0x00, 0x13}, {0x0a, 0x01}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x32}, {0x00, 0x23}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x40}, {0x00, 0x04}, + {0x00, 0x41}, {0x06, 0x01}, {0x02, 0x01}, {0x00, 0x14}, + {0x02, 0x01}, {0x00, 0x33}, {0x00, 0x42}, {0x04, 0x01}, + {0x02, 0x01}, {0x00, 0x24}, {0x00, 0x50}, {0x02, 0x01}, + {0x00, 0x43}, {0x00, 0x34}, {0x8a, 0x01}, {0x28, 0x01}, + {0x10, 0x01}, {0x06, 0x01}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0x05}, {0x00, 0x15}, {0x00, 0x51}, {0x04, 0x01}, + {0x02, 0x01}, {0x00, 0x52}, {0x00, 0x25}, {0x04, 0x01}, + {0x02, 0x01}, {0x00, 0x44}, {0x00, 0x35}, {0x00, 0x53}, + {0x0a, 0x01}, {0x06, 0x01}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0x60}, {0x00, 0x06}, {0x00, 0x61}, {0x02, 0x01}, + {0x00, 0x16}, {0x00, 0x62}, {0x08, 0x01}, {0x04, 0x01}, + {0x02, 0x01}, {0x00, 0x26}, {0x00, 0x54}, {0x02, 0x01}, + {0x00, 0x45}, {0x00, 0x63}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0x36}, {0x00, 0x70}, {0x00, 0x71}, {0x28, 0x01}, + {0x12, 0x01}, {0x08, 0x01}, {0x02, 0x01}, {0x00, 0x17}, + {0x02, 0x01}, {0x00, 0x07}, {0x02, 0x01}, {0x00, 0x55}, + {0x00, 0x64}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x72}, + {0x00, 0x27}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x46}, + {0x00, 0x65}, {0x00, 0x73}, {0x0a, 0x01}, {0x06, 0x01}, + {0x02, 0x01}, {0x00, 0x37}, {0x02, 0x01}, {0x00, 0x56}, + {0x00, 0x08}, {0x02, 0x01}, {0x00, 0x80}, {0x00, 0x81}, + {0x06, 0x01}, {0x02, 0x01}, {0x00, 0x18}, {0x02, 0x01}, + {0x00, 0x74}, {0x00, 0x47}, {0x02, 0x01}, {0x00, 0x82}, + {0x02, 0x01}, {0x00, 0x28}, {0x00, 0x66}, {0x18, 0x01}, + {0x0e, 0x01}, {0x08, 0x01}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0x83}, {0x00, 0x38}, {0x02, 0x01}, {0x00, 0x75}, + {0x00, 0x84}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x48}, + {0x00, 0x90}, {0x00, 0x91}, {0x06, 0x01}, {0x02, 0x01}, + {0x00, 0x19}, {0x02, 0x01}, {0x00, 0x09}, {0x00, 0x76}, + {0x02, 0x01}, {0x00, 0x92}, {0x00, 0x29}, {0x0e, 0x01}, + {0x08, 0x01}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x85}, + {0x00, 0x58}, {0x02, 0x01}, {0x00, 0x93}, {0x00, 0x39}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0xa0}, {0x00, 0x0a}, + {0x00, 0x1a}, {0x08, 0x01}, {0x02, 0x01}, {0x00, 0xa2}, + {0x02, 0x01}, {0x00, 0x67}, {0x02, 0x01}, {0x00, 0x57}, + {0x00, 0x49}, {0x06, 0x01}, {0x02, 0x01}, {0x00, 0x94}, + {0x02, 0x01}, {0x00, 0x77}, {0x00, 0x86}, {0x02, 0x01}, + {0x00, 0xa1}, {0x02, 0x01}, {0x00, 0x68}, {0x00, 0x95}, + {0xdc, 0x01}, {0x7e, 0x01}, {0x32, 0x01}, {0x1a, 0x01}, + {0x0c, 0x01}, {0x06, 0x01}, {0x02, 0x01}, {0x00, 0x2a}, + {0x02, 0x01}, {0x00, 0x59}, {0x00, 0x3a}, {0x02, 0x01}, + {0x00, 0xa3}, {0x02, 0x01}, {0x00, 0x87}, {0x00, 0x78}, + {0x08, 0x01}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0xa4}, + {0x00, 0x4a}, {0x02, 0x01}, {0x00, 0x96}, {0x00, 0x69}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0xb0}, {0x00, 0x0b}, + {0x00, 0xb1}, {0x0a, 0x01}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0x1b}, {0x00, 0xb2}, {0x02, 0x01}, {0x00, 0x2b}, + {0x02, 0x01}, {0x00, 0xa5}, {0x00, 0x5a}, {0x06, 0x01}, + {0x02, 0x01}, {0x00, 0xb3}, {0x02, 0x01}, {0x00, 0xa6}, + {0x00, 0x6a}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0xb4}, + {0x00, 0x4b}, {0x02, 0x01}, {0x00, 0x0c}, {0x00, 0xc1}, + {0x1e, 0x01}, {0x0e, 0x01}, {0x06, 0x01}, {0x04, 0x01}, + {0x02, 0x01}, {0x00, 0xb5}, {0x00, 0xc2}, {0x00, 0x2c}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0xa7}, {0x00, 0xc3}, + {0x02, 0x01}, {0x00, 0x6b}, {0x00, 0xc4}, {0x08, 0x01}, + {0x02, 0x01}, {0x00, 0x1d}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0x88}, {0x00, 0x97}, {0x00, 0x3b}, {0x04, 0x01}, + {0x02, 0x01}, {0x00, 0xd1}, {0x00, 0xd2}, {0x02, 0x01}, + {0x00, 0x2d}, {0x00, 0xd3}, {0x12, 0x01}, {0x06, 0x01}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x1e}, {0x00, 0x2e}, + {0x00, 0xe2}, {0x06, 0x01}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0x79}, {0x00, 0x98}, {0x00, 0xc0}, {0x02, 0x01}, + {0x00, 0x1c}, {0x02, 0x01}, {0x00, 0x89}, {0x00, 0x5b}, + {0x0e, 0x01}, {0x06, 0x01}, {0x02, 0x01}, {0x00, 0x3c}, + {0x02, 0x01}, {0x00, 0x7a}, {0x00, 0xb6}, {0x04, 0x01}, + {0x02, 0x01}, {0x00, 0x4c}, {0x00, 0x99}, {0x02, 0x01}, + {0x00, 0xa8}, {0x00, 0x8a}, {0x06, 0x01}, {0x02, 0x01}, + {0x00, 0x0d}, {0x02, 0x01}, {0x00, 0xc5}, {0x00, 0x5c}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x3d}, {0x00, 0xc6}, + {0x02, 0x01}, {0x00, 0x6c}, {0x00, 0x9a}, {0x58, 0x01}, + {0x56, 0x01}, {0x24, 0x01}, {0x10, 0x01}, {0x08, 0x01}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x8b}, {0x00, 0x4d}, + {0x02, 0x01}, {0x00, 0xc7}, {0x00, 0x7c}, {0x04, 0x01}, + {0x02, 0x01}, {0x00, 0xd5}, {0x00, 0x5d}, {0x02, 0x01}, + {0x00, 0xe0}, {0x00, 0x0e}, {0x08, 0x01}, {0x02, 0x01}, + {0x00, 0xe3}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0xd0}, + {0x00, 0xb7}, {0x00, 0x7b}, {0x06, 0x01}, {0x04, 0x01}, + {0x02, 0x01}, {0x00, 0xa9}, {0x00, 0xb8}, {0x00, 0xd4}, + {0x02, 0x01}, {0x00, 0xe1}, {0x02, 0x01}, {0x00, 0xaa}, + {0x00, 0xb9}, {0x18, 0x01}, {0x0a, 0x01}, {0x06, 0x01}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x9b}, {0x00, 0xd6}, + {0x00, 0x6d}, {0x02, 0x01}, {0x00, 0x3e}, {0x00, 0xc8}, + {0x06, 0x01}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x8c}, + {0x00, 0xe4}, {0x00, 0x4e}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0xd7}, {0x00, 0xe5}, {0x02, 0x01}, {0x00, 0xba}, + {0x00, 0xab}, {0x0c, 0x01}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0x9c}, {0x00, 0xe6}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0x6e}, {0x00, 0xd8}, {0x02, 0x01}, {0x00, 0x8d}, + {0x00, 0xbb}, {0x08, 0x01}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0xe7}, {0x00, 0x9d}, {0x02, 0x01}, {0x00, 0xe8}, + {0x00, 0x8e}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0xcb}, + {0x00, 0xbc}, {0x00, 0x9e}, {0x00, 0xf1}, {0x02, 0x01}, + {0x00, 0x1f}, {0x02, 0x01}, {0x00, 0x0f}, {0x00, 0x2f}, + {0x42, 0x01}, {0x38, 0x01}, {0x02, 0x01}, {0x00, 0xf2}, + {0x34, 0x01}, {0x32, 0x01}, {0x14, 0x01}, {0x08, 0x01}, + {0x02, 0x01}, {0x00, 0xbd}, {0x02, 0x01}, {0x00, 0x5e}, + {0x02, 0x01}, {0x00, 0x7d}, {0x00, 0xc9}, {0x06, 0x01}, + {0x02, 0x01}, {0x00, 0xca}, {0x02, 0x01}, {0x00, 0xac}, + {0x00, 0x7e}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0xda}, + {0x00, 0xad}, {0x00, 0xcc}, {0x0a, 0x01}, {0x06, 0x01}, + {0x02, 0x01}, {0x00, 0xae}, {0x02, 0x01}, {0x00, 0xdb}, + {0x00, 0xdc}, {0x02, 0x01}, {0x00, 0xcd}, {0x00, 0xbe}, + {0x06, 0x01}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0xeb}, + {0x00, 0xed}, {0x00, 0xee}, {0x06, 0x01}, {0x04, 0x01}, + {0x02, 0x01}, {0x00, 0xd9}, {0x00, 0xea}, {0x00, 0xe9}, + {0x02, 0x01}, {0x00, 0xde}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0xdd}, {0x00, 0xec}, {0x00, 0xce}, {0x00, 0x3f}, + {0x00, 0xf0}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0xf3}, + {0x00, 0xf4}, {0x02, 0x01}, {0x00, 0x4f}, {0x02, 0x01}, + {0x00, 0xf5}, {0x00, 0x5f}, {0x0a, 0x01}, {0x02, 0x01}, + {0x00, 0xff}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0xf6}, + {0x00, 0x6f}, {0x02, 0x01}, {0x00, 0xf7}, {0x00, 0x7f}, + {0x0c, 0x01}, {0x06, 0x01}, {0x02, 0x01}, {0x00, 0x8f}, + {0x02, 0x01}, {0x00, 0xf8}, {0x00, 0xf9}, {0x04, 0x01}, + {0x02, 0x01}, {0x00, 0x9f}, {0x00, 0xfa}, {0x00, 0xaf}, + {0x08, 0x01}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0xfb}, + {0x00, 0xbf}, {0x02, 0x01}, {0x00, 0xfc}, {0x00, 0xcf}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0xfd}, {0x00, 0xdf}, + {0x02, 0x01}, {0x00, 0xfe}, {0x00, 0xef} +}; + +static const guchar huffbits_24[512][2] = { + {0x3c, 0x01}, {0x08, 0x01}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0x00}, {0x00, 0x10}, {0x02, 0x01}, {0x00, 0x01}, + {0x00, 0x11}, {0x0e, 0x01}, {0x06, 0x01}, {0x04, 0x01}, + {0x02, 0x01}, {0x00, 0x20}, {0x00, 0x02}, {0x00, 0x21}, + {0x02, 0x01}, {0x00, 0x12}, {0x02, 0x01}, {0x00, 0x22}, + {0x02, 0x01}, {0x00, 0x30}, {0x00, 0x03}, {0x0e, 0x01}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x31}, {0x00, 0x13}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x32}, {0x00, 0x23}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x40}, {0x00, 0x04}, + {0x00, 0x41}, {0x08, 0x01}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0x14}, {0x00, 0x33}, {0x02, 0x01}, {0x00, 0x42}, + {0x00, 0x24}, {0x06, 0x01}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0x43}, {0x00, 0x34}, {0x00, 0x51}, {0x06, 0x01}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x50}, {0x00, 0x05}, + {0x00, 0x15}, {0x02, 0x01}, {0x00, 0x52}, {0x00, 0x25}, + {0xfa, 0x01}, {0x62, 0x01}, {0x22, 0x01}, {0x12, 0x01}, + {0x0a, 0x01}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x44}, + {0x00, 0x53}, {0x02, 0x01}, {0x00, 0x35}, {0x02, 0x01}, + {0x00, 0x60}, {0x00, 0x06}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0x61}, {0x00, 0x16}, {0x02, 0x01}, {0x00, 0x62}, + {0x00, 0x26}, {0x08, 0x01}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0x54}, {0x00, 0x45}, {0x02, 0x01}, {0x00, 0x63}, + {0x00, 0x36}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x71}, + {0x00, 0x55}, {0x02, 0x01}, {0x00, 0x64}, {0x00, 0x46}, + {0x20, 0x01}, {0x0e, 0x01}, {0x06, 0x01}, {0x02, 0x01}, + {0x00, 0x72}, {0x02, 0x01}, {0x00, 0x27}, {0x00, 0x37}, + {0x02, 0x01}, {0x00, 0x73}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0x70}, {0x00, 0x07}, {0x00, 0x17}, {0x0a, 0x01}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x65}, {0x00, 0x56}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x80}, {0x00, 0x08}, + {0x00, 0x81}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x74}, + {0x00, 0x47}, {0x02, 0x01}, {0x00, 0x18}, {0x00, 0x82}, + {0x10, 0x01}, {0x08, 0x01}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0x28}, {0x00, 0x66}, {0x02, 0x01}, {0x00, 0x83}, + {0x00, 0x38}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x75}, + {0x00, 0x57}, {0x02, 0x01}, {0x00, 0x84}, {0x00, 0x48}, + {0x08, 0x01}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x91}, + {0x00, 0x19}, {0x02, 0x01}, {0x00, 0x92}, {0x00, 0x76}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x67}, {0x00, 0x29}, + {0x02, 0x01}, {0x00, 0x85}, {0x00, 0x58}, {0x5c, 0x01}, + {0x22, 0x01}, {0x10, 0x01}, {0x08, 0x01}, {0x04, 0x01}, + {0x02, 0x01}, {0x00, 0x93}, {0x00, 0x39}, {0x02, 0x01}, + {0x00, 0x94}, {0x00, 0x49}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0x77}, {0x00, 0x86}, {0x02, 0x01}, {0x00, 0x68}, + {0x00, 0xa1}, {0x08, 0x01}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0xa2}, {0x00, 0x2a}, {0x02, 0x01}, {0x00, 0x95}, + {0x00, 0x59}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0xa3}, + {0x00, 0x3a}, {0x02, 0x01}, {0x00, 0x87}, {0x02, 0x01}, + {0x00, 0x78}, {0x00, 0x4a}, {0x16, 0x01}, {0x0c, 0x01}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0xa4}, {0x00, 0x96}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x69}, {0x00, 0xb1}, + {0x02, 0x01}, {0x00, 0x1b}, {0x00, 0xa5}, {0x06, 0x01}, + {0x02, 0x01}, {0x00, 0xb2}, {0x02, 0x01}, {0x00, 0x5a}, + {0x00, 0x2b}, {0x02, 0x01}, {0x00, 0x88}, {0x00, 0xb3}, + {0x10, 0x01}, {0x0a, 0x01}, {0x06, 0x01}, {0x02, 0x01}, + {0x00, 0x90}, {0x02, 0x01}, {0x00, 0x09}, {0x00, 0xa0}, + {0x02, 0x01}, {0x00, 0x97}, {0x00, 0x79}, {0x04, 0x01}, + {0x02, 0x01}, {0x00, 0xa6}, {0x00, 0x6a}, {0x00, 0xb4}, + {0x0c, 0x01}, {0x06, 0x01}, {0x02, 0x01}, {0x00, 0x1a}, + {0x02, 0x01}, {0x00, 0x0a}, {0x00, 0xb0}, {0x02, 0x01}, + {0x00, 0x3b}, {0x02, 0x01}, {0x00, 0x0b}, {0x00, 0xc0}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x4b}, {0x00, 0xc1}, + {0x02, 0x01}, {0x00, 0x98}, {0x00, 0x89}, {0x43, 0x01}, + {0x22, 0x01}, {0x10, 0x01}, {0x08, 0x01}, {0x04, 0x01}, + {0x02, 0x01}, {0x00, 0x1c}, {0x00, 0xb5}, {0x02, 0x01}, + {0x00, 0x5b}, {0x00, 0xc2}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0x2c}, {0x00, 0xa7}, {0x02, 0x01}, {0x00, 0x7a}, + {0x00, 0xc3}, {0x0a, 0x01}, {0x06, 0x01}, {0x02, 0x01}, + {0x00, 0x3c}, {0x02, 0x01}, {0x00, 0x0c}, {0x00, 0xd0}, + {0x02, 0x01}, {0x00, 0xb6}, {0x00, 0x6b}, {0x04, 0x01}, + {0x02, 0x01}, {0x00, 0xc4}, {0x00, 0x4c}, {0x02, 0x01}, + {0x00, 0x99}, {0x00, 0xa8}, {0x10, 0x01}, {0x08, 0x01}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x8a}, {0x00, 0xc5}, + {0x02, 0x01}, {0x00, 0x5c}, {0x00, 0xd1}, {0x04, 0x01}, + {0x02, 0x01}, {0x00, 0xb7}, {0x00, 0x7b}, {0x02, 0x01}, + {0x00, 0x1d}, {0x00, 0xd2}, {0x09, 0x01}, {0x04, 0x01}, + {0x02, 0x01}, {0x00, 0x2d}, {0x00, 0xd3}, {0x02, 0x01}, + {0x00, 0x3d}, {0x00, 0xc6}, {0x55, 0xfa}, {0x04, 0x01}, + {0x02, 0x01}, {0x00, 0x6c}, {0x00, 0xa9}, {0x02, 0x01}, + {0x00, 0x9a}, {0x00, 0xd4}, {0x20, 0x01}, {0x10, 0x01}, + {0x08, 0x01}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0xb8}, + {0x00, 0x8b}, {0x02, 0x01}, {0x00, 0x4d}, {0x00, 0xc7}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x7c}, {0x00, 0xd5}, + {0x02, 0x01}, {0x00, 0x5d}, {0x00, 0xe1}, {0x08, 0x01}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x1e}, {0x00, 0xe2}, + {0x02, 0x01}, {0x00, 0xaa}, {0x00, 0xb9}, {0x04, 0x01}, + {0x02, 0x01}, {0x00, 0x9b}, {0x00, 0xe3}, {0x02, 0x01}, + {0x00, 0xd6}, {0x00, 0x6d}, {0x14, 0x01}, {0x0a, 0x01}, + {0x06, 0x01}, {0x02, 0x01}, {0x00, 0x3e}, {0x02, 0x01}, + {0x00, 0x2e}, {0x00, 0x4e}, {0x02, 0x01}, {0x00, 0xc8}, + {0x00, 0x8c}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0xe4}, + {0x00, 0xd7}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x7d}, + {0x00, 0xab}, {0x00, 0xe5}, {0x0a, 0x01}, {0x04, 0x01}, + {0x02, 0x01}, {0x00, 0xba}, {0x00, 0x5e}, {0x02, 0x01}, + {0x00, 0xc9}, {0x02, 0x01}, {0x00, 0x9c}, {0x00, 0x6e}, + {0x08, 0x01}, {0x02, 0x01}, {0x00, 0xe6}, {0x02, 0x01}, + {0x00, 0x0d}, {0x02, 0x01}, {0x00, 0xe0}, {0x00, 0x0e}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0xd8}, {0x00, 0x8d}, + {0x02, 0x01}, {0x00, 0xbb}, {0x00, 0xca}, {0x4a, 0x01}, + {0x02, 0x01}, {0x00, 0xff}, {0x40, 0x01}, {0x3a, 0x01}, + {0x20, 0x01}, {0x10, 0x01}, {0x08, 0x01}, {0x04, 0x01}, + {0x02, 0x01}, {0x00, 0xac}, {0x00, 0xe7}, {0x02, 0x01}, + {0x00, 0x7e}, {0x00, 0xd9}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0x9d}, {0x00, 0xe8}, {0x02, 0x01}, {0x00, 0x8e}, + {0x00, 0xcb}, {0x08, 0x01}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0xbc}, {0x00, 0xda}, {0x02, 0x01}, {0x00, 0xad}, + {0x00, 0xe9}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x9e}, + {0x00, 0xcc}, {0x02, 0x01}, {0x00, 0xdb}, {0x00, 0xbd}, + {0x10, 0x01}, {0x08, 0x01}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0xea}, {0x00, 0xae}, {0x02, 0x01}, {0x00, 0xdc}, + {0x00, 0xcd}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0xeb}, + {0x00, 0xbe}, {0x02, 0x01}, {0x00, 0xdd}, {0x00, 0xec}, + {0x08, 0x01}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0xce}, + {0x00, 0xed}, {0x02, 0x01}, {0x00, 0xde}, {0x00, 0xee}, + {0x00, 0x0f}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0xf0}, + {0x00, 0x1f}, {0x00, 0xf1}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0xf2}, {0x00, 0x2f}, {0x02, 0x01}, {0x00, 0xf3}, + {0x00, 0x3f}, {0x12, 0x01}, {0x08, 0x01}, {0x04, 0x01}, + {0x02, 0x01}, {0x00, 0xf4}, {0x00, 0x4f}, {0x02, 0x01}, + {0x00, 0xf5}, {0x00, 0x5f}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0xf6}, {0x00, 0x6f}, {0x02, 0x01}, {0x00, 0xf7}, + {0x02, 0x01}, {0x00, 0x7f}, {0x00, 0x8f}, {0x0a, 0x01}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0xf8}, {0x00, 0xf9}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x9f}, {0x00, 0xaf}, + {0x00, 0xfa}, {0x08, 0x01}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0xfb}, {0x00, 0xbf}, {0x02, 0x01}, {0x00, 0xfc}, + {0x00, 0xcf}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0xfd}, + {0x00, 0xdf}, {0x02, 0x01}, {0x00, 0xfe}, {0x00, 0xef} + +}; + +static const guchar huffbits_32[31][2] = { + {0x02, 0x01}, {0x00, 0x00}, {0x08, 0x01}, {0x04, 0x01}, + {0x02, 0x01}, {0x00, 0x08}, {0x00, 0x04}, {0x02, 0x01}, + {0x00, 0x01}, {0x00, 0x02}, {0x08, 0x01}, {0x04, 0x01}, + {0x02, 0x01}, {0x00, 0x0c}, {0x00, 0x0a}, {0x02, 0x01}, + {0x00, 0x03}, {0x00, 0x06}, {0x06, 0x01}, {0x02, 0x01}, + {0x00, 0x09}, {0x02, 0x01}, {0x00, 0x05}, {0x00, 0x07}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x0e}, {0x00, 0x0d}, + {0x02, 0x01}, {0x00, 0x0f}, {0x00, 0x0b} +}; + +static const guchar huffbits_33[31][2] = { + {0x10, 0x01}, {0x08, 0x01}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0x00}, {0x00, 0x01}, {0x02, 0x01}, {0x00, 0x02}, + {0x00, 0x03}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x04}, + {0x00, 0x05}, {0x02, 0x01}, {0x00, 0x06}, {0x00, 0x07}, + {0x08, 0x01}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x08}, + {0x00, 0x09}, {0x02, 0x01}, {0x00, 0x0a}, {0x00, 0x0b}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x0c}, {0x00, 0x0d}, + {0x02, 0x01}, {0x00, 0x0e}, {0x00, 0x0f} +}; + +/* Array of decoder table structures */ +static const struct huffcodetab huff_tables[] = { + /* 0 */ {0, 0, 0, 0, FALSE, NULL}, + /* 1 */ {7, 2, 2, 0, FALSE, huffbits_1}, + /* 2 */ {17, 3, 3, 0, FALSE, huffbits_2}, + /* 3 */ {17, 3, 3, 0, FALSE, huffbits_3}, + /* 4 */ {0, 0, 0, 0, FALSE, NULL}, + /* 5 */ {31, 4, 4, 0, FALSE, huffbits_5}, + /* 6 */ {31, 4, 4, 0, FALSE, huffbits_6}, + /* 7 */ {71, 6, 6, 0, FALSE, huffbits_7}, + /* 8 */ {71, 6, 6, 0, FALSE, huffbits_8}, + /* 9 */ {71, 6, 6, 0, FALSE, huffbits_9}, + /* 10 */ {127, 8, 8, 0, FALSE, huffbits_10}, + /* 11 */ {127, 8, 8, 0, FALSE, huffbits_11}, + /* 12 */ {127, 8, 8, 0, FALSE, huffbits_12}, + /* 13 */ {511, 16, 16, 0, FALSE, huffbits_13}, + /* 14 */ {0, 0, 0, 0, FALSE, NULL}, + /* 15 */ {511, 16, 16, 0, FALSE, huffbits_15}, + /* 16 */ {511, 16, 16, 1, FALSE, huffbits_16}, + /* 17 */ {511, 16, 16, 2, FALSE, huffbits_16}, + /* 18 */ {511, 16, 16, 3, FALSE, huffbits_16}, + /* 19 */ {511, 16, 16, 4, FALSE, huffbits_16}, + /* 20 */ {511, 16, 16, 6, FALSE, huffbits_16}, + /* 21 */ {511, 16, 16, 8, FALSE, huffbits_16}, + /* 22 */ {511, 16, 16, 10, FALSE, huffbits_16}, + /* 23 */ {511, 16, 16, 13, FALSE, huffbits_16}, + /* 24 */ {512, 16, 16, 4, FALSE, huffbits_24}, + /* 25 */ {512, 16, 16, 5, FALSE, huffbits_24}, + /* 26 */ {512, 16, 16, 6, FALSE, huffbits_24}, + /* 27 */ {512, 16, 16, 7, FALSE, huffbits_24}, + /* 28 */ {512, 16, 16, 8, FALSE, huffbits_24}, + /* 29 */ {512, 16, 16, 9, FALSE, huffbits_24}, + /* 30 */ {512, 16, 16, 11, FALSE, huffbits_24}, + /* 31 */ {512, 16, 16, 13, FALSE, huffbits_24}, + /* 32 */ {31, 1, 16, 0, TRUE, huffbits_32}, + /* 33 */ {31, 1, 16, 0, TRUE, huffbits_33} +}; + +static const gfloat pow_2_table[] = { +0.000000000000000f, 0.000000000000000f, 0.000000000000000f, 0.000000000000000f, +0.000000000000000f, 0.000000000000000f, 0.000000000000000f, 0.000000000000000f, +0.000000000000000f, 0.000000000000000f, 0.000000000000000f, 0.000000000000000f, +0.000000000000000f, 0.000000000000000f, 0.000000000000000f, 0.000000000000000f, +0.000000000000000f, 0.000000000000000f, 0.000000000000000f, 0.000000000000000f, +0.000000000000000f, 0.000000000000000f, 0.000000000000000f, 0.000000000000000f, +0.000000000000000f, 0.000000000000000f, 0.000000000000000f, 0.000000000000000f, +0.000000000000000f, 0.000000000000000f, 0.000000000000000f, 0.000000000000000f, +0.000000000000000f, 0.000000000000000f, 0.000000000000000f, 0.000000000000000f, +0.000000000000000f, 0.000000000000000f, 0.000000000000000f, 0.000000000000000f, +0.000000000000000f, 0.000000000000000f, 0.000000000000000f, 0.000000000000000f, +0.000000000000000f, 0.000000000000000f, 0.000000000000000f, 0.000000000000000f, +0.000000000000000f, 0.000000000000000f, 0.000000000000000f, 0.000000000000000f, +0.000000000000000f, 0.000000000000000f, 0.000000000000000f, 0.000000000000000f, +0.000000000000000f, 0.000000000000000f, 0.000000000000000f, 0.000000000000000f, +0.000000000000000f, 0.000000000000000f, 0.000000000000000f, 0.000000000000000f, +0.000000000000000f, 0.000000000000000f, 0.000000000000000f, 0.000000000000000f, +0.000000000000000f, 0.000000000000000f, 0.000000000000000f, 0.000000000000000f, +0.000000000000000f, 0.000000000000000f, 0.000000000000000f, 0.000000000000000f, +0.000000000000000f, 0.000000000000000f, 0.000000000000000f, 0.000000000000000f, +0.000000000000000f, 0.000000000000000f, 0.000000000000000f, 0.000000000000000f, +0.000000000000000f, 0.000000000000000f, 0.000000000000000f, 0.000000000000000f, +0.000000000000000f, 0.000000000000000f, 0.000000000000000f, 0.000000000000000f, +0.000000000000000f, 0.000000000000000f, 0.000000000000000f, 0.000000000000000f, +0.000000000000000f, 0.000000000000000f, 0.000000000000000f, 0.000000000000000f, +0.000000000000000f, 0.000000000000000f, 0.000000000000000f, 0.000000000000000f, +0.000000000000000f, 0.000000000000000f, 0.000000000000000f, 0.000000000000000f, +0.000000000000000f, 0.000000000000000f, 0.000000000000000f, 0.000000000000000f, +0.000000000000000f, 0.000000000000000f, 0.000000000000000f, 0.000000000000000f, +0.000000000000000f, 0.000000000000000f, 0.000000000000000f, 0.000000000000000f, +0.000000000000000f, 0.000000000000000f, 0.000000000000000f, 0.000000000000001f, +0.000000000000001f, 0.000000000000001f, 0.000000000000001f, 0.000000000000001f, +0.000000000000001f, 0.000000000000001f, 0.000000000000002f, 0.000000000000002f, +0.000000000000003f, 0.000000000000003f, 0.000000000000004f, 0.000000000000004f, +0.000000000000005f, 0.000000000000006f, 0.000000000000007f, 0.000000000000008f, +0.000000000000010f, 0.000000000000012f, 0.000000000000014f, 0.000000000000017f, +0.000000000000020f, 0.000000000000024f, 0.000000000000028f, 0.000000000000034f, +0.000000000000040f, 0.000000000000048f, 0.000000000000057f, 0.000000000000068f, +0.000000000000080f, 0.000000000000096f, 0.000000000000114f, 0.000000000000135f, +0.000000000000161f, 0.000000000000191f, 0.000000000000227f, 0.000000000000270f, +0.000000000000322f, 0.000000000000382f, 0.000000000000455f, 0.000000000000541f, +0.000000000000643f, 0.000000000000765f, 0.000000000000909f, 0.000000000001082f, +0.000000000001286f, 0.000000000001530f, 0.000000000001819f, 0.000000000002163f, +0.000000000002572f, 0.000000000003059f, 0.000000000003638f, 0.000000000004326f, +0.000000000005145f, 0.000000000006118f, 0.000000000007276f, 0.000000000008653f, +0.000000000010290f, 0.000000000012237f, 0.000000000014552f, 0.000000000017305f, +0.000000000020580f, 0.000000000024473f, 0.000000000029104f, 0.000000000034610f, +0.000000000041159f, 0.000000000048947f, 0.000000000058208f, 0.000000000069221f, +0.000000000082318f, 0.000000000097893f, 0.000000000116415f, 0.000000000138442f, +0.000000000164636f, 0.000000000195786f, 0.000000000232831f, 0.000000000276884f, +0.000000000329272f, 0.000000000391573f, 0.000000000465661f, 0.000000000553768f, +0.000000000658544f, 0.000000000783146f, 0.000000000931323f, 0.000000001107535f, +0.000000001317089f, 0.000000001566292f, 0.000000001862645f, 0.000000002215071f, +0.000000002634178f, 0.000000003132583f, 0.000000003725290f, 0.000000004430142f, +0.000000005268356f, 0.000000006265167f, 0.000000007450581f, 0.000000008860283f, +0.000000010536712f, 0.000000012530333f, 0.000000014901161f, 0.000000017720566f, +0.000000021073424f, 0.000000025060666f, 0.000000029802322f, 0.000000035441133f, +0.000000042146848f, 0.000000050121333f, 0.000000059604645f, 0.000000070882265f, +0.000000084293696f, 0.000000100242666f, 0.000000119209290f, 0.000000141764531f, +0.000000168587391f, 0.000000200485331f, 0.000000238418579f, 0.000000283529062f, +0.000000337174782f, 0.000000400970663f, 0.000000476837158f, 0.000000567058123f, +0.000000674349565f, 0.000000801941326f, 0.000000953674316f, 0.000001134116246f, +0.000001348699129f, 0.000001603882652f, 0.000001907348633f, 0.000002268232492f, +0.000002697398259f, 0.000003207765303f, 0.000003814697266f, 0.000004536464985f, +0.000005394796517f, 0.000006415530606f, 0.000007629394531f, 0.000009072929970f, +0.000010789593034f, 0.000012831061213f, 0.000015258789062f, 0.000018145859940f, +0.000021579186068f, 0.000025662122425f, 0.000030517578125f, 0.000036291719880f, +0.000043158372137f, 0.000051324244851f, 0.000061035156250f, 0.000072583439760f, +0.000086316744273f, 0.000102648489701f, 0.000122070312500f, 0.000145166879520f, +0.000172633488546f, 0.000205296979402f, 0.000244140625000f, 0.000290333759040f, +0.000345266977092f, 0.000410593958804f, 0.000488281250000f, 0.000580667518079f, +0.000690533954185f, 0.000821187917609f, 0.000976562500000f, 0.001161335036159f, +0.001381067908369f, 0.001642375835218f, 0.001953125000000f, 0.002322670072317f, +0.002762135816738f, 0.003284751670435f, 0.003906250000000f, 0.004645340144634f, +0.005524271633476f, 0.006569503340870f, 0.007812500000000f, 0.009290680289268f, +0.011048543266952f, 0.013139006681740f, 0.015625000000000f, 0.018581360578537f, +0.022097086533904f, 0.026278013363481f, 0.031250000000000f, 0.037162721157074f, +0.044194173067808f, 0.052556026726961f, 0.062500000000000f, 0.074325442314148f, +0.088388346135616f, 0.105112053453922f, 0.125000000000000f, 0.148650884628296f, +0.176776692271233f, 0.210224106907845f, 0.250000000000000f, 0.297301769256592f, +0.353553384542465f, 0.420448213815689f, 0.500000000000000f, 0.594603538513184f, +0.707106769084930f, 0.840896427631378f, 1.000000000000000f, 1.189207077026367f, +1.414213538169861f, 1.681792855262756f, 2.000000000000000f, 2.378414154052734f, +2.828427076339722f, 3.363585710525513f, 4.000000000000000f, 4.756828308105469f, +5.656854152679443f, 6.727171421051025f, 8.000000000000000f, 9.513656616210938f, +11.313708305358887f, 13.454342842102051f, 16.000000000000000f, 19.027313232421875f, +22.627416610717773f, 26.908685684204102f, 32.000000000000000f, 38.054626464843750f, +45.254833221435547f, 53.817371368408203f, 64.000000000000000f, 76.109252929687500f, +90.509666442871094f, 107.634742736816406f, 128.000000000000000f, 152.218505859375000f, +181.019332885742188f, 215.269485473632812f, 256.000000000000000f, 304.437011718750000f, +362.038665771484375f, 430.538970947265625f, 512.000000000000000f, 608.874023437500000f, +724.077331542968750f, 861.077941894531250f, 1024.000000000000000f, 1217.748046875000000f, +1448.154663085937500f, 1722.155883789062500f, 2048.000000000000000f, 2435.496093750000000f +}; + +static const gfloat pow_43_table[] = { +0.0000000000f, 1.0000000000f, 2.5198421478f, 4.3267488480f, +6.3496046066f, 8.5498800278f, 10.9027242661f, 13.3905191422f, +16.0000019073f, 18.7207565308f, 21.5443496704f, 24.4637832642f, +27.4731445312f, 30.5673542023f, 33.7419967651f, 36.9931869507f, +40.3174781799f, 43.7117919922f, 47.1733512878f, 50.6996383667f, +54.2883605957f, 57.9374160767f, 61.6448745728f, 65.4089508057f, +69.2279891968f, 73.1004562378f, 77.0249099731f, 81.0000076294f, +85.0245056152f, 89.0971984863f, 93.2169876099f, 97.3828125000f, +101.5936813354f, 105.8486480713f, 110.1468200684f, 114.4873352051f, +118.8694000244f, 123.2922286987f, 127.7550811768f, 132.2572631836f, +136.7980957031f, 141.3769226074f, 145.9931335449f, 150.6461334229f, +155.3353576660f, 160.0602264404f, 164.8202209473f, 169.6148529053f, +174.4436035156f, 179.3060150146f, 184.2015991211f, 189.1299438477f, +194.0906066895f, 199.0831756592f, 204.1072387695f, 209.1624145508f, +214.2483215332f, 219.3645935059f, 224.5108795166f, 229.6868286133f, +234.8920898438f, 240.1263732910f, 245.3893127441f, 250.6806488037f, +256.0000305176f, 261.3472290039f, 266.7218933105f, 272.1237792969f, +277.5525817871f, 283.0080871582f, 288.4900207520f, 293.9981079102f, +299.5321350098f, 305.0918273926f, 310.6769409180f, 316.2872924805f, +321.9226379395f, 327.5827636719f, 333.2674255371f, 338.9764404297f, +344.7096252441f, 350.4667053223f, 356.2475585938f, 362.0519409180f, +367.8796691895f, 373.7305908203f, 379.6044921875f, 385.5012207031f, +391.4205627441f, 397.3623962402f, 403.3265075684f, 409.3127441406f, +415.3209533691f, 421.3509826660f, 427.4026489258f, 433.4758300781f, +439.5703430176f, 445.6860656738f, 451.8228454590f, 457.9805297852f, +464.1589660645f, 470.3580322266f, 476.5776062012f, 482.8175354004f, +489.0776977539f, 495.3579711914f, 501.6581726074f, 507.9782409668f, +514.3180541992f, 520.6774291992f, 527.0562744141f, 533.4545288086f, +539.8719482422f, 546.3085327148f, 552.7641601562f, 559.2387084961f, +565.7319946289f, 572.2439575195f, 578.7745361328f, 585.3236083984f, +591.8909912109f, 598.4766845703f, 605.0805664062f, 611.7024536133f, +618.3423461914f, 625.0001220703f, 631.6756591797f, 638.3688964844f, +645.0797119141f, 651.8080444336f, 658.5537109375f, 665.3167724609f, +672.0970458984f, 678.8944702148f, 685.7089233398f, 692.5404052734f, +699.3887329102f, 706.2538452148f, 713.1357421875f, 720.0342407227f, +726.9493408203f, 733.8808593750f, 740.8288574219f, 747.7931518555f, +754.7736816406f, 761.7703857422f, 768.7832031250f, 775.8120727539f, +782.8568725586f, 789.9176025391f, 796.9940795898f, 804.0863647461f, +811.1942749023f, 818.3178100586f, 825.4568481445f, 832.6113891602f, +839.7813110352f, 846.9666137695f, 854.1671752930f, 861.3829345703f, +868.6138305664f, 875.8598022461f, 883.1207885742f, 890.3967285156f, +897.6875610352f, 904.9932861328f, 912.3137207031f, 919.6488647461f, +926.9987182617f, 934.3631591797f, 941.7420654297f, 949.1355590820f, +956.5433959961f, 963.9656372070f, 971.4022216797f, 978.8530273438f, +986.3180541992f, 993.7972412109f, 1001.2904663086f, 1008.7977905273f, +1016.3190917969f, 1023.8543701172f, 1031.4035644531f, 1038.9665527344f, +1046.5432128906f, 1054.1337890625f, 1061.7379150391f, 1069.3558349609f, +1076.9871826172f, 1084.6322021484f, 1092.2906494141f, 1099.9626464844f, +1107.6479492188f, 1115.3465576172f, 1123.0585937500f, 1130.7838134766f, +1138.5222167969f, 1146.2739257812f, 1154.0385742188f, 1161.8164062500f, +1169.6072998047f, 1177.4112548828f, 1185.2280273438f, 1193.0577392578f, +1200.9003906250f, 1208.7558593750f, 1216.6240234375f, 1224.5050048828f, +1232.3986816406f, 1240.3049316406f, 1248.2238769531f, 1256.1553955078f, +1264.0994873047f, 1272.0560302734f, 1280.0250244141f, 1288.0064697266f, +1296.0002441406f, 1304.0064697266f, 1312.0249023438f, 1320.0556640625f, +1328.0986328125f, 1336.1538085938f, 1344.2211914062f, 1352.3006591797f, +1360.3922119141f, 1368.4957275391f, 1376.6113281250f, 1384.7388916016f, +1392.8784179688f, 1401.0299072266f, 1409.1932373047f, 1417.3684082031f, +1425.5552978516f, 1433.7540283203f, 1441.9644775391f, 1450.1866455078f, +1458.4205322266f, 1466.6660156250f, 1474.9230957031f, 1483.1917724609f, +1491.4719238281f, 1499.7636718750f, 1508.0667724609f, 1516.3814697266f, +1524.7075195312f, 1533.0449218750f, 1541.3936767578f, 1549.7537841797f, +1558.1251220703f, 1566.5078125000f, 1574.9016113281f, 1583.3067626953f, +1591.7230224609f, 1600.1503906250f, 1608.5888671875f, 1617.0384521484f, +1625.4990234375f, 1633.9707031250f, 1642.4533691406f, 1650.9468994141f, +1659.4515380859f, 1667.9669189453f, 1676.4932861328f, 1685.0305175781f, +1693.5784912109f, 1702.1373291016f, 1710.7069091797f, 1719.2872314453f, +1727.8782958984f, 1736.4801025391f, 1745.0925292969f, 1753.7155761719f, +1762.3492431641f, 1770.9934082031f, 1779.6483154297f, 1788.3135986328f, +1796.9895019531f, 1805.6759033203f, 1814.3726806641f, 1823.0798339844f, +1831.7974853516f, 1840.5256347656f, 1849.2639160156f, 1858.0126953125f, +1866.7717285156f, 1875.5410156250f, 1884.3206787109f, 1893.1104736328f, +1901.9105224609f, 1910.7207031250f, 1919.5411376953f, 1928.3717041016f, +1937.2124023438f, 1946.0631103516f, 1954.9239501953f, 1963.7949218750f, +1972.6757812500f, 1981.5666503906f, 1990.4676513672f, 1999.3785400391f, +2008.2993164062f, 2017.2299804688f, 2026.1706542969f, 2035.1212158203f, +2044.0815429688f, 2053.0517578125f, 2062.0317382812f, 2071.0214843750f, +2080.0209960938f, 2089.0302734375f, 2098.0493164062f, 2107.0781250000f, +2116.1164550781f, 2125.1645507812f, 2134.2221679688f, 2143.2895507812f, +2152.3664550781f, 2161.4528808594f, 2170.5490722656f, 2179.6545410156f, +2188.7697753906f, 2197.8942871094f, 2207.0283203125f, 2216.1718750000f, +2225.3249511719f, 2234.4873046875f, 2243.6591796875f, 2252.8405761719f, +2262.0310058594f, 2271.2309570312f, 2280.4401855469f, 2289.6586914062f, +2298.8864746094f, 2308.1237792969f, 2317.3701171875f, 2326.6257324219f, +2335.8903808594f, 2345.1645507812f, 2354.4475097656f, 2363.7399902344f, +2373.0415039062f, 2382.3520507812f, 2391.6718750000f, 2401.0004882812f, +2410.3383789062f, 2419.6853027344f, 2429.0412597656f, 2438.4062500000f, +2447.7802734375f, 2457.1630859375f, 2466.5551757812f, 2475.9560546875f, +2485.3657226562f, 2494.7844238281f, 2504.2121582031f, 2513.6486816406f, +2523.0939941406f, 2532.5483398438f, 2542.0112304688f, 2551.4831542969f, +2560.9638671875f, 2570.4531250000f, 2579.9514160156f, 2589.4584960938f, +2598.9741210938f, 2608.4985351562f, 2618.0314941406f, 2627.5734863281f, +2637.1237792969f, 2646.6828613281f, 2656.2507324219f, 2665.8271484375f, +2675.4121093750f, 2685.0056152344f, 2694.6079101562f, 2704.2185058594f, +2713.8378906250f, 2723.4655761719f, 2733.1020507812f, 2742.7468261719f, +2752.4001464844f, 2762.0617675781f, 2771.7321777344f, 2781.4108886719f, +2791.0979003906f, 2800.7934570312f, 2810.4973144531f, 2820.2097167969f, +2829.9301757812f, 2839.6594238281f, 2849.3967285156f, 2859.1423339844f, +2868.8962402344f, 2878.6586914062f, 2888.4291992188f, 2898.2080078125f, +2907.9951171875f, 2917.7905273438f, 2927.5942382812f, 2937.4060058594f, +2947.2258300781f, 2957.0541992188f, 2966.8903808594f, 2976.7348632812f, +2986.5876464844f, 2996.4484863281f, 3006.3173828125f, 3016.1943359375f, +3026.0793457031f, 3035.9726562500f, 3045.8737792969f, 3055.7832031250f, +3065.7004394531f, 3075.6259765625f, 3085.5593261719f, 3095.5007324219f, +3105.4499511719f, 3115.4074707031f, 3125.3728027344f, 3135.3459472656f, +3145.3271484375f, 3155.3161621094f, 3165.3132324219f, 3175.3183593750f, +3185.3310546875f, 3195.3518066406f, 3205.3803710938f, 3215.4167480469f, +3225.4609375000f, 3235.5131835938f, 3245.5729980469f, 3255.6406250000f, +3265.7160644531f, 3275.7993164062f, 3285.8903808594f, 3295.9892578125f, +3306.0957031250f, 3316.2099609375f, 3326.3320312500f, 3336.4616699219f, +3346.5988769531f, 3356.7441406250f, 3366.8967285156f, 3377.0571289062f, +3387.2250976562f, 3397.4008789062f, 3407.5839843750f, 3417.7749023438f, +3427.9736328125f, 3438.1796875000f, 3448.3933105469f, 3458.6145019531f, +3468.8432617188f, 3479.0795898438f, 3489.3234863281f, 3499.5749511719f, +3509.8339843750f, 3520.1003417969f, 3530.3742675781f, 3540.6555175781f, +3550.9445800781f, 3561.2407226562f, 3571.5446777344f, 3581.8557128906f, +3592.1743164062f, 3602.5004882812f, 3612.8339843750f, 3623.1748046875f, +3633.5229492188f, 3643.8786621094f, 3654.2414550781f, 3664.6118164062f, +3674.9895019531f, 3685.3745117188f, 3695.7668457031f, 3706.1665039062f, +3716.5732421875f, 3726.9875488281f, 3737.4091796875f, 3747.8378906250f, +3758.2739257812f, 3768.7170410156f, 3779.1677246094f, 3789.6254882812f, +3800.0903320312f, 3810.5625000000f, 3821.0419921875f, 3831.5285644531f, +3842.0222167969f, 3852.5231933594f, 3863.0312500000f, 3873.5463867188f, +3884.0688476562f, 3894.5981445312f, 3905.1347656250f, 3915.6787109375f, +3926.2294921875f, 3936.7873535156f, 3947.3522949219f, 3957.9245605469f, +3968.5036621094f, 3979.0898437500f, 3989.6831054688f, 4000.2834472656f, +4010.8906250000f, 4021.5048828125f, 4032.1262207031f, 4042.7546386719f, +4053.3898925781f, 4064.0322265625f, 4074.6816406250f, 4085.3378906250f, +4096.0009765625f, 4106.6713867188f, 4117.3481445312f, 4128.0322265625f, +4138.7231445312f, 4149.4208984375f, 4160.1254882812f, 4170.8374023438f, +4181.5556640625f, 4192.2812500000f, 4203.0136718750f, 4213.7524414062f, +4224.4985351562f, 4235.2514648438f, 4246.0107421875f, 4256.7773437500f, +4267.5502929688f, 4278.3305664062f, 4289.1171875000f, 4299.9111328125f, +4310.7114257812f, 4321.5185546875f, 4332.3325195312f, 4343.1533203125f, +4353.9804687500f, 4364.8149414062f, 4375.6557617188f, 4386.5034179688f, +4397.3574218750f, 4408.2187500000f, 4419.0864257812f, 4429.9609375000f, +4440.8417968750f, 4451.7294921875f, 4462.6240234375f, 4473.5249023438f, +4484.4326171875f, 4495.3471679688f, 4506.2680664062f, 4517.1958007812f, +4528.1298828125f, 4539.0708007812f, 4550.0180664062f, 4560.9721679688f, +4571.9326171875f, 4582.8999023438f, 4593.8735351562f, 4604.8540039062f, +4615.8408203125f, 4626.8339843750f, 4637.8339843750f, 4648.8403320312f, +4659.8535156250f, 4670.8725585938f, 4681.8989257812f, 4692.9311523438f, +4703.9702148438f, 4715.0156250000f, 4726.0673828125f, 4737.1259765625f, +4748.1904296875f, 4759.2617187500f, 4770.3398437500f, 4781.4238281250f, +4792.5141601562f, 4803.6113281250f, 4814.7148437500f, 4825.8247070312f, +4836.9409179688f, 4848.0634765625f, 4859.1923828125f, 4870.3276367188f, +4881.4692382812f, 4892.6176757812f, 4903.7719726562f, 4914.9326171875f, +4926.1000976562f, 4937.2734375000f, 4948.4531250000f, 4959.6391601562f, +4970.8315429688f, 4982.0302734375f, 4993.2353515625f, 5004.4467773438f, +5015.6640625000f, 5026.8881835938f, 5038.1181640625f, 5049.3544921875f, +5060.5971679688f, 5071.8461914062f, 5083.1010742188f, 5094.3627929688f, +5105.6303710938f, 5116.9042968750f, 5128.1840820312f, 5139.4702148438f, +5150.7626953125f, 5162.0615234375f, 5173.3662109375f, 5184.6772460938f, +5195.9946289062f, 5207.3178710938f, 5218.6469726562f, 5229.9829101562f, +5241.3247070312f, 5252.6723632812f, 5264.0268554688f, 5275.3867187500f, +5286.7529296875f, 5298.1254882812f, 5309.5039062500f, 5320.8886718750f, +5332.2792968750f, 5343.6762695312f, 5355.0791015625f, 5366.4882812500f, +5377.9028320312f, 5389.3242187500f, 5400.7514648438f, 5412.1845703125f, +5423.6235351562f, 5435.0688476562f, 5446.5200195312f, 5457.9775390625f, +5469.4409179688f, 5480.9101562500f, 5492.3857421875f, 5503.8666992188f, +5515.3540039062f, 5526.8476562500f, 5538.3466796875f, 5549.8520507812f, +5561.3632812500f, 5572.8803710938f, 5584.4038085938f, 5595.9326171875f, +5607.4677734375f, 5619.0087890625f, 5630.5556640625f, 5642.1083984375f, +5653.6669921875f, 5665.2319335938f, 5676.8022460938f, 5688.3789062500f, +5699.9609375000f, 5711.5493164062f, 5723.1435546875f, 5734.7436523438f, +5746.3491210938f, 5757.9609375000f, 5769.5786132812f, 5781.2021484375f, +5792.8315429688f, 5804.4663085938f, 5816.1074218750f, 5827.7543945312f, +5839.4067382812f, 5851.0649414062f, 5862.7294921875f, 5874.3994140625f, +5886.0751953125f, 5897.7568359375f, 5909.4443359375f, 5921.1376953125f, +5932.8364257812f, 5944.5410156250f, 5956.2514648438f, 5967.9677734375f, +5979.6899414062f, 5991.4174804688f, 6003.1513671875f, 6014.8906250000f, +6026.6352539062f, 6038.3862304688f, 6050.1425781250f, 6061.9047851562f, +6073.6723632812f, 6085.4458007812f, 6097.2250976562f, 6109.0102539062f, +6120.8007812500f, 6132.5971679688f, 6144.3989257812f, 6156.2065429688f, +6168.0200195312f, 6179.8388671875f, 6191.6635742188f, 6203.4936523438f, +6215.3295898438f, 6227.1713867188f, 6239.0185546875f, 6250.8710937500f, +6262.7294921875f, 6274.5937500000f, 6286.4633789062f, 6298.3383789062f, +6310.2192382812f, 6322.1059570312f, 6333.9980468750f, 6345.8955078125f, +6357.7988281250f, 6369.7075195312f, 6381.6215820312f, 6393.5415039062f, +6405.4672851562f, 6417.3984375000f, 6429.3349609375f, 6441.2768554688f, +6453.2246093750f, 6465.1777343750f, 6477.1362304688f, 6489.1005859375f, +6501.0703125000f, 6513.0454101562f, 6525.0263671875f, 6537.0126953125f, +6549.0043945312f, 6561.0019531250f, 6573.0043945312f, 6585.0126953125f, +6597.0263671875f, 6609.0454101562f, 6621.0703125000f, 6633.1000976562f, +6645.1357421875f, 6657.1767578125f, 6669.2231445312f, 6681.2753906250f, +6693.3325195312f, 6705.3955078125f, 6717.4633789062f, 6729.5371093750f, +6741.6162109375f, 6753.7006835938f, 6765.7905273438f, 6777.8857421875f, +6789.9863281250f, 6802.0927734375f, 6814.2041015625f, 6826.3208007812f, +6838.4428710938f, 6850.5708007812f, 6862.7036132812f, 6874.8417968750f, +6886.9858398438f, 6899.1347656250f, 6911.2890625000f, 6923.4487304688f, +6935.6137695312f, 6947.7841796875f, 6959.9599609375f, 6972.1411132812f, +6984.3276367188f, 6996.5190429688f, 7008.7163085938f, 7020.9184570312f, +7033.1259765625f, 7045.3388671875f, 7057.5571289062f, 7069.7807617188f, +7082.0097656250f, 7094.2436523438f, 7106.4829101562f, 7118.7275390625f, +7130.9775390625f, 7143.2329101562f, 7155.4931640625f, 7167.7587890625f, +7180.0297851562f, 7192.3061523438f, 7204.5874023438f, 7216.8740234375f, +7229.1660156250f, 7241.4628906250f, 7253.7656250000f, 7266.0732421875f, +7278.3857421875f, 7290.7036132812f, 7303.0268554688f, 7315.3554687500f, +7327.6889648438f, 7340.0278320312f, 7352.3715820312f, 7364.7207031250f, +7377.0751953125f, 7389.4345703125f, 7401.7993164062f, 7414.1689453125f, +7426.5439453125f, 7438.9243164062f, 7451.3095703125f, 7463.7001953125f, +7476.0957031250f, 7488.4965820312f, 7500.9023437500f, 7513.3134765625f, +7525.7294921875f, 7538.1503906250f, 7550.5771484375f, 7563.0083007812f, +7575.4453125000f, 7587.8867187500f, 7600.3334960938f, 7612.7856445312f, +7625.2426757812f, 7637.7045898438f, 7650.1718750000f, 7662.6440429688f, +7675.1215820312f, 7687.6040039062f, 7700.0913085938f, 7712.5839843750f, +7725.0815429688f, 7737.5839843750f, 7750.0917968750f, 7762.6044921875f, +7775.1225585938f, 7787.6450195312f, 7800.1728515625f, 7812.7060546875f, +7825.2441406250f, 7837.7871093750f, 7850.3349609375f, 7862.8876953125f, +7875.4458007812f, 7888.0087890625f, 7900.5771484375f, 7913.1499023438f, +7925.7280273438f, 7938.3110351562f, 7950.8989257812f, 7963.4921875000f, +7976.0898437500f, 7988.6928710938f, 8001.3007812500f, 8013.9135742188f, +8026.5317382812f, 8039.1542968750f, 8051.7822265625f, 8064.4150390625f, +8077.0527343750f, 8089.6953125000f, 8102.3427734375f, 8114.9951171875f, +8127.6528320312f, 8140.3149414062f, 8152.9824218750f, 8165.6542968750f, +8178.3315429688f, 8191.0136718750f, 8203.7001953125f, 8216.3925781250f, +8229.0888671875f, 8241.7910156250f, 8254.4970703125f, 8267.2089843750f, +8279.9248046875f, 8292.6464843750f, 8305.3730468750f, 8318.1035156250f, +8330.8398437500f, 8343.5800781250f, 8356.3261718750f, 8369.0761718750f, +8381.8310546875f, 8394.5917968750f, 8407.3564453125f, 8420.1269531250f, +8432.9013671875f, 8445.6806640625f, 8458.4648437500f, 8471.2539062500f, +8484.0488281250f, 8496.8476562500f, 8509.6513671875f, 8522.4589843750f, +8535.2724609375f, 8548.0908203125f, 8560.9140625000f, 8573.7412109375f, +8586.5742187500f, 8599.4111328125f, 8612.2539062500f, 8625.1005859375f, +8637.9521484375f, 8650.8085937500f, 8663.6699218750f, 8676.5361328125f, +8689.4072265625f, 8702.2822265625f, 8715.1630859375f, 8728.0478515625f, +8740.9375000000f, 8753.8320312500f, 8766.7314453125f, 8779.6357421875f, +8792.5449218750f, 8805.4580078125f, 8818.3769531250f, 8831.2998046875f, +8844.2275390625f, 8857.1601562500f, 8870.0976562500f, 8883.0390625000f, +8895.9853515625f, 8908.9375000000f, 8921.8935546875f, 8934.8544921875f, +8947.8193359375f, 8960.7900390625f, 8973.7646484375f, 8986.7441406250f, +8999.7285156250f, 9012.7177734375f, 9025.7109375000f, 9038.7099609375f, +9051.7128906250f, 9064.7197265625f, 9077.7324218750f, 9090.7500000000f, +9103.7714843750f, 9116.7978515625f, 9129.8281250000f, 9142.8642578125f, +9155.9042968750f, 9168.9492187500f, 9181.9990234375f, 9195.0527343750f, +9208.1123046875f, 9221.1757812500f, 9234.2431640625f, 9247.3164062500f, +9260.3935546875f, 9273.4755859375f, 9286.5625000000f, 9299.6533203125f, +9312.7490234375f, 9325.8496093750f, 9338.9541015625f, 9352.0644531250f, +9365.1787109375f, 9378.2968750000f, 9391.4208984375f, 9404.5488281250f, +9417.6806640625f, 9430.8183593750f, 9443.9599609375f, 9457.1064453125f, +9470.2568359375f, 9483.4121093750f, 9496.5722656250f, 9509.7373046875f, +9522.9062500000f, 9536.0800781250f, 9549.2578125000f, 9562.4404296875f, +9575.6279296875f, 9588.8193359375f, 9602.0166015625f, 9615.2167968750f, +9628.4228515625f, 9641.6328125000f, 9654.8466796875f, 9668.0664062500f, +9681.2890625000f, 9694.5175781250f, 9707.7500000000f, 9720.9873046875f, +9734.2285156250f, 9747.4746093750f, 9760.7255859375f, 9773.9804687500f, +9787.2402343750f, 9800.5039062500f, 9813.7724609375f, 9827.0458984375f, +9840.3232421875f, 9853.6054687500f, 9866.8916015625f, 9880.1826171875f, +9893.4785156250f, 9906.7783203125f, 9920.0830078125f, 9933.3916015625f, +9946.7050781250f, 9960.0224609375f, 9973.3447265625f, 9986.6718750000f, +10000.0029296875f, 10013.3378906250f, 10026.6787109375f, 10040.0224609375f, +10053.3720703125f, 10066.7246093750f, 10080.0830078125f, 10093.4453125000f, +10106.8115234375f, 10120.1826171875f, 10133.5576171875f, 10146.9375000000f, +10160.3222656250f, 10173.7109375000f, 10187.1035156250f, 10200.5009765625f, +10213.9033203125f, 10227.3095703125f, 10240.7197265625f, 10254.1347656250f, +10267.5546875000f, 10280.9785156250f, 10294.4062500000f, 10307.8388671875f, +10321.2763671875f, 10334.7177734375f, 10348.1630859375f, 10361.6132812500f, +10375.0673828125f, 10388.5263671875f, 10401.9892578125f, 10415.4570312500f, +10428.9287109375f, 10442.4052734375f, 10455.8857421875f, 10469.3710937500f, +10482.8603515625f, 10496.3535156250f, 10509.8515625000f, 10523.3544921875f, +10536.8603515625f, 10550.3720703125f, 10563.8867187500f, 10577.4062500000f, +10590.9306640625f, 10604.4589843750f, 10617.9912109375f, 10631.5283203125f, +10645.0693359375f, 10658.6152343750f, 10672.1650390625f, 10685.7187500000f, +10699.2773437500f, 10712.8398437500f, 10726.4072265625f, 10739.9785156250f, +10753.5537109375f, 10767.1337890625f, 10780.7177734375f, 10794.3066406250f, +10807.8984375000f, 10821.4960937500f, 10835.0966796875f, 10848.7031250000f, +10862.3125000000f, 10875.9267578125f, 10889.5449218750f, 10903.1669921875f, +10916.7939453125f, 10930.4257812500f, 10944.0605468750f, 10957.7001953125f, +10971.3437500000f, 10984.9921875000f, 10998.6445312500f, 11012.3007812500f, +11025.9619140625f, 11039.6269531250f, 11053.2958984375f, 11066.9697265625f, +11080.6474609375f, 11094.3291015625f, 11108.0156250000f, 11121.7060546875f, +11135.4003906250f, 11149.0986328125f, 11162.8017578125f, 11176.5087890625f, +11190.2207031250f, 11203.9365234375f, 11217.6562500000f, 11231.3798828125f, +11245.1083984375f, 11258.8408203125f, 11272.5771484375f, 11286.3183593750f, +11300.0625000000f, 11313.8115234375f, 11327.5654296875f, 11341.3232421875f, +11355.0839843750f, 11368.8505859375f, 11382.6201171875f, 11396.3945312500f, +11410.1728515625f, 11423.9550781250f, 11437.7421875000f, 11451.5322265625f, +11465.3271484375f, 11479.1269531250f, 11492.9296875000f, 11506.7373046875f, +11520.5488281250f, 11534.3642578125f, 11548.1845703125f, 11562.0087890625f, +11575.8359375000f, 11589.6689453125f, 11603.5048828125f, 11617.3457031250f, +11631.1904296875f, 11645.0390625000f, 11658.8916015625f, 11672.7480468750f, +11686.6093750000f, 11700.4746093750f, 11714.3437500000f, 11728.2177734375f, +11742.0947265625f, 11755.9765625000f, 11769.8623046875f, 11783.7519531250f, +11797.6455078125f, 11811.5439453125f, 11825.4462890625f, 11839.3525390625f, +11853.2626953125f, 11867.1767578125f, 11881.0947265625f, 11895.0175781250f, +11908.9443359375f, 11922.8750000000f, 11936.8095703125f, 11950.7480468750f, +11964.6914062500f, 11978.6376953125f, 11992.5888671875f, 12006.5439453125f, +12020.5029296875f, 12034.4658203125f, 12048.4335937500f, 12062.4042968750f, +12076.3798828125f, 12090.3593750000f, 12104.3427734375f, 12118.3300781250f, +12132.3212890625f, 12146.3164062500f, 12160.3164062500f, 12174.3193359375f, +12188.3271484375f, 12202.3388671875f, 12216.3544921875f, 12230.3740234375f, +12244.3974609375f, 12258.4257812500f, 12272.4570312500f, 12286.4931640625f, +12300.5322265625f, 12314.5761718750f, 12328.6240234375f, 12342.6757812500f, +12356.7314453125f, 12370.7910156250f, 12384.8544921875f, 12398.9228515625f, +12412.9941406250f, 12427.0703125000f, 12441.1494140625f, 12455.2333984375f, +12469.3212890625f, 12483.4121093750f, 12497.5078125000f, 12511.6074218750f, +12525.7109375000f, 12539.8183593750f, 12553.9296875000f, 12568.0458984375f, +12582.1650390625f, 12596.2880859375f, 12610.4160156250f, 12624.5468750000f, +12638.6826171875f, 12652.8212890625f, 12666.9648437500f, 12681.1113281250f, +12695.2626953125f, 12709.4179687500f, 12723.5771484375f, 12737.7392578125f, +12751.9062500000f, 12766.0771484375f, 12780.2519531250f, 12794.4306640625f, +12808.6132812500f, 12822.7998046875f, 12836.9902343750f, 12851.1845703125f, +12865.3828125000f, 12879.5849609375f, 12893.7910156250f, 12908.0009765625f, +12922.2148437500f, 12936.4326171875f, 12950.6542968750f, 12964.8798828125f, +12979.1093750000f, 12993.3427734375f, 13007.5800781250f, 13021.8212890625f, +13036.0664062500f, 13050.3154296875f, 13064.5683593750f, 13078.8251953125f, +13093.0859375000f, 13107.3505859375f, 13121.6191406250f, 13135.8906250000f, +13150.1669921875f, 13164.4472656250f, 13178.7314453125f, 13193.0195312500f, +13207.3105468750f, 13221.6064453125f, 13235.9062500000f, 13250.2089843750f, +13264.5166015625f, 13278.8271484375f, 13293.1425781250f, 13307.4609375000f, +13321.7832031250f, 13336.1103515625f, 13350.4404296875f, 13364.7744140625f, +13379.1123046875f, 13393.4541015625f, 13407.7998046875f, 13422.1494140625f, +13436.5029296875f, 13450.8593750000f, 13465.2207031250f, 13479.5849609375f, +13493.9541015625f, 13508.3261718750f, 13522.7031250000f, 13537.0830078125f, +13551.4667968750f, 13565.8544921875f, 13580.2460937500f, 13594.6416015625f, +13609.0410156250f, 13623.4433593750f, 13637.8505859375f, 13652.2617187500f, +13666.6757812500f, 13681.0937500000f, 13695.5156250000f, 13709.9414062500f, +13724.3710937500f, 13738.8046875000f, 13753.2421875000f, 13767.6826171875f, +13782.1279296875f, 13796.5761718750f, 13811.0283203125f, 13825.4843750000f, +13839.9443359375f, 13854.4082031250f, 13868.8759765625f, 13883.3466796875f, +13897.8222656250f, 13912.3007812500f, 13926.7832031250f, 13941.2695312500f, +13955.7597656250f, 13970.2539062500f, 13984.7509765625f, 13999.2529296875f, +14013.7578125000f, 14028.2666015625f, 14042.7792968750f, 14057.2958984375f, +14071.8154296875f, 14086.3398437500f, 14100.8671875000f, 14115.3984375000f, +14129.9335937500f, 14144.4726562500f, 14159.0156250000f, 14173.5615234375f, +14188.1113281250f, 14202.6650390625f, 14217.2226562500f, 14231.7841796875f, +14246.3486328125f, 14260.9179687500f, 14275.4902343750f, 14290.0664062500f, +14304.6464843750f, 14319.2294921875f, 14333.8164062500f, 14348.4082031250f, +14363.0029296875f, 14377.6005859375f, 14392.2031250000f, 14406.8085937500f, +14421.4179687500f, 14436.0312500000f, 14450.6484375000f, 14465.2695312500f, +14479.8935546875f, 14494.5214843750f, 14509.1533203125f, 14523.7880859375f, +14538.4277343750f, 14553.0703125000f, 14567.7167968750f, 14582.3671875000f, +14597.0205078125f, 14611.6777343750f, 14626.3388671875f, 14641.0039062500f, +14655.6728515625f, 14670.3447265625f, 14685.0205078125f, 14699.7001953125f, +14714.3837890625f, 14729.0703125000f, 14743.7607421875f, 14758.4550781250f, +14773.1523437500f, 14787.8544921875f, 14802.5595703125f, 14817.2685546875f, +14831.9804687500f, 14846.6962890625f, 14861.4160156250f, 14876.1396484375f, +14890.8671875000f, 14905.5976562500f, 14920.3320312500f, 14935.0693359375f, +14949.8115234375f, 14964.5566406250f, 14979.3056640625f, 14994.0576171875f, +15008.8144531250f, 15023.5742187500f, 15038.3369140625f, 15053.1044921875f, +15067.8750000000f, 15082.6494140625f, 15097.4267578125f, 15112.2080078125f, +15126.9931640625f, 15141.7822265625f, 15156.5742187500f, 15171.3701171875f, +15186.1699218750f, 15200.9726562500f, 15215.7792968750f, 15230.5898437500f, +15245.4042968750f, 15260.2216796875f, 15275.0429687500f, 15289.8671875000f, +15304.6962890625f, 15319.5273437500f, 15334.3632812500f, 15349.2021484375f, +15364.0449218750f, 15378.8916015625f, 15393.7412109375f, 15408.5947265625f, +15423.4521484375f, 15438.3125000000f, 15453.1767578125f, 15468.0439453125f, +15482.9160156250f, 15497.7900390625f, 15512.6689453125f, 15527.5507812500f, +15542.4365234375f, 15557.3261718750f, 15572.2187500000f, 15587.1152343750f, +15602.0146484375f, 15616.9179687500f, 15631.8251953125f, 15646.7353515625f, +15661.6494140625f, 15676.5673828125f, 15691.4882812500f, 15706.4130859375f, +15721.3417968750f, 15736.2734375000f, 15751.2089843750f, 15766.1474609375f, +15781.0898437500f, 15796.0361328125f, 15810.9853515625f, 15825.9384765625f, +15840.8955078125f, 15855.8554687500f, 15870.8193359375f, 15885.7861328125f, +15900.7568359375f, 15915.7314453125f, 15930.7089843750f, 15945.6904296875f, +15960.6748046875f, 15975.6630859375f, 15990.6552734375f, 16005.6503906250f, +16020.6494140625f, 16035.6513671875f, 16050.6572265625f, 16065.6669921875f, +16080.6796875000f, 16095.6962890625f, 16110.7158203125f, 16125.7392578125f, +16140.7666015625f, 16155.7968750000f, 16170.8310546875f, 16185.8681640625f, +16200.9091796875f, 16215.9531250000f, 16231.0009765625f, 16246.0527343750f, +16261.1074218750f, 16276.1660156250f, 16291.2275390625f, 16306.2929687500f, +16321.3613281250f, 16336.4335937500f, 16351.5097656250f, 16366.5888671875f, +16381.6708984375f, 16396.7578125000f, 16411.8476562500f, 16426.9394531250f, +16442.0371093750f, 16457.1367187500f, 16472.2402343750f, 16487.3476562500f, +16502.4570312500f, 16517.5722656250f, 16532.6894531250f, 16547.8105468750f, +16562.9335937500f, 16578.0605468750f, 16593.1933593750f, 16608.3281250000f, +16623.4648437500f, 16638.6074218750f, 16653.7519531250f, 16668.9003906250f, +16684.0527343750f, 16699.2070312500f, 16714.3652343750f, 16729.5273437500f, +16744.6933593750f, 16759.8632812500f, 16775.0351562500f, 16790.2109375000f, +16805.3906250000f, 16820.5722656250f, 16835.7597656250f, 16850.9492187500f, +16866.1425781250f, 16881.3378906250f, 16896.5371093750f, 16911.7421875000f, +16926.9472656250f, 16942.1582031250f, 16957.3710937500f, 16972.5878906250f, +16987.8085937500f, 17003.0332031250f, 17018.2597656250f, 17033.4902343750f, +17048.7246093750f, 17063.9609375000f, 17079.2031250000f, 17094.4472656250f, +17109.6933593750f, 17124.9453125000f, 17140.1992187500f, 17155.4570312500f, +17170.7187500000f, 17185.9824218750f, 17201.2519531250f, 17216.5234375000f, +17231.7968750000f, 17247.0761718750f, 17262.3574218750f, 17277.6425781250f, +17292.9296875000f, 17308.2207031250f, 17323.5156250000f, 17338.8144531250f, +17354.1171875000f, 17369.4218750000f, 17384.7304687500f, 17400.0429687500f, +17415.3574218750f, 17430.6757812500f, 17445.9980468750f, 17461.3242187500f, +17476.6523437500f, 17491.9843750000f, 17507.3203125000f, 17522.6582031250f, +17538.0000000000f, 17553.3457031250f, 17568.6953125000f, 17584.0468750000f, +17599.4023437500f, 17614.7617187500f, 17630.1250000000f, 17645.4902343750f, +17660.8593750000f, 17676.2304687500f, 17691.6074218750f, 17706.9863281250f, +17722.3671875000f, 17737.7539062500f, 17753.1425781250f, 17768.5351562500f, +17783.9296875000f, 17799.3300781250f, 17814.7324218750f, 17830.1367187500f, +17845.5468750000f, 17860.9589843750f, 17876.3750000000f, 17891.7929687500f, +17907.2167968750f, 17922.6406250000f, 17938.0703125000f, 17953.5019531250f, +17968.9375000000f, 17984.3769531250f, 17999.8183593750f, 18015.2656250000f, +18030.7128906250f, 18046.1660156250f, 18061.6210937500f, 18077.0800781250f, +18092.5429687500f, 18108.0078125000f, 18123.4765625000f, 18138.9472656250f, +18154.4238281250f, 18169.9023437500f, 18185.3828125000f, 18200.8691406250f, +18216.3574218750f, 18231.8496093750f, 18247.3437500000f, 18262.8417968750f, +18278.3437500000f, 18293.8496093750f, 18309.3574218750f, 18324.8691406250f, +18340.3828125000f, 18355.9023437500f, 18371.4218750000f, 18386.9472656250f, +18402.4746093750f, 18418.0058593750f, 18433.5410156250f, 18449.0781250000f, +18464.6191406250f, 18480.1640625000f, 18495.7109375000f, 18511.2617187500f, +18526.8164062500f, 18542.3730468750f, 18557.9335937500f, 18573.4980468750f, +18589.0644531250f, 18604.6347656250f, 18620.2089843750f, 18635.7851562500f, +18651.3652343750f, 18666.9492187500f, 18682.5351562500f, 18698.1250000000f, +18713.7187500000f, 18729.3144531250f, 18744.9160156250f, 18760.5175781250f, +18776.1250000000f, 18791.7343750000f, 18807.3457031250f, 18822.9609375000f, +18838.5800781250f, 18854.2031250000f, 18869.8281250000f, 18885.4570312500f, +18901.0898437500f, 18916.7246093750f, 18932.3632812500f, 18948.0058593750f, +18963.6503906250f, 18979.2988281250f, 18994.9492187500f, 19010.6035156250f, +19026.2617187500f, 19041.9238281250f, 19057.5878906250f, 19073.2558593750f, +19088.9257812500f, 19104.5996093750f, 19120.2773437500f, 19135.9570312500f, +19151.6406250000f, 19167.3281250000f, 19183.0175781250f, 19198.7109375000f, +19214.4082031250f, 19230.1074218750f, 19245.8105468750f, 19261.5156250000f, +19277.2246093750f, 19292.9375000000f, 19308.6542968750f, 19324.3730468750f, +19340.0937500000f, 19355.8203125000f, 19371.5488281250f, 19387.2792968750f, +19403.0156250000f, 19418.7519531250f, 19434.4941406250f, 19450.2382812500f, +19465.9863281250f, 19481.7363281250f, 19497.4902343750f, 19513.2480468750f, +19529.0078125000f, 19544.7714843750f, 19560.5390625000f, 19576.3085937500f, +19592.0820312500f, 19607.8574218750f, 19623.6367187500f, 19639.4199218750f, +19655.2050781250f, 19670.9941406250f, 19686.7851562500f, 19702.5820312500f, +19718.3789062500f, 19734.1816406250f, 19749.9863281250f, 19765.7929687500f, +19781.6054687500f, 19797.4199218750f, 19813.2363281250f, 19829.0566406250f, +19844.8808593750f, 19860.7070312500f, 19876.5371093750f, 19892.3710937500f, +19908.2070312500f, 19924.0468750000f, 19939.8886718750f, 19955.7343750000f, +19971.5839843750f, 19987.4355468750f, 20003.2910156250f, 20019.1484375000f, +20035.0097656250f, 20050.8750000000f, 20066.7421875000f, 20082.6132812500f, +20098.4882812500f, 20114.3652343750f, 20130.2460937500f, 20146.1289062500f, +20162.0156250000f, 20177.9042968750f, 20193.7968750000f, 20209.6933593750f, +20225.5937500000f, 20241.4941406250f, 20257.4003906250f, 20273.3085937500f, +20289.2207031250f, 20305.1347656250f, 20321.0527343750f, 20336.9746093750f, +20352.8984375000f, 20368.8242187500f, 20384.7558593750f, 20400.6875000000f, +20416.6250000000f, 20432.5644531250f, 20448.5078125000f, 20464.4531250000f, +20480.4023437500f, 20496.3535156250f, 20512.3085937500f, 20528.2675781250f, +20544.2285156250f, 20560.1933593750f, 20576.1601562500f, 20592.1308593750f, +20608.1054687500f, 20624.0820312500f, 20640.0625000000f, 20656.0449218750f, +20672.0312500000f, 20688.0195312500f, 20704.0117187500f, 20720.0078125000f, +20736.0058593750f, 20752.0078125000f, 20768.0117187500f, 20784.0195312500f, +20800.0312500000f, 20816.0449218750f, 20832.0625000000f, 20848.0820312500f, +20864.1054687500f, 20880.1308593750f, 20896.1601562500f, 20912.1933593750f, +20928.2285156250f, 20944.2656250000f, 20960.3085937500f, 20976.3535156250f, +20992.4003906250f, 21008.4511718750f, 21024.5058593750f, 21040.5625000000f, +21056.6210937500f, 21072.6855468750f, 21088.7519531250f, 21104.8203125000f, +21120.8925781250f, 21136.9667968750f, 21153.0468750000f, 21169.1269531250f, +21185.2109375000f, 21201.2988281250f, 21217.3906250000f, 21233.4843750000f, +21249.5800781250f, 21265.6796875000f, 21281.7832031250f, 21297.8886718750f, +21313.9980468750f, 21330.1093750000f, 21346.2246093750f, 21362.3417968750f, +21378.4628906250f, 21394.5878906250f, 21410.7148437500f, 21426.8437500000f, +21442.9765625000f, 21459.1132812500f, 21475.2519531250f, 21491.3945312500f, +21507.5410156250f, 21523.6894531250f, 21539.8398437500f, 21555.9941406250f, +21572.1523437500f, 21588.3125000000f, 21604.4746093750f, 21620.6425781250f, +21636.8125000000f, 21652.9843750000f, 21669.1601562500f, 21685.3378906250f, +21701.5195312500f, 21717.7050781250f, 21733.8925781250f, 21750.0820312500f, +21766.2753906250f, 21782.4726562500f, 21798.6718750000f, 21814.8750000000f, +21831.0800781250f, 21847.2890625000f, 21863.5019531250f, 21879.7167968750f, +21895.9335937500f, 21912.1542968750f, 21928.3789062500f, 21944.6054687500f, +21960.8339843750f, 21977.0664062500f, 21993.3027343750f, 22009.5410156250f, +22025.7832031250f, 22042.0273437500f, 22058.2753906250f, 22074.5273437500f, +22090.7792968750f, 22107.0371093750f, 22123.2968750000f, 22139.5585937500f, +22155.8242187500f, 22172.0937500000f, 22188.3652343750f, 22204.6386718750f, +22220.9179687500f, 22237.1972656250f, 22253.4804687500f, 22269.7675781250f, +22286.0566406250f, 22302.3496093750f, 22318.6445312500f, 22334.9433593750f, +22351.2441406250f, 22367.5488281250f, 22383.8574218750f, 22400.1660156250f, +22416.4804687500f, 22432.7968750000f, 22449.1152343750f, 22465.4375000000f, +22481.7636718750f, 22498.0917968750f, 22514.4218750000f, 22530.7558593750f, +22547.0937500000f, 22563.4335937500f, 22579.7753906250f, 22596.1210937500f, +22612.4707031250f, 22628.8222656250f, 22645.1777343750f, 22661.5351562500f, +22677.8964843750f, 22694.2597656250f, 22710.6250000000f, 22726.9960937500f, +22743.3671875000f, 22759.7421875000f, 22776.1210937500f, 22792.5019531250f, +22808.8867187500f, 22825.2734375000f, 22841.6640625000f, 22858.0566406250f, +22874.4531250000f, 22890.8515625000f, 22907.2539062500f, 22923.6582031250f, +22940.0664062500f, 22956.4765625000f, 22972.8906250000f, 22989.3066406250f, +23005.7265625000f, 23022.1484375000f, 23038.5742187500f, 23055.0019531250f, +23071.4335937500f, 23087.8671875000f, 23104.3046875000f, 23120.7460937500f, +23137.1875000000f, 23153.6347656250f, 23170.0820312500f, 23186.5332031250f, +23202.9882812500f, 23219.4453125000f, 23235.9062500000f, 23252.3691406250f, +23268.8359375000f, 23285.3046875000f, 23301.7773437500f, 23318.2519531250f, +23334.7304687500f, 23351.2109375000f, 23367.6953125000f, 23384.1816406250f, +23400.6699218750f, 23417.1640625000f, 23433.6582031250f, 23450.1562500000f, +23466.6582031250f, 23483.1621093750f, 23499.6679687500f, 23516.1777343750f, +23532.6914062500f, 23549.2070312500f, 23565.7246093750f, 23582.2460937500f, +23598.7714843750f, 23615.2988281250f, 23631.8281250000f, 23648.3613281250f, +23664.8964843750f, 23681.4355468750f, 23697.9785156250f, 23714.5214843750f, +23731.0703125000f, 23747.6191406250f, 23764.1738281250f, 23780.7285156250f, +23797.2890625000f, 23813.8496093750f, 23830.4140625000f, 23846.9824218750f, +23863.5527343750f, 23880.1269531250f, 23896.7031250000f, 23913.2812500000f, +23929.8632812500f, 23946.4492187500f, 23963.0351562500f, 23979.6269531250f, +23996.2207031250f, 24012.8164062500f, 24029.4160156250f, 24046.0175781250f, +24062.6230468750f, 24079.2304687500f, 24095.8417968750f, 24112.4550781250f, +24129.0703125000f, 24145.6894531250f, 24162.3125000000f, 24178.9375000000f, +24195.5644531250f, 24212.1953125000f, 24228.8300781250f, 24245.4648437500f, +24262.1054687500f, 24278.7480468750f, 24295.3925781250f, 24312.0390625000f, +24328.6914062500f, 24345.3437500000f, 24362.0000000000f, 24378.6601562500f, +24395.3222656250f, 24411.9863281250f, 24428.6542968750f, 24445.3242187500f, +24461.9980468750f, 24478.6738281250f, 24495.3535156250f, 24512.0351562500f, +24528.7207031250f, 24545.4082031250f, 24562.0976562500f, 24578.7910156250f, +24595.4882812500f, 24612.1875000000f, 24628.8886718750f, 24645.5937500000f, +24662.3007812500f, 24679.0117187500f, 24695.7246093750f, 24712.4394531250f, +24729.1582031250f, 24745.8808593750f, 24762.6054687500f, 24779.3320312500f, +24796.0625000000f, 24812.7949218750f, 24829.5312500000f, 24846.2695312500f, +24863.0117187500f, 24879.7558593750f, 24896.5019531250f, 24913.2519531250f, +24930.0039062500f, 24946.7597656250f, 24963.5175781250f, 24980.2792968750f, +24997.0429687500f, 25013.8105468750f, 25030.5800781250f, 25047.3515625000f, +25064.1269531250f, 25080.9042968750f, 25097.6855468750f, 25114.4687500000f, +25131.2558593750f, 25148.0449218750f, 25164.8359375000f, 25181.6308593750f, +25198.4277343750f, 25215.2285156250f, 25232.0312500000f, 25248.8378906250f, +25265.6464843750f, 25282.4589843750f, 25299.2734375000f, 25316.0898437500f, +25332.9101562500f, 25349.7324218750f, 25366.5585937500f, 25383.3867187500f, +25400.2167968750f, 25417.0507812500f, 25433.8886718750f, 25450.7265625000f, +25467.5703125000f, 25484.4140625000f, 25501.2617187500f, 25518.1132812500f, +25534.9667968750f, 25551.8222656250f, 25568.6816406250f, 25585.5429687500f, +25602.4082031250f, 25619.2753906250f, 25636.1445312500f, 25653.0175781250f, +25669.8925781250f, 25686.7714843750f, 25703.6523437500f, 25720.5371093750f, +25737.4238281250f, 25754.3125000000f, 25771.2050781250f, 25788.0996093750f, +25804.9980468750f, 25821.8984375000f, 25838.8027343750f, 25855.7070312500f, +25872.6171875000f, 25889.5273437500f, 25906.4433593750f, 25923.3593750000f, +25940.2792968750f, 25957.2031250000f, 25974.1269531250f, 25991.0566406250f, +26007.9863281250f, 26024.9199218750f, 26041.8574218750f, 26058.7968750000f, +26075.7382812500f, 26092.6816406250f, 26109.6308593750f, 26126.5800781250f, +26143.5332031250f, 26160.4882812500f, 26177.4472656250f, 26194.4082031250f, +26211.3730468750f, 26228.3398437500f, 26245.3085937500f, 26262.2812500000f, +26279.2558593750f, 26296.2324218750f, 26313.2128906250f, 26330.1972656250f, +26347.1816406250f, 26364.1718750000f, 26381.1621093750f, 26398.1562500000f, +26415.1523437500f, 26432.1523437500f, 26449.1542968750f, 26466.1601562500f, +26483.1679687500f, 26500.1777343750f, 26517.1914062500f, 26534.2070312500f, +26551.2265625000f, 26568.2480468750f, 26585.2714843750f, 26602.2988281250f, +26619.3281250000f, 26636.3593750000f, 26653.3945312500f, 26670.4335937500f, +26687.4726562500f, 26704.5156250000f, 26721.5625000000f, 26738.6113281250f, +26755.6621093750f, 26772.7167968750f, 26789.7734375000f, 26806.8320312500f, +26823.8945312500f, 26840.9589843750f, 26858.0273437500f, 26875.0976562500f, +26892.1699218750f, 26909.2460937500f, 26926.3242187500f, 26943.4062500000f, +26960.4902343750f, 26977.5761718750f, 26994.6660156250f, 27011.7578125000f, +27028.8515625000f, 27045.9492187500f, 27063.0507812500f, 27080.1523437500f, +27097.2578125000f, 27114.3671875000f, 27131.4765625000f, 27148.5917968750f, +27165.7070312500f, 27182.8261718750f, 27199.9472656250f, 27217.0722656250f, +27234.1992187500f, 27251.3300781250f, 27268.4609375000f, 27285.5976562500f, +27302.7343750000f, 27319.8750000000f, 27337.0175781250f, 27354.1640625000f, +27371.3125000000f, 27388.4648437500f, 27405.6191406250f, 27422.7753906250f, +27439.9335937500f, 27457.0957031250f, 27474.2617187500f, 27491.4277343750f, +27508.5976562500f, 27525.7714843750f, 27542.9472656250f, 27560.1250000000f, +27577.3046875000f, 27594.4882812500f, 27611.6757812500f, 27628.8632812500f, +27646.0546875000f, 27663.2500000000f, 27680.4472656250f, 27697.6464843750f, +27714.8476562500f, 27732.0527343750f, 27749.2597656250f, 27766.4707031250f, +27783.6835937500f, 27800.8984375000f, 27818.1171875000f, 27835.3378906250f, +27852.5605468750f, 27869.7871093750f, 27887.0156250000f, 27904.2480468750f, +27921.4824218750f, 27938.7187500000f, 27955.9589843750f, 27973.2011718750f, +27990.4453125000f, 28007.6933593750f, 28024.9433593750f, 28042.1953125000f, +28059.4511718750f, 28076.7089843750f, 28093.9707031250f, 28111.2324218750f, +28128.5000000000f, 28145.7675781250f, 28163.0390625000f, 28180.3125000000f, +28197.5898437500f, 28214.8691406250f, 28232.1503906250f, 28249.4355468750f, +28266.7226562500f, 28284.0117187500f, 28301.3046875000f, 28318.5996093750f, +28335.8984375000f, 28353.1992187500f, 28370.5019531250f, 28387.8066406250f, +28405.1152343750f, 28422.4257812500f, 28439.7402343750f, 28457.0566406250f, +28474.3750000000f, 28491.6972656250f, 28509.0214843750f, 28526.3476562500f, +28543.6757812500f, 28561.0078125000f, 28578.3437500000f, 28595.6816406250f, +28613.0214843750f, 28630.3632812500f, 28647.7089843750f, 28665.0566406250f, +28682.4062500000f, 28699.7597656250f, 28717.1152343750f, 28734.4726562500f, +28751.8339843750f, 28769.1972656250f, 28786.5644531250f, 28803.9335937500f, +28821.3046875000f, 28838.6777343750f, 28856.0546875000f, 28873.4335937500f, +28890.8164062500f, 28908.2011718750f, 28925.5878906250f, 28942.9765625000f, +28960.3691406250f, 28977.7636718750f, 28995.1621093750f, 29012.5625000000f, +29029.9648437500f, 29047.3710937500f, 29064.7773437500f, 29082.1894531250f, +29099.6015625000f, 29117.0175781250f, 29134.4355468750f, 29151.8574218750f, +29169.2812500000f, 29186.7070312500f, 29204.1347656250f, 29221.5664062500f, +29239.0019531250f, 29256.4375000000f, 29273.8769531250f, 29291.3183593750f, +29308.7636718750f, 29326.2109375000f, 29343.6601562500f, 29361.1113281250f, +29378.5664062500f, 29396.0234375000f, 29413.4843750000f, 29430.9472656250f, +29448.4121093750f, 29465.8789062500f, 29483.3496093750f, 29500.8222656250f, +29518.2988281250f, 29535.7753906250f, 29553.2578125000f, 29570.7402343750f, +29588.2265625000f, 29605.7148437500f, 29623.2050781250f, 29640.6992187500f, +29658.1953125000f, 29675.6933593750f, 29693.1953125000f, 29710.6992187500f, +29728.2050781250f, 29745.7148437500f, 29763.2265625000f, 29780.7402343750f, +29798.2578125000f, 29815.7773437500f, 29833.2988281250f, 29850.8222656250f, +29868.3496093750f, 29885.8808593750f, 29903.4121093750f, 29920.9472656250f, +29938.4843750000f, 29956.0234375000f, 29973.5664062500f, 29991.1113281250f, +30008.6601562500f, 30026.2089843750f, 30043.7617187500f, 30061.3183593750f, +30078.8750000000f, 30096.4355468750f, 30114.0000000000f, 30131.5644531250f, +30149.1328125000f, 30166.7031250000f, 30184.2773437500f, 30201.8535156250f, +30219.4316406250f, 30237.0117187500f, 30254.5957031250f, 30272.1816406250f, +30289.7695312500f, 30307.3613281250f, 30324.9550781250f, 30342.5507812500f, +30360.1503906250f, 30377.7519531250f, 30395.3554687500f, 30412.9609375000f, +30430.5703125000f, 30448.1816406250f, 30465.7968750000f, 30483.4140625000f, +30501.0332031250f, 30518.6542968750f, 30536.2792968750f, 30553.9042968750f, +30571.5351562500f, 30589.1660156250f, 30606.8007812500f, 30624.4375000000f, +30642.0781250000f, 30659.7187500000f, 30677.3632812500f, 30695.0117187500f, +30712.6601562500f, 30730.3125000000f, 30747.9687500000f, 30765.6250000000f, +30783.2851562500f, 30800.9472656250f, 30818.6132812500f, 30836.2792968750f, +30853.9492187500f, 30871.6230468750f, 30889.2968750000f, 30906.9746093750f, +30924.6542968750f, 30942.3378906250f, 30960.0234375000f, 30977.7109375000f, +30995.4003906250f, 31013.0937500000f, 31030.7890625000f, 31048.4863281250f, +31066.1855468750f, 31083.8886718750f, 31101.5937500000f, 31119.3027343750f, +31137.0117187500f, 31154.7246093750f, 31172.4414062500f, 31190.1582031250f, +31207.8789062500f, 31225.6015625000f, 31243.3281250000f, 31261.0546875000f, +31278.7851562500f, 31296.5195312500f, 31314.2539062500f, 31331.9921875000f, +31349.7324218750f, 31367.4765625000f, 31385.2226562500f, 31402.9707031250f, +31420.7207031250f, 31438.4726562500f, 31456.2285156250f, 31473.9863281250f, +31491.7480468750f, 31509.5117187500f, 31527.2773437500f, 31545.0449218750f, +31562.8144531250f, 31580.5878906250f, 31598.3632812500f, 31616.1425781250f, +31633.9218750000f, 31651.7050781250f, 31669.4921875000f, 31687.2792968750f, +31705.0703125000f, 31722.8632812500f, 31740.6582031250f, 31758.4570312500f, +31776.2578125000f, 31794.0605468750f, 31811.8652343750f, 31829.6738281250f, +31847.4843750000f, 31865.2968750000f, 31883.1132812500f, 31900.9316406250f, +31918.7519531250f, 31936.5742187500f, 31954.4003906250f, 31972.2285156250f, +31990.0585937500f, 32007.8906250000f, 32025.7265625000f, 32043.5644531250f, +32061.4042968750f, 32079.2480468750f, 32097.0937500000f, 32114.9414062500f, +32132.7910156250f, 32150.6445312500f, 32168.5000000000f, 32186.3574218750f, +32204.2167968750f, 32222.0800781250f, 32239.9453125000f, 32257.8125000000f, +32275.6835937500f, 32293.5566406250f, 32311.4316406250f, 32329.3085937500f, +32347.1875000000f, 32365.0703125000f, 32382.9550781250f, 32400.8437500000f, +32418.7324218750f, 32436.6250000000f, 32454.5195312500f, 32472.4179687500f, +32490.3183593750f, 32508.2207031250f, 32526.1250000000f, 32544.0312500000f, +32561.9414062500f, 32579.8535156250f, 32597.7675781250f, 32615.6855468750f, +32633.6035156250f, 32651.5253906250f, 32669.4511718750f, 32687.3769531250f, +32705.3066406250f, 32723.2382812500f, 32741.1738281250f, 32759.1093750000f, +32777.0468750000f, 32794.9921875000f, 32812.9335937500f, 32830.8789062500f, +32848.8281250000f, 32866.7812500000f, 32884.7343750000f, 32902.6914062500f, +32920.6484375000f, 32938.6132812500f, 32956.5742187500f, 32974.5429687500f, +32992.5078125000f, 33010.4804687500f, 33028.4531250000f, 33046.4296875000f, +33064.4101562500f, 33082.3906250000f, 33100.3710937500f, 33118.3593750000f, +33136.3476562500f, 33154.3359375000f, 33172.3281250000f, 33190.3242187500f, +33208.3242187500f, 33226.3242187500f, 33244.3242187500f, 33262.3320312500f, +33280.3398437500f, 33298.3476562500f, 33316.3632812500f, 33334.3750000000f, +33352.3945312500f, 33370.4140625000f, 33388.4375000000f, 33406.4609375000f, +33424.4882812500f, 33442.5156250000f, 33460.5507812500f, 33478.5820312500f, +33496.6210937500f, 33514.6601562500f, 33532.6992187500f, 33550.7460937500f, +33568.7929687500f, 33586.8398437500f, 33604.8906250000f, 33622.9453125000f, +33641.0039062500f, 33659.0625000000f, 33677.1210937500f, 33695.1835937500f, +33713.2500000000f, 33731.3203125000f, 33749.3906250000f, 33767.4648437500f, +33785.5390625000f, 33803.6171875000f, 33821.6992187500f, 33839.7812500000f, +33857.8671875000f, 33875.9531250000f, 33894.0429687500f, 33912.1367187500f, +33930.2304687500f, 33948.3281250000f, 33966.4296875000f, 33984.5312500000f, +34002.6328125000f, 34020.7421875000f, 34038.8515625000f, 34056.9609375000f, +34075.0781250000f, 34093.1953125000f, 34111.3125000000f, 34129.4335937500f, +34147.5585937500f, 34165.6835937500f, 34183.8125000000f, 34201.9453125000f, +34220.0781250000f, 34238.2148437500f, 34256.3515625000f, 34274.4921875000f, +34292.6367187500f, 34310.7812500000f, 34328.9296875000f, 34347.0781250000f, +34365.2304687500f, 34383.3867187500f, 34401.5429687500f, 34419.7031250000f, +34437.8671875000f, 34456.0312500000f, 34474.1992187500f, 34492.3671875000f, +34510.5390625000f, 34528.7109375000f, 34546.8906250000f, 34565.0703125000f, +34583.2500000000f, 34601.4335937500f, 34619.6210937500f, 34637.8085937500f, +34656.0000000000f, 34674.1914062500f, 34692.3867187500f, 34710.5859375000f, +34728.7851562500f, 34746.9882812500f, 34765.1953125000f, 34783.4023437500f, +34801.6132812500f, 34819.8242187500f, 34838.0390625000f, 34856.2578125000f, +34874.4765625000f, 34892.6992187500f, 34910.9218750000f, 34929.1484375000f, +34947.3789062500f, 34965.6093750000f, 34983.8437500000f, 35002.0781250000f, +35020.3164062500f, 35038.5585937500f, 35056.8007812500f, 35075.0468750000f, +35093.2968750000f, 35111.5468750000f, 35129.8007812500f, 35148.0546875000f, +35166.3125000000f, 35184.5703125000f, 35202.8359375000f, 35221.0976562500f, +35239.3671875000f, 35257.6367187500f, 35275.9062500000f, 35294.1796875000f, +35312.4570312500f, 35330.7382812500f, 35349.0195312500f, 35367.3007812500f, +35385.5859375000f, 35403.8750000000f, 35422.1679687500f, 35440.4609375000f, +35458.7539062500f, 35477.0507812500f, 35495.3515625000f, 35513.6562500000f, +35531.9609375000f, 35550.2656250000f, 35568.5781250000f, 35586.8867187500f, +35605.2031250000f, 35623.5195312500f, 35641.8398437500f, 35660.1601562500f, +35678.4843750000f, 35696.8085937500f, 35715.1367187500f, 35733.4687500000f, +35751.8007812500f, 35770.1367187500f, 35788.4765625000f, 35806.8164062500f, +35825.1562500000f, 35843.5039062500f, 35861.8476562500f, 35880.1992187500f, +35898.5507812500f, 35916.9062500000f, 35935.2617187500f, 35953.6210937500f, +35971.9804687500f, 35990.3437500000f, 36008.7109375000f, 36027.0781250000f, +36045.4492187500f, 36063.8242187500f, 36082.1992187500f, 36100.5781250000f, +36118.9570312500f, 36137.3398437500f, 36155.7226562500f, 36174.1093750000f, +36192.5000000000f, 36210.8906250000f, 36229.2851562500f, 36247.6796875000f, +36266.0820312500f, 36284.4804687500f, 36302.8828125000f, 36321.2890625000f, +36339.6992187500f, 36358.1093750000f, 36376.5195312500f, 36394.9375000000f, +36413.3515625000f, 36431.7734375000f, 36450.1953125000f, 36468.6210937500f, +36487.0468750000f, 36505.4765625000f, 36523.9062500000f, 36542.3398437500f, +36560.7773437500f, 36579.2148437500f, 36597.6562500000f, 36616.0976562500f, +36634.5429687500f, 36652.9921875000f, 36671.4414062500f, 36689.8906250000f, +36708.3476562500f, 36726.8046875000f, 36745.2617187500f, 36763.7226562500f, +36782.1875000000f, 36800.6562500000f, 36819.1210937500f, 36837.5937500000f, +36856.0664062500f, 36874.5429687500f, 36893.0195312500f, 36911.5000000000f, +36929.9804687500f, 36948.4648437500f, 36966.9531250000f, 36985.4414062500f, +37003.9335937500f, 37022.4296875000f, 37040.9257812500f, 37059.4218750000f, +37077.9218750000f, 37096.4257812500f, 37114.9335937500f, 37133.4414062500f, +37151.9492187500f, 37170.4609375000f, 37188.9765625000f, 37207.4921875000f, +37226.0117187500f, 37244.5351562500f, 37263.0585937500f, 37281.5859375000f, +37300.1132812500f, 37318.6445312500f, 37337.1757812500f, 37355.7109375000f, +37374.2500000000f, 37392.7890625000f, 37411.3320312500f, 37429.8789062500f, +37448.4257812500f, 37466.9726562500f, 37485.5234375000f, 37504.0781250000f, +37522.6328125000f, 37541.1914062500f, 37559.7539062500f, 37578.3164062500f, +37596.8828125000f, 37615.4492187500f, 37634.0195312500f, 37652.5898437500f, +37671.1640625000f, 37689.7421875000f, 37708.3203125000f, 37726.9023437500f, +37745.4843750000f, 37764.0703125000f, 37782.6601562500f, 37801.2500000000f, +37819.8437500000f, 37838.4375000000f, 37857.0351562500f, 37875.6328125000f, +37894.2343750000f, 37912.8398437500f, 37931.4453125000f, 37950.0546875000f, +37968.6679687500f, 37987.2812500000f, 38005.8945312500f, 38024.5117187500f, +38043.1328125000f, 38061.7539062500f, 38080.3789062500f, 38099.0078125000f, +38117.6367187500f, 38136.2656250000f, 38154.9023437500f, 38173.5390625000f, +38192.1757812500f, 38210.8164062500f, 38229.4570312500f, 38248.1054687500f, +38266.7500000000f, 38285.4023437500f, 38304.0546875000f, 38322.7070312500f, +38341.3632812500f, 38360.0234375000f, 38378.6835937500f, 38397.3476562500f, +38416.0117187500f, 38434.6796875000f, 38453.3515625000f, 38472.0234375000f, +38490.6953125000f, 38509.3750000000f, 38528.0546875000f, 38546.7343750000f, +38565.4179687500f, 38584.1054687500f, 38602.7929687500f, 38621.4843750000f, +38640.1757812500f, 38658.8710937500f, 38677.5664062500f, 38696.2656250000f, +38714.9687500000f, 38733.6718750000f, 38752.3789062500f, 38771.0859375000f, +38789.7968750000f, 38808.5117187500f, 38827.2265625000f, 38845.9453125000f, +38864.6640625000f, 38883.3867187500f, 38902.1093750000f, 38920.8359375000f, +38939.5664062500f, 38958.2968750000f, 38977.0312500000f, 38995.7656250000f, +39014.5039062500f, 39033.2421875000f, 39051.9843750000f, 39070.7304687500f, +39089.4765625000f, 39108.2265625000f, 39126.9765625000f, 39145.7304687500f, +39164.4882812500f, 39183.2460937500f, 39202.0039062500f, 39220.7695312500f, +39239.5312500000f, 39258.3007812500f, 39277.0703125000f, 39295.8398437500f, +39314.6132812500f, 39333.3906250000f, 39352.1679687500f, 39370.9492187500f, +39389.7304687500f, 39408.5156250000f, 39427.3046875000f, 39446.0937500000f, +39464.8867187500f, 39483.6796875000f, 39502.4765625000f, 39521.2734375000f, +39540.0742187500f, 39558.8789062500f, 39577.6835937500f, 39596.4882812500f, +39615.3007812500f, 39634.1093750000f, 39652.9257812500f, 39671.7421875000f, +39690.5585937500f, 39709.3789062500f, 39728.2031250000f, 39747.0273437500f, +39765.8554687500f, 39784.6875000000f, 39803.5195312500f, 39822.3515625000f, +39841.1875000000f, 39860.0273437500f, 39878.8671875000f, 39897.7109375000f, +39916.5546875000f, 39935.4023437500f, 39954.2539062500f, 39973.1054687500f, +39991.9570312500f, 40010.8164062500f, 40029.6718750000f, 40048.5351562500f, +40067.3984375000f, 40086.2617187500f, 40105.1289062500f, 40124.0000000000f, +40142.8710937500f, 40161.7460937500f, 40180.6210937500f, 40199.5000000000f, +40218.3828125000f, 40237.2656250000f, 40256.1484375000f, 40275.0390625000f, +40293.9257812500f, 40312.8203125000f, 40331.7109375000f, 40350.6093750000f, +40369.5078125000f, 40388.4062500000f, 40407.3125000000f, 40426.2148437500f, +40445.1250000000f, 40464.0312500000f, 40482.9453125000f, 40501.8593750000f, +40520.7734375000f, 40539.6914062500f, 40558.6132812500f, 40577.5351562500f, +40596.4609375000f, 40615.3906250000f, 40634.3164062500f, 40653.2500000000f, +40672.1835937500f, 40691.1210937500f, 40710.0585937500f, 40729.0000000000f, +40747.9414062500f, 40766.8867187500f, 40785.8320312500f, 40804.7812500000f, +40823.7343750000f, 40842.6875000000f, 40861.6445312500f, 40880.6015625000f, +40899.5625000000f, 40918.5234375000f, 40937.4882812500f, 40956.4531250000f, +40975.4257812500f, 40994.3945312500f, 41013.3671875000f, 41032.3437500000f, +41051.3203125000f, 41070.3007812500f, 41089.2851562500f, 41108.2695312500f, +41127.2539062500f, 41146.2421875000f, 41165.2343750000f, 41184.2265625000f, +41203.2226562500f, 41222.2187500000f, 41241.2187500000f, 41260.2226562500f, +41279.2265625000f, 41298.2343750000f, 41317.2421875000f, 41336.2500000000f, +41355.2656250000f, 41374.2812500000f, 41393.2968750000f, 41412.3164062500f, +41431.3359375000f, 41450.3593750000f, 41469.3867187500f, 41488.4140625000f, +41507.4453125000f, 41526.4765625000f, 41545.5117187500f, 41564.5507812500f, +41583.5898437500f, 41602.6289062500f, 41621.6718750000f, 41640.7187500000f, +41659.7656250000f, 41678.8164062500f, 41697.8671875000f, 41716.9218750000f, +41735.9804687500f, 41755.0390625000f, 41774.0976562500f, 41793.1601562500f, +41812.2265625000f, 41831.2929687500f, 41850.3632812500f, 41869.4335937500f, +41888.5078125000f, 41907.5859375000f, 41926.6640625000f, 41945.7421875000f, +41964.8242187500f, 41983.9101562500f, 42002.9960937500f, 42022.0859375000f, +42041.1757812500f, 42060.2695312500f, 42079.3671875000f, 42098.4648437500f, +42117.5625000000f, 42136.6679687500f, 42155.7695312500f, 42174.8750000000f, +42193.9843750000f, 42213.0976562500f, 42232.2070312500f, 42251.3242187500f, +42270.4414062500f, 42289.5585937500f, 42308.6796875000f, 42327.8046875000f, +42346.9296875000f, 42366.0585937500f, 42385.1875000000f, 42404.3203125000f, +42423.4570312500f, 42442.5937500000f, 42461.7304687500f, 42480.8710937500f, +42500.0156250000f, 42519.1601562500f, 42538.3085937500f, 42557.4570312500f, +42576.6093750000f, 42595.7617187500f, 42614.9179687500f, 42634.0781250000f, +42653.2382812500f, 42672.3984375000f, 42691.5664062500f, 42710.7304687500f, +42729.8984375000f, 42749.0703125000f, 42768.2460937500f, 42787.4179687500f, +42806.5976562500f, 42825.7773437500f, 42844.9570312500f, 42864.1445312500f, +42883.3281250000f, 42902.5156250000f, 42921.7070312500f, 42940.8984375000f, +42960.0937500000f, 42979.2929687500f, 42998.4921875000f, 43017.6914062500f, +43036.8945312500f, 43056.1015625000f, 43075.3085937500f, 43094.5156250000f, +43113.7304687500f, 43132.9414062500f, 43152.1601562500f, 43171.3789062500f, +43190.5976562500f, 43209.8203125000f, 43229.0468750000f, 43248.2734375000f, +43267.5000000000f, 43286.7304687500f, 43305.9648437500f, 43325.1992187500f, +43344.4375000000f, 43363.6757812500f, 43382.9179687500f, 43402.1640625000f, +43421.4101562500f, 43440.6562500000f, 43459.9062500000f, 43479.1601562500f, +43498.4140625000f, 43517.6718750000f, 43536.9296875000f, 43556.1914062500f, +43575.4531250000f, 43594.7187500000f, 43613.9882812500f, 43633.2578125000f, +43652.5273437500f, 43671.8007812500f, 43691.0781250000f, 43710.3554687500f, +43729.6367187500f, 43748.9179687500f, 43768.2031250000f, 43787.4882812500f, +43806.7773437500f, 43826.0664062500f, 43845.3593750000f, 43864.6562500000f, +43883.9531250000f, 43903.2500000000f, 43922.5546875000f, 43941.8554687500f, +43961.1601562500f, 43980.4687500000f, 43999.7773437500f, 44019.0898437500f, +44038.4062500000f, 44057.7226562500f, 44077.0390625000f, 44096.3593750000f, +44115.6835937500f, 44135.0078125000f, 44154.3320312500f, 44173.6640625000f, +44192.9921875000f, 44212.3281250000f, 44231.6601562500f, 44251.0000000000f, +44270.3398437500f, 44289.6796875000f, 44309.0234375000f, 44328.3710937500f, +44347.7187500000f, 44367.0664062500f, 44386.4179687500f, 44405.7734375000f, +44425.1289062500f, 44444.4882812500f, 44463.8476562500f, 44483.2109375000f, +44502.5781250000f, 44521.9414062500f, 44541.3125000000f, 44560.6835937500f, +44580.0546875000f, 44599.4296875000f, 44618.8085937500f, 44638.1875000000f, +44657.5703125000f, 44676.9531250000f, 44696.3398437500f, 44715.7265625000f, +44735.1171875000f, 44754.5078125000f, 44773.9023437500f, 44793.3007812500f, +44812.6992187500f, 44832.0976562500f, 44851.5000000000f, 44870.9062500000f, +44890.3125000000f, 44909.7226562500f, 44929.1328125000f, 44948.5468750000f, +44967.9609375000f, 44987.3789062500f, 45006.7968750000f, 45026.2187500000f, +45045.6406250000f, 45065.0664062500f, 45084.4960937500f, 45103.9257812500f, +45123.3593750000f, 45142.7929687500f, 45162.2265625000f, 45181.6640625000f, +45201.1054687500f, 45220.5468750000f, 45239.9921875000f, 45259.4414062500f, +45278.8867187500f, 45298.3398437500f, 45317.7929687500f, 45337.2460937500f, +45356.7031250000f, 45376.1640625000f, 45395.6250000000f, 45415.0859375000f, +45434.5507812500f, 45454.0195312500f, 45473.4882812500f, 45492.9609375000f, +45512.4335937500f, 45531.9101562500f, 45551.3867187500f, 45570.8671875000f, +45590.3515625000f, 45609.8359375000f, 45629.3203125000f, 45648.8085937500f, +45668.3007812500f, 45687.7929687500f, 45707.2851562500f, 45726.7812500000f, +45746.2812500000f, 45765.7812500000f, 45785.2851562500f, 45804.7890625000f, +45824.2968750000f, 45843.8046875000f, 45863.3164062500f, 45882.8320312500f, +45902.3437500000f, 45921.8632812500f, 45941.3828125000f, 45960.9023437500f, +45980.4257812500f, 45999.9531250000f, 46019.4804687500f, 46039.0117187500f, +46058.5429687500f, 46078.0781250000f, 46097.6132812500f, 46117.1484375000f, +46136.6914062500f, 46156.2343750000f, 46175.7773437500f, 46195.3242187500f, +46214.8710937500f, 46234.4218750000f, 46253.9765625000f, 46273.5273437500f, +46293.0859375000f, 46312.6445312500f, 46332.2070312500f, 46351.7695312500f, +46371.3320312500f, 46390.8984375000f, 46410.4687500000f, 46430.0390625000f, +46449.6132812500f, 46469.1875000000f, 46488.7656250000f, 46508.3437500000f, +46527.9257812500f, 46547.5117187500f, 46567.0976562500f, 46586.6835937500f, +46606.2734375000f, 46625.8632812500f, 46645.4570312500f, 46665.0546875000f, +46684.6523437500f, 46704.2539062500f, 46723.8554687500f, 46743.4570312500f, +46763.0664062500f, 46782.6718750000f, 46802.2812500000f, 46821.8945312500f, +46841.5117187500f, 46861.1250000000f, 46880.7460937500f, 46900.3632812500f, +46919.9882812500f, 46939.6132812500f, 46959.2382812500f, 46978.8671875000f, +46998.5000000000f, 47018.1328125000f, 47037.7656250000f, 47057.4023437500f, +47077.0429687500f, 47096.6835937500f, 47116.3242187500f, 47135.9726562500f, +47155.6171875000f, 47175.2656250000f, 47194.9179687500f, 47214.5703125000f, +47234.2265625000f, 47253.8828125000f, 47273.5429687500f, 47293.2070312500f, +47312.8710937500f, 47332.5351562500f, 47352.2031250000f, 47371.8710937500f, +47391.5429687500f, 47411.2187500000f, 47430.8945312500f, 47450.5703125000f, +47470.2500000000f, 47489.9335937500f, 47509.6171875000f, 47529.3007812500f, +47548.9921875000f, 47568.6796875000f, 47588.3710937500f, 47608.0664062500f, +47627.7617187500f, 47647.4609375000f, 47667.1601562500f, 47686.8632812500f, +47706.5664062500f, 47726.2734375000f, 47745.9843750000f, 47765.6914062500f, +47785.4062500000f, 47805.1210937500f, 47824.8359375000f, 47844.5546875000f, +47864.2773437500f, 47884.0000000000f, 47903.7226562500f, 47923.4492187500f, +47943.1796875000f, 47962.9101562500f, 47982.6406250000f, 48002.3750000000f, +48022.1132812500f, 48041.8515625000f, 48061.5937500000f, 48081.3359375000f, +48101.0820312500f, 48120.8281250000f, 48140.5781250000f, 48160.3281250000f, +48180.0820312500f, 48199.8359375000f, 48219.5937500000f, 48239.3515625000f, +48259.1132812500f, 48278.8750000000f, 48298.6406250000f, 48318.4101562500f, +48338.1757812500f, 48357.9492187500f, 48377.7226562500f, 48397.4960937500f, +48417.2734375000f, 48437.0546875000f, 48456.8359375000f, 48476.6171875000f, +48496.4023437500f, 48516.1914062500f, 48535.9804687500f, 48555.7734375000f, +48575.5664062500f, 48595.3593750000f, 48615.1601562500f, 48634.9570312500f, +48654.7578125000f, 48674.5625000000f, 48694.3671875000f, 48714.1757812500f, +48733.9843750000f, 48753.7968750000f, 48773.6093750000f, 48793.4257812500f, +48813.2421875000f, 48833.0625000000f, 48852.8867187500f, 48872.7070312500f, +48892.5351562500f, 48912.3632812500f, 48932.1914062500f, 48952.0234375000f, +48971.8554687500f, 48991.6914062500f, 49011.5312500000f, 49031.3710937500f, +49051.2109375000f, 49071.0546875000f, 49090.9023437500f, 49110.7500000000f, +49130.5976562500f, 49150.4492187500f, 49170.3046875000f, 49190.1601562500f, +49210.0195312500f, 49229.8789062500f, 49249.7382812500f, 49269.6015625000f, +49289.4687500000f, 49309.3359375000f, 49329.2070312500f, 49349.0781250000f, +49368.9531250000f, 49388.8281250000f, 49408.7070312500f, 49428.5859375000f, +49448.4687500000f, 49468.3515625000f, 49488.2382812500f, 49508.1250000000f, +49528.0156250000f, 49547.9062500000f, 49567.8007812500f, 49587.6953125000f, +49607.5937500000f, 49627.4921875000f, 49647.3945312500f, 49667.2968750000f, +49687.2031250000f, 49707.1132812500f, 49727.0234375000f, 49746.9335937500f, +49766.8476562500f, 49786.7617187500f, 49806.6796875000f, 49826.6015625000f, +49846.5234375000f, 49866.4453125000f, 49886.3710937500f, 49906.3007812500f, +49926.2304687500f, 49946.1601562500f, 49966.0937500000f, 49986.0312500000f, +50005.9687500000f, 50025.9062500000f, 50045.8476562500f, 50065.7929687500f, +50085.7382812500f, 50105.6835937500f, 50125.6328125000f, 50145.5859375000f, +50165.5390625000f, 50185.4960937500f, 50205.4531250000f, 50225.4101562500f, +50245.3750000000f, 50265.3359375000f, 50285.3007812500f, 50305.2695312500f, +50325.2382812500f, 50345.2109375000f, 50365.1835937500f, 50385.1601562500f, +50405.1367187500f, 50425.1132812500f, 50445.0976562500f, 50465.0781250000f, +50485.0664062500f, 50505.0507812500f, 50525.0390625000f, 50545.0312500000f, +50565.0234375000f, 50585.0195312500f, 50605.0156250000f, 50625.0156250000f, +50645.0156250000f, 50665.0195312500f, 50685.0234375000f, 50705.0312500000f, +50725.0429687500f, 50745.0507812500f, 50765.0664062500f, 50785.0781250000f, +50805.0976562500f, 50825.1132812500f, 50845.1367187500f, 50865.1601562500f, +50885.1835937500f, 50905.2109375000f, 50925.2382812500f, 50945.2695312500f, +50965.3007812500f, 50985.3359375000f, 51005.3710937500f, 51025.4101562500f, +51045.4531250000f, 51065.4921875000f, 51085.5390625000f, 51105.5859375000f, +51125.6328125000f, 51145.6835937500f, 51165.7343750000f, 51185.7890625000f, +51205.8437500000f, 51225.9023437500f, 51245.9648437500f, 51266.0273437500f, +51286.0898437500f, 51306.1562500000f, 51326.2226562500f, 51346.2929687500f, +51366.3671875000f, 51386.4375000000f, 51406.5156250000f, 51426.5937500000f, +51446.6718750000f, 51466.7539062500f, 51486.8359375000f, 51506.9218750000f, +51527.0117187500f, 51547.1015625000f, 51567.1914062500f, 51587.2851562500f, +51607.3789062500f, 51627.4765625000f, 51647.5781250000f, 51667.6796875000f, +51687.7812500000f, 51707.8867187500f, 51727.9921875000f, 51748.1015625000f, +51768.2148437500f, 51788.3281250000f, 51808.4414062500f, 51828.5585937500f, +51848.6757812500f, 51868.7968750000f, 51888.9218750000f, 51909.0468750000f, +51929.1718750000f, 51949.3007812500f, 51969.4296875000f, 51989.5625000000f, +52009.6992187500f, 52029.8359375000f, 52049.9726562500f, 52070.1132812500f, +52090.2539062500f, 52110.3984375000f, 52130.5468750000f, 52150.6914062500f, +52170.8437500000f, 52190.9960937500f, 52211.1484375000f, 52231.3046875000f, +52251.4609375000f, 52271.6210937500f, 52291.7851562500f, 52311.9492187500f, +52332.1132812500f, 52352.2812500000f, 52372.4492187500f, 52392.6210937500f, +52412.7929687500f, 52432.9687500000f, 52453.1484375000f, 52473.3281250000f, +52493.5078125000f, 52513.6914062500f, 52533.8750000000f, 52554.0625000000f, +52574.2500000000f, 52594.4414062500f, 52614.6328125000f, 52634.8281250000f, +52655.0273437500f, 52675.2226562500f, 52695.4257812500f, 52715.6289062500f, +52735.8320312500f, 52756.0390625000f, 52776.2460937500f, 52796.4570312500f, +52816.6679687500f, 52836.8828125000f, 52857.0976562500f, 52877.3164062500f, +52897.5351562500f, 52917.7578125000f, 52937.9804687500f, 52958.2070312500f, +52978.4335937500f, 52998.6640625000f, 53018.8945312500f, 53039.1289062500f, +53059.3632812500f, 53079.6015625000f, 53099.8398437500f, 53120.0820312500f, +53140.3242187500f, 53160.5703125000f, 53180.8164062500f, 53201.0664062500f, +53221.3164062500f, 53241.5664062500f, 53261.8242187500f, 53282.0781250000f, +53302.3359375000f, 53322.5976562500f, 53342.8593750000f, 53363.1250000000f, +53383.3906250000f, 53403.6562500000f, 53423.9296875000f, 53444.1992187500f, +53464.4726562500f, 53484.7500000000f, 53505.0273437500f, 53525.3046875000f, +53545.5898437500f, 53565.8710937500f, 53586.1562500000f, 53606.4453125000f, +53626.7343750000f, 53647.0234375000f, 53667.3164062500f, 53687.6132812500f, +53707.9101562500f, 53728.2070312500f, 53748.5078125000f, 53768.8125000000f, +53789.1171875000f, 53809.4218750000f, 53829.7304687500f, 53850.0390625000f, +53870.3515625000f, 53890.6679687500f, 53910.9804687500f, 53931.3007812500f, +53951.6210937500f, 53971.9414062500f, 53992.2656250000f, 54012.5898437500f, +54032.9179687500f, 54053.2460937500f, 54073.5781250000f, 54093.9140625000f, +54114.2460937500f, 54134.5859375000f, 54154.9218750000f, 54175.2656250000f, +54195.6054687500f, 54215.9531250000f, 54236.2968750000f, 54256.6484375000f, +54276.9960937500f, 54297.3476562500f, 54317.7031250000f, 54338.0585937500f, +54358.4179687500f, 54378.7773437500f, 54399.1406250000f, 54419.5039062500f, +54439.8671875000f, 54460.2343750000f, 54480.6054687500f, 54500.9765625000f, +54521.3515625000f, 54541.7265625000f, 54562.1015625000f, 54582.4804687500f, +54602.8632812500f, 54623.2460937500f, 54643.6289062500f, 54664.0156250000f, +54684.4062500000f, 54704.7968750000f, 54725.1875000000f, 54745.5820312500f, +54765.9765625000f, 54786.3750000000f, 54806.7734375000f, 54827.1757812500f, +54847.5820312500f, 54867.9843750000f, 54888.3945312500f, 54908.8046875000f, +54929.2148437500f, 54949.6289062500f, 54970.0429687500f, 54990.4609375000f, +55010.8789062500f, 55031.3007812500f, 55051.7226562500f, 55072.1445312500f, +55092.5742187500f, 55113.0000000000f, 55133.4296875000f, 55153.8632812500f, +55174.2968750000f, 55194.7343750000f, 55215.1718750000f, 55235.6093750000f, +55256.0507812500f, 55276.4960937500f, 55296.9414062500f, 55317.3867187500f, +55337.8359375000f, 55358.2890625000f, 55378.7421875000f, 55399.1953125000f, +55419.6523437500f, 55440.1093750000f, 55460.5703125000f, 55481.0351562500f, +55501.4960937500f, 55521.9648437500f, 55542.4335937500f, 55562.9023437500f, +55583.3750000000f, 55603.8476562500f, 55624.3242187500f, 55644.8007812500f, +55665.2812500000f, 55685.7617187500f, 55706.2421875000f, 55726.7304687500f, +55747.2148437500f, 55767.7031250000f, 55788.1953125000f, 55808.6875000000f, +55829.1796875000f, 55849.6796875000f, 55870.1757812500f, 55890.6757812500f, +55911.1796875000f, 55931.6796875000f, 55952.1875000000f, 55972.6953125000f, +55993.2031250000f, 56013.7148437500f, 56034.2265625000f, 56054.7421875000f, +56075.2617187500f, 56095.7773437500f, 56116.3007812500f, 56136.8242187500f, +56157.3476562500f, 56177.8750000000f, 56198.4023437500f, 56218.9296875000f, +56239.4648437500f, 56259.9960937500f, 56280.5312500000f, 56301.0703125000f, +56321.6093750000f, 56342.1523437500f, 56362.6953125000f, 56383.2382812500f, +56403.7851562500f, 56424.3359375000f, 56444.8867187500f, 56465.4375000000f, +56485.9921875000f, 56506.5468750000f, 56527.1054687500f, 56547.6679687500f, +56568.2265625000f, 56588.7929687500f, 56609.3593750000f, 56629.9257812500f, +56650.4960937500f, 56671.0664062500f, 56691.6406250000f, 56712.2148437500f, +56732.7890625000f, 56753.3671875000f, 56773.9492187500f, 56794.5312500000f, +56815.1171875000f, 56835.7031250000f, 56856.2890625000f, 56876.8789062500f, +56897.4726562500f, 56918.0664062500f, 56938.6601562500f, 56959.2578125000f, +56979.8593750000f, 57000.4570312500f, 57021.0625000000f, 57041.6679687500f, +57062.2734375000f, 57082.8828125000f, 57103.4921875000f, 57124.1054687500f, +57144.7187500000f, 57165.3320312500f, 57185.9531250000f, 57206.5703125000f, +57227.1914062500f, 57247.8164062500f, 57268.4414062500f, 57289.0664062500f, +57309.6953125000f, 57330.3281250000f, 57350.9609375000f, 57371.5937500000f, +57392.2304687500f, 57412.8710937500f, 57433.5078125000f, 57454.1523437500f, +57474.7968750000f, 57495.4414062500f, 57516.0898437500f, 57536.7382812500f, +57557.3906250000f, 57578.0429687500f, 57598.6953125000f, 57619.3515625000f, +57640.0117187500f, 57660.6718750000f, 57681.3359375000f, 57702.0000000000f, +57722.6640625000f, 57743.3320312500f, 57764.0039062500f, 57784.6718750000f, +57805.3476562500f, 57826.0234375000f, 57846.6992187500f, 57867.3789062500f, +57888.0585937500f, 57908.7421875000f, 57929.4257812500f, 57950.1132812500f, +57970.8007812500f, 57991.4921875000f, 58012.1835937500f, 58032.8750000000f, +58053.5703125000f, 58074.2695312500f, 58094.9687500000f, 58115.6679687500f, +58136.3710937500f, 58157.0781250000f, 58177.7851562500f, 58198.4921875000f, +58219.2031250000f, 58239.9140625000f, 58260.6289062500f, 58281.3437500000f, +58302.0625000000f, 58322.7812500000f, 58343.5039062500f, 58364.2265625000f, +58384.9492187500f, 58405.6796875000f, 58426.4062500000f, 58447.1367187500f, +58467.8710937500f, 58488.6054687500f, 58509.3398437500f, 58530.0781250000f, +58550.8164062500f, 58571.5585937500f, 58592.3007812500f, 58613.0468750000f, +58633.7929687500f, 58654.5429687500f, 58675.2929687500f, 58696.0468750000f, +58716.8007812500f, 58737.5585937500f, 58758.3164062500f, 58779.0742187500f, +58799.8359375000f, 58820.6015625000f, 58841.3671875000f, 58862.1328125000f, +58882.9023437500f, 58903.6718750000f, 58924.4453125000f, 58945.2187500000f, +58965.9960937500f, 58986.7734375000f, 59007.5546875000f, 59028.3359375000f, +59049.1210937500f, 59069.9062500000f, 59090.6953125000f, 59111.4843750000f, +59132.2734375000f, 59153.0664062500f, 59173.8632812500f, 59194.6562500000f, +59215.4570312500f, 59236.2578125000f, 59257.0585937500f, 59277.8632812500f, +59298.6679687500f, 59319.4765625000f, 59340.2851562500f, 59361.0976562500f, +59381.9101562500f, 59402.7226562500f, 59423.5390625000f, 59444.3593750000f, +59465.1796875000f, 59486.0000000000f, 59506.8242187500f, 59527.6484375000f, +59548.4765625000f, 59569.3085937500f, 59590.1367187500f, 59610.9726562500f, +59631.8046875000f, 59652.6445312500f, 59673.4804687500f, 59694.3203125000f, +59715.1640625000f, 59736.0078125000f, 59756.8515625000f, 59777.6992187500f, +59798.5507812500f, 59819.4023437500f, 59840.2539062500f, 59861.1093750000f, +59881.9648437500f, 59902.8242187500f, 59923.6835937500f, 59944.5468750000f, +59965.4101562500f, 59986.2773437500f, 60007.1445312500f, 60028.0117187500f, +60048.8828125000f, 60069.7578125000f, 60090.6328125000f, 60111.5078125000f, +60132.3867187500f, 60153.2656250000f, 60174.1484375000f, 60195.0312500000f, +60215.9179687500f, 60236.8046875000f, 60257.6953125000f, 60278.5859375000f, +60299.4804687500f, 60320.3750000000f, 60341.2695312500f, 60362.1679687500f, +60383.0703125000f, 60403.9687500000f, 60424.8750000000f, 60445.7812500000f, +60466.6875000000f, 60487.5976562500f, 60508.5078125000f, 60529.4218750000f, +60550.3359375000f, 60571.2500000000f, 60592.1679687500f, 60613.0898437500f, +60634.0117187500f, 60654.9335937500f, 60675.8593750000f, 60696.7890625000f, +60717.7148437500f, 60738.6484375000f, 60759.5781250000f, 60780.5156250000f, +60801.4492187500f, 60822.3867187500f, 60843.3281250000f, 60864.2695312500f, +60885.2148437500f, 60906.1601562500f, 60927.1054687500f, 60948.0546875000f, +60969.0039062500f, 60989.9570312500f, 61010.9101562500f, 61031.8671875000f, +61052.8242187500f, 61073.7851562500f, 61094.7460937500f, 61115.7109375000f, +61136.6757812500f, 61157.6406250000f, 61178.6093750000f, 61199.5820312500f, +61220.5546875000f, 61241.5273437500f, 61262.5039062500f, 61283.4804687500f, +61304.4609375000f, 61325.4414062500f, 61346.4257812500f, 61367.4101562500f, +61388.3945312500f, 61409.3828125000f, 61430.3750000000f, 61451.3671875000f, +61472.3593750000f, 61493.3554687500f, 61514.3515625000f, 61535.3515625000f, +61556.3515625000f, 61577.3554687500f, 61598.3593750000f, 61619.3671875000f, +61640.3750000000f, 61661.3828125000f, 61682.3945312500f, 61703.4101562500f, +61724.4257812500f, 61745.4414062500f, 61766.4609375000f, 61787.4804687500f, +61808.5039062500f, 61829.5273437500f, 61850.5546875000f, 61871.5820312500f, +61892.6132812500f, 61913.6445312500f, 61934.6757812500f, 61955.7109375000f, +61976.7460937500f, 61997.7851562500f, 62018.8281250000f, 62039.8671875000f, +62060.9140625000f, 62081.9570312500f, 62103.0039062500f, 62124.0546875000f, +62145.1054687500f, 62166.1562500000f, 62187.2109375000f, 62208.2695312500f, +62229.3281250000f, 62250.3867187500f, 62271.4492187500f, 62292.5117187500f, +62313.5781250000f, 62334.6445312500f, 62355.7148437500f, 62376.7851562500f, +62397.8554687500f, 62418.9296875000f, 62440.0078125000f, 62461.0820312500f, +62482.1640625000f, 62503.2460937500f, 62524.3281250000f, 62545.4140625000f, +62566.5000000000f, 62587.5859375000f, 62608.6757812500f, 62629.7695312500f, +62650.8632812500f, 62671.9570312500f, 62693.0546875000f, 62714.1562500000f, +62735.2539062500f, 62756.3593750000f, 62777.4609375000f, 62798.5703125000f, +62819.6757812500f, 62840.7851562500f, 62861.8984375000f, 62883.0117187500f, +62904.1250000000f, 62925.2421875000f, 62946.3593750000f, 62967.4804687500f, +62988.6015625000f, 63009.7265625000f, 63030.8515625000f, 63051.9804687500f, +63073.1093750000f, 63094.2382812500f, 63115.3710937500f, 63136.5078125000f, +63157.6445312500f, 63178.7812500000f, 63199.9218750000f, 63221.0625000000f, +63242.2070312500f, 63263.3515625000f, 63284.4960937500f, 63305.6445312500f, +63326.7968750000f, 63347.9492187500f, 63369.1015625000f, 63390.2578125000f, +63411.4140625000f, 63432.5742187500f, 63453.7343750000f, 63474.8984375000f, +63496.0625000000f, 63517.2304687500f, 63538.3984375000f, 63559.5664062500f, +63580.7382812500f, 63601.9101562500f, 63623.0859375000f, 63644.2656250000f, +63665.4414062500f, 63686.6210937500f, 63707.8046875000f, 63728.9882812500f, +63750.1757812500f, 63771.3632812500f, 63792.5507812500f, 63813.7421875000f, +63834.9335937500f, 63856.1289062500f, 63877.3242187500f, 63898.5234375000f, +63919.7226562500f, 63940.9257812500f, 63962.1289062500f, 63983.3320312500f, +64004.5390625000f, 64025.7460937500f, 64046.9570312500f, 64068.1679687500f, +64089.3828125000f, 64110.5976562500f, 64131.8164062500f, 64153.0351562500f, +64174.2539062500f, 64195.4765625000f, 64216.7031250000f, 64237.9296875000f, +64259.1562500000f, 64280.3867187500f, 64301.6171875000f, 64322.8515625000f, +64344.0859375000f, 64365.3203125000f, 64386.5585937500f, 64407.8007812500f, +64429.0429687500f, 64450.2851562500f, 64471.5312500000f, 64492.7773437500f, +64514.0273437500f, 64535.2773437500f, 64556.5312500000f, 64577.7851562500f, +64599.0390625000f, 64620.2968750000f, 64641.5546875000f, 64662.8164062500f, +64684.0781250000f, 64705.3437500000f, 64726.6093750000f, 64747.8789062500f, +64769.1484375000f, 64790.4218750000f, 64811.6953125000f, 64832.9687500000f, +64854.2460937500f, 64875.5234375000f, 64896.8046875000f, 64918.0859375000f, +64939.3710937500f, 64960.6562500000f, 64981.9414062500f, 65003.2304687500f, +65024.5234375000f, 65045.8164062500f, 65067.1093750000f, 65088.4062500000f, +65109.7031250000f, 65131.0000000000f, 65152.3046875000f, 65173.6054687500f, +65194.9101562500f, 65216.2187500000f, 65237.5234375000f, 65258.8359375000f, +65280.1484375000f, 65301.4609375000f, 65322.7734375000f, 65344.0937500000f, +65365.4101562500f, 65386.7304687500f, 65408.0546875000f, 65429.3750000000f, +65450.7031250000f, 65472.0312500000f, 65493.3593750000f, 65514.6875000000f, +65536.0234375000f, 65557.3593750000f, 65578.6953125000f, 65600.0312500000f, +65621.3671875000f, 65642.7109375000f, 65664.0546875000f, 65685.3984375000f, +65706.7421875000f, 65728.0937500000f, 65749.4453125000f, 65770.7968750000f, +65792.1484375000f, 65813.5000000000f, 65834.8593750000f, 65856.2187500000f, +65877.5781250000f, 65898.9375000000f, 65920.3046875000f, 65941.6718750000f, +65963.0390625000f, 65984.4062500000f, 66005.7734375000f, 66027.1484375000f, +66048.5234375000f, 66069.8984375000f, 66091.2734375000f, 66112.6562500000f, +66134.0312500000f, 66155.4140625000f, 66176.8046875000f, 66198.1875000000f, +66219.5781250000f, 66240.9687500000f, 66262.3593750000f, 66283.7500000000f, +66305.1484375000f, 66326.5390625000f, 66347.9375000000f, 66369.3359375000f, +66390.7421875000f, 66412.1484375000f, 66433.5468750000f, 66454.9531250000f, +66476.3671875000f, 66497.7734375000f, 66519.1875000000f, 66540.6015625000f, +66562.0156250000f, 66583.4375000000f, 66604.8515625000f, 66626.2734375000f, +66647.6953125000f, 66669.1171875000f, 66690.5468750000f, 66711.9765625000f, +66733.4062500000f, 66754.8359375000f, 66776.2656250000f, 66797.7031250000f, +66819.1406250000f, 66840.5781250000f, 66862.0156250000f, 66883.4531250000f, +66904.8984375000f, 66926.3437500000f, 66947.7890625000f, 66969.2343750000f, +66990.6875000000f, 67012.1406250000f, 67033.5937500000f, 67055.0468750000f, +67076.5078125000f, 67097.9609375000f, 67119.4218750000f, 67140.8828125000f, +67162.3515625000f, 67183.8125000000f, 67205.2812500000f, 67226.7500000000f, +67248.2187500000f, 67269.6953125000f, 67291.1640625000f, 67312.6406250000f, +67334.1171875000f, 67355.6015625000f, 67377.0781250000f, 67398.5625000000f, +67420.0468750000f, 67441.5312500000f, 67463.0234375000f, 67484.5078125000f, +67506.0000000000f, 67527.4921875000f, 67548.9843750000f, 67570.4843750000f, +67591.9843750000f, 67613.4843750000f, 67634.9843750000f, 67656.4843750000f, +67677.9921875000f, 67699.5000000000f, 67721.0078125000f, 67742.5156250000f, +67764.0234375000f, 67785.5390625000f, 67807.0546875000f, 67828.5703125000f, +67850.0859375000f, 67871.6093750000f, 67893.1328125000f, 67914.6562500000f, +67936.1796875000f, 67957.7031250000f, 67979.2343750000f, 68000.7656250000f, +68022.2968750000f, 68043.8281250000f, 68065.3671875000f, 68086.9062500000f, +68108.4453125000f, 68129.9843750000f, 68151.5234375000f, 68173.0703125000f, +68194.6171875000f, 68216.1640625000f, 68237.7109375000f, 68259.2578125000f, +68280.8125000000f, 68302.3671875000f, 68323.9218750000f, 68345.4843750000f, +68367.0390625000f, 68388.6015625000f, 68410.1640625000f, 68431.7265625000f, +68453.2968750000f, 68474.8593750000f, 68496.4296875000f, 68518.0000000000f, +68539.5781250000f, 68561.1484375000f, 68582.7265625000f, 68604.3046875000f, +68625.8828125000f, 68647.4687500000f, 68669.0468750000f, 68690.6328125000f, +68712.2187500000f, 68733.8046875000f, 68755.3984375000f, 68776.9921875000f, +68798.5859375000f, 68820.1796875000f, 68841.7734375000f, 68863.3750000000f, +68884.9687500000f, 68906.5703125000f, 68928.1796875000f, 68949.7812500000f, +68971.3906250000f, 68993.0000000000f, 69014.6093750000f, 69036.2187500000f, +69057.8359375000f, 69079.4453125000f, 69101.0625000000f, 69122.6796875000f, +69144.3046875000f, 69165.9218750000f, 69187.5468750000f, 69209.1718750000f, +69230.8046875000f, 69252.4296875000f, 69274.0625000000f, 69295.6953125000f, +69317.3281250000f, 69338.9609375000f, 69360.6015625000f, 69382.2343750000f, +69403.8750000000f, 69425.5234375000f, 69447.1640625000f, 69468.8125000000f, +69490.4609375000f, 69512.1093750000f, 69533.7578125000f, 69555.4062500000f, +69577.0625000000f, 69598.7187500000f, 69620.3750000000f, 69642.0390625000f, +69663.6953125000f, 69685.3593750000f, 69707.0234375000f, 69728.6875000000f, +69750.3593750000f, 69772.0234375000f, 69793.6953125000f, 69815.3671875000f, +69837.0390625000f, 69858.7187500000f, 69880.3984375000f, 69902.0781250000f, +69923.7578125000f, 69945.4375000000f, 69967.1250000000f, 69988.8125000000f, +70010.5000000000f, 70032.1875000000f, 70053.8750000000f, 70075.5703125000f, +70097.2656250000f, 70118.9609375000f, 70140.6562500000f, 70162.3593750000f, +70184.0625000000f, 70205.7578125000f, 70227.4687500000f, 70249.1718750000f, +70270.8828125000f, 70292.5859375000f, 70314.2968750000f, 70336.0156250000f, +70357.7265625000f, 70379.4453125000f, 70401.1640625000f, 70422.8828125000f, +70444.6015625000f, 70466.3281250000f, 70488.0468750000f, 70509.7734375000f, +70531.5078125000f, 70553.2343750000f, 70574.9687500000f, 70596.6953125000f, +70618.4296875000f, 70640.1718750000f, 70661.9062500000f, 70683.6484375000f, +70705.3906250000f, 70727.1328125000f, 70748.8750000000f, 70770.6250000000f, +70792.3671875000f, 70814.1171875000f, 70835.8671875000f, 70857.6250000000f, +70879.3750000000f, 70901.1328125000f, 70922.8906250000f, 70944.6484375000f, +70966.4140625000f, 70988.1796875000f, 71009.9375000000f, 71031.7109375000f, +71053.4765625000f, 71075.2421875000f, 71097.0156250000f, 71118.7890625000f, +71140.5625000000f, 71162.3437500000f, 71184.1171875000f, 71205.8984375000f, +71227.6796875000f, 71249.4609375000f, 71271.2500000000f, 71293.0312500000f, +71314.8203125000f, 71336.6093750000f, 71358.4062500000f, 71380.1953125000f, +71401.9921875000f, 71423.7890625000f, 71445.5859375000f, 71467.3828125000f, +71489.1875000000f, 71510.9921875000f, 71532.7968750000f, 71554.6015625000f, +71576.4062500000f, 71598.2187500000f, 71620.0312500000f, 71641.8437500000f, +71663.6562500000f, 71685.4687500000f, 71707.2890625000f, 71729.1093750000f, +71750.9296875000f, 71772.7500000000f, 71794.5781250000f, 71816.4062500000f, +71838.2343750000f, 71860.0625000000f, 71881.8906250000f, 71903.7265625000f, +71925.5625000000f, 71947.3984375000f, 71969.2343750000f, 71991.0703125000f, +72012.9140625000f, 72034.7578125000f, 72056.6015625000f, 72078.4453125000f, +72100.2968750000f, 72122.1484375000f, 72144.0000000000f, 72165.8515625000f, +72187.7031250000f, 72209.5625000000f, 72231.4140625000f, 72253.2734375000f, +72275.1406250000f, 72297.0000000000f, 72318.8671875000f, 72340.7265625000f, +72362.6015625000f, 72384.4687500000f, 72406.3359375000f, 72428.2109375000f, +72450.0859375000f, 72471.9609375000f, 72493.8359375000f, 72515.7187500000f, +72537.6015625000f, 72559.4843750000f, 72581.3671875000f, 72603.2500000000f, +72625.1406250000f, 72647.0234375000f, 72668.9140625000f, 72690.8125000000f, +72712.7031250000f, 72734.6015625000f, 72756.5000000000f, 72778.3984375000f, +72800.2968750000f, 72822.1953125000f, 72844.1015625000f, 72866.0078125000f, +72887.9140625000f, 72909.8203125000f, 72931.7343750000f, 72953.6484375000f, +72975.5625000000f, 72997.4765625000f, 73019.3906250000f, 73041.3125000000f, +73063.2343750000f, 73085.1562500000f, 73107.0781250000f, 73129.0000000000f, +73150.9296875000f, 73172.8593750000f, 73194.7890625000f, 73216.7187500000f, +73238.6562500000f, 73260.5859375000f, 73282.5234375000f, 73304.4609375000f, +73326.4062500000f, 73348.3437500000f, 73370.2890625000f, 73392.2343750000f, +73414.1796875000f, 73436.1328125000f, 73458.0781250000f, 73480.0312500000f, +73501.9843750000f, 73523.9375000000f, 73545.8984375000f, 73567.8515625000f, +73589.8125000000f, 73611.7734375000f, 73633.7343750000f, 73655.7031250000f, +73677.6718750000f, 73699.6328125000f, 73721.6093750000f, 73743.5781250000f, +73765.5468750000f, 73787.5234375000f, 73809.5000000000f, 73831.4765625000f, +73853.4609375000f, 73875.4375000000f, 73897.4218750000f, 73919.4062500000f, +73941.3906250000f, 73963.3750000000f, 73985.3671875000f, 74007.3593750000f, +74029.3515625000f, 74051.3437500000f, 74073.3437500000f, 74095.3359375000f, +74117.3359375000f, 74139.3359375000f, 74161.3437500000f, 74183.3437500000f, +74205.3515625000f, 74227.3593750000f, 74249.3671875000f, 74271.3750000000f, +74293.3906250000f, 74315.3984375000f, 74337.4140625000f, 74359.4375000000f, +74381.4531250000f, 74403.4687500000f, 74425.4921875000f, 74447.5156250000f, +74469.5390625000f, 74491.5703125000f, 74513.6015625000f, 74535.6250000000f, +74557.6562500000f, 74579.6953125000f, 74601.7265625000f, 74623.7656250000f, +74645.8046875000f, 74667.8437500000f, 74689.8828125000f, 74711.9296875000f, +74733.9687500000f, 74756.0156250000f, 74778.0625000000f, 74800.1171875000f, +74822.1640625000f, 74844.2187500000f, 74866.2734375000f, 74888.3281250000f, +74910.3828125000f, 74932.4453125000f, 74954.5078125000f, 74976.5703125000f, +74998.6328125000f, 75020.6953125000f, 75042.7656250000f, 75064.8359375000f, +75086.9062500000f, 75108.9765625000f, 75131.0546875000f, 75153.1250000000f, +75175.2031250000f, 75197.2812500000f, 75219.3593750000f, 75241.4453125000f, +75263.5312500000f, 75285.6171875000f, 75307.7031250000f, 75329.7890625000f, +75351.8828125000f, 75373.9687500000f, 75396.0625000000f, 75418.1562500000f, +75440.2578125000f, 75462.3515625000f, 75484.4531250000f, 75506.5546875000f, +75528.6562500000f, 75550.7656250000f, 75572.8671875000f, 75594.9765625000f, +75617.0859375000f, 75639.1953125000f, 75661.3125000000f, 75683.4218750000f, +75705.5390625000f, 75727.6562500000f, 75749.7734375000f, 75771.8984375000f, +75794.0156250000f, 75816.1406250000f, 75838.2656250000f, 75860.3984375000f, +75882.5234375000f, 75904.6562500000f, 75926.7890625000f, 75948.9218750000f, +75971.0546875000f, 75993.1953125000f, 76015.3281250000f, 76037.4687500000f, +76059.6171875000f, 76081.7578125000f, 76103.8984375000f, 76126.0468750000f, +76148.1953125000f, 76170.3437500000f, 76192.5000000000f, 76214.6484375000f, +76236.8046875000f, 76258.9609375000f, 76281.1171875000f, 76303.2812500000f, +76325.4375000000f, 76347.6015625000f, 76369.7656250000f, 76391.9296875000f, +76414.1015625000f, 76436.2734375000f, 76458.4375000000f, 76480.6093750000f, +76502.7890625000f, 76524.9609375000f, 76547.1406250000f, 76569.3203125000f, +76591.5000000000f, 76613.6796875000f, 76635.8671875000f, 76658.0468750000f, +76680.2343750000f, 76702.4218750000f, 76724.6171875000f, 76746.8046875000f, +76769.0000000000f, 76791.1953125000f, 76813.3906250000f, 76835.5859375000f, +76857.7890625000f, 76879.9921875000f, 76902.1953125000f, 76924.3984375000f, +76946.6015625000f, 76968.8125000000f, 76991.0156250000f, 77013.2265625000f, +77035.4453125000f, 77057.6562500000f, 77079.8750000000f, 77102.0859375000f, +77124.3046875000f, 77146.5312500000f, 77168.7500000000f, 77190.9765625000f, +77213.1953125000f, 77235.4296875000f, 77257.6562500000f, 77279.8828125000f, +77302.1171875000f, 77324.3515625000f, 77346.5859375000f, 77368.8203125000f, +77391.0546875000f, 77413.2968750000f, 77435.5390625000f, 77457.7812500000f, +77480.0234375000f, 77502.2734375000f, 77524.5234375000f, 77546.7656250000f, +77569.0234375000f, 77591.2734375000f, 77613.5234375000f, 77635.7812500000f, +77658.0390625000f, 77680.2968750000f, 77702.5546875000f, 77724.8203125000f, +77747.0859375000f, 77769.3515625000f, 77791.6171875000f, 77813.8828125000f, +77836.1562500000f, 77858.4218750000f, 77880.6953125000f, 77902.9765625000f, +77925.2500000000f, 77947.5312500000f, 77969.8046875000f, 77992.0859375000f, +78014.3671875000f, 78036.6562500000f, 78058.9375000000f, 78081.2265625000f, +78103.5156250000f, 78125.8046875000f, 78148.1015625000f, 78170.3906250000f, +78192.6875000000f, 78214.9843750000f, 78237.2812500000f, 78259.5859375000f, +78281.8828125000f, 78304.1875000000f, 78326.4921875000f, 78348.8046875000f, +78371.1093750000f, 78393.4218750000f, 78415.7265625000f, 78438.0468750000f, +78460.3593750000f, 78482.6718750000f, 78504.9921875000f, 78527.3125000000f, +78549.6328125000f, 78571.9531250000f, 78594.2812500000f, 78616.6015625000f, +78638.9296875000f, 78661.2578125000f, 78683.5859375000f, 78705.9218750000f, +78728.2578125000f, 78750.5859375000f, 78772.9296875000f, 78795.2656250000f, +78817.6015625000f, 78839.9453125000f, 78862.2890625000f, 78884.6328125000f, +78906.9765625000f, 78929.3281250000f, 78951.6796875000f, 78974.0234375000f, +78996.3828125000f, 79018.7343750000f, 79041.0859375000f, 79063.4453125000f, +79085.8046875000f, 79108.1640625000f, 79130.5312500000f, 79152.8906250000f, +79175.2578125000f, 79197.6250000000f, 79219.9921875000f, 79242.3593750000f, +79264.7343750000f, 79287.1015625000f, 79309.4765625000f, 79331.8593750000f, +79354.2343750000f, 79376.6093750000f, 79398.9921875000f, 79421.3750000000f, +79443.7578125000f, 79466.1484375000f, 79488.5312500000f, 79510.9218750000f, +79533.3125000000f, 79555.7031250000f, 79578.1015625000f, 79600.4921875000f, +79622.8906250000f, 79645.2890625000f, 79667.6875000000f, 79690.0859375000f, +79712.4921875000f, 79734.8984375000f, 79757.3046875000f, 79779.7109375000f, +79802.1171875000f, 79824.5312500000f, 79846.9453125000f, 79869.3593750000f, +79891.7734375000f, 79914.1875000000f, 79936.6093750000f, 79959.0312500000f, +79981.4531250000f, 80003.8750000000f, 80026.2968750000f, 80048.7265625000f, +80071.1562500000f, 80093.5859375000f, 80116.0156250000f, 80138.4453125000f, +80160.8828125000f, 80183.3203125000f, 80205.7578125000f, 80228.1953125000f, +80250.6328125000f, 80273.0781250000f, 80295.5234375000f, 80317.9687500000f, +80340.4140625000f, 80362.8593750000f, 80385.3125000000f, 80407.7656250000f, +80430.2187500000f, 80452.6718750000f, 80475.1250000000f, 80497.5859375000f, +80520.0468750000f, 80542.5078125000f, 80564.9687500000f, 80587.4296875000f, +80609.8984375000f, 80632.3671875000f, 80654.8359375000f, 80677.3046875000f, +80699.7734375000f, 80722.2500000000f, 80744.7265625000f, 80767.2031250000f, +80789.6796875000f, 80812.1640625000f, 80834.6406250000f, 80857.1250000000f, +80879.6093750000f, 80902.0937500000f, 80924.5859375000f, 80947.0703125000f, +80969.5625000000f, 80992.0546875000f, 81014.5468750000f, 81037.0468750000f, +81059.5390625000f, 81082.0390625000f, 81104.5390625000f, 81127.0390625000f, +81149.5468750000f, 81172.0468750000f, 81194.5546875000f, 81217.0625000000f, +81239.5703125000f, 81262.0859375000f, 81284.6015625000f, 81307.1093750000f, +81329.6250000000f, 81352.1484375000f, 81374.6640625000f, 81397.1875000000f, +81419.7031250000f, 81442.2265625000f, 81464.7578125000f, 81487.2812500000f, +81509.8125000000f, 81532.3359375000f, 81554.8671875000f, 81577.4062500000f, +81599.9375000000f, 81622.4765625000f, 81645.0078125000f, 81667.5468750000f, +81690.0937500000f, 81712.6328125000f, 81735.1796875000f, 81757.7187500000f, +81780.2656250000f, 81802.8203125000f, 81825.3671875000f, 81847.9218750000f, +81870.4687500000f, 81893.0234375000f, 81915.5859375000f, 81938.1406250000f, +81960.7031250000f, 81983.2578125000f, 82005.8203125000f, 82028.3906250000f, +82050.9531250000f, 82073.5234375000f, 82096.0859375000f, 82118.6562500000f, +82141.2265625000f, 82163.8046875000f, 82186.3750000000f, 82208.9531250000f, +82231.5312500000f, 82254.1093750000f, 82276.6953125000f, 82299.2734375000f, +82321.8593750000f, 82344.4453125000f, 82367.0312500000f, 82389.6250000000f, +82412.2109375000f, 82434.8046875000f, 82457.3984375000f, 82479.9921875000f, +82502.5859375000f, 82525.1875000000f, 82547.7890625000f, 82570.3906250000f, +82592.9921875000f, 82615.5937500000f, 82638.2031250000f, 82660.8046875000f, +82683.4140625000f, 82706.0234375000f, 82728.6406250000f, 82751.2500000000f, +82773.8671875000f, 82796.4843750000f, 82819.1015625000f, 82841.7187500000f, +82864.3437500000f, 82886.9687500000f, 82909.5859375000f, 82932.2187500000f, +82954.8437500000f, 82977.4687500000f, 83000.1015625000f, 83022.7343750000f, +83045.3671875000f, 83068.0000000000f, 83090.6406250000f, 83113.2812500000f, +83135.9140625000f, 83158.5546875000f, 83181.2031250000f, 83203.8437500000f, +83226.4921875000f, 83249.1406250000f, 83271.7890625000f, 83294.4375000000f, +83317.0937500000f, 83339.7421875000f, 83362.3984375000f, 83385.0546875000f, +83407.7109375000f, 83430.3750000000f, 83453.0312500000f, 83475.6953125000f, +83498.3593750000f, 83521.0312500000f, 83543.6953125000f, 83566.3671875000f, +83589.0312500000f, 83611.7109375000f, 83634.3828125000f, 83657.0546875000f, +83679.7343750000f, 83702.4140625000f, 83725.0937500000f, 83747.7734375000f, +83770.4531250000f, 83793.1406250000f, 83815.8281250000f, 83838.5156250000f, +83861.2031250000f, 83883.8906250000f, 83906.5859375000f, 83929.2734375000f, +83951.9687500000f, 83974.6718750000f, 83997.3671875000f, 84020.0703125000f, +84042.7656250000f, 84065.4687500000f, 84088.1718750000f, 84110.8828125000f, +84133.5859375000f, 84156.2968750000f, 84179.0078125000f, 84201.7187500000f, +84224.4296875000f, 84247.1484375000f, 84269.8671875000f, 84292.5859375000f, +84315.3046875000f, 84338.0234375000f, 84360.7500000000f, 84383.4687500000f, +84406.1953125000f, 84428.9218750000f, 84451.6484375000f, 84474.3828125000f, +84497.1171875000f, 84519.8437500000f, 84542.5859375000f, 84565.3203125000f, +84588.0546875000f, 84610.7968750000f, 84633.5390625000f, 84656.2812500000f, +84679.0234375000f, 84701.7734375000f, 84724.5156250000f, 84747.2656250000f, +84770.0156250000f, 84792.7656250000f, 84815.5234375000f, 84838.2734375000f, +84861.0312500000f, 84883.7890625000f, 84906.5468750000f, 84929.3125000000f, +84952.0703125000f, 84974.8359375000f, 84997.6015625000f, 85020.3671875000f, +85043.1406250000f, 85065.9062500000f, 85088.6796875000f, 85111.4531250000f, +85134.2265625000f, 85157.0000000000f, 85179.7812500000f, 85202.5625000000f, +85225.3359375000f, 85248.1250000000f, 85270.9062500000f, 85293.6875000000f, +85316.4765625000f, 85339.2656250000f, 85362.0546875000f, 85384.8437500000f, +85407.6406250000f, 85430.4375000000f, 85453.2265625000f, 85476.0234375000f, +85498.8281250000f, 85521.6250000000f, 85544.4296875000f, 85567.2343750000f, +85590.0390625000f, 85612.8437500000f, 85635.6484375000f, 85658.4609375000f, +85681.2734375000f, 85704.0859375000f, 85726.8984375000f, 85749.7109375000f, +85772.5312500000f, 85795.3515625000f, 85818.1718750000f, 85840.9921875000f, +85863.8125000000f, 85886.6406250000f, 85909.4687500000f, 85932.2968750000f, +85955.1250000000f, 85977.9531250000f, 86000.7890625000f, 86023.6171875000f, +86046.4531250000f, 86069.2890625000f, 86092.1328125000f, 86114.9687500000f, +86137.8125000000f, 86160.6562500000f, 86183.5000000000f, 86206.3437500000f, +86229.1953125000f, 86252.0390625000f, 86274.8906250000f, 86297.7421875000f, +86320.6015625000f, 86343.4531250000f, 86366.3125000000f, 86389.1718750000f, +86412.0312500000f, 86434.8906250000f, 86457.7500000000f, 86480.6171875000f, +86503.4843750000f, 86526.3515625000f, 86549.2187500000f, 86572.0859375000f, +86594.9609375000f, 86617.8281250000f, 86640.7031250000f, 86663.5859375000f, +86686.4609375000f, 86709.3359375000f, 86732.2187500000f, 86755.1015625000f, +86777.9843750000f, 86800.8750000000f, 86823.7578125000f, 86846.6484375000f, +86869.5390625000f, 86892.4296875000f, 86915.3203125000f, 86938.2109375000f, +86961.1093750000f, 86984.0078125000f, 87006.9062500000f, 87029.8046875000f, +87052.7109375000f, 87075.6093750000f, 87098.5156250000f, 87121.4218750000f, +87144.3281250000f, 87167.2421875000f, 87190.1484375000f, 87213.0625000000f, +87235.9765625000f, 87258.8906250000f, 87281.8125000000f, 87304.7265625000f, +87327.6484375000f, 87350.5703125000f, 87373.4921875000f, 87396.4140625000f, +87419.3437500000f, 87442.2734375000f, 87465.1953125000f, 87488.1328125000f, +87511.0625000000f, 87533.9921875000f, 87556.9296875000f, 87579.8671875000f, +87602.8046875000f, 87625.7421875000f, 87648.6875000000f, 87671.6250000000f, +87694.5703125000f, 87717.5156250000f, 87740.4609375000f, 87763.4140625000f, +87786.3593750000f, 87809.3125000000f, 87832.2656250000f, 87855.2187500000f, +87878.1796875000f, 87901.1328125000f, 87924.0937500000f, 87947.0546875000f, +87970.0156250000f, 87992.9765625000f, 88015.9453125000f, 88038.9062500000f, +88061.8750000000f, 88084.8437500000f, 88107.8203125000f, 88130.7890625000f, +88153.7656250000f, 88176.7421875000f, 88199.7187500000f, 88222.6953125000f, +88245.6718750000f, 88268.6562500000f, 88291.6406250000f, 88314.6250000000f, +88337.6093750000f, 88360.5937500000f, 88383.5859375000f, 88406.5781250000f, +88429.5703125000f, 88452.5625000000f, 88475.5546875000f, 88498.5546875000f, +88521.5468750000f, 88544.5468750000f, 88567.5468750000f, 88590.5546875000f, +88613.5546875000f, 88636.5625000000f, 88659.5703125000f, 88682.5781250000f, +88705.5859375000f, 88728.6015625000f, 88751.6093750000f, 88774.6250000000f, +88797.6406250000f, 88820.6562500000f, 88843.6796875000f, 88866.6953125000f, +88889.7187500000f, 88912.7421875000f, 88935.7656250000f, 88958.7890625000f, +88981.8203125000f, 89004.8515625000f, 89027.8828125000f, 89050.9140625000f, +89073.9453125000f, 89096.9843750000f, 89120.0156250000f, 89143.0546875000f, +89166.0937500000f, 89189.1328125000f, 89212.1796875000f, 89235.2265625000f, +89258.2656250000f, 89281.3125000000f, 89304.3671875000f, 89327.4140625000f, +89350.4687500000f, 89373.5156250000f, 89396.5703125000f, 89419.6328125000f, +89442.6875000000f, 89465.7421875000f, 89488.8046875000f, 89511.8671875000f, +89534.9296875000f, 89558.0000000000f, 89581.0625000000f, 89604.1328125000f, +89627.2031250000f, 89650.2734375000f, 89673.3437500000f, 89696.4140625000f, +89719.4921875000f, 89742.5703125000f, 89765.6484375000f, 89788.7265625000f, +89811.8046875000f, 89834.8906250000f, 89857.9765625000f, 89881.0625000000f, +89904.1484375000f, 89927.2343750000f, 89950.3281250000f, 89973.4140625000f, +89996.5078125000f, 90019.6015625000f, 90042.7031250000f, 90065.7968750000f, +90088.8984375000f, 90112.0000000000f, 90135.1015625000f, 90158.2031250000f, +90181.3046875000f, 90204.4140625000f, 90227.5234375000f, 90250.6328125000f, +90273.7421875000f, 90296.8515625000f, 90319.9687500000f, 90343.0859375000f, +90366.2031250000f, 90389.3203125000f, 90412.4375000000f, 90435.5625000000f, +90458.6796875000f, 90481.8046875000f, 90504.9296875000f, 90528.0625000000f, +90551.1875000000f, 90574.3203125000f, 90597.4531250000f, 90620.5859375000f, +90643.7187500000f, 90666.8515625000f, 90689.9921875000f, 90713.1328125000f, +90736.2656250000f, 90759.4140625000f, 90782.5546875000f, 90805.7031250000f, +90828.8437500000f, 90851.9921875000f, 90875.1406250000f, 90898.2968750000f, +90921.4453125000f, 90944.6015625000f, 90967.7578125000f, 90990.9140625000f, +91014.0703125000f, 91037.2265625000f, 91060.3906250000f, 91083.5546875000f, +91106.7187500000f, 91129.8828125000f, 91153.0468750000f, 91176.2187500000f, +91199.3828125000f, 91222.5546875000f, 91245.7343750000f, 91268.9062500000f, +91292.0781250000f, 91315.2578125000f, 91338.4375000000f, 91361.6171875000f, +91384.7968750000f, 91407.9843750000f, 91431.1640625000f, 91454.3515625000f, +91477.5390625000f, 91500.7265625000f, 91523.9218750000f, 91547.1093750000f, +91570.3046875000f, 91593.5000000000f, 91616.6953125000f, 91639.8906250000f, +91663.0937500000f, 91686.2890625000f, 91709.4921875000f, 91732.6953125000f, +91755.9062500000f, 91779.1093750000f, 91802.3203125000f, 91825.5312500000f, +91848.7421875000f, 91871.9531250000f, 91895.1640625000f, 91918.3828125000f, +91941.5937500000f, 91964.8125000000f, 91988.0390625000f, 92011.2578125000f, +92034.4765625000f, 92057.7031250000f, 92080.9296875000f, 92104.1562500000f, +92127.3828125000f, 92150.6171875000f, 92173.8437500000f, 92197.0781250000f, +92220.3125000000f, 92243.5468750000f, 92266.7890625000f, 92290.0234375000f, +92313.2656250000f, 92336.5078125000f, 92359.7500000000f, 92382.9921875000f, +92406.2421875000f, 92429.4921875000f, 92452.7421875000f, 92475.9921875000f, +92499.2421875000f, 92522.4921875000f, 92545.7500000000f, 92569.0078125000f, +92592.2656250000f, 92615.5234375000f, 92638.7812500000f, 92662.0468750000f, +92685.3125000000f, 92708.5781250000f, 92731.8437500000f, 92755.1093750000f, +92778.3750000000f, 92801.6484375000f, 92824.9218750000f, 92848.1953125000f, +92871.4687500000f, 92894.7500000000f, 92918.0234375000f, 92941.3046875000f, +92964.5859375000f, 92987.8671875000f, 93011.1562500000f, 93034.4375000000f, +93057.7265625000f, 93081.0156250000f, 93104.3046875000f, 93127.5937500000f, +93150.8906250000f, 93174.1796875000f, 93197.4765625000f, 93220.7734375000f, +93244.0781250000f, 93267.3750000000f, 93290.6796875000f, 93313.9765625000f, +93337.2812500000f, 93360.5859375000f, 93383.8984375000f, 93407.2031250000f, +93430.5156250000f, 93453.8281250000f, 93477.1406250000f, 93500.4531250000f, +93523.7734375000f, 93547.0859375000f, 93570.4062500000f, 93593.7265625000f, +93617.0468750000f, 93640.3750000000f, 93663.6953125000f, 93687.0234375000f, +93710.3515625000f, 93733.6796875000f, 93757.0156250000f, 93780.3437500000f, +93803.6796875000f, 93827.0156250000f, 93850.3515625000f, 93873.6875000000f, +93897.0234375000f, 93920.3671875000f, 93943.7109375000f, 93967.0546875000f, +93990.3984375000f, 94013.7421875000f, 94037.0937500000f, 94060.4453125000f, +94083.7968750000f, 94107.1484375000f, 94130.5000000000f, 94153.8515625000f, +94177.2109375000f, 94200.5703125000f, 94223.9296875000f, 94247.2890625000f, +94270.6562500000f, 94294.0156250000f, 94317.3828125000f, 94340.7500000000f, +94364.1171875000f, 94387.4843750000f, 94410.8593750000f, 94434.2343750000f, +94457.6015625000f, 94480.9843750000f, 94504.3593750000f, 94527.7343750000f, +94551.1171875000f, 94574.5000000000f, 94597.8828125000f, 94621.2656250000f, +94644.6484375000f, 94668.0390625000f, 94691.4218750000f, 94714.8125000000f, +94738.2109375000f, 94761.6015625000f, 94784.9921875000f, 94808.3906250000f, +94831.7890625000f, 94855.1875000000f, 94878.5859375000f, 94901.9843750000f, +94925.3906250000f, 94948.7968750000f, 94972.2031250000f, 94995.6093750000f, +95019.0156250000f, 95042.4296875000f, 95065.8359375000f, 95089.2500000000f, +95112.6640625000f, 95136.0859375000f, 95159.5000000000f, 95182.9218750000f, +95206.3359375000f, 95229.7578125000f, 95253.1796875000f, 95276.6093750000f, +95300.0312500000f, 95323.4609375000f, 95346.8906250000f, 95370.3203125000f, +95393.7500000000f, 95417.1875000000f, 95440.6171875000f, 95464.0546875000f, +95487.4921875000f, 95510.9296875000f, 95534.3750000000f, 95557.8125000000f, +95581.2578125000f, 95604.7031250000f, 95628.1484375000f, 95651.5937500000f, +95675.0468750000f, 95698.5000000000f, 95721.9453125000f, 95745.3984375000f, +95768.8593750000f, 95792.3125000000f, 95815.7734375000f, 95839.2265625000f, +95862.6875000000f, 95886.1562500000f, 95909.6171875000f, 95933.0781250000f, +95956.5468750000f, 95980.0156250000f, 96003.4843750000f, 96026.9531250000f, +96050.4296875000f, 96073.8984375000f, 96097.3750000000f, 96120.8515625000f, +96144.3281250000f, 96167.8046875000f, 96191.2890625000f, 96214.7734375000f, +96238.2578125000f, 96261.7421875000f, 96285.2265625000f, 96308.7109375000f, +96332.2031250000f, 96355.6953125000f, 96379.1875000000f, 96402.6796875000f, +96426.1718750000f, 96449.6718750000f, 96473.1640625000f, 96496.6640625000f, +96520.1640625000f, 96543.6718750000f, 96567.1718750000f, 96590.6796875000f, +96614.1875000000f, 96637.6953125000f, 96661.2031250000f, 96684.7109375000f, +96708.2265625000f, 96731.7343750000f, 96755.2500000000f, 96778.7656250000f, +96802.2890625000f, 96825.8046875000f, 96849.3281250000f, 96872.8515625000f, +96896.3750000000f, 96919.8984375000f, 96943.4218750000f, 96966.9531250000f, +96990.4843750000f, 97014.0156250000f, 97037.5468750000f, 97061.0781250000f, +97084.6093750000f, 97108.1484375000f, 97131.6875000000f, 97155.2265625000f, +97178.7656250000f, 97202.3125000000f, 97225.8515625000f, 97249.3984375000f, +97272.9453125000f, 97296.4921875000f, 97320.0390625000f, 97343.5937500000f, +97367.1406250000f, 97390.6953125000f, 97414.2500000000f, 97437.8046875000f, +97461.3671875000f, 97484.9218750000f, 97508.4843750000f, 97532.0468750000f, +97555.6093750000f, 97579.1718750000f, 97602.7421875000f, 97626.3125000000f, +97649.8750000000f, 97673.4531250000f, 97697.0234375000f, 97720.5937500000f, +97744.1718750000f, 97767.7421875000f, 97791.3203125000f, 97814.9062500000f, +97838.4843750000f, 97862.0625000000f, 97885.6484375000f, 97909.2343750000f, +97932.8203125000f, 97956.4062500000f, 97979.9921875000f, 98003.5859375000f, +98027.1796875000f, 98050.7734375000f, 98074.3671875000f, 98097.9609375000f, +98121.5625000000f, 98145.1562500000f, 98168.7578125000f, 98192.3593750000f, +98215.9609375000f, 98239.5703125000f, 98263.1718750000f, 98286.7812500000f, +98310.3906250000f, 98334.0000000000f, 98357.6171875000f, 98381.2265625000f, +98404.8437500000f, 98428.4609375000f, 98452.0781250000f, 98475.6953125000f, +98499.3125000000f, 98522.9375000000f, 98546.5546875000f, 98570.1796875000f, +98593.8125000000f, 98617.4375000000f, 98641.0625000000f, 98664.6953125000f, +98688.3281250000f, 98711.9609375000f, 98735.5937500000f, 98759.2265625000f, +98782.8671875000f, 98806.5078125000f, 98830.1484375000f, 98853.7890625000f, +98877.4296875000f, 98901.0703125000f, 98924.7187500000f, 98948.3671875000f, +98972.0156250000f, 98995.6640625000f, 99019.3203125000f, 99042.9687500000f, +99066.6250000000f, 99090.2812500000f, 99113.9375000000f, 99137.5937500000f, +99161.2578125000f, 99184.9140625000f, 99208.5781250000f, 99232.2421875000f, +99255.9062500000f, 99279.5781250000f, 99303.2421875000f, 99326.9140625000f, +99350.5859375000f, 99374.2578125000f, 99397.9296875000f, 99421.6093750000f, +99445.2812500000f, 99468.9609375000f, 99492.6406250000f, 99516.3203125000f, +99540.0000000000f, 99563.6875000000f, 99587.3750000000f, 99611.0625000000f, +99634.7500000000f, 99658.4375000000f, 99682.1250000000f, 99705.8203125000f, +99729.5156250000f, 99753.2109375000f, 99776.9062500000f, 99800.6015625000f, +99824.3046875000f, 99848.0000000000f, 99871.7031250000f, 99895.4062500000f, +99919.1093750000f, 99942.8203125000f, 99966.5234375000f, 99990.2343750000f, +100013.9453125000f, 100037.6562500000f, 100061.3750000000f, 100085.0859375000f, +100108.8046875000f, 100132.5234375000f, 100156.2421875000f, 100179.9609375000f, +100203.6796875000f, 100227.4062500000f, 100251.1328125000f, 100274.8515625000f, +100298.5859375000f, 100322.3125000000f, 100346.0390625000f, 100369.7734375000f, +100393.5078125000f, 100417.2421875000f, 100440.9765625000f, 100464.7109375000f, +100488.4531250000f, 100512.1953125000f, 100535.9296875000f, 100559.6796875000f, +100583.4218750000f, 100607.1640625000f, 100630.9140625000f, 100654.6640625000f, +100678.4140625000f, 100702.1640625000f, 100725.9140625000f, 100749.6718750000f, +100773.4218750000f, 100797.1796875000f, 100820.9375000000f, 100844.6953125000f, +100868.4609375000f, 100892.2187500000f, 100915.9843750000f, 100939.7500000000f, +100963.5156250000f, 100987.2890625000f, 101011.0546875000f, 101034.8281250000f, +101058.6015625000f, 101082.3750000000f, 101106.1484375000f, 101129.9218750000f, +101153.7031250000f, 101177.4843750000f, 101201.2578125000f, 101225.0468750000f, +101248.8281250000f, 101272.6093750000f, 101296.3984375000f, 101320.1875000000f, +101343.9765625000f, 101367.7656250000f, 101391.5546875000f, 101415.3515625000f, +101439.1406250000f, 101462.9375000000f, 101486.7343750000f, 101510.5390625000f, +101534.3359375000f, 101558.1406250000f, 101581.9375000000f, 101605.7421875000f, +101629.5468750000f, 101653.3593750000f, 101677.1640625000f, 101700.9765625000f, +101724.7890625000f, 101748.6015625000f, 101772.4140625000f, 101796.2265625000f, +101820.0468750000f, 101843.8671875000f, 101867.6875000000f, 101891.5078125000f, +101915.3281250000f, 101939.1484375000f, 101962.9765625000f, 101986.8046875000f, +102010.6328125000f, 102034.4609375000f, 102058.2890625000f, 102082.1250000000f, +102105.9609375000f, 102129.7890625000f, 102153.6250000000f, 102177.4687500000f, +102201.3046875000f, 102225.1484375000f, 102248.9843750000f, 102272.8281250000f, +102296.6718750000f, 102320.5234375000f, 102344.3671875000f, 102368.2187500000f, +102392.0703125000f, 102415.9218750000f, 102439.7734375000f, 102463.6250000000f, +102487.4843750000f, 102511.3437500000f, 102535.1953125000f, 102559.0625000000f, +102582.9218750000f, 102606.7812500000f, 102630.6484375000f, 102654.5156250000f, +102678.3828125000f, 102702.2500000000f, 102726.1171875000f, 102749.9921875000f, +102773.8593750000f, 102797.7343750000f, 102821.6093750000f, 102845.4843750000f, +102869.3671875000f, 102893.2421875000f, 102917.1250000000f, 102941.0078125000f, +102964.8906250000f, 102988.7734375000f, 103012.6640625000f, 103036.5468750000f, +103060.4375000000f, 103084.3281250000f, 103108.2187500000f, 103132.1171875000f, +103156.0078125000f, 103179.9062500000f, 103203.8046875000f, 103227.7031250000f, +103251.6015625000f, 103275.5000000000f, 103299.4062500000f, 103323.3125000000f, +103347.2187500000f, 103371.1250000000f, 103395.0312500000f, 103418.9375000000f, +103442.8515625000f, 103466.7656250000f, 103490.6796875000f, 103514.5937500000f, +103538.5078125000f, 103562.4296875000f, 103586.3515625000f, 103610.2656250000f, +103634.1875000000f, 103658.1171875000f, 103682.0390625000f, 103705.9687500000f, +103729.8906250000f, 103753.8203125000f, 103777.7500000000f, 103801.6875000000f, +103825.6171875000f, 103849.5546875000f, 103873.4921875000f, 103897.4296875000f, +103921.3671875000f, 103945.3046875000f, 103969.2500000000f, 103993.1875000000f, +104017.1328125000f, 104041.0781250000f, 104065.0312500000f, 104088.9765625000f, +104112.9296875000f, 104136.8750000000f, 104160.8281250000f, 104184.7812500000f, +104208.7421875000f, 104232.6953125000f, 104256.6562500000f, 104280.6171875000f, +104304.5703125000f, 104328.5390625000f, 104352.5000000000f, 104376.4687500000f, +104400.4296875000f, 104424.3984375000f, 104448.3671875000f, 104472.3359375000f, +104496.3125000000f, 104520.2812500000f, 104544.2578125000f, 104568.2343750000f, +104592.2109375000f, 104616.1875000000f, 104640.1718750000f, 104664.1484375000f, +104688.1328125000f, 104712.1171875000f, 104736.1015625000f, 104760.0937500000f, +104784.0781250000f, 104808.0703125000f, 104832.0625000000f, 104856.0546875000f, +104880.0468750000f, 104904.0390625000f, 104928.0390625000f, 104952.0390625000f, +104976.0390625000f, 105000.0390625000f, 105024.0390625000f, 105048.0390625000f, +105072.0468750000f, 105096.0546875000f, 105120.0625000000f, 105144.0703125000f, +105168.0781250000f, 105192.0937500000f, 105216.1015625000f, 105240.1171875000f, +105264.1328125000f, 105288.1484375000f, 105312.1718750000f, 105336.1875000000f, +105360.2109375000f, 105384.2343750000f, 105408.2578125000f, 105432.2812500000f, +105456.3125000000f, 105480.3359375000f, 105504.3671875000f, 105528.3984375000f, +105552.4296875000f, 105576.4609375000f, 105600.5000000000f, 105624.5390625000f, +105648.5703125000f, 105672.6093750000f, 105696.6562500000f, 105720.6953125000f, +105744.7343750000f, 105768.7812500000f, 105792.8281250000f, 105816.8750000000f, +105840.9218750000f, 105864.9765625000f, 105889.0234375000f, 105913.0781250000f, +105937.1328125000f, 105961.1875000000f, 105985.2421875000f, 106009.3046875000f, +106033.3593750000f, 106057.4218750000f, 106081.4843750000f, 106105.5468750000f, +106129.6171875000f, 106153.6796875000f, 106177.7500000000f, 106201.8203125000f, +106225.8906250000f, 106249.9609375000f, 106274.0312500000f, 106298.1093750000f, +106322.1796875000f, 106346.2578125000f, 106370.3359375000f, 106394.4218750000f, +106418.5000000000f, 106442.5859375000f, 106466.6640625000f, 106490.7500000000f, +106514.8359375000f, 106538.9296875000f, 106563.0156250000f, 106587.1093750000f, +106611.2031250000f, 106635.2968750000f, 106659.3906250000f, 106683.4843750000f, +106707.5859375000f, 106731.6796875000f, 106755.7812500000f, 106779.8828125000f, +106803.9843750000f, 106828.0937500000f, 106852.1953125000f, 106876.3046875000f, +106900.4140625000f, 106924.5234375000f, 106948.6328125000f, 106972.7500000000f, +106996.8593750000f, 107020.9765625000f, 107045.0937500000f, 107069.2109375000f, +107093.3281250000f, 107117.4531250000f, 107141.5703125000f, 107165.6953125000f, +107189.8203125000f, 107213.9453125000f, 107238.0781250000f, 107262.2031250000f, +107286.3359375000f, 107310.4687500000f, 107334.6015625000f, 107358.7343750000f, +107382.8671875000f, 107407.0078125000f, 107431.1484375000f, 107455.2812500000f, +107479.4296875000f, 107503.5703125000f, 107527.7109375000f, 107551.8593750000f, +107576.0078125000f, 107600.1562500000f, 107624.3046875000f, 107648.4531250000f, +107672.6015625000f, 107696.7578125000f, 107720.9140625000f, 107745.0703125000f, +107769.2265625000f, 107793.3828125000f, 107817.5468750000f, 107841.7031250000f, +107865.8671875000f, 107890.0312500000f, 107914.1953125000f, 107938.3671875000f, +107962.5312500000f, 107986.7031250000f, 108010.8750000000f, 108035.0468750000f, +108059.2187500000f, 108083.3984375000f, 108107.5703125000f, 108131.7500000000f, +108155.9296875000f, 108180.1093750000f, 108204.2890625000f, 108228.4765625000f, +108252.6562500000f, 108276.8437500000f, 108301.0312500000f, 108325.2187500000f, +108349.4062500000f, 108373.6015625000f, 108397.7968750000f, 108421.9843750000f, +108446.1796875000f, 108470.3828125000f, 108494.5781250000f, 108518.7734375000f, +108542.9765625000f, 108567.1796875000f, 108591.3828125000f, 108615.5859375000f, +108639.7968750000f, 108664.0000000000f, 108688.2109375000f, 108712.4218750000f, +108736.6328125000f, 108760.8437500000f, 108785.0546875000f, 108809.2734375000f, +108833.4921875000f, 108857.7109375000f, 108881.9296875000f, 108906.1484375000f, +108930.3671875000f, 108954.5937500000f, 108978.8203125000f, 109003.0468750000f, +109027.2734375000f, 109051.5000000000f, 109075.7343750000f, 109099.9609375000f, +109124.1953125000f, 109148.4296875000f, 109172.6640625000f, 109196.9062500000f, +109221.1406250000f, 109245.3828125000f, 109269.6250000000f, 109293.8671875000f, +109318.1093750000f, 109342.3515625000f, 109366.6015625000f, 109390.8515625000f, +109415.1015625000f, 109439.3515625000f, 109463.6015625000f, 109487.8515625000f, +109512.1093750000f, 109536.3671875000f, 109560.6171875000f, 109584.8828125000f, +109609.1406250000f, 109633.3984375000f, 109657.6640625000f, 109681.9296875000f, +109706.1953125000f, 109730.4609375000f, 109754.7265625000f, 109778.9921875000f, +109803.2656250000f, 109827.5390625000f, 109851.8125000000f, 109876.0859375000f, +109900.3593750000f, 109924.6406250000f, 109948.9218750000f, 109973.1953125000f, +109997.4765625000f, 110021.7656250000f, 110046.0468750000f, 110070.3281250000f, +110094.6171875000f, 110118.9062500000f, 110143.1953125000f, 110167.4843750000f, +110191.7812500000f, 110216.0703125000f, 110240.3671875000f, 110264.6640625000f, +110288.9609375000f, 110313.2578125000f, 110337.5625000000f, 110361.8593750000f, +110386.1640625000f, 110410.4687500000f, 110434.7734375000f, 110459.0781250000f, +110483.3906250000f, 110507.6953125000f, 110532.0078125000f, 110556.3203125000f, +110580.6328125000f, 110604.9453125000f, 110629.2656250000f, 110653.5781250000f, +110677.8984375000f, 110702.2187500000f, 110726.5390625000f, 110750.8671875000f, +110775.1875000000f, 110799.5156250000f, 110823.8437500000f, 110848.1718750000f, +110872.5000000000f, 110896.8281250000f, 110921.1640625000f, 110945.4921875000f, +110969.8281250000f, 110994.1640625000f, 111018.5078125000f, 111042.8437500000f, +111067.1796875000f, 111091.5234375000f, 111115.8671875000f, 111140.2109375000f, +111164.5546875000f, 111188.9062500000f, 111213.2500000000f, 111237.6015625000f, +111261.9531250000f, 111286.3046875000f, 111310.6562500000f, 111335.0156250000f, +111359.3671875000f, 111383.7265625000f, 111408.0859375000f, 111432.4453125000f, +111456.8046875000f, 111481.1718750000f, 111505.5312500000f, 111529.8984375000f, +111554.2656250000f, 111578.6328125000f, 111603.0000000000f, 111627.3750000000f, +111651.7500000000f, 111676.1171875000f, 111700.4921875000f, 111724.8671875000f, +111749.2500000000f, 111773.6250000000f, 111798.0078125000f, 111822.3906250000f, +111846.7734375000f, 111871.1562500000f, 111895.5390625000f, 111919.9296875000f, +111944.3125000000f, 111968.7031250000f, 111993.0937500000f, 112017.4843750000f, +112041.8828125000f, 112066.2734375000f, 112090.6718750000f, 112115.0703125000f, +112139.4687500000f, 112163.8671875000f, 112188.2734375000f, 112212.6718750000f, +112237.0781250000f, 112261.4843750000f, 112285.8906250000f, 112310.2968750000f, +112334.7031250000f, 112359.1171875000f, 112383.5312500000f, 112407.9453125000f, +112432.3593750000f, 112456.7734375000f, 112481.1875000000f, 112505.6093750000f, +112530.0312500000f, 112554.4453125000f, 112578.8750000000f, 112603.2968750000f, +112627.7187500000f, 112652.1484375000f, 112676.5781250000f, 112701.0078125000f, +112725.4375000000f, 112749.8671875000f, 112774.2968750000f, 112798.7343750000f, +112823.1718750000f, 112847.6093750000f, 112872.0468750000f, 112896.4843750000f, +112920.9296875000f, 112945.3671875000f, 112969.8125000000f, 112994.2578125000f, +113018.7031250000f, 113043.1484375000f, 113067.6015625000f, 113092.0546875000f, +113116.5000000000f, 113140.9531250000f, 113165.4140625000f, 113189.8671875000f, +113214.3203125000f, 113238.7812500000f, 113263.2421875000f, 113287.7031250000f, +113312.1640625000f, 113336.6250000000f, 113361.0937500000f, 113385.5625000000f, +113410.0234375000f, 113434.4921875000f, 113458.9687500000f, 113483.4375000000f, +113507.9062500000f, 113532.3828125000f, 113556.8593750000f, 113581.3359375000f, +113605.8125000000f, 113630.2968750000f, 113654.7734375000f, 113679.2578125000f, +113703.7421875000f, 113728.2265625000f, 113752.7109375000f, 113777.1953125000f, +113801.6875000000f, 113826.1796875000f, 113850.6640625000f, 113875.1562500000f, +113899.6562500000f, 113924.1484375000f, 113948.6484375000f, 113973.1406250000f, +113997.6406250000f, 114022.1406250000f, 114046.6484375000f, 114071.1484375000f, +114095.6484375000f, 114120.1562500000f, 114144.6640625000f, 114169.1718750000f, +114193.6796875000f, 114218.1953125000f, 114242.7031250000f, 114267.2187500000f, +114291.7343750000f, 114316.2500000000f, 114340.7656250000f, 114365.2890625000f, +114389.8046875000f, 114414.3281250000f, 114438.8515625000f, 114463.3750000000f, +114487.8984375000f, 114512.4296875000f, 114536.9531250000f, 114561.4843750000f, +114586.0156250000f, 114610.5468750000f, 114635.0859375000f, 114659.6171875000f, +114684.1562500000f, 114708.6875000000f, 114733.2265625000f, 114757.7656250000f, +114782.3125000000f, 114806.8515625000f, 114831.3984375000f, 114855.9375000000f, +114880.4843750000f, 114905.0390625000f, 114929.5859375000f, 114954.1328125000f, +114978.6875000000f, 115003.2421875000f, 115027.7968750000f, 115052.3515625000f, +115076.9062500000f, 115101.4609375000f, 115126.0234375000f, 115150.5859375000f, +115175.1484375000f, 115199.7109375000f, 115224.2734375000f, 115248.8437500000f, +115273.4062500000f, 115297.9765625000f, 115322.5468750000f, 115347.1171875000f, +115371.6953125000f, 115396.2656250000f, 115420.8437500000f, 115445.4140625000f, +115469.9921875000f, 115494.5781250000f, 115519.1562500000f, 115543.7343750000f, +115568.3203125000f, 115592.9062500000f, 115617.4921875000f, 115642.0781250000f, +115666.6640625000f, 115691.2578125000f, 115715.8437500000f, 115740.4375000000f, +115765.0312500000f, 115789.6250000000f, 115814.2265625000f, 115838.8203125000f, +115863.4218750000f, 115888.0234375000f, 115912.6250000000f, 115937.2265625000f, +115961.8281250000f, 115986.4296875000f, 116011.0390625000f, 116035.6484375000f, +116060.2578125000f, 116084.8671875000f, 116109.4765625000f, 116134.0937500000f, +116158.7109375000f, 116183.3203125000f, 116207.9375000000f, 116232.5546875000f, +116257.1796875000f, 116281.7968750000f, 116306.4218750000f, 116331.0468750000f, +116355.6718750000f, 116380.2968750000f, 116404.9218750000f, 116429.5546875000f, +116454.1796875000f, 116478.8125000000f, 116503.4453125000f, 116528.0781250000f, +116552.7187500000f, 116577.3515625000f, 116601.9921875000f, 116626.6328125000f, +116651.2734375000f, 116675.9140625000f, 116700.5546875000f, 116725.2031250000f, +116749.8437500000f, 116774.4921875000f, 116799.1406250000f, 116823.7890625000f, +116848.4375000000f, 116873.0937500000f, 116897.7500000000f, 116922.3984375000f, +116947.0546875000f, 116971.7187500000f, 116996.3750000000f, 117021.0312500000f, +117045.6953125000f, 117070.3593750000f, 117095.0234375000f, 117119.6875000000f, +117144.3515625000f, 117169.0234375000f, 117193.6875000000f, 117218.3593750000f, +117243.0312500000f, 117267.7031250000f, 117292.3828125000f, 117317.0546875000f, +117341.7343750000f, 117366.4140625000f, 117391.0937500000f, 117415.7734375000f, +117440.4531250000f, 117465.1328125000f, 117489.8203125000f, 117514.5078125000f, +117539.1953125000f, 117563.8828125000f, 117588.5703125000f, 117613.2656250000f, +117637.9531250000f, 117662.6484375000f, 117687.3437500000f, 117712.0390625000f, +117736.7421875000f, 117761.4375000000f, 117786.1406250000f, 117810.8359375000f, +117835.5390625000f, 117860.2500000000f, 117884.9531250000f, 117909.6562500000f, +117934.3671875000f, 117959.0781250000f, 117983.7890625000f, 118008.5000000000f, +118033.2109375000f, 118057.9296875000f, 118082.6406250000f, 118107.3593750000f, +118132.0781250000f, 118156.7968750000f, 118181.5156250000f, 118206.2421875000f, +118230.9609375000f, 118255.6875000000f, 118280.4140625000f, 118305.1406250000f, +118329.8671875000f, 118354.6015625000f, 118379.3281250000f, 118404.0625000000f, +118428.7968750000f, 118453.5312500000f, 118478.2734375000f, 118503.0078125000f, +118527.7500000000f, 118552.4843750000f, 118577.2265625000f, 118601.9687500000f, +118626.7187500000f, 118651.4609375000f, 118676.2109375000f, 118700.9531250000f, +118725.7031250000f, 118750.4531250000f, 118775.2109375000f, 118799.9609375000f, +118824.7187500000f, 118849.4687500000f, 118874.2265625000f, 118898.9843750000f, +118923.7500000000f, 118948.5078125000f, 118973.2734375000f, 118998.0312500000f, +119022.7968750000f, 119047.5625000000f, 119072.3281250000f, 119097.1015625000f, +119121.8671875000f, 119146.6406250000f, 119171.4140625000f, 119196.1875000000f, +119220.9609375000f, 119245.7421875000f, 119270.5156250000f, 119295.2968750000f, +119320.0781250000f, 119344.8593750000f, 119369.6406250000f, 119394.4218750000f, +119419.2109375000f, 119444.0000000000f, 119468.7812500000f, 119493.5703125000f, +119518.3671875000f, 119543.1562500000f, 119567.9531250000f, 119592.7421875000f, +119617.5390625000f, 119642.3359375000f, 119667.1328125000f, 119691.9375000000f, +119716.7343750000f, 119741.5390625000f, 119766.3437500000f, 119791.1484375000f, +119815.9531250000f, 119840.7578125000f, 119865.5703125000f, 119890.3750000000f, +119915.1875000000f, 119940.0000000000f, 119964.8125000000f, 119989.6328125000f, +120014.4453125000f, 120039.2656250000f, 120064.0781250000f, 120088.8984375000f, +120113.7265625000f, 120138.5468750000f, 120163.3671875000f, 120188.1953125000f, +120213.0234375000f, 120237.8515625000f, 120262.6796875000f, 120287.5078125000f, +120312.3437500000f, 120337.1718750000f, 120362.0078125000f, 120386.8437500000f, +120411.6796875000f, 120436.5156250000f, 120461.3593750000f, 120486.1953125000f, +120511.0390625000f, 120535.8828125000f, 120560.7265625000f, 120585.5703125000f, +120610.4218750000f, 120635.2656250000f, 120660.1171875000f, 120684.9687500000f, +120709.8203125000f, 120734.6718750000f, 120759.5312500000f, 120784.3828125000f, +120809.2421875000f, 120834.1015625000f, 120858.9609375000f, 120883.8203125000f, +120908.6875000000f, 120933.5468750000f, 120958.4140625000f, 120983.2812500000f, +121008.1484375000f, 121033.0156250000f, 121057.8828125000f, 121082.7578125000f, +121107.6250000000f, 121132.5000000000f, 121157.3750000000f, 121182.2500000000f, +121207.1328125000f, 121232.0078125000f, 121256.8906250000f, 121281.7734375000f, +121306.6562500000f, 121331.5390625000f, 121356.4218750000f, 121381.3125000000f, +121406.1953125000f, 121431.0859375000f, 121455.9765625000f, 121480.8671875000f, +121505.7656250000f, 121530.6562500000f, 121555.5546875000f, 121580.4531250000f, +121605.3515625000f, 121630.2500000000f, 121655.1484375000f, 121680.0468750000f, +121704.9531250000f, 121729.8593750000f, 121754.7656250000f, 121779.6718750000f, +121804.5781250000f, 121829.4843750000f, 121854.3984375000f, 121879.3125000000f, +121904.2265625000f, 121929.1406250000f, 121954.0546875000f, 121978.9687500000f, +122003.8906250000f, 122028.8125000000f, 122053.7343750000f, 122078.6562500000f, +122103.5781250000f, 122128.5000000000f, 122153.4296875000f, 122178.3515625000f, +122203.2812500000f, 122228.2109375000f, 122253.1484375000f, 122278.0781250000f, +122303.0078125000f, 122327.9453125000f, 122352.8828125000f, 122377.8203125000f, +122402.7578125000f, 122427.6953125000f, 122452.6406250000f, 122477.5859375000f, +122502.5234375000f, 122527.4687500000f, 122552.4218750000f, 122577.3671875000f, +122602.3125000000f, 122627.2656250000f, 122652.2187500000f, 122677.1718750000f, +122702.1250000000f, 122727.0781250000f, 122752.0390625000f, 122776.9921875000f, +122801.9531250000f, 122826.9140625000f, 122851.8750000000f, 122876.8359375000f, +122901.8046875000f, 122926.7656250000f, 122951.7343750000f, 122976.7031250000f, +123001.6718750000f, 123026.6406250000f, 123051.6093750000f, 123076.5859375000f, +123101.5625000000f, 123126.5390625000f, 123151.5156250000f, 123176.4921875000f, +123201.4687500000f, 123226.4531250000f, 123251.4296875000f, 123276.4140625000f, +123301.3984375000f, 123326.3828125000f, 123351.3750000000f, 123376.3593750000f, +123401.3515625000f, 123426.3437500000f, 123451.3359375000f, 123476.3281250000f, +123501.3203125000f, 123526.3203125000f, 123551.3125000000f, 123576.3125000000f, +123601.3125000000f, 123626.3125000000f, 123651.3125000000f, 123676.3203125000f, +123701.3203125000f, 123726.3281250000f, 123751.3359375000f, 123776.3437500000f, +123801.3515625000f, 123826.3671875000f, 123851.3750000000f, 123876.3906250000f, +123901.4062500000f, 123926.4218750000f, 123951.4375000000f, 123976.4609375000f, +124001.4765625000f, 124026.5000000000f, 124051.5234375000f, 124076.5468750000f, +124101.5703125000f, 124126.5937500000f, 124151.6250000000f, 124176.6562500000f, +124201.6796875000f, 124226.7109375000f, 124251.7500000000f, 124276.7812500000f, +124301.8125000000f, 124326.8515625000f, 124351.8906250000f, 124376.9296875000f, +124401.9687500000f, 124427.0078125000f, 124452.0546875000f, 124477.0937500000f, +124502.1406250000f, 124527.1875000000f, 124552.2343750000f, 124577.2812500000f, +124602.3359375000f, 124627.3828125000f, 124652.4375000000f, 124677.4921875000f, +124702.5468750000f, 124727.6015625000f, 124752.6640625000f, 124777.7187500000f, +124802.7812500000f, 124827.8437500000f, 124852.9062500000f, 124877.9687500000f, +124903.0312500000f, 124928.1015625000f, 124953.1640625000f, 124978.2343750000f, +125003.3046875000f, 125028.3750000000f, 125053.4531250000f, 125078.5234375000f, +125103.6015625000f, 125128.6796875000f, 125153.7578125000f, 125178.8359375000f, +125203.9140625000f, 125228.9921875000f, 125254.0781250000f, 125279.1640625000f, +125304.2500000000f, 125329.3359375000f, 125354.4218750000f, 125379.5078125000f, +125404.6015625000f, 125429.6953125000f, 125454.7890625000f, 125479.8828125000f, +125504.9765625000f, 125530.0703125000f, 125555.1718750000f, 125580.2656250000f, +125605.3671875000f, 125630.4687500000f, 125655.5703125000f, 125680.6796875000f, +125705.7812500000f, 125730.8906250000f, 125756.0000000000f, 125781.1093750000f, +125806.2187500000f, 125831.3281250000f, 125856.4375000000f, 125881.5546875000f, +125906.6718750000f, 125931.7890625000f, 125956.9062500000f, 125982.0234375000f, +126007.1484375000f, 126032.2656250000f, 126057.3906250000f, 126082.5156250000f, +126107.6406250000f, 126132.7656250000f, 126157.8906250000f, 126183.0234375000f, +126208.1562500000f, 126233.2812500000f, 126258.4140625000f, 126283.5546875000f, +126308.6875000000f, 126333.8203125000f, 126358.9609375000f, 126384.1015625000f, +126409.2421875000f, 126434.3828125000f, 126459.5234375000f, 126484.6718750000f, +126509.8125000000f, 126534.9609375000f, 126560.1093750000f, 126585.2578125000f, +126610.4140625000f, 126635.5625000000f, 126660.7109375000f, 126685.8671875000f, +126711.0234375000f, 126736.1796875000f, 126761.3359375000f, 126786.5000000000f, +126811.6562500000f, 126836.8203125000f, 126861.9843750000f, 126887.1484375000f, +126912.3125000000f, 126937.4765625000f, 126962.6484375000f, 126987.8203125000f, +127012.9843750000f, 127038.1562500000f, 127063.3281250000f, 127088.5078125000f, +127113.6796875000f, 127138.8593750000f, 127164.0390625000f, 127189.2109375000f, +127214.3984375000f, 127239.5781250000f, 127264.7578125000f, 127289.9453125000f, +127315.1328125000f, 127340.3125000000f, 127365.5000000000f, 127390.6953125000f, +127415.8828125000f, 127441.0781250000f, 127466.2656250000f, 127491.4609375000f, +127516.6562500000f, 127541.8515625000f, 127567.0546875000f, 127592.2500000000f, +127617.4531250000f, 127642.6484375000f, 127667.8515625000f, 127693.0625000000f, +127718.2656250000f, 127743.4687500000f, 127768.6796875000f, 127793.8906250000f, +127819.0937500000f, 127844.3125000000f, 127869.5234375000f, 127894.7343750000f, +127919.9531250000f, 127945.1640625000f, 127970.3828125000f, 127995.6015625000f, +128020.8203125000f, 128046.0468750000f, 128071.2656250000f, 128096.4921875000f, +128121.7187500000f, 128146.9453125000f, 128172.1718750000f, 128197.3984375000f, +128222.6328125000f, 128247.8593750000f, 128273.0937500000f, 128298.3281250000f, +128323.5625000000f, 128348.7968750000f, 128374.0390625000f, 128399.2734375000f, +128424.5156250000f, 128449.7578125000f, 128475.0000000000f, 128500.2421875000f, +128525.4921875000f, 128550.7343750000f, 128575.9843750000f, 128601.2343750000f, +128626.4843750000f, 128651.7343750000f, 128676.9843750000f, 128702.2421875000f, +128727.4921875000f, 128752.7500000000f, 128778.0078125000f, 128803.2656250000f, +128828.5234375000f, 128853.7890625000f, 128879.0468750000f, 128904.3125000000f, +128929.5781250000f, 128954.8437500000f, 128980.1093750000f, 129005.3828125000f, +129030.6484375000f, 129055.9218750000f, 129081.1953125000f, 129106.4687500000f, +129131.7421875000f, 129157.0156250000f, 129182.2968750000f, 129207.5703125000f, +129232.8515625000f, 129258.1328125000f, 129283.4140625000f, 129308.6953125000f, +129333.9843750000f, 129359.2656250000f, 129384.5546875000f, 129409.8437500000f, +129435.1328125000f, 129460.4218750000f, 129485.7187500000f, 129511.0078125000f, +129536.3046875000f, 129561.6015625000f, 129586.8984375000f, 129612.1953125000f, +129637.4921875000f, 129662.7968750000f, 129688.0937500000f, 129713.3984375000f, +129738.7031250000f, 129764.0078125000f, 129789.3203125000f, 129814.6250000000f, +129839.9375000000f, 129865.2421875000f, 129890.5546875000f, 129915.8671875000f, +129941.1875000000f, 129966.5000000000f, 129991.8125000000f, 130017.1328125000f, +130042.4531250000f, 130067.7734375000f, 130093.0937500000f, 130118.4218750000f, +130143.7421875000f, 130169.0703125000f, 130194.3906250000f, 130219.7187500000f, +130245.0546875000f, 130270.3828125000f, 130295.7109375000f, 130321.0468750000f, +130346.3828125000f, 130371.7187500000f, 130397.0546875000f, 130422.3906250000f, +130447.7265625000f, 130473.0703125000f, 130498.4062500000f, 130523.7500000000f, +130549.0937500000f, 130574.4375000000f, 130599.7890625000f, 130625.1328125000f, +130650.4843750000f, 130675.8359375000f, 130701.1875000000f, 130726.5390625000f, +130751.8906250000f, 130777.2421875000f, 130802.6015625000f, 130827.9609375000f, +130853.3203125000f, 130878.6796875000f, 130904.0390625000f, 130929.3984375000f, +130954.7656250000f, 130980.1250000000f, 131005.4921875000f, 131030.8593750000f, +131056.2265625000f, 131081.5937500000f, 131106.9687500000f, 131132.3437500000f, +131157.7187500000f, 131183.0937500000f, 131208.4687500000f, 131233.8437500000f, +131259.2187500000f, 131284.5937500000f, 131309.9843750000f, 131335.3593750000f, +131360.7500000000f, 131386.1250000000f, 131411.5156250000f, 131436.9062500000f, +131462.2968750000f, 131487.6875000000f, 131513.0781250000f, 131538.4687500000f, +131563.8593750000f, 131589.2500000000f, 131614.6406250000f, 131640.0468750000f, +131665.4375000000f, 131690.8437500000f, 131716.2343750000f, 131741.6406250000f, +131767.0468750000f, 131792.4531250000f, 131817.8593750000f, 131843.2656250000f, +131868.6718750000f, 131894.0781250000f, 131919.4843750000f, 131944.8906250000f, +131970.3125000000f, 131995.7187500000f, 132021.1406250000f, 132046.5468750000f, +132071.9687500000f, 132097.3906250000f, 132122.8125000000f, 132148.2343750000f, +132173.6562500000f, 132199.0781250000f, 132224.5000000000f, 132249.9218750000f, +132275.3593750000f, 132300.7812500000f, 132326.2187500000f, 132351.6406250000f, +132377.0781250000f, 132402.5156250000f, 132427.9375000000f, 132453.3750000000f, +132478.8125000000f, 132504.2500000000f, 132529.6875000000f, 132555.1406250000f, +132580.5781250000f, 132606.0156250000f, 132631.4687500000f, 132656.9062500000f, +132682.3593750000f, 132707.7968750000f, 132733.2500000000f, 132758.7031250000f, +132784.1562500000f, 132809.6093750000f, 132835.0625000000f, 132860.5156250000f, +132885.9687500000f, 132911.4375000000f, 132936.8906250000f, 132962.3437500000f, +132987.8125000000f, 133013.2656250000f, 133038.7343750000f, 133064.2031250000f, +133089.6718750000f, 133115.1406250000f, 133140.6093750000f, 133166.0781250000f, +133191.5468750000f, 133217.0156250000f, 133242.4843750000f, 133267.9687500000f, +133293.4375000000f, 133318.9218750000f, 133344.3906250000f, 133369.8750000000f, +133395.3593750000f, 133420.8437500000f, 133446.3281250000f, 133471.8125000000f, +133497.2968750000f, 133522.7812500000f, 133548.2656250000f, 133573.7500000000f, +133599.2500000000f, 133624.7343750000f, 133650.2343750000f, 133675.7343750000f, +133701.2187500000f, 133726.7187500000f, 133752.2187500000f, 133777.7187500000f, +133803.2187500000f, 133828.7187500000f, 133854.2187500000f, 133879.7187500000f, +133905.2343750000f, 133930.7343750000f, 133956.2500000000f, 133981.7500000000f, +134007.2656250000f, 134032.7812500000f, 134058.2812500000f, 134083.7968750000f, +134109.3125000000f, 134134.8281250000f, 134160.3437500000f, 134185.8593750000f, +134211.3906250000f, 134236.9062500000f, 134262.4218750000f, 134287.9531250000f, +134313.4843750000f, 134339.0000000000f, 134364.5312500000f, 134390.0625000000f, +134415.5937500000f, 134441.1250000000f, 134466.6562500000f, 134492.1875000000f, +134517.7187500000f, 134543.2500000000f, 134568.7968750000f, 134594.3281250000f, +134619.8593750000f, 134645.4062500000f, 134670.9531250000f, 134696.4843750000f, +134722.0312500000f, 134747.5781250000f, 134773.1250000000f, 134798.6718750000f, +134824.2187500000f, 134849.7656250000f, 134875.3281250000f, 134900.8750000000f, +134926.4375000000f, 134951.9843750000f, 134977.5468750000f, 135003.0937500000f, +135028.6562500000f, 135054.2187500000f, 135079.7812500000f, 135105.3437500000f, +135130.9062500000f, 135156.4687500000f, 135182.0312500000f, 135207.5937500000f, +135233.1718750000f, 135258.7343750000f, 135284.3125000000f, 135309.8750000000f, +135335.4531250000f, 135361.0312500000f, 135386.6093750000f, 135412.1718750000f, +135437.7500000000f, 135463.3437500000f, 135488.9218750000f, 135514.5000000000f, +135540.0781250000f, 135565.6718750000f, 135591.2500000000f, 135616.8437500000f, +135642.4218750000f, 135668.0156250000f, 135693.6093750000f, 135719.1875000000f, +135744.7812500000f, 135770.3750000000f, 135795.9687500000f, 135821.5625000000f, +135847.1718750000f, 135872.7656250000f, 135898.3593750000f, 135923.9687500000f, +135949.5625000000f, 135975.1718750000f, 136000.7812500000f, 136026.3750000000f, +136051.9843750000f, 136077.5937500000f, 136103.2031250000f, 136128.8125000000f, +136154.4218750000f, 136180.0468750000f, 136205.6562500000f, 136231.2656250000f, +136256.8906250000f, 136282.5000000000f, 136308.1250000000f, 136333.7343750000f, +136359.3593750000f, 136384.9843750000f, 136410.6093750000f, 136436.2343750000f, +136461.8593750000f, 136487.4843750000f, 136513.1093750000f, 136538.7500000000f, +136564.3750000000f, 136590.0000000000f, 136615.6406250000f, 136641.2812500000f, +136666.9062500000f, 136692.5468750000f, 136718.1875000000f, 136743.8281250000f, +136769.4687500000f, 136795.1093750000f, 136820.7500000000f, 136846.3906250000f, +136872.0468750000f, 136897.6875000000f, 136923.3281250000f, 136948.9843750000f, +136974.6250000000f, 137000.2812500000f, 137025.9375000000f, 137051.5937500000f, +137077.2500000000f, 137102.9062500000f, 137128.5625000000f, 137154.2187500000f, +137179.8750000000f, 137205.5312500000f, 137231.2031250000f, 137256.8593750000f, +137282.5312500000f, 137308.1875000000f, 137333.8593750000f, 137359.5312500000f, +137385.2031250000f, 137410.8750000000f, 137436.5468750000f, 137462.2187500000f, +137487.8906250000f, 137513.5625000000f, 137539.2343750000f, 137564.9218750000f, +137590.5937500000f, 137616.2812500000f, 137641.9531250000f, 137667.6406250000f, +137693.3281250000f, 137719.0000000000f, 137744.6875000000f, 137770.3750000000f, +137796.0625000000f, 137821.7656250000f, 137847.4531250000f, 137873.1406250000f, +137898.8281250000f, 137924.5312500000f, 137950.2187500000f, 137975.9218750000f, +138001.6250000000f, 138027.3125000000f, 138053.0156250000f, 138078.7187500000f, +138104.4218750000f, 138130.1250000000f, 138155.8281250000f, 138181.5468750000f, +138207.2500000000f, 138232.9531250000f, 138258.6718750000f, 138284.3750000000f, +138310.0937500000f, 138335.7968750000f, 138361.5156250000f, 138387.2343750000f, +138412.9531250000f, 138438.6718750000f, 138464.3906250000f, 138490.1093750000f, +138515.8281250000f, 138541.5468750000f, 138567.2812500000f, 138593.0000000000f, +138618.7343750000f, 138644.4531250000f, 138670.1875000000f, 138695.9218750000f, +138721.6562500000f, 138747.3750000000f, 138773.1093750000f, 138798.8437500000f, +138824.5937500000f, 138850.3281250000f, 138876.0625000000f, 138901.7968750000f, +138927.5468750000f, 138953.2812500000f, 138979.0312500000f, 139004.7812500000f, +139030.5156250000f, 139056.2656250000f, 139082.0156250000f, 139107.7656250000f, +139133.5156250000f, 139159.2656250000f, 139185.0156250000f, 139210.7812500000f, +139236.5312500000f, 139262.2812500000f, 139288.0468750000f, 139313.7968750000f, +139339.5625000000f, 139365.3281250000f, 139391.0937500000f, 139416.8437500000f, +139442.6093750000f, 139468.3750000000f, 139494.1562500000f, 139519.9218750000f, +139545.6875000000f, 139571.4531250000f, 139597.2343750000f, 139623.0000000000f, +139648.7812500000f, 139674.5468750000f, 139700.3281250000f, 139726.1093750000f, +139751.8906250000f, 139777.6718750000f, 139803.4531250000f, 139829.2343750000f, +139855.0156250000f, 139880.7968750000f, 139906.5937500000f, 139932.3750000000f, +139958.1562500000f, 139983.9531250000f, 140009.7500000000f, 140035.5312500000f, +140061.3281250000f, 140087.1250000000f, 140112.9218750000f, 140138.7187500000f, +140164.5156250000f, 140190.3125000000f, 140216.1093750000f, 140241.9218750000f, +140267.7187500000f, 140293.5156250000f, 140319.3281250000f, 140345.1406250000f, +140370.9375000000f, 140396.7500000000f, 140422.5625000000f, 140448.3750000000f, +140474.1875000000f, 140500.0000000000f, 140525.8125000000f, 140551.6250000000f, +140577.4531250000f, 140603.2656250000f, 140629.0781250000f, 140654.9062500000f, +140680.7187500000f, 140706.5468750000f, 140732.3750000000f, 140758.2031250000f, +140784.0312500000f, 140809.8593750000f, 140835.6875000000f, 140861.5156250000f, +140887.3437500000f, 140913.1718750000f, 140939.0156250000f, 140964.8437500000f, +140990.6875000000f, 141016.5156250000f, 141042.3593750000f, 141068.2031250000f, +141094.0312500000f, 141119.8750000000f, 141145.7187500000f, 141171.5625000000f, +141197.4062500000f, 141223.2656250000f, 141249.1093750000f, 141274.9531250000f, +141300.8125000000f, 141326.6562500000f, 141352.5156250000f, 141378.3593750000f, +141404.2187500000f, 141430.0781250000f, 141455.9375000000f, 141481.7968750000f, +141507.6562500000f, 141533.5156250000f, 141559.3750000000f, 141585.2343750000f, +141611.0937500000f, 141636.9687500000f, 141662.8281250000f, 141688.7031250000f, +141714.5781250000f, 141740.4375000000f, 141766.3125000000f, 141792.1875000000f, +141818.0625000000f, 141843.9375000000f, 141869.8125000000f, 141895.6875000000f, +141921.5625000000f, 141947.4531250000f, 141973.3281250000f, 141999.2031250000f, +142025.0937500000f, 142050.9843750000f, 142076.8593750000f, 142102.7500000000f, +142128.6406250000f, 142154.5312500000f, 142180.4218750000f, 142206.3125000000f, +142232.2031250000f, 142258.0937500000f, 142283.9843750000f, 142309.8906250000f, +142335.7812500000f, 142361.6875000000f, 142387.5781250000f, 142413.4843750000f, +142439.3906250000f, 142465.2968750000f, 142491.1875000000f, 142517.0937500000f, +142543.0000000000f, 142568.9218750000f, 142594.8281250000f, 142620.7343750000f, +142646.6406250000f, 142672.5625000000f, 142698.4687500000f, 142724.3906250000f, +142750.3125000000f, 142776.2187500000f, 142802.1406250000f, 142828.0625000000f, +142853.9843750000f, 142879.9062500000f, 142905.8281250000f, 142931.7500000000f, +142957.6718750000f, 142983.6093750000f, 143009.5312500000f, 143035.4687500000f, +143061.3906250000f, 143087.3281250000f, 143113.2500000000f, 143139.1875000000f, +143165.1250000000f, 143191.0625000000f, 143217.0000000000f, 143242.9375000000f, +143268.8750000000f, 143294.8125000000f, 143320.7656250000f, 143346.7031250000f, +143372.6562500000f, 143398.5937500000f, 143424.5468750000f, 143450.4843750000f, +143476.4375000000f, 143502.3906250000f, 143528.3437500000f, 143554.2968750000f, +143580.2500000000f, 143606.2031250000f, 143632.1562500000f, 143658.1093750000f, +143684.0781250000f, 143710.0312500000f, 143736.0000000000f, 143761.9531250000f, +143787.9218750000f, 143813.8906250000f, 143839.8437500000f, 143865.8125000000f, +143891.7812500000f, 143917.7500000000f, 143943.7187500000f, 143969.7031250000f, +143995.6718750000f, 144021.6406250000f, 144047.6250000000f, 144073.5937500000f, +144099.5781250000f, 144125.5468750000f, 144151.5312500000f, 144177.5156250000f, +144203.5000000000f, 144229.4687500000f, 144255.4531250000f, 144281.4531250000f, +144307.4375000000f, 144333.4218750000f, 144359.4062500000f, 144385.4062500000f, +144411.3906250000f, 144437.3906250000f, 144463.3750000000f, 144489.3750000000f, +144515.3750000000f, 144541.3593750000f, 144567.3593750000f, 144593.3593750000f, +144619.3593750000f, 144645.3593750000f, 144671.3750000000f, 144697.3750000000f, +144723.3750000000f, 144749.3906250000f, 144775.3906250000f, 144801.4062500000f, +144827.4062500000f, 144853.4218750000f, 144879.4375000000f, 144905.4531250000f, +144931.4687500000f, 144957.4843750000f, 144983.5000000000f, 145009.5156250000f, +145035.5312500000f, 145061.5625000000f, 145087.5781250000f, 145113.5937500000f, +145139.6250000000f, 145165.6562500000f, 145191.6718750000f, 145217.7031250000f, +145243.7343750000f, 145269.7656250000f, 145295.7968750000f, 145321.8281250000f, +145347.8593750000f, 145373.8906250000f, 145399.9218750000f, 145425.9687500000f, +145452.0000000000f, 145478.0468750000f, 145504.0781250000f, 145530.1250000000f, +145556.1718750000f, 145582.2187500000f, 145608.2500000000f, 145634.2968750000f, +145660.3437500000f, 145686.4062500000f, 145712.4531250000f, 145738.5000000000f, +145764.5468750000f, 145790.6093750000f, 145816.6562500000f, 145842.7187500000f, +145868.7656250000f, 145894.8281250000f, 145920.8906250000f, 145946.9531250000f, +145973.0156250000f, 145999.0781250000f, 146025.1406250000f, 146051.2031250000f, +146077.2656250000f, 146103.3281250000f, 146129.4062500000f, 146155.4687500000f, +146181.5468750000f, 146207.6093750000f, 146233.6875000000f, 146259.7656250000f, +146285.8437500000f, 146311.9218750000f, 146338.0000000000f, 146364.0781250000f, +146390.1562500000f, 146416.2343750000f, 146442.3125000000f, 146468.3906250000f, +146494.4843750000f, 146520.5625000000f, 146546.6562500000f, 146572.7500000000f, +146598.8281250000f, 146624.9218750000f, 146651.0156250000f, 146677.1093750000f, +146703.2031250000f, 146729.2968750000f, 146755.3906250000f, 146781.4843750000f, +146807.5937500000f, 146833.6875000000f, 146859.7968750000f, 146885.8906250000f, +146912.0000000000f, 146938.0937500000f, 146964.2031250000f, 146990.3125000000f, +147016.4218750000f, 147042.5312500000f, 147068.6406250000f, 147094.7500000000f, +147120.8593750000f, 147146.9687500000f, 147173.0937500000f, 147199.2031250000f, +147225.3281250000f, 147251.4375000000f, 147277.5625000000f, 147303.6875000000f, +147329.7968750000f, 147355.9218750000f, 147382.0468750000f, 147408.1718750000f, +147434.2968750000f, 147460.4218750000f, 147486.5625000000f, 147512.6875000000f, +147538.8125000000f, 147564.9531250000f, 147591.0781250000f, 147617.2187500000f, +147643.3593750000f, 147669.4843750000f, 147695.6250000000f, 147721.7656250000f, +147747.9062500000f, 147774.0468750000f, 147800.1875000000f, 147826.3281250000f, +147852.4843750000f, 147878.6250000000f, 147904.7812500000f, 147930.9218750000f, +147957.0781250000f, 147983.2187500000f, 148009.3750000000f, 148035.5312500000f, +148061.6875000000f, 148087.8437500000f, 148114.0000000000f, 148140.1562500000f, +148166.3125000000f, 148192.4687500000f, 148218.6250000000f, 148244.7968750000f, +148270.9531250000f, 148297.1250000000f, 148323.2812500000f, 148349.4531250000f, +148375.6250000000f, 148401.7968750000f, 148427.9531250000f, 148454.1250000000f, +148480.2968750000f, 148506.4843750000f, 148532.6562500000f, 148558.8281250000f, +148585.0000000000f, 148611.1875000000f, 148637.3593750000f, 148663.5468750000f, +148689.7187500000f, 148715.9062500000f, 148742.0937500000f, 148768.2812500000f, +148794.4687500000f, 148820.6562500000f, 148846.8437500000f, 148873.0312500000f, +148899.2187500000f, 148925.4218750000f, 148951.6093750000f, 148977.7968750000f, +149004.0000000000f, 149030.1875000000f, 149056.3906250000f, 149082.5937500000f, +149108.7968750000f, 149135.0000000000f, 149161.2031250000f, 149187.4062500000f, +149213.6093750000f, 149239.8125000000f, 149266.0156250000f, 149292.2187500000f, +149318.4375000000f, 149344.6406250000f, 149370.8593750000f, 149397.0625000000f, +149423.2812500000f, 149449.5000000000f, 149475.7187500000f, 149501.9375000000f, +149528.1562500000f, 149554.3750000000f, 149580.5937500000f, 149606.8125000000f, +149633.0312500000f, 149659.2656250000f, 149685.4843750000f, 149711.7187500000f, +149737.9375000000f, 149764.1718750000f, 149790.4062500000f, 149816.6250000000f, +149842.8593750000f, 149869.0937500000f, 149895.3281250000f, 149921.5625000000f, +149947.8125000000f, 149974.0468750000f, 150000.2812500000f, 150026.5312500000f, +150052.7656250000f, 150079.0156250000f, 150105.2500000000f, 150131.5000000000f, +150157.7500000000f, 150183.9843750000f, 150210.2343750000f, 150236.4843750000f, +150262.7343750000f, 150288.9843750000f, 150315.2500000000f, 150341.5000000000f, +150367.7500000000f, 150394.0156250000f, 150420.2656250000f, 150446.5312500000f, +150472.7812500000f, 150499.0468750000f, 150525.3125000000f, 150551.5781250000f, +150577.8437500000f, 150604.1093750000f, 150630.3750000000f, 150656.6406250000f, +150682.9062500000f, 150709.1718750000f, 150735.4531250000f, 150761.7187500000f, +150788.0000000000f, 150814.2656250000f, 150840.5468750000f, 150866.8281250000f, +150893.1093750000f, 150919.3750000000f, 150945.6562500000f, 150971.9375000000f, +150998.2343750000f, 151024.5156250000f, 151050.7968750000f, 151077.0781250000f, +151103.3750000000f, 151129.6562500000f, 151155.9531250000f, 151182.2343750000f, +151208.5312500000f, 151234.8281250000f, 151261.1250000000f, 151287.4062500000f, +151313.7031250000f, 151340.0000000000f, 151366.3125000000f, 151392.6093750000f, +151418.9062500000f, 151445.2031250000f, 151471.5156250000f, 151497.8125000000f, +151524.1250000000f, 151550.4375000000f, 151576.7343750000f, 151603.0468750000f, +151629.3593750000f, 151655.6718750000f, 151681.9843750000f, 151708.2968750000f, +151734.6093750000f, 151760.9218750000f, 151787.2500000000f, 151813.5625000000f, +151839.8750000000f, 151866.2031250000f, 151892.5156250000f, 151918.8437500000f, +151945.1718750000f, 151971.5000000000f, 151997.8281250000f, 152024.1562500000f, +152050.4843750000f, 152076.8125000000f, 152103.1406250000f, 152129.4687500000f, +152155.7968750000f, 152182.1406250000f, 152208.4687500000f, 152234.8125000000f, +152261.1406250000f, 152287.4843750000f, 152313.8281250000f, 152340.1718750000f, +152366.5156250000f, 152392.8593750000f, 152419.2031250000f, 152445.5468750000f, +152471.8906250000f, 152498.2343750000f, 152524.5781250000f, 152550.9375000000f, +152577.2812500000f, 152603.6406250000f, 152630.0000000000f, 152656.3437500000f, +152682.7031250000f, 152709.0625000000f, 152735.4218750000f, 152761.7812500000f, +152788.1406250000f, 152814.5000000000f, 152840.8593750000f, 152867.2187500000f, +152893.5937500000f, 152919.9531250000f, 152946.3281250000f, 152972.6875000000f, +152999.0625000000f, 153025.4375000000f, 153051.8125000000f, 153078.1718750000f, +153104.5468750000f, 153130.9218750000f, 153157.2968750000f, 153183.6875000000f, +153210.0625000000f, 153236.4375000000f, 153262.8125000000f, 153289.2031250000f, +153315.5781250000f, 153341.9687500000f, 153368.3593750000f, 153394.7343750000f, +153421.1250000000f, 153447.5156250000f, 153473.9062500000f, 153500.2968750000f, +153526.6875000000f, 153553.0781250000f, 153579.4843750000f, 153605.8750000000f, +153632.2656250000f, 153658.6718750000f, 153685.0625000000f, 153711.4687500000f, +153737.8750000000f, 153764.2656250000f, 153790.6718750000f, 153817.0781250000f, +153843.4843750000f, 153869.8906250000f, 153896.2968750000f, 153922.7031250000f, +153949.1250000000f, 153975.5312500000f, 154001.9375000000f, 154028.3593750000f, +154054.7656250000f, 154081.1875000000f, 154107.6093750000f, 154134.0312500000f, +154160.4375000000f, 154186.8593750000f, 154213.2812500000f, 154239.7031250000f, +154266.1250000000f, 154292.5625000000f, 154318.9843750000f, 154345.4062500000f, +154371.8437500000f, 154398.2656250000f, 154424.7031250000f, 154451.1250000000f, +154477.5625000000f, 154504.0000000000f, 154530.4375000000f, 154556.8750000000f, +154583.3125000000f, 154609.7500000000f, 154636.1875000000f, 154662.6250000000f, +154689.0625000000f, 154715.5156250000f, 154741.9531250000f, 154768.4062500000f, +154794.8437500000f, 154821.2968750000f, 154847.7500000000f, 154874.1875000000f, +154900.6406250000f, 154927.0937500000f, 154953.5468750000f, 154980.0000000000f, +155006.4687500000f, 155032.9218750000f, 155059.3750000000f, 155085.8281250000f, +155112.2968750000f, 155138.7500000000f, 155165.2187500000f, 155191.6875000000f, +155218.1406250000f, 155244.6093750000f, 155271.0781250000f, 155297.5468750000f, +155324.0156250000f, 155350.4843750000f, 155376.9531250000f, 155403.4375000000f, +155429.9062500000f, 155456.3750000000f, 155482.8593750000f, 155509.3281250000f, +155535.8125000000f, 155562.2812500000f, 155588.7656250000f, 155615.2500000000f, +155641.7343750000f, 155668.2187500000f, 155694.7031250000f, 155721.1875000000f, +155747.6718750000f, 155774.1562500000f, 155800.6562500000f, 155827.1406250000f, +155853.6406250000f, 155880.1250000000f, 155906.6250000000f, 155933.1093750000f, +155959.6093750000f, 155986.1093750000f, 156012.6093750000f, 156039.1093750000f, +156065.6093750000f, 156092.1093750000f, 156118.6093750000f, 156145.1093750000f, +156171.6250000000f, 156198.1250000000f, 156224.6406250000f, 156251.1406250000f, +156277.6562500000f, 156304.1718750000f, 156330.6718750000f, 156357.1875000000f, +156383.7031250000f, 156410.2187500000f, 156436.7343750000f, 156463.2500000000f, +156489.7656250000f, 156516.2968750000f, 156542.8125000000f, 156569.3281250000f, +156595.8593750000f, 156622.3750000000f, 156648.9062500000f, 156675.4375000000f, +156701.9687500000f, 156728.4843750000f, 156755.0156250000f, 156781.5468750000f, +156808.0781250000f, 156834.6093750000f, 156861.1562500000f, 156887.6875000000f, +156914.2187500000f, 156940.7656250000f, 156967.2968750000f, 156993.8437500000f, +157020.3750000000f, 157046.9218750000f, 157073.4687500000f, 157100.0156250000f, +157126.5625000000f, 157153.1093750000f, 157179.6562500000f, 157206.2031250000f, +157232.7500000000f, 157259.2968750000f, 157285.8593750000f, 157312.4062500000f, +157338.9531250000f, 157365.5156250000f, 157392.0781250000f, 157418.6250000000f, +157445.1875000000f, 157471.7500000000f, 157498.3125000000f, 157524.8750000000f, +157551.4375000000f, 157578.0000000000f, 157604.5625000000f, 157631.1406250000f, +157657.7031250000f, 157684.2656250000f, 157710.8437500000f, 157737.4062500000f, +157763.9843750000f, 157790.5625000000f, 157817.1406250000f, 157843.7031250000f, +157870.2812500000f, 157896.8593750000f, 157923.4375000000f, 157950.0312500000f, +157976.6093750000f, 158003.1875000000f, 158029.7656250000f, 158056.3593750000f, +158082.9375000000f, 158109.5312500000f, 158136.1250000000f, 158162.7031250000f, +158189.2968750000f, 158215.8906250000f, 158242.4843750000f, 158269.0781250000f, +158295.6718750000f, 158322.2656250000f, 158348.8593750000f, 158375.4531250000f, +158402.0625000000f, 158428.6562500000f, 158455.2656250000f, 158481.8593750000f, +158508.4687500000f, 158535.0781250000f, 158561.6718750000f, 158588.2812500000f, +158614.8906250000f, 158641.5000000000f, 158668.1093750000f, 158694.7187500000f, +158721.3437500000f, 158747.9531250000f, 158774.5625000000f, 158801.1875000000f, +158827.7968750000f, 158854.4218750000f, 158881.0312500000f, 158907.6562500000f, +158934.2812500000f, 158960.9062500000f, 158987.5312500000f, 159014.1562500000f, +159040.7812500000f, 159067.4062500000f, 159094.0312500000f, 159120.6562500000f, +159147.2968750000f, 159173.9218750000f, 159200.5625000000f, 159227.1875000000f, +159253.8281250000f, 159280.4687500000f, 159307.0937500000f, 159333.7343750000f, +159360.3750000000f, 159387.0156250000f, 159413.6562500000f, 159440.2968750000f, +159466.9531250000f, 159493.5937500000f, 159520.2343750000f, 159546.8906250000f, +159573.5312500000f, 159600.1875000000f, 159626.8281250000f, 159653.4843750000f, +159680.1406250000f, 159706.7968750000f, 159733.4531250000f, 159760.1093750000f, +159786.7656250000f, 159813.4218750000f, 159840.0781250000f, 159866.7343750000f, +159893.4062500000f, 159920.0625000000f, 159946.7187500000f, 159973.3906250000f, +160000.0625000000f, 160026.7187500000f, 160053.3906250000f, 160080.0625000000f, +160106.7343750000f, 160133.4062500000f, 160160.0781250000f, 160186.7500000000f, +160213.4218750000f, 160240.1093750000f, 160266.7812500000f, 160293.4531250000f, +160320.1406250000f, 160346.8125000000f, 160373.5000000000f, 160400.1875000000f, +160426.8593750000f, 160453.5468750000f, 160480.2343750000f, 160506.9218750000f, +160533.6093750000f, 160560.2968750000f, 160587.0000000000f, 160613.6875000000f, +160640.3750000000f, 160667.0781250000f, 160693.7656250000f, 160720.4687500000f, +160747.1562500000f, 160773.8593750000f, 160800.5625000000f, 160827.2500000000f, +160853.9531250000f, 160880.6562500000f, 160907.3593750000f, 160934.0781250000f, +160960.7812500000f, 160987.4843750000f, 161014.1875000000f, 161040.9062500000f, +161067.6093750000f, 161094.3281250000f, 161121.0312500000f, 161147.7500000000f, +161174.4687500000f, 161201.1875000000f, 161227.8906250000f, 161254.6093750000f, +161281.3281250000f, 161308.0625000000f, 161334.7812500000f, 161361.5000000000f, +161388.2187500000f, 161414.9531250000f, 161441.6718750000f, 161468.4062500000f, +161495.1250000000f, 161521.8593750000f, 161548.5937500000f, 161575.3281250000f, +161602.0468750000f, 161628.7812500000f, 161655.5156250000f, 161682.2656250000f, +161709.0000000000f, 161735.7343750000f, 161762.4687500000f, 161789.2187500000f, +161815.9531250000f, 161842.7031250000f, 161869.4375000000f, 161896.1875000000f, +161922.9375000000f, 161949.6718750000f, 161976.4218750000f, 162003.1718750000f, +162029.9218750000f, 162056.6718750000f, 162083.4375000000f, 162110.1875000000f, +162136.9375000000f, 162163.6875000000f, 162190.4531250000f, 162217.2031250000f, +162243.9687500000f, 162270.7343750000f, 162297.4843750000f, 162324.2500000000f, +162351.0156250000f, 162377.7812500000f, 162404.5468750000f, 162431.3125000000f, +162458.0781250000f, 162484.8437500000f, 162511.6250000000f, 162538.3906250000f, +162565.1718750000f, 162591.9375000000f, 162618.7187500000f, 162645.4843750000f, +162672.2656250000f, 162699.0468750000f, 162725.8281250000f, 162752.6093750000f, +162779.3906250000f, 162806.1718750000f, 162832.9531250000f, 162859.7343750000f, +162886.5156250000f, 162913.3125000000f, 162940.0937500000f, 162966.8750000000f, +162993.6718750000f, 163020.4687500000f, 163047.2500000000f, 163074.0468750000f, +163100.8437500000f, 163127.6406250000f, 163154.4375000000f, 163181.2343750000f, +163208.0312500000f, 163234.8281250000f, 163261.6250000000f, 163288.4375000000f, +163315.2343750000f, 163342.0468750000f, 163368.8437500000f, 163395.6562500000f, +163422.4687500000f, 163449.2656250000f, 163476.0781250000f, 163502.8906250000f, +163529.7031250000f, 163556.5156250000f, 163583.3281250000f, 163610.1406250000f, +163636.9687500000f, 163663.7812500000f, 163690.5937500000f, 163717.4218750000f, +163744.2343750000f, 163771.0625000000f, 163797.8906250000f, 163824.7031250000f, +163851.5312500000f, 163878.3593750000f, 163905.1875000000f, 163932.0156250000f, +163958.8437500000f, 163985.6718750000f, 164012.5000000000f, 164039.3437500000f, +164066.1718750000f, 164093.0156250000f, 164119.8437500000f, 164146.6875000000f, +164173.5156250000f, 164200.3593750000f, 164227.2031250000f, 164254.0468750000f, +164280.8906250000f, 164307.7343750000f, 164334.5781250000f, 164361.4218750000f, +164388.2656250000f, 164415.1093750000f, 164441.9687500000f, 164468.8125000000f, +164495.6718750000f, 164522.5156250000f, 164549.3750000000f, 164576.2343750000f, +164603.0781250000f, 164629.9375000000f, 164656.7968750000f, 164683.6562500000f, +164710.5156250000f, 164737.3750000000f, 164764.2343750000f, 164791.1093750000f, +164817.9687500000f, 164844.8281250000f, 164871.7031250000f, 164898.5781250000f, +164925.4375000000f, 164952.3125000000f, 164979.1875000000f, 165006.0468750000f, +165032.9218750000f, 165059.7968750000f, 165086.6718750000f, 165113.5468750000f, +165140.4375000000f, 165167.3125000000f, 165194.1875000000f, 165221.0781250000f, +165247.9531250000f, 165274.8437500000f, 165301.7187500000f, 165328.6093750000f, +165355.5000000000f, 165382.3750000000f, 165409.2656250000f, 165436.1562500000f, +165463.0468750000f, 165489.9375000000f, 165516.8281250000f +}; + +/* pre-calculated cos() in steps of PI/72, PI/18 and PI/24 for MDCT calcs. + * The Octave formula to generate each table is given in the comment above it */ +/* 0.5 ./ cos ((1:12) .* pi/24) */ +static const gfloat cos24_table[] = { + 5.043144802900764167574720886477734893560409545898437500000000e-01f, + 5.176380902050414789528076653368771076202392578125000000000000e-01f, + 5.411961001461970122150546558259520679712295532226562500000000e-01f, + 5.773502691896257310588680411456152796745300292968750000000000e-01f, + 6.302362070051322762154200063378084450960159301757812500000000e-01f, + 7.071067811865474617150084668537601828575134277343750000000000e-01f, + 8.213398158522907666068135768000502139329910278320312500000000e-01f, + 9.999999999999997779553950749686919152736663818359375000000000e-01f, + 1.306562964876376353728915091778617352247238159179687500000000e+00f, + 1.931851652578136846472034449107013642787933349609375000000000e+00f, + 3.830648787770190910606515899416990578174591064453125000000000e+00f, + 8.165889364191922000000000000000000000000000000000000000000000e+15f +}; + +/* cos ((0:8) .* pi/18) */ +static const gfloat cos18_table[] = { + 1.000000000000000000000000000000000000000000000000000000000000e+00f, + 9.848077530122080203156542665965389460325241088867187500000000e-01f, + 9.396926207859084279050421173451468348503112792968750000000000e-01f, + 8.660254037844387076106045242340769618749618530273437500000000e-01f, + 7.660444431189780134516809084743726998567581176757812500000000e-01f, + 6.427876096865393629187224178167525678873062133789062500000000e-01f, + 5.000000000000001110223024625156540423631668090820312500000000e-01f, + 3.420201433256688239303855425532674416899681091308593750000000e-01f, + 1.736481776669304144533612088707741349935531616210937500000000e-01f +}; + +/* 0.5 ./ cos ((1:35) .* pi/72)) */ +static const gfloat icos72_table[] = { + 5.004763425816599609063928255636710673570632934570312500000000e-01f, + 5.019099187716736798492433990759309381246566772460937500000000e-01f, + 5.043144802900764167574720886477734893560409545898437500000000e-01f, + 5.077133059428725614381505693017970770597457885742187500000000e-01f, + 5.121397571572545714957414020318537950515747070312500000000000e-01f, + 5.176380902050414789528076653368771076202392578125000000000000e-01f, + 5.242645625704053236049162478593643754720687866210937500000000e-01f, + 5.320888862379560269033618169487453997135162353515625000000000e-01f, + 5.411961001461970122150546558259520679712295532226562500000000e-01f, + 5.516889594812458552652856269560288637876510620117187500000000e-01f, + 5.636909734331712051869089918909594416618347167968750000000000e-01f, + 5.773502691896257310588680411456152796745300292968750000000000e-01f, + 5.928445237170802961657045671017840504646301269531250000000000e-01f, + 6.103872943807280293526673631276935338973999023437500000000000e-01f, + 6.302362070051321651931175438221544027328491210937500000000000e-01f, + 6.527036446661392821155800447741057723760604858398437500000000e-01f, + 6.781708524546284921896699415810871869325637817382812500000000e-01f, + 7.071067811865474617150084668537601828575134277343750000000000e-01f, + 7.400936164611303658134033867099788039922714233398437500000000e-01f, + 7.778619134302061643992942663317080587148666381835937500000000e-01f, + 8.213398158522907666068135768000502139329910278320312500000000e-01f, + 8.717233978105488612087015098950359970331192016601562500000000e-01f, + 9.305794983517888807611484480730723589658737182617187500000000e-01f, + 9.999999999999997779553950749686919152736663818359375000000000e-01f, + 1.082840285100100219395358180918265134096145629882812500000000e+00f, + 1.183100791576249255498964885191526263952255249023437500000000e+00f, + 1.306562964876376353728915091778617352247238159179687500000000e+00f, + 1.461902200081543146126250576344318687915802001953125000000000e+00f, + 1.662754761711521034328598034335300326347351074218750000000000e+00f, + 1.931851652578135070115195048856548964977264404296875000000000e+00f, + 2.310113157672649020213384574162773787975311279296875000000000e+00f, + 2.879385241571815523542454684502445161342620849609375000000000e+00f, + 3.830648787770197127855453800293616950511932373046875000000000e+00f, + 5.736856622834929808618653623852878808975219726562500000000000e+00f, + 1.146279281302667207853573927422985434532165527343750000000000e+01f +}; + +static const gfloat mdct_swin[4][36] = { + { + 0.0436193869f, 0.1305261850f, 0.2164396197f, 0.3007057905f, 0.3826834261f, 0.4617486000f, + 0.5372996330f, 0.6087614298f, 0.6755902171f, 0.7372773290f, 0.7933533192f, 0.8433914185f, + 0.8870108128f, 0.9238795042f, 0.9537169337f, 0.9762960076f, 0.9914448857f, 0.9990482330f, + 0.9990482330f, 0.9914448857f, 0.9762960076f, 0.9537169337f, 0.9238795042f, 0.8870108128f, + 0.8433914185f, 0.7933533192f, 0.7372773290f, 0.6755902171f, 0.6087614298f, 0.5372996330f, + 0.4617486000f, 0.3826834261f, 0.3007057905f, 0.2164396197f, 0.1305261850f, 0.0436193869f, + }, { + 0.0436193869f, 0.1305261850f, 0.2164396197f, 0.3007057905f, 0.3826834261f, 0.4617486000f, + 0.5372996330f, 0.6087614298f, 0.6755902171f, 0.7372773290f, 0.7933533192f, 0.8433914185f, + 0.8870108128f, 0.9238795042f, 0.9537169337f, 0.9762960076f, 0.9914448857f, 0.9990482330f, + 1.0000000000f, 1.0000000000f, 1.0000000000f, 1.0000000000f, 1.0000000000f, 1.0000000000f, + 0.9914448857f, 0.9238795042f, 0.7933533192f, 0.6087614298f, 0.3826834261f, 0.1305261850f, + 0.0000000000f, 0.0000000000f, 0.0000000000f, 0.0000000000f, 0.0000000000f, 0.0000000000f, + }, { + 0.1305261850f, 0.3826834261f, 0.6087614298f, 0.7933533192f, 0.9238795042f, 0.9914448857f, + 0.9914448857f, 0.9238795042f, 0.7933533192f, 0.6087614298f, 0.3826834261f, 0.1305261850f, + 0.0000000000f, 0.0000000000f, 0.0000000000f, 0.0000000000f, 0.0000000000f, 0.0000000000f, + 0.0000000000f, 0.0000000000f, 0.0000000000f, 0.0000000000f, 0.0000000000f, 0.0000000000f, + 0.0000000000f, 0.0000000000f, 0.0000000000f, 0.0000000000f, 0.0000000000f, 0.0000000000f, + 0.0000000000f, 0.0000000000f, 0.0000000000f, 0.0000000000f, 0.0000000000f, 0.0000000000f, + }, { + 0.0000000000f, 0.0000000000f, 0.0000000000f, 0.0000000000f, 0.0000000000f, 0.0000000000f, + 0.1305261850f, 0.3826834261f, 0.6087614298f, 0.7933533192f, 0.9238795042f, 0.9914448857f, + 1.0000000000f, 1.0000000000f, 1.0000000000f, 1.0000000000f, 1.0000000000f, 1.0000000000f, + 0.9990482330f, 0.9914448857f, 0.9762960076f, 0.9537169337f, 0.9238795042f, 0.8870108128f, + 0.8433914185f, 0.7933533192f, 0.7372773290f, 0.6755902171f, 0.6087614298f, 0.5372996330f, + 0.4617486000f, 0.3826834261f, 0.3007057905f, 0.2164396197f, 0.1305261850f, 0.0436193869f, + } +}; + +/* pre-calculated table for (1.0 / (2.0 * cos ((2*i+1) * (M_PI / (64))))) + * for i 0:31 */ +/* 0.5 ./ cos (((2 .* 0:30)+1) .* pi/64) */ +/* NOTE: The table is already offset by pi/64 at index 0, by the +1 term + ie, index x yields 0.5 / cos ((2*x+1) * pi/64) */ +static const gfloat synth_cos64_table[] = { + 5.0060299823519627260e-01f, 5.0241928618815567820e-01f, + 5.0547095989754364798e-01f, 5.0979557910415917998e-01f, + 5.1544730992262455249e-01f, 5.2249861493968885462e-01f, + 5.3104259108978413284e-01f, 5.4119610014619701222e-01f, + 5.5310389603444454210e-01f, 5.6694403481635768927e-01f, + 5.8293496820613388554e-01f, 6.0134488693504528634e-01f, + 6.2250412303566482475e-01f, 6.4682178335999007679e-01f, + 6.7480834145500567800e-01f, 7.0710678118654746172e-01f, + 7.4453627100229857749e-01f, 7.8815462345125020249e-01f, + 8.3934964541552681272e-01f, 8.9997622313641556513e-01f, + 9.7256823786196078263e-01f, 1.0606776859903470633e+00f, + 1.1694399334328846596e+00f, 1.3065629648763763537e+00f, + 1.4841646163141661852e+00f, 1.7224470982383341955e+00f, + 2.0577810099534108446e+00f, 2.5629154477415054814e+00f, + 3.4076084184687189804e+00f, 5.1011486186891552563e+00f, + 1.0190008123548032870e+01f +}; + +static __CACHE_LINE_DECL_ALIGN(const gfloat dewindow[512]) = { + 0.000000000f, -0.000015259f, -0.000015259f, -0.000015259f, + -0.000015259f, -0.000015259f, -0.000015259f, -0.000030518f, + -0.000030518f, -0.000030518f, -0.000030518f, -0.000045776f, + -0.000045776f, -0.000061035f, -0.000061035f, -0.000076294f, + -0.000076294f, -0.000091553f, -0.000106812f, -0.000106812f, + -0.000122070f, -0.000137329f, -0.000152588f, -0.000167847f, + -0.000198364f, -0.000213623f, -0.000244141f, -0.000259399f, + -0.000289917f, -0.000320435f, -0.000366211f, -0.000396729f, + -0.000442505f, -0.000473022f, -0.000534058f, -0.000579834f, + -0.000625610f, -0.000686646f, -0.000747681f, -0.000808716f, + -0.000885010f, -0.000961304f, -0.001037598f, -0.001113892f, + -0.001205444f, -0.001296997f, -0.001388550f, -0.001480103f, + -0.001586914f, -0.001693726f, -0.001785278f, -0.001907349f, + -0.002014160f, -0.002120972f, -0.002243042f, -0.002349854f, + -0.002456665f, -0.002578735f, -0.002685547f, -0.002792358f, + -0.002899170f, -0.002990723f, -0.003082275f, -0.003173828f, + 0.003250122f, 0.003326416f, 0.003387451f, 0.003433228f, + 0.003463745f, 0.003479004f, 0.003479004f, 0.003463745f, + 0.003417969f, 0.003372192f, 0.003280640f, 0.003173828f, + 0.003051758f, 0.002883911f, 0.002700806f, 0.002487183f, + 0.002227783f, 0.001937866f, 0.001617432f, 0.001266479f, + 0.000869751f, 0.000442505f, -0.000030518f, -0.000549316f, + -0.001098633f, -0.001693726f, -0.002334595f, -0.003005981f, + -0.003723145f, -0.004486084f, -0.005294800f, -0.006118774f, + -0.007003784f, -0.007919312f, -0.008865356f, -0.009841919f, + -0.010848999f, -0.011886597f, -0.012939453f, -0.014022827f, + -0.015121460f, -0.016235352f, -0.017349243f, -0.018463135f, + -0.019577026f, -0.020690918f, -0.021789551f, -0.022857666f, + -0.023910522f, -0.024932861f, -0.025909424f, -0.026840210f, + -0.027725220f, -0.028533936f, -0.029281616f, -0.029937744f, + -0.030532837f, -0.031005859f, -0.031387329f, -0.031661987f, + -0.031814575f, -0.031845093f, -0.031738281f, -0.031478882f, + 0.031082153f, 0.030517578f, 0.029785156f, 0.028884888f, + 0.027801514f, 0.026535034f, 0.025085449f, 0.023422241f, + 0.021575928f, 0.019531250f, 0.017257690f, 0.014801025f, + 0.012115479f, 0.009231567f, 0.006134033f, 0.002822876f, + -0.000686646f, -0.004394531f, -0.008316040f, -0.012420654f, + -0.016708374f, -0.021179199f, -0.025817871f, -0.030609131f, + -0.035552979f, -0.040634155f, -0.045837402f, -0.051132202f, + -0.056533813f, -0.061996460f, -0.067520142f, -0.073059082f, + -0.078628540f, -0.084182739f, -0.089706421f, -0.095169067f, + -0.100540161f, -0.105819702f, -0.110946655f, -0.115921021f, + -0.120697021f, -0.125259399f, -0.129562378f, -0.133590698f, + -0.137298584f, -0.140670776f, -0.143676758f, -0.146255493f, + -0.148422241f, -0.150115967f, -0.151306152f, -0.151962280f, + -0.152069092f, -0.151596069f, -0.150497437f, -0.148773193f, + -0.146362305f, -0.143264771f, -0.139450073f, -0.134887695f, + -0.129577637f, -0.123474121f, -0.116577148f, -0.108856201f, + 0.100311279f, 0.090927124f, 0.080688477f, 0.069595337f, + 0.057617187f, 0.044784546f, 0.031082153f, 0.016510010f, + 0.001068115f, -0.015228271f, -0.032379150f, -0.050354004f, + -0.069168091f, -0.088775635f, -0.109161377f, -0.130310059f, + -0.152206421f, -0.174789429f, -0.198059082f, -0.221984863f, + -0.246505737f, -0.271591187f, -0.297210693f, -0.323318481f, + -0.349868774f, -0.376800537f, -0.404083252f, -0.431655884f, + -0.459472656f, -0.487472534f, -0.515609741f, -0.543823242f, + -0.572036743f, -0.600219727f, -0.628295898f, -0.656219482f, + -0.683914185f, -0.711318970f, -0.738372803f, -0.765029907f, + -0.791213989f, -0.816864014f, -0.841949463f, -0.866363525f, + -0.890090942f, -0.913055420f, -0.935195923f, -0.956481934f, + -0.976852417f, -0.996246338f, -1.014617920f, -1.031936646f, + -1.048156738f, -1.063217163f, -1.077117920f, -1.089782715f, + -1.101211548f, -1.111373901f, -1.120223999f, -1.127746582f, + -1.133926392f, -1.138763428f, -1.142211914f, -1.144287109f, + 1.144989014f, 1.144287109f, 1.142211914f, 1.138763428f, + 1.133926392f, 1.127746582f, 1.120223999f, 1.111373901f, + 1.101211548f, 1.089782715f, 1.077117920f, 1.063217163f, + 1.048156738f, 1.031936646f, 1.014617920f, 0.996246338f, + 0.976852417f, 0.956481934f, 0.935195923f, 0.913055420f, + 0.890090942f, 0.866363525f, 0.841949463f, 0.816864014f, + 0.791213989f, 0.765029907f, 0.738372803f, 0.711318970f, + 0.683914185f, 0.656219482f, 0.628295898f, 0.600219727f, + 0.572036743f, 0.543823242f, 0.515609741f, 0.487472534f, + 0.459472656f, 0.431655884f, 0.404083252f, 0.376800537f, + 0.349868774f, 0.323318481f, 0.297210693f, 0.271591187f, + 0.246505737f, 0.221984863f, 0.198059082f, 0.174789429f, + 0.152206421f, 0.130310059f, 0.109161377f, 0.088775635f, + 0.069168091f, 0.050354004f, 0.032379150f, 0.015228271f, + -0.001068115f, -0.016510010f, -0.031082153f, -0.044784546f, + -0.057617187f, -0.069595337f, -0.080688477f, -0.090927124f, + 0.100311279f, 0.108856201f, 0.116577148f, 0.123474121f, + 0.129577637f, 0.134887695f, 0.139450073f, 0.143264771f, + 0.146362305f, 0.148773193f, 0.150497437f, 0.151596069f, + 0.152069092f, 0.151962280f, 0.151306152f, 0.150115967f, + 0.148422241f, 0.146255493f, 0.143676758f, 0.140670776f, + 0.137298584f, 0.133590698f, 0.129562378f, 0.125259399f, + 0.120697021f, 0.115921021f, 0.110946655f, 0.105819702f, + 0.100540161f, 0.095169067f, 0.089706421f, 0.084182739f, + 0.078628540f, 0.073059082f, 0.067520142f, 0.061996460f, + 0.056533813f, 0.051132202f, 0.045837402f, 0.040634155f, + 0.035552979f, 0.030609131f, 0.025817871f, 0.021179199f, + 0.016708374f, 0.012420654f, 0.008316040f, 0.004394531f, + 0.000686646f, -0.002822876f, -0.006134033f, -0.009231567f, + -0.012115479f, -0.014801025f, -0.017257690f, -0.019531250f, + -0.021575928f, -0.023422241f, -0.025085449f, -0.026535034f, + -0.027801514f, -0.028884888f, -0.029785156f, -0.030517578f, + 0.031082153f, 0.031478882f, 0.031738281f, 0.031845093f, + 0.031814575f, 0.031661987f, 0.031387329f, 0.031005859f, + 0.030532837f, 0.029937744f, 0.029281616f, 0.028533936f, + 0.027725220f, 0.026840210f, 0.025909424f, 0.024932861f, + 0.023910522f, 0.022857666f, 0.021789551f, 0.020690918f, + 0.019577026f, 0.018463135f, 0.017349243f, 0.016235352f, + 0.015121460f, 0.014022827f, 0.012939453f, 0.011886597f, + 0.010848999f, 0.009841919f, 0.008865356f, 0.007919312f, + 0.007003784f, 0.006118774f, 0.005294800f, 0.004486084f, + 0.003723145f, 0.003005981f, 0.002334595f, 0.001693726f, + 0.001098633f, 0.000549316f, 0.000030518f, -0.000442505f, + -0.000869751f, -0.001266479f, -0.001617432f, -0.001937866f, + -0.002227783f, -0.002487183f, -0.002700806f, -0.002883911f, + -0.003051758f, -0.003173828f, -0.003280640f, -0.003372192f, + -0.003417969f, -0.003463745f, -0.003479004f, -0.003479004f, + -0.003463745f, -0.003433228f, -0.003387451f, -0.003326416f, + 0.003250122f, 0.003173828f, 0.003082275f, 0.002990723f, + 0.002899170f, 0.002792358f, 0.002685547f, 0.002578735f, + 0.002456665f, 0.002349854f, 0.002243042f, 0.002120972f, + 0.002014160f, 0.001907349f, 0.001785278f, 0.001693726f, + 0.001586914f, 0.001480103f, 0.001388550f, 0.001296997f, + 0.001205444f, 0.001113892f, 0.001037598f, 0.000961304f, + 0.000885010f, 0.000808716f, 0.000747681f, 0.000686646f, + 0.000625610f, 0.000579834f, 0.000534058f, 0.000473022f, + 0.000442505f, 0.000396729f, 0.000366211f, 0.000320435f, + 0.000289917f, 0.000259399f, 0.000244141f, 0.000213623f, + 0.000198364f, 0.000167847f, 0.000152588f, 0.000137329f, + 0.000122070f, 0.000106812f, 0.000106812f, 0.000091553f, + 0.000076294f, 0.000076294f, 0.000061035f, 0.000061035f, + 0.000045776f, 0.000045776f, 0.000030518f, 0.000030518f, + 0.000030518f, 0.000030518f, 0.000015259f, 0.000015259f, + 0.000015259f, 0.000015259f, 0.000015259f, 0.000015259f +}; + +/*********************************************************************** + * Use the header information to select the subband allocation table + **********************************************************************/ +static void +II_pick_table (frame_params * fr_ps) +{ + int table, ver, lay, bsp, br_per_ch, sfrq; + + ver = fr_ps->header.version; + lay = fr_ps->header.layer - 1; + bsp = fr_ps->header.bitrate_idx; + + /* decision rules refer to per-channel bitrates (kbits/sec/chan) */ + if (ver == MPEG_VERSION_1) { + br_per_ch = bitrates_v1[lay][bsp] / fr_ps->stereo; + + sfrq = s_rates[ver][fr_ps->header.srate_idx]; + + /* MPEG-1 */ + if ((sfrq == 48000 && br_per_ch >= 56) || + (br_per_ch >= 56 && br_per_ch <= 80)) + table = 0; + else if (sfrq != 48000 && br_per_ch >= 96) + table = 1; + else if (sfrq != 32000 && br_per_ch <= 48) + table = 2; + else + table = 3; + + } else { + /* br_per_ch = bitrates_v2[lay][bsp] / fr_ps->stereo; */ + + /* MPEG-2 LSF */ + table = 4; + } + + fr_ps->sblimit = ba_tables[table].sub_bands; + fr_ps->alloc = &ba_tables[table].alloc; +} + +static int +js_bound (gint lay, gint m_ext) +{ + /* layer + mode_ext -> jsbound */ + static const int jsb_table[3][4] = { + {4, 8, 12, 16}, {4, 8, 12, 16}, {0, 4, 8, 16} + }; + + if (lay < 1 || lay > 3 || m_ext < 0 || m_ext > 3) { + GST_WARNING ("js_bound bad layer/modext (%d/%d)\n", lay, m_ext); + return 0; + } + return (jsb_table[lay - 1][m_ext]); +} + +static void +hdr_to_frps (frame_params * fr_ps) +{ + fr_header *hdr = &fr_ps->header; + + fr_ps->actual_mode = hdr->mode; + fr_ps->stereo = (hdr->mode == MPG_MD_MONO) ? 1 : 2; + fr_ps->sblimit = SBLIMIT; + + if (hdr->mode == MPG_MD_JOINT_STEREO) + fr_ps->jsbound = js_bound (hdr->layer, hdr->mode_ext); + else + fr_ps->jsbound = fr_ps->sblimit; +} + +/***************************************************************************** +* +* CRC error protection package +* +*****************************************************************************/ +static void +update_CRC (const guint data, const guint length, guint * crc) +{ + unsigned int masking, carry; + + masking = 1 << length; + + while ((masking >>= 1)) { + carry = *crc & 0x8000; + *crc <<= 1; + if (!carry ^ !(data & masking)) + *crc ^= CRC16_POLYNOMIAL; + } + *crc &= 0xffff; +} + +static void +I_CRC_calc (const frame_params * fr_ps, guint bit_alloc[2][SBLIMIT], + guint * crc) +{ + gint i, k; + const fr_header *hdr = &fr_ps->header; + const gint stereo = fr_ps->stereo; + const gint jsbound = fr_ps->jsbound; + + *crc = 0xffff; /* changed from '0' 92-08-11 shn */ + update_CRC (hdr->bitrate_idx, 4, crc); + update_CRC (hdr->srate_idx, 2, crc); + update_CRC (hdr->padding, 1, crc); + update_CRC (hdr->extension, 1, crc); + update_CRC (hdr->mode, 2, crc); + update_CRC (hdr->mode_ext, 2, crc); + update_CRC (hdr->copyright, 1, crc); + update_CRC (hdr->original, 1, crc); + update_CRC (hdr->emphasis, 2, crc); + + for (i = 0; i < SBLIMIT; i++) + for (k = 0; k < ((i < jsbound) ? stereo : 1); k++) + update_CRC (bit_alloc[k][i], 4, crc); +} + +static void +II_CRC_calc (const frame_params * fr_ps, guint bit_alloc[2][SBLIMIT], + guint scfsi[2][SBLIMIT], guint * crc) +{ + gint i, k; + const fr_header *hdr = &fr_ps->header; + const gint stereo = fr_ps->stereo; + const gint sblimit = fr_ps->sblimit; + const gint jsbound = fr_ps->jsbound; + const al_table *alloc = fr_ps->alloc; + + *crc = 0xffff; /* changed from '0' 92-08-11 shn */ + + update_CRC (hdr->bitrate_idx, 4, crc); + update_CRC (hdr->srate_idx, 2, crc); + update_CRC (hdr->padding, 1, crc); + update_CRC (hdr->extension, 1, crc); + update_CRC (hdr->mode, 2, crc); + update_CRC (hdr->mode_ext, 2, crc); + update_CRC (hdr->copyright, 1, crc); + update_CRC (hdr->original, 1, crc); + update_CRC (hdr->emphasis, 2, crc); + + for (i = 0; i < sblimit; i++) + for (k = 0; k < ((i < jsbound) ? stereo : 1); k++) + update_CRC (bit_alloc[k][i], (*alloc)[i][0].bits, crc); + + for (i = 0; i < sblimit; i++) + for (k = 0; k < stereo; k++) + if (bit_alloc[k][i]) + update_CRC (scfsi[k][i], 2, crc); +} + +/* + * 2 Bitstream buffer implementations. 1 reading from a provided + * data pointer, the other from a fixed size ring buffer. + */ + +/* Create and initialise a new bitstream reader */ +Bit_stream_struc * +bs_new () +{ + Bit_stream_struc *bs; + + bs = (Bit_stream_struc *) calloc(1, sizeof(Bit_stream_struc)); + g_return_val_if_fail (bs != NULL, NULL); + + bs->master.cur_bit = 8; + bs->master.size = 0; + bs->master.cur_used = 0; + bs->read.cur_bit = 8; + bs->read.size = 0; + bs->read.cur_used = 0; + return bs; +} + +/* Release a bitstream reader */ +void +bs_free (Bit_stream_struc * bs) +{ + g_return_if_fail (bs != NULL); + + free (bs); +} + +/* Set data as the stream for processing */ +gboolean +bs_set_data (Bit_stream_struc * bs, const guint8 * data, gsize size) +{ + g_return_val_if_fail (bs != NULL, FALSE); + g_return_val_if_fail (data != NULL, FALSE); + g_return_val_if_fail (size != 0, FALSE); + + bs->master.data = data; + bs->master.cur_byte = (guint8 *) data; + bs->master.size = size; + bs->master.bitpos = 0; + bs->master.cur_used = 0; + bs_reset (bs); + return TRUE; +} + +/* Advance N bits on the indicated BSreader */ +static inline void +bs_eat (Bit_stream_struc * bs, BSReader * read, guint32 Nbits) +{ + while (Nbits > 0) { + gint k; + + /* Check for the data limit */ + if (read->cur_used >= read->size) { + return; + } + + if (Nbits < 8 || read->cur_bit != 8) { + /* Take as many bits as we can from the current byte */ + k = MIN (Nbits, read->cur_bit); + + /* Adjust our tracking vars */ + read->cur_bit -= k; + Nbits -= k; + read->bitpos += k; + + /* Move to the next byte if we consumed the current one */ + if (read->cur_bit == 0) { + read->cur_bit = 8; + read->cur_used++; + read->cur_byte++; + } + } else { + /* Take as many bytes as we can from current buffer */ + k = MIN (Nbits / 8, (guint32)(read->size - read->cur_used)); + + read->cur_used += k; + read->cur_byte += k; + + /* convert to bits */ + k *= 8; + read->bitpos += k; + Nbits -= k; + } + } +} + +/* Advance the master position by Nbits */ +void +bs_consume (Bit_stream_struc * bs, guint32 Nbits) +{ +#if 0 + static gint n = 0; + GST_DEBUG ("%d Consumed %d bits to end at %" G_GUINT64_FORMAT, + n++, Nbits, bs_pos (bs) + Nbits); +#endif + bs_eat (bs, &bs->master, Nbits); +} + +/* Advance the read position by Nbits */ +void +bs_skipbits (Bit_stream_struc * bs, guint32 Nbits) +{ + bs_eat (bs, &bs->read, Nbits); +} + +/* Advances the read position to the first bit of next frame or + * last byte in the buffer when the sync code is not found */ +gboolean +bs_seek_sync (Bit_stream_struc * bs) +{ + gboolean res = FALSE; + guint8 last_byte; + guint8 *start_pos; + + /* Align to the start of the next byte */ + if (bs->read.cur_bit != BS_BYTE_SIZE) { + bs->read.bitpos += (BS_BYTE_SIZE - bs->read.cur_bit); + bs->read.cur_bit = BS_BYTE_SIZE; + bs->read.cur_used++; + bs->read.cur_byte++; + } + + /* Ensure there's still some data to read */ + if (G_UNLIKELY (bs->read.cur_used >= bs->read.size)) { + return FALSE; + } + + start_pos = bs->read.cur_byte; + while (bs->read.cur_used < bs->read.size - 1) { + last_byte = bs->read.cur_byte[0]; + bs->read.cur_byte++; + bs->read.cur_used++; + + if (last_byte == 0xff && bs->read.cur_byte[0] >= 0xe0) { + /* Found a sync word */ + res = TRUE; + break; + } + } + /* Update the tracked position in the reader */ + bs->read.bitpos += BS_BYTE_SIZE * (bs->read.cur_byte - start_pos); + + if (res) { + /* Move past the first 3 bits of 2nd sync byte */ + bs->read.cur_bit = 5; + bs->read.bitpos += 3; + } + + return res; +} + +/* Extract N bytes from the bitstream into the out array. */ +void +bs_getbytes (Bit_stream_struc * bs, guint8 * out, guint32 N) +{ + gint j = N; + gint to_take; + + while (j > 0) { + /* Move to the next byte if we consumed any bits of the current one */ + if (bs->read.cur_bit != 8) { + bs->read.cur_bit = 8; + bs->read.cur_used++; + bs->read.cur_byte++; + } + + /* Check for the data limit */ + if (bs->read.cur_used >= bs->read.size) { + GST_WARNING ("Attempted to read beyond buffer"); + return; + } + + /* Take as many bytes as we can from the current buffer */ + to_take = MIN (j, (gint) (bs->read.size - bs->read.cur_used)); + memcpy (out, bs->read.cur_byte, to_take); + + out += to_take; + bs->read.cur_byte += to_take; + bs->read.cur_used += to_take; + j -= to_take; + bs->read.bitpos += (to_take * 8); + } +} + +static void +h_setbuf (huffdec_bitbuf * bb, guint8 * buf, guint size) +{ + bb->avail = size; + bb->buf_byte_idx = 0; + bb->buf_bit_idx = 8; + bb->buf = buf; +#if ENABLE_OPT_BS + if (buf) { + /* First load of the accumulator, assumes that size >= 4 */ + bb->buf_bit_idx = 32; + bb->remaining = bb->avail - 4; + + /* we need reverse the byte order */ + bb->accumulator = (guint) buf[3]; + bb->accumulator |= (guint) (buf[2]) << 8; + bb->accumulator |= (guint) (buf[1]) << 16; + bb->accumulator |= (guint) (buf[0]) << 24; + + bb->buf_byte_idx += 4; + } else { + bb->remaining = 0; + bb->accumulator = 0; + } +#endif +} + +static void +h_reset (huffdec_bitbuf * bb) +{ + h_setbuf (bb, NULL, 0); +} + +#if ENABLE_OPT_BS +static void +h_rewindNbits (huffdec_bitbuf * bb, guint N) +{ + guint bits = 0; + guint bytes = 0; + if (N <= (BS_ACUM_SIZE - bb->buf_bit_idx)) + bb->buf_bit_idx += N; + else { + N -= (BS_ACUM_SIZE - bb->buf_bit_idx); + bb->buf_bit_idx = 0; + bits = 8 - (N % 8); + bytes = (N + 8 + BS_ACUM_SIZE) >> 3; + if (bb->buf_byte_idx >= bytes) + bb->buf_byte_idx -= bytes; + else + bb->buf_byte_idx = 0; + bb->remaining += bytes; + h_getbits (bb, bits); + } +} +#else +void +static h_rewindNbits (huffdec_bitbuf * bb, guint N) +{ + guint32 byte_off; + + byte_off = (bb->buf_bit_idx + N) / 8; + + g_return_if_fail (bb->buf_byte_idx >= byte_off); + + bb->buf_bit_idx += N; + + if (bb->buf_bit_idx >= 8) { + bb->buf_bit_idx -= 8 * byte_off; + bb->buf_byte_idx -= byte_off; + } +} +#endif + +/* Constant declarations */ +static const gfloat multiple[64] = { + 2.00000000000000f, 1.58740105196820f, 1.25992104989487f, + 1.00000000000000f, 0.79370052598410f, 0.62996052494744f, 0.50000000000000f, + 0.39685026299205f, 0.31498026247372f, 0.25000000000000f, 0.19842513149602f, + 0.15749013123686f, 0.12500000000000f, 0.09921256574801f, 0.07874506561843f, + 0.06250000000000f, 0.04960628287401f, 0.03937253280921f, 0.03125000000000f, + 0.02480314143700f, 0.01968626640461f, 0.01562500000000f, 0.01240157071850f, + 0.00984313320230f, 0.00781250000000f, 0.00620078535925f, 0.00492156660115f, + 0.00390625000000f, 0.00310039267963f, 0.00246078330058f, 0.00195312500000f, + 0.00155019633981f, 0.00123039165029f, 0.00097656250000f, 0.00077509816991f, + 0.00061519582514f, 0.00048828125000f, 0.00038754908495f, 0.00030759791257f, + 0.00024414062500f, 0.00019377454248f, 0.00015379895629f, 0.00012207031250f, + 0.00009688727124f, 0.00007689947814f, 0.00006103515625f, 0.00004844363562f, + 0.00003844973907f, 0.00003051757813f, 0.00002422181781f, 0.00001922486954f, + 0.00001525878906f, 0.00001211090890f, 0.00000961243477f, 0.00000762939453f, + 0.00000605545445f, 0.00000480621738f, 0.00000381469727f, 0.00000302772723f, + 0.00000240310869f, 0.00000190734863f, 0.00000151386361f, 0.00000120155435f, + 1e-20f +}; + +/************************************************************* + * + * This module parses the starting 21 bits of the header + * + * **********************************************************/ + +static gboolean +read_main_header (Bit_stream_struc * bs, fr_header * hdr) +{ + if (bs_bits_avail (bs) < HEADER_LNGTH) { + return FALSE; + } + + /* Read 2 bits as version, since we're doing the MPEG2.5 thing + * of an 11 bit sync word and 2 bit version */ + hdr->version = bs_getbits (bs, 2); + hdr->layer = 4 - bs_getbits (bs, 2); + + /* error_protection TRUE indicates there is a CRC */ + hdr->error_protection = !bs_get1bit (bs); + hdr->bitrate_idx = bs_getbits (bs, 4); + hdr->srate_idx = bs_getbits (bs, 2); + hdr->padding = bs_get1bit (bs); + hdr->extension = bs_get1bit (bs); + hdr->mode = bs_getbits (bs, 2); + hdr->mode_ext = bs_getbits (bs, 2); + + hdr->copyright = bs_get1bit (bs); + hdr->original = bs_get1bit (bs); + hdr->emphasis = bs_getbits (bs, 2); + + return TRUE; +} + +/*************************************************************** + * + * This module contains the core of the decoder ie all the + * computational routines. (Layer I and II only) + * Functions are common to both layer unless + * otherwise specified. + * + ***************************************************************/ + +/************ Layer I, Layer II & Layer III ******************/ +static gboolean +read_header (mp3tl * tl, fr_header * hdr) +{ + Bit_stream_struc *bs = tl->bs; + + if (!read_main_header (bs, hdr)) + return FALSE; + + switch (hdr->layer) { + case 1: + hdr->bits_per_slot = 32; + hdr->frame_samples = 384; + break; + case 2: + hdr->bits_per_slot = 8; + hdr->frame_samples = 1152; + break; + case 3: + hdr->bits_per_slot = 8; + switch (hdr->version) { + case MPEG_VERSION_1: + hdr->frame_samples = 1152; + break; + case MPEG_VERSION_2: + case MPEG_VERSION_2_5: + hdr->frame_samples = 576; + break; + default: + return FALSE; + } + break; + default: + /* Layer must be 1, 2 or 3 */ + return FALSE; + } + + /* Sample rate index cannot be 0x03 (reserved value) */ + /* Bitrate index cannot be 0x0f (forbidden) */ + if (hdr->srate_idx == 0x03 || hdr->bitrate_idx == 0x0f) { + return FALSE; + } + + hdr->channels = (hdr->mode == MPG_MD_MONO) ? 1 : 2; + hdr->sample_rate = s_rates[hdr->version][hdr->srate_idx]; + hdr->bitrate = 0; + /*Free format as bitrate index is 0 */ + if (hdr->bitrate_idx == 0) { + /*Calculate Only for the first free format frame since the stream + * is of constant bitrate */ + if (tl->free_first) { + Bit_stream_struc org_bs; + fr_header hdr1; + guint N; + /*copy the orignal bitsream structure */ + memcpy (&org_bs, bs, sizeof (Bit_stream_struc)); + + /*Seek to next mp3 sync word and loop till there is data in the bitstream buffer */ + while (bs_seek_sync (bs)) { + if (!read_main_header (bs, &hdr1)) + return FALSE; + + /*Checks if the original and forwarded frames header details are same + *if yes then calculate free format bitrate else seek to next frame*/ + if (hdr->version == hdr1.version && + hdr->layer == hdr1.layer && + hdr->error_protection == hdr1.error_protection && + hdr->bitrate_idx == hdr1.bitrate_idx && + hdr->srate_idx == hdr1.srate_idx) { + /*Calculates distance between 2 valid frames */ + N = (guint)(bs->read.cur_used - org_bs.read.cur_used); + /*Copies back the original bitsream to main bs structure */ + memcpy (bs, &org_bs, sizeof (Bit_stream_struc)); + + /*Free format bitrate in kbps that will be used for future reference */ + tl->free_bitrate = + (hdr->sample_rate * (N - hdr->padding + 1) * 8 / hdr->frame_samples) / 1000; + hdr->bitrate = tl->free_bitrate * 1000; + tl->free_first = FALSE; + break; + } + } + } else + /*for all frames copy the same free format bitrate as the stream is cbr */ + hdr->bitrate = tl->free_bitrate * 1000; + } else if (hdr->version == MPEG_VERSION_1) + hdr->bitrate = bitrates_v1[hdr->layer - 1][hdr->bitrate_idx] * 1000; + else + hdr->bitrate = bitrates_v2[hdr->layer - 1][hdr->bitrate_idx] * 1000; + + if (hdr->sample_rate == 0 || hdr->bitrate == 0) { + return FALSE; + } + + /* Magic formula for calculating the size of a frame based on + * the duration of the frame and the bitrate */ + hdr->frame_slots = (hdr->frame_samples / hdr->bits_per_slot) + * hdr->bitrate / hdr->sample_rate + hdr->padding; + + /* Number of bits we need for decode is frame_slots * slot_size */ + hdr->frame_bits = hdr->frame_slots * hdr->bits_per_slot; + if (hdr->frame_bits <= 32) { + return FALSE; /* Invalid header */ + } + + return TRUE; +} + +#define MPEG1_STEREO_SI_SLOTS 32 +#define MPEG1_MONO_SI_SLOTS 17 +#define MPEG2_LSF_STEREO_SI_SLOTS 17 +#define MPEG2_LSF_MONO_SI_SLOTS 9 + +#define SAMPLE_RATES 3 +#define BIT_RATES 15 + +/* For layer 3 only - the number of slots for main data of + * current frame. In Layer 3, 1 slot = 1 byte */ +static gboolean +set_hdr_data_slots (fr_header * hdr) +{ + int nSlots; + + if (hdr->layer != 3) { + hdr->side_info_slots = 0; + hdr->main_slots = 0; + return TRUE; + } + + nSlots = hdr->frame_slots - hdr->padding; + +#if 0 + if (hdr->version == MPEG_VERSION_1) { + static const gint MPEG1_slot_table[SAMPLE_RATES][BIT_RATES] = { + {0, 104, 130, 156, 182, 208, 261, 313, 365, 417, 522, 626, 731, 835, 1044}, + {0, 96, 120, 144, 168, 192, 240, 288, 336, 384, 480, 576, 672, 768, 960}, + {0, 144, 180, 216, 252, 288, 360, 432, 504, 576, 720, 864, 1008, 1152, 1440} + }; + g_print ("Calced %d main slots, table says %d\n", nSlots, + MPEG1_slot_table[hdr->srate_idx][hdr->bitrate_idx]); + } else { + static const gint MPEG2_LSF_slot_table[SAMPLE_RATES][BIT_RATES] = { + {0, 26, 52, 78, 104, 130, 156, 182, 208, 261, 313, 365, 417, 470, 522}, + {0, 24, 48, 72, 96, 120, 144, 168, 192, 240, 288, 336, 384, 432, 480}, + {0, 36, 72, 108, 144, 180, 216, 252, 288, 360, 432, 504, 576, 648, 720} + }; + g_print ("Calced %d main slots, table says %d\n", nSlots, + MPEG2_LSF_slot_table[hdr->srate_idx][hdr->bitrate_idx]); + } +#endif + + if (hdr->version == MPEG_VERSION_1) { + if (hdr->channels == 1) + hdr->side_info_slots = MPEG1_MONO_SI_SLOTS; + else + hdr->side_info_slots = MPEG1_STEREO_SI_SLOTS; + } else { + if (hdr->channels == 1) + hdr->side_info_slots = MPEG2_LSF_MONO_SI_SLOTS; + else + hdr->side_info_slots = MPEG2_LSF_STEREO_SI_SLOTS; + } + nSlots -= hdr->side_info_slots; + + if (hdr->padding) + nSlots++; + + nSlots -= 4; + if (hdr->error_protection) + nSlots -= 2; + + if (nSlots < 0) + return FALSE; + + hdr->main_slots = nSlots; + + return TRUE; +} + +/******************************************************************* + * + * The bit allocation information is decoded. Layer I + * has 4 bit per subband whereas Layer II is Ws and bit rate + * dependent. + * + ********************************************************************/ + +/**************************** Layer II *************/ +static void +II_decode_bitalloc (Bit_stream_struc * bs, guint32 bit_alloc[2][SBLIMIT], + frame_params * fr_ps) +{ + int sb, ch; + int stereo = fr_ps->stereo; + int sblimit = fr_ps->sblimit; + int jsbound = fr_ps->jsbound; + const al_table *alloc = fr_ps->alloc; + + for (sb = 0; sb < jsbound; sb++) + for (ch = 0; ch < stereo; ch++) { + bit_alloc[ch][sb] = (char) bs_getbits (bs, (*alloc)[sb][0].bits); + } + + for (sb = jsbound; sb < sblimit; sb++) { + /* expand to 2 channels */ + bit_alloc[0][sb] = bit_alloc[1][sb] = bs_getbits (bs, (*alloc)[sb][0].bits); + } + + /* Zero the rest of the array */ + for (sb = sblimit; sb < SBLIMIT; sb++) + for (ch = 0; ch < stereo; ch++) + bit_alloc[ch][sb] = 0; +} + +/**************************** Layer I *************/ + +static void +I_decode_bitalloc (Bit_stream_struc * bs, guint32 bit_alloc[2][SBLIMIT], + frame_params * fr_ps) +{ + int i, j; + int stereo = fr_ps->stereo; + +// int sblimit = fr_ps->sblimit; + int jsbound = fr_ps->jsbound; + + for (i = 0; i < jsbound; i++) + for (j = 0; j < stereo; j++) { + bit_alloc[j][i] = bs_getbits (bs, 4); + } + + for (i = jsbound; i < SBLIMIT; i++) { + guint32 b = bs_getbits (bs, 4); + + for (j = 0; j < stereo; j++) + bit_alloc[j][i] = b; + } +} + +/***************************************************************** + * + * The following two functions implement the layer I and II + * format of scale factor extraction. Layer I involves reading + * 6 bit per subband as scale factor. Layer II requires reading + * first the scfsi which in turn indicate the number of scale factors + * transmitted. + * Layer I : I_decode_scale + * Layer II : II_decode_scale + * + ****************************************************************/ + +/************************** Layer I stuff ************************/ +static void +I_decode_scale (Bit_stream_struc * bs, guint32 bit_alloc[2][SBLIMIT], + guint32 scale_index[2][3][SBLIMIT], frame_params * fr_ps) +{ + int i, j; + int stereo = fr_ps->stereo; + +// int sblimit = fr_ps->sblimit; + + for (i = 0; i < SBLIMIT; i++) + for (j = 0; j < stereo; j++) { + if (!bit_alloc[j][i]) + scale_index[j][0][i] = SCALE_RANGE - 1; + else { + /* 6 bit per scale factor */ + scale_index[j][0][i] = bs_getbits (bs, 6); + } + } +} + +/*************************** Layer II stuff ***************************/ + +static void +II_decode_scale (Bit_stream_struc * bs, + guint scfsi[2][SBLIMIT], + guint bit_alloc[2][SBLIMIT], + guint scale_index[2][3][SBLIMIT], frame_params * fr_ps) +{ + int sb, ch; + int stereo = fr_ps->stereo; + int sblimit = fr_ps->sblimit; + + for (sb = 0; sb < sblimit; sb++) + for (ch = 0; ch < stereo; ch++) /* 2 bit scfsi */ + if (bit_alloc[ch][sb]) { + scfsi[ch][sb] = bs_getbits (bs, 2); + } + + for (sb = sblimit; sb < SBLIMIT; sb++) + for (ch = 0; ch < stereo; ch++) + scfsi[ch][sb] = 0; + + for (sb = 0; sb < sblimit; sb++) { + for (ch = 0; ch < stereo; ch++) { + if (bit_alloc[ch][sb]) { + switch (scfsi[ch][sb]) { + /* all three scale factors transmitted */ + case 0: + scale_index[ch][0][sb] = bs_getbits (bs, 6); + scale_index[ch][1][sb] = bs_getbits (bs, 6); + scale_index[ch][2][sb] = bs_getbits (bs, 6); + break; + /* scale factor 1 & 3 transmitted */ + case 1: + scale_index[ch][0][sb] = + scale_index[ch][1][sb] = bs_getbits (bs, 6); + scale_index[ch][2][sb] = bs_getbits (bs, 6); + break; + /* scale factor 1 & 2 transmitted */ + case 3: + scale_index[ch][0][sb] = bs_getbits (bs, 6); + scale_index[ch][1][sb] = + scale_index[ch][2][sb] = bs_getbits (bs, 6); + break; + /* only one scale factor transmitted */ + case 2: + scale_index[ch][0][sb] = + scale_index[ch][1][sb] = + scale_index[ch][2][sb] = bs_getbits (bs, 6); + break; + default: + break; + } + } else { + scale_index[ch][0][sb] = + scale_index[ch][1][sb] = scale_index[ch][2][sb] = SCALE_RANGE - 1; + } + } + } + for (sb = sblimit; sb < SBLIMIT; sb++) { + for (ch = 0; ch < stereo; ch++) { + scale_index[ch][0][sb] = + scale_index[ch][1][sb] = scale_index[ch][2][sb] = SCALE_RANGE - 1; + } + } +} + +/************************************************************** + * + * The following two routines take care of reading the + * compressed sample from the bit stream for both layer 1 and + * layer 2. For layer 1, read the number of bits as indicated + * by the bit_alloc information. For layer 2, if grouping is + * indicated for a particular subband, then the sample size has + * to be read from the bits_group and the merged samples has + * to be decompose into the three distinct samples. Otherwise, + * it is the same for as layer one. + * + **************************************************************/ + +/******************************* Layer I stuff ******************/ + +static void +I_buffer_sample (Bit_stream_struc * bs, + guint sample[2][3][SBLIMIT], + guint bit_alloc[2][SBLIMIT], frame_params * fr_ps) +{ + int i, j, k; + int stereo = fr_ps->stereo; + +// int sblimit = fr_ps->sblimit; + int jsbound = fr_ps->jsbound; + unsigned int s; + + for (i = 0; i < jsbound; i++) { + for (j = 0; j < stereo; j++) { + k = bit_alloc[j][i]; + if (k == 0) + sample[j][0][i] = 0; + else + sample[j][0][i] = bs_getbits (bs, k + 1); + } + } + for (i = jsbound; i < SBLIMIT; i++) { + k = bit_alloc[0][i]; + if (k == 0) + s = 0; + else + s = bs_getbits (bs, k + 1); + + for (j = 0; j < stereo; j++) + sample[j][0][i] = s; + } +} + +/*************************** Layer II stuff ************************/ + +static void +II_buffer_sample (Bit_stream_struc * bs, guint sample[2][3][SBLIMIT], + guint bit_alloc[2][SBLIMIT], frame_params * fr_ps) +{ + int sb, ch, k; + int stereo = fr_ps->stereo; + int sblimit = fr_ps->sblimit; + int jsbound = fr_ps->jsbound; + const al_table *alloc = fr_ps->alloc; + + for (sb = 0; sb < sblimit; sb++) { + for (ch = 0; ch < ((sb < jsbound) ? stereo : 1); ch++) { + guint allocation = bit_alloc[ch][sb]; + if (allocation) { + /* check for grouping in subband */ + if (alloc[0][sb][allocation].group == 3) { + k = alloc[0][sb][allocation].bits; + sample[ch][0][sb] = bs_getbits (bs, k); + sample[ch][1][sb] = bs_getbits (bs, k); + sample[ch][2][sb] = bs_getbits (bs, k); + } else { /* bit_alloc = 3, 5, 9 */ + unsigned int nlevels, c = 0; + + nlevels = alloc[0][sb][allocation].steps; + k = alloc[0][sb][allocation].bits; + c = bs_getbits (bs, k); + for (k = 0; k < 3; k++) { + sample[ch][k][sb] = c % nlevels; + c /= nlevels; + } + } + } else { /* for no sample transmitted */ + sample[ch][0][sb] = 0; + sample[ch][1][sb] = 0; + sample[ch][2][sb] = 0; + } + if (stereo == 2 && sb >= jsbound) { /* joint stereo : copy L to R */ + sample[1][0][sb] = sample[0][0][sb]; + sample[1][1][sb] = sample[0][1][sb]; + sample[1][2][sb] = sample[0][2][sb]; + } + } + } + for (sb = sblimit; sb < SBLIMIT; sb++) + for (ch = 0; ch < stereo; ch++) { + sample[ch][0][sb] = 0; + sample[ch][1][sb] = 0; + sample[ch][2][sb] = 0; + } +} + +/************************************************************** + * + * Restore the compressed sample to a factional number. + * first complement the MSB of the sample + * for layer I : + * Use s = (s' + 2^(-nb+1) ) * 2^nb / (2^nb-1) + * for Layer II : + * Use the formula s = s' * c + d + * + **************************************************************/ +static const gfloat c_table[17] = { + 1.33333333333f, 1.60000000000f, 1.14285714286f, + 1.77777777777f, 1.06666666666f, 1.03225806452f, + 1.01587301587f, 1.00787401575f, 1.00392156863f, + 1.00195694716f, 1.00097751711f, 1.00048851979f, + 1.00024420024f, 1.00012208522f, 1.00006103888f, + 1.00003051851f, 1.00001525902f +}; + +static const gfloat d_table[17] = { + 0.500000000f, 0.500000000f, 0.250000000f, 0.500000000f, + 0.125000000f, 0.062500000f, 0.031250000f, 0.015625000f, + 0.007812500f, 0.003906250f, 0.001953125f, 0.0009765625f, + 0.00048828125f, 0.00024414063f, 0.00012207031f, + 0.00006103516f, 0.00003051758f +}; + +/************************** Layer II stuff ************************/ + +static void +II_dequant_and_scale_sample (guint sample[2][3][SBLIMIT], + guint bit_alloc[2][SBLIMIT], float fraction[2][3][SBLIMIT], + guint scale_index[2][3][SBLIMIT], int scale_block, frame_params * fr_ps) +{ + int sb, gr, ch, x; + int stereo = fr_ps->stereo; + int sblimit = fr_ps->sblimit; + const al_table *alloc = fr_ps->alloc; + + for (sb = 0; sb < sblimit; sb++) { + for (ch = 0; ch < stereo; ch++) { + guint allocation = bit_alloc[ch][sb]; + + if (allocation != 0) { + gfloat scale_val, val; + gfloat c_quant, d_quant; + + c_quant = c_table[alloc[0][sb][allocation].quant]; + d_quant = d_table[alloc[0][sb][allocation].quant]; + scale_val = multiple[scale_index[ch][scale_block][sb]]; + + for (gr = 0; gr < 3; gr++) { + /* locate MSB in the sample */ + x = 0; + while ((1UL << x) < (*alloc)[sb][allocation].steps) + x++; + + /* MSB inversion */ + if (((sample[ch][gr][sb] >> (x - 1)) & 1) == 1) + val = 0.0f; + else + val = -1.0f; + + /* Form a 2's complement sample */ + val += (gfloat) ((double) (sample[ch][gr][sb] & ((1 << (x - 1)) - 1)) + / (double) (1L << (x - 1))); + + /* Dequantize the sample */ + val += d_quant; + val *= c_quant; + + /* And scale */ + val *= scale_val; + fraction[ch][gr][sb] = val; + } + } else { + fraction[ch][0][sb] = 0.0f; + fraction[ch][1][sb] = 0.0f; + fraction[ch][2][sb] = 0.0f; + } + } + } + + for (sb = sblimit; sb < SBLIMIT; sb++) + for (ch = 0; ch < stereo; ch++) { + fraction[ch][0][sb] = 0.0f; + fraction[ch][1][sb] = 0.0f; + fraction[ch][2][sb] = 0.0f; + } +} + +/***************************** Layer I stuff ***********************/ + +static void +I_dequant_and_scale_sample (guint sample[2][3][SBLIMIT], + float fraction[2][3][SBLIMIT], + guint bit_alloc[2][SBLIMIT], + guint scale_index[2][3][SBLIMIT], frame_params * fr_ps) +{ + int sb, ch; + guint nb; + int stereo = fr_ps->stereo; + + for (sb = 0; sb < SBLIMIT; sb++) { + for (ch = 0; ch < stereo; ch++) { + guint allocation = bit_alloc[ch][sb]; + + if (allocation != 0) { + double val; + + nb = allocation + 1; + + if (((sample[ch][0][sb] >> allocation) & 1) != 0) + val = 0.0; + else + val = -1.0; + + val += (double) (sample[ch][0][sb] & ((1 << allocation) - 1)) / + (double) (1L << (nb - 1)); + + val = + (double) (val + 1.0 / (1L << allocation)) * + (double) (1L << nb) / ((1L << nb) - 1); + + val *= (double) multiple[scale_index[ch][0][sb]]; + + fraction[ch][0][sb] = (gfloat) val; + } else + fraction[ch][0][sb] = 0.0f; + } + } +} + +/***************************************************************** + * + * The following are the subband synthesis routines. They apply + * to both layer I and layer II stereo or mono. The user has to + * decide what parameters are to be passed to the routines. + * + ***************************************************************/ + +/* Write output samples into the outBuf, incrementing psamples for each + * sample, wrapping at bufSize */ +static inline void +out_fifo (short pcm_sample[2][SSLIMIT][SBLIMIT], int num, + frame_params * fr_ps, gint16 * outBuf, guint32 * psamples, guint32 bufSize) +{ + int i, j, k, l; + int stereo = fr_ps->stereo; + k = *psamples; + if (stereo == 2) { + for (i = 0; i < num; i++) { + for (j = 0; j < SBLIMIT; j++) { + outBuf[k] = pcm_sample[0][i][j]; + outBuf[k+1] = pcm_sample[1][i][j]; + k += 2; + k %= bufSize; + } + } + } else if (stereo == 1) { + for (i = 0; i < num; i++) { + for (j = 0; j < SBLIMIT; j++) { + outBuf[k] = pcm_sample[0][i][j]; + k++; + k %= bufSize; + } + } + } else { + for (i = 0; i < num; i++) { + for (j = 0; j < SBLIMIT; j++) { + for (l = 0; l < stereo; l++) { + outBuf[k] = pcm_sample[l][i][j]; + k++; + k %= bufSize; + } + } + } + } + *psamples = k; +} + +/************************************************************* + * + * Pass the subband sample through the synthesis window + * + **************************************************************/ + +/* create in synthesis filter */ +static void +init_syn_filter (frame_params * fr_ps) +{ + int i, k; + gfloat (*filter)[32]; + + filter = fr_ps->filter; + + for (i = 0; i < 64; i++) + for (k = 0; k < 32; k++) { + if ((filter[i][k] = 1e9f * cosf ((float)((PI64 * i + PI4) * (2 * k + 1)))) >= 0.0f) + modff (filter[i][k] + 0.5f, &filter[i][k]); + else + modff (filter[i][k] - 0.5f, &filter[i][k]); + filter[i][k] *= 1e-9f; + } + + for (i = 0; i < 2; i++) + fr_ps->bufOffset[i] = 64; +} + +/*************************************************************** + * + * Window the restored sample + * + ***************************************************************/ + +#define INV_SQRT_2 (7.071067811865474617150084668537e-01f) + +#if defined(USE_ARM_NEON) +static const __CACHE_LINE_DECL_ALIGN(float dct8_k[8]) = { + INV_SQRT_2, 0.0f, 5.4119610014619701222e-01f, 1.3065629648763763537e+00f, + 5.0979557910415917998e-01f, 6.0134488693504528634e-01f, + 8.9997622313641556513e-01f, 2.5629154477415054814e+00f +}; + +STATIC_INLINE void +MPG_DCT_8 (gfloat in[8], gfloat out[8]) +{ + __asm__ volatile ( + "vld1.64 {q0-q1}, [%[dct8_k],:128] \n\t" /* read dct8_k */ + "vld1.64 {q2-q3}, [%[in],:128] \n\t" /* read in */ + "vrev64.f32 q3, q3 \n\t" + "vswp d6, d7 \n\t" + "vadd.f32 q4, q2, q3 \n\t" /* ei0, ei2, ei3, ei1 */ + "vadd.f32 s28, s16, s19 \n\t" /* t0 = ei0 + ei1 */ + "vadd.f32 s29, s17, s18 \n\t" /* t1 = ei2 + ei3 */ + "vsub.f32 s30, s16, s19 \n\t" /* t2 = ei0 - ei1 */ + "vsub.f32 s31, s17, s18 \n\t" /* t3 = ei2 - ei3 */ + "vmul.f32 d15, d15, d1 \n\t" + "vsub.f32 q5, q2, q3 \n\t" /* oi0', oi1', oi2', oi3' */ + "vsub.f32 s27, s30, s31 \n\t" /* t4' = t2 - t3 */ + "vadd.f32 s24, s28, s29 \n\t" /* out0 = t0 + t1 */ + "vmul.f32 q5, q5, q1 \n\t" /* oi0, oi1, oi2, oi3 */ + "vmul.f32 s27, s27, s0 \n\t" /* out6 = t4 */ + "vadd.f32 s25, s30, s31 \n\t" /* out2 = t2 + t3 */ + "vadd.f32 s25, s25, s27 \n\t" /* out2 = t2 + t3 + t4 */ + "vsub.f32 s26, s28, s29 \n\t" /* out4' = t0 - t1 */ + "vrev64.f32 d11, d11 \n\t" + "vmul.f32 s26, s26, s0 \n\t" /* out4 = (t0 -t1) * INV_SQRT_2 */ + "vadd.f32 d4, d10, d11 \n\t" /* t0,t1 = oi0 + oi3, oi1 + oi2 */ + "vsub.f32 d5, d10, d11 \n\t" /* t2',t3' = oi0 - oi3, oi1 - oi3 */ + "vmul.f32 d5, d5, d1 \n\t" /* t2, t3 */ + "vadd.f32 s12, s10, s11 \n\t" /* t4 = t2 + t3 */ + "vsub.f32 s13, s10, s11 \n\t" /* t5' = t2 - t3 */ + "vmul.f32 s31, s13, s0 \n\t" /* out7 = oo3 = t5 */ + "vadd.f32 s14, s8, s9 \n\t" /* oo0 = t0 + t1 */ + "vadd.f32 s15, s12, s31 \n\t" /* oo1 = t4 + t5 */ + "vsub.f32 s16, s8, s9 \n\t" /* oo2' = t0 - t1 */ + "vmul.f32 s16, s16, s0 \n\t" /* oo2 */ + "vadd.f32 s28, s14, s15 \n\t" /* out1 = oo0 + oo1 */ + "vadd.f32 s29, s15, s16 \n\t" /* out3 = oo1 + oo2 */ + "vadd.f32 s30, s16, s31 \n\t" /* out5 = oo2 + oo3 */ + "vst2.32 {q6, q7}, [%[out],:128] \n\t" + : [in] "+&r" (in), + [out] "+&r" (out) + : [dct8_k] "r" (dct8_k) + : "memory", "cc", + "q0", "q1", "q2", "q3", "q4", "q5", "q6", "q7" + ); +} +#else +STATIC_INLINE void +MPG_DCT_8 (gfloat in[8], gfloat out[8]) +{ + gfloat even_in[4]; + gfloat odd_in[4], odd_out[4]; + gfloat tmp[6]; + + /* Even indices */ + even_in[0] = in[0] + in[7]; + even_in[1] = in[3] + in[4]; + even_in[2] = in[1] + in[6]; + even_in[3] = in[2] + in[5]; + + tmp[0] = even_in[0] + even_in[1]; + tmp[1] = even_in[2] + even_in[3]; + tmp[2] = (even_in[0] - even_in[1]) * synth_cos64_table[7]; + tmp[3] = (even_in[2] - even_in[3]) * synth_cos64_table[23]; + tmp[4] = (gfloat) ((tmp[2] - tmp[3]) * INV_SQRT_2); + + out[0] = tmp[0] + tmp[1]; + out[2] = tmp[2] + tmp[3] + tmp[4]; + out[4] = (gfloat) ((tmp[0] - tmp[1]) * INV_SQRT_2); + out[6] = tmp[4]; + + /* Odd indices */ + odd_in[0] = (in[0] - in[7]) * synth_cos64_table[3]; + odd_in[1] = (in[1] - in[6]) * synth_cos64_table[11]; + odd_in[2] = (in[2] - in[5]) * synth_cos64_table[19]; + odd_in[3] = (in[3] - in[4]) * synth_cos64_table[27]; + + tmp[0] = odd_in[0] + odd_in[3]; + tmp[1] = odd_in[1] + odd_in[2]; + tmp[2] = (odd_in[0] - odd_in[3]) * synth_cos64_table[7]; + tmp[3] = (odd_in[1] - odd_in[2]) * synth_cos64_table[23]; + tmp[4] = tmp[2] + tmp[3]; + tmp[5] = (gfloat) ((tmp[2] - tmp[3]) * INV_SQRT_2); + + odd_out[0] = tmp[0] + tmp[1]; + odd_out[1] = tmp[4] + tmp[5]; + odd_out[2] = (gfloat) ((tmp[0] - tmp[1]) * INV_SQRT_2); + odd_out[3] = tmp[5]; + + out[1] = odd_out[0] + odd_out[1]; + out[3] = odd_out[1] + odd_out[2]; + out[5] = odd_out[2] + odd_out[3]; + out[7] = odd_out[3]; +} +#endif + +#if defined(USE_ARM_NEON) + +static const __CACHE_LINE_DECL_ALIGN(float dct16_k[8]) = { + 5.0241928618815567820e-01f, 5.2249861493968885462e-01f, + 5.6694403481635768927e-01f, 6.4682178335999007679e-01f, + 6.4682178335999007679e-01f, 1.0606776859903470633e+00f, + 1.7224470982383341955e+00f, 5.1011486186891552563e+00f +}; + +STATIC_INLINE void +MPG_DCT_16 (gfloat in[16], gfloat out[16]) +{ + __CACHE_LINE_DECL_ALIGN(gfloat even_in[8]); + __CACHE_LINE_DECL_ALIGN(gfloat even_out[8]); + __CACHE_LINE_DECL_ALIGN(gfloat odd_in[8]); + __CACHE_LINE_DECL_ALIGN(gfloat odd_out[8]); + + __asm__ volatile ( + "vld1.64 {q0-q1}, [%[dct16_k],:128] \n\t" /* read dct16_k */ + "vld1.64 {q2-q3}, [%[in],:128]! \n\t" /* read in */ + "vld1.64 {q4-q5}, [%[in],:128] \n\t" /* read in */ + "vrev64.f32 q4, q4 \n\t" + "vrev64.f32 q5, q5 \n\t" + "vswp d8, d9 \n\t" + "vswp d10, d11 \n\t" + "vadd.f32 q6, q2, q4 \n\t" + "vadd.f32 q7, q3, q5 \n\t" + "vst1.64 {q6-q7}, [%[even_in],:128] \n\t" + "vsub.f32 q6, q2, q4 \n\t" + "vsub.f32 q7, q3, q5 \n\t" + "vmul.f32 q6, q6, q0 \n\t" + "vmul.f32 q7, q7, q1 \n\t" + "vst1.64 {q6-q7}, [%[odd_in],:128] \n\t" + : [in] "+&r" (in) + : [even_in] "r" (even_in), [odd_in] "r" (odd_in), [dct16_k] "r" (dct8_k) + : "memory", "cc", + "q0", "q1", "q2", "q3", "q4", "q5", "q6", "q7" + ); + + MPG_DCT_8 (even_in, even_out); + MPG_DCT_8 (odd_in, odd_out); + + __asm__ volatile ( + "vld1.64 {q0-q1}, [%[even_out],:128] \n\t" + "vld1.64 {q2-q3}, [%[odd_out],:128] \n\t" + "vswp q1, q2 \n\t" + "vadd.f32 s4, s4, s5 \n\t" + "vadd.f32 s5, s5, s6 \n\t" + "vadd.f32 s6, s6, s7 \n\t" + "vadd.f32 s7, s7, s12 \n\t" + "vst2.32 {q0-q1}, [%[out],:128]! \n\t" + "vadd.f32 s12, s12, s13 \n\t" + "vadd.f32 s13, s13, s14 \n\t" + "vadd.f32 s14, s14, s15 \n\t" + "vst2.32 {q2-q3}, [%[out],:128]! \n\t" + : [out] "+&r" (out) + : [even_out] "r" (even_out), [odd_out] "r" (odd_out) + : "memory", "cc", + "q0", "q1", "q2", "q3" + ); +} +#else +STATIC_INLINE void +MPG_DCT_16 (gfloat in[16], gfloat out[16]) +{ + __CACHE_LINE_DECL_ALIGN(gfloat even_in[8]); + __CACHE_LINE_DECL_ALIGN(gfloat even_out[8]); + __CACHE_LINE_DECL_ALIGN(gfloat odd_in[8]); + __CACHE_LINE_DECL_ALIGN(gfloat odd_out[8]); + gfloat a, b; + + a = in[0]; b = in[15]; + even_in[0] = a + b; + odd_in[0] = (a - b) * synth_cos64_table[1]; + a = in[1]; b = in[14]; + even_in[1] = a + b; + odd_in[1] = (a - b) * synth_cos64_table[5]; + a = in[2]; b = in[13]; + even_in[2] = a + b; + odd_in[2] = (a - b) * synth_cos64_table[9]; + a = in[3]; b = in[12]; + even_in[3] = a + b; + odd_in[3] = (a - b) * synth_cos64_table[13]; + a = in[4]; b = in[11]; + even_in[4] = a + b; + odd_in[4] = (a - b) * synth_cos64_table[17]; + a = in[5]; b = in[10]; + even_in[5] = a + b; + odd_in[5] = (a - b) * synth_cos64_table[21]; + a = in[6]; b = in[9]; + even_in[6] = a + b; + odd_in[6] = (a - b) * synth_cos64_table[25]; + a = in[7]; b = in[8]; + even_in[7] = a + b; + odd_in[7] = (a - b) * synth_cos64_table[29]; + + MPG_DCT_8 (even_in, even_out); + MPG_DCT_8 (odd_in, odd_out); + + out[0] = even_out[0]; + out[1] = odd_out[0] + odd_out[1]; + out[2] = even_out[1]; + out[3] = odd_out[1] + odd_out[2]; + out[4] = even_out[2]; + out[5] = odd_out[2] + odd_out[3]; + out[6] = even_out[3]; + out[7] = odd_out[3] + odd_out[4]; + out[8] = even_out[4]; + out[9] = odd_out[4] + odd_out[5]; + out[10] = even_out[5]; + out[11] = odd_out[5] + odd_out[6]; + out[12] = even_out[6]; + out[13] = odd_out[6] + odd_out[7]; + out[14] = even_out[7]; + out[15] = odd_out[7]; +} +#endif + +STATIC_INLINE void +MPG_DCT_32 (gfloat in[32], gfloat out[32]) +{ + gint i; + __CACHE_LINE_DECL_ALIGN(gfloat even_in[16]); + __CACHE_LINE_DECL_ALIGN(gfloat even_out[16]); + __CACHE_LINE_DECL_ALIGN(gfloat odd_in[16]); + __CACHE_LINE_DECL_ALIGN(gfloat odd_out[16]); + + for (i = 0; i < 16; i++) { + even_in[i] = in[i] + in[31 - i]; + odd_in[i] = (in[i] - in[31 - i]) * synth_cos64_table[2 * i]; + } + + MPG_DCT_16 (even_in, even_out); + MPG_DCT_16 (odd_in, odd_out); + + for (i = 0; i < 15; i++) { + out[2 * i] = even_out[i]; + out[2 * i + 1] = odd_out[i] + odd_out[i + 1]; + } + out[30] = even_out[15]; + out[31] = odd_out[15]; +} + +#if defined(USE_ARM_NEON) + +#define WIN_MAC \ + " vld1.32 {q0-q1}, [r3,:128], r5 \n\t" /* read win */ \ + " vld1.32 {q2-q3}, [r4,:128], r5 \n\t" /* read uvec */ \ + " pld [r3] \n\t" \ + " pld [r4] \n\t" \ + " vmla.f32 q4, q0, q2 \n\t" /* acc += uvec * win */ \ + " vmla.f32 q5, q1, q3 \n\t" + +STATIC_INLINE void +mp3_dewindow_output (gfloat *uvec, short *samples, gfloat* window) +{ + __asm__ volatile ( + "pld [%[win]] \n\t" + "pld [%[uvec]] \n\t" + "mov r5, #32*4 \n\t" /* step = 32 floats */ + "mov ip, #4 \n\t" /* ip = 4 */ + "0: \n\t" + " add r3, %[win], r5 \n\t" /* pw = win */ + " add r4, %[uvec], r5 \n\t" /* puvec = uvec */ + " vld1.32 {q0-q1}, [%[win],:128]! \n\t" /* read win */ + " vld1.32 {q2-q3}, [%[uvec],:128]! \n\t" /* read uvec */ + " pld [r3] \n\t" + " pld [r4] \n\t" + " vmul.f32 q4, q0, q2 \n\t" /* acc = uvec * win */ + " vmul.f32 q5, q1, q3 \n\t" + WIN_MAC + WIN_MAC + WIN_MAC + WIN_MAC + WIN_MAC + WIN_MAC + WIN_MAC + WIN_MAC + WIN_MAC + WIN_MAC + WIN_MAC + WIN_MAC + WIN_MAC + WIN_MAC + WIN_MAC + " vcvt.s32.f32 q4, q4, #31 \n\t" + " vcvt.s32.f32 q5, q5, #31 \n\t" + " vshrn.s32 d0, q4, #16 \n\t" + " vshrn.s32 d1, q5, #16 \n\t" + " vst1.64 {d0-d1}, [%[samp],:128]! \n\t" + " pld [%[win]] \n\t" + " pld [%[uvec]] \n\t" + " subs ip, ip, #1 \n\t" + " bne 0b \n\t" + + : [win] "+&r" (window), + [samp] "+&r" (samples), + [uvec] "+&r" (uvec) + : + : "memory", "cc", "ip", "r3", "r4", "r5", + "q0", "q1", "q2", "q3", "q4", "q5" + ); +} + +#else +STATIC_INLINE void +mp3_dewindow_output (gfloat *u_vec, short *samples, gfloat* window) +{ + gint i; + gfloat *u_vec0; + + /* dewindowing */ + for (i = 0; i < HAN_SIZE; i++) + u_vec[i] *= dewindow[i]; + + /* Now calculate 32 samples */ + for (i = 0; i < 32; i++) { + gfloat sum; + u_vec0 = u_vec + i; + sum = u_vec0[1 << 5]; + sum += u_vec0[2 << 5]; + sum += u_vec0[3 << 5]; + sum += u_vec0[4 << 5]; + sum += u_vec0[5 << 5]; + sum += u_vec0[6 << 5]; + sum += u_vec0[7 << 5]; + sum += u_vec0[8 << 5]; + sum += u_vec0[9 << 5]; + sum += u_vec0[10 << 5]; + sum += u_vec0[11 << 5]; + sum += u_vec0[12 << 5]; + sum += u_vec0[13 << 5]; + sum += u_vec0[14 << 5]; + sum += u_vec0[15 << 5]; + u_vec0[0] += sum; + } + + for (i = 0; i < 32; i++) { + gfloat sample = u_vec[i]; + if (sample > 0) { + sample = sample * SCALE + 0.5f; + if (sample < (SCALE - 1)) { + samples[i] = (short) (sample); + } else { + samples[i] = (short) (SCALE - 1); + } + } else { + sample = sample * SCALE - 0.5f; + if (sample > -SCALE) { + samples[i] = (short) (sample); + } else { + samples[i] = (short) (-SCALE); + } + } + } +} +#endif + +#if defined(USE_ARM_NEON) +STATIC_INLINE void +build_uvec (gfloat *u_vec, gfloat *cur_synbuf, gint k) +{ + __asm__ volatile ( + "mov ip, #8 \n\t" /* i = 8 */ + "mov r5, #512 \n\t" + "sub r5, r5, #1 \n\t" /* r5 = 511 */ + "veor d0, d0 \n\t" + "0: \n\t" + " add r4, %[k], #16 \n\t" + " add r4, %[cur_synbuf], r4, lsl #2 \n\t" + " pld [r4] \n\t" + " mov r3, %[u_vec] \n\t" + " vstr s0, [r3, #16*4] \n\t" + " vld1.64 {q1-q2}, [r4,:128]! \n\t" + " vld1.64 {q3-q4}, [r4,:128]! \n\t" + " vst1.64 {q1-q2}, [r3,:128]! \n\t" + " vst1.64 {q3-q4}, [r3,:128]! \n\t" + " add r3, r3, #4 \n\t" + " vneg.f32 q1, q1 \n\t" + " vneg.f32 q2, q2 \n\t" + " vneg.f32 q3, q3 \n\t" + " vneg.f32 q4, q4 \n\t" + " vrev64.f32 q1, q1 \n\t" + " vrev64.f32 q2, q2 \n\t" + " vrev64.f32 q3, q3 \n\t" + " vrev64.f32 q4, q4 \n\t" + " vswp d2, d3 \n\t" + " vswp d6, d7 \n\t" + " vswp d8, d9 \n\t" + " vswp d4, d5 \n\t" + " vst1.64 {q4}, [r3]! \n\t" + " vst1.64 {q3}, [r3]! \n\t" + " vst1.64 {q2}, [r3]! \n\t" + " vst1.64 {q1}, [r3]! \n\t" + " add %[k], %[k], #32 \n\t" /* k += 32 */ + " and %[k], %[k], r5 \n\t" /* k &= 511 */ + " add r4, %[cur_synbuf], %[k], lsl #2\n\t" + " pld [r4] \n\t" + " add r3, %[u_vec], #48*4 \n\t" + " vld1.64 {q1-q2}, [r4,:128]! \n\t" + " vld1.64 {q3-q4}, [r4,:128]! \n\t" + " vldr.32 s2, [r4] \n\t" + " vneg.f32 q1, q1 \n\t" + " vneg.f32 q2, q2 \n\t" + " vneg.f32 q3, q3 \n\t" + " vneg.f32 q4, q4 \n\t" + " vst1.64 {q1-q2}, [r3,:128]! \n\t" + " vst1.64 {q3-q4}, [r3,:128]! \n\t" + " vneg.f32 s2, s2 \n\t" + " add r3, %[u_vec], #32*4 \n\t" + " vrev64.f32 q1, q1 \n\t" + " vrev64.f32 q3, q3 \n\t" + " vrev64.f32 q4, q4 \n\t" + " vrev64.f32 q2, q2 \n\t" + " vswp d2, d3 \n\t" + " vswp d6, d7 \n\t" + " vswp d8, d9 \n\t" + " vswp d4, d5 \n\t" + " vstmia r3!, {s2} \n\t" + " vstmia r3!, {q4} \n\t" + " vstmia r3!, {q3} \n\t" + " vstmia r3!, {q2} \n\t" + " vstmia r3!, {q1} \n\t" + " subs ip, ip, #1 \n\t" /* i-- */ + " add %[u_vec], %[u_vec], #64*4 \n\t" + " add %[k], %[k], #32 \n\t" /* k += 32 */ + " and %[k], %[k], r5 \n\t" /* k &= 511 */ + " bne 0b \n\t" + : [u_vec] "+&r" (u_vec), [k] "+&r" (k) + : [cur_synbuf] "r" (cur_synbuf) + : "memory", "cc", "r3", "r4", "r5", "ip", + "q0", "q1", "q2", "q3", "q4" + ); +} +#else +STATIC_INLINE void +build_uvec (gfloat *u_vec, gfloat *cur_synbuf, gint k) +{ + gint i, j; + + for (j = 0; j < 8; j++) { + for (i = 0; i < 16; i++) { + /* Copy first 32 elements */ + u_vec [i] = cur_synbuf [k + i + 16]; + u_vec [i + 17] = -cur_synbuf [k + 31 - i]; + } + + /* k wraps at the synthesis buffer boundary */ + k = (k + 32) & 511; + + for (i = 0; i < 16; i++) { + /* Copy next 32 elements */ + u_vec [i + 32] = -cur_synbuf [k + 16 - i]; + u_vec [i + 48] = -cur_synbuf [k + i]; + } + u_vec [16] = 0; + + /* k wraps at the synthesis buffer boundary */ + k = (k + 32) & 511; + u_vec += 64; + } +} +#endif + +/* Synthesis matrixing variant which uses a 32 point DCT */ +static void +mp3_SubBandSynthesis (mp3tl * tl ATTR_UNUSED, frame_params * fr_ps, + float *polyPhaseIn, gint channel, short *samples) +{ + gint k; + gfloat *cur_synbuf = fr_ps->synbuf[channel]; + __CACHE_LINE_DECL_ALIGN(gfloat u_vec[HAN_SIZE]); + + /* Shift down 32 samples in the fifo, which should always leave room */ + k = fr_ps->bufOffset[channel]; + k = (k - 32) & 511; + fr_ps->bufOffset[channel] = k; + + /* DCT part */ + MPG_DCT_32 (polyPhaseIn, cur_synbuf + k); + + /* Build the U vector */ + build_uvec (u_vec, cur_synbuf, k); + + /* Dewindow and output samples */ + mp3_dewindow_output (u_vec, samples, (gfloat*) dewindow); +} + +/************************* Layer III routines **********************/ + +static gboolean +III_get_side_info (guint8 * data, III_side_info_t * si, frame_params * fr_ps) +{ + int ch, gr, i; + int stereo = fr_ps->stereo; + huffdec_bitbuf bb; + + h_setbuf (&bb, data, fr_ps->header.side_info_slots); + + if (fr_ps->header.version == MPEG_VERSION_1) { + si->main_data_begin = h_getbits (&bb, 9); + if (stereo == 1) + si->private_bits = h_getbits (&bb, 5); + else + si->private_bits = h_getbits (&bb, 3); + + for (ch = 0; ch < stereo; ch++) { + guint8 scfsi = (guint8) h_getbits (&bb, 4); + si->scfsi[0][ch] = scfsi & 0x08; + si->scfsi[1][ch] = scfsi & 0x04; + si->scfsi[2][ch] = scfsi & 0x02; + si->scfsi[3][ch] = scfsi & 0x01; + } + + for (gr = 0; gr < 2; gr++) { + for (ch = 0; ch < stereo; ch++) { + gr_info_t *gi = &(si->gr[gr][ch]); + + gi->part2_3_length = h_getbits (&bb, 12); + gi->big_values = h_getbits (&bb, 9); + /* Add 116 to avoid doing it in the III_dequantize loop */ + gi->global_gain = h_getbits (&bb, 8) + 116; + gi->scalefac_compress = h_getbits (&bb, 4); + gi->window_switching_flag = h_get1bit (&bb); + if (gi->window_switching_flag) { + gi->block_type = h_getbits (&bb, 2); + gi->mixed_block_flag = h_get1bit (&bb); + gi->table_select[0] = h_getbits (&bb, 5); + gi->table_select[1] = h_getbits (&bb, 5); + for (i = 0; i < 3; i++) + gi->subblock_gain[i] = h_getbits (&bb, 3); + + if (gi->block_type == 0) { + GST_WARNING ("Side info bad: block_type == 0 in split block."); + return FALSE; + } else if (gi->block_type == 2 && gi->mixed_block_flag == 0) { + gi->region0_count = 8; /* MI 9; */ + gi->region1_count = 12; + } else { + gi->region0_count = 7; /* MI 8; */ + gi->region1_count = 13; + } + } else { + for (i = 0; i < 3; i++) + gi->table_select[i] = h_getbits (&bb, 5); + gi->region0_count = h_getbits (&bb, 4); + gi->region1_count = h_getbits (&bb, 3); + gi->block_type = 0; + } + gi->preflag = h_get1bit (&bb); + /* Add 1 & multiply by 2 to avoid doing it in the III_dequantize loop */ + gi->scalefac_scale = 2 * (h_get1bit (&bb) + 1); + gi->count1table_select = h_get1bit (&bb); + } + } + } else { /* Layer 3 LSF */ + + si->main_data_begin = h_getbits (&bb, 8); + if (stereo == 1) + si->private_bits = h_getbits (&bb, 1); + else + si->private_bits = h_getbits (&bb, 2); + + for (gr = 0; gr < 1; gr++) { + for (ch = 0; ch < stereo; ch++) { + gr_info_t *gi = &(si->gr[gr][ch]); + + gi->part2_3_length = h_getbits (&bb, 12); + gi->big_values = h_getbits (&bb, 9); + /* Add 116 to avoid doing it in the III_dequantize loop */ + gi->global_gain = h_getbits (&bb, 8) + 116; + gi->scalefac_compress = h_getbits (&bb, 9); + gi->window_switching_flag = h_get1bit (&bb); + if (gi->window_switching_flag) { + gi->block_type = h_getbits (&bb, 2); + gi->mixed_block_flag = h_get1bit (&bb); + gi->table_select[0] = h_getbits (&bb, 5); + gi->table_select[1] = h_getbits (&bb, 5); + for (i = 0; i < 3; i++) + gi->subblock_gain[i] = h_getbits (&bb, 3); + + /* Set region_count parameters since they are + * implicit in this case. */ + if (gi->block_type == 0) { + GST_WARNING ("Side info bad: block_type == 0 in split block.\n"); + return FALSE; + } else if (gi->block_type == 2 && gi->mixed_block_flag == 0) { + gi->region0_count = 8; /* MI 9; */ + gi->region1_count = 12; + } else { + gi->region0_count = 7; /* MI 8; */ + gi->region1_count = 13; + } + } else { + for (i = 0; i < 3; i++) + gi->table_select[i] = h_getbits (&bb, 5); + gi->region0_count = h_getbits (&bb, 4); + gi->region1_count = h_getbits (&bb, 3); + gi->block_type = 0; + } + + gi->preflag = 0; + /* Add 1 & multiply by 2 to avoid doing it in the III_dequantize loop */ + gi->scalefac_scale = 2 * (h_get1bit (&bb) + 1); + gi->count1table_select = h_get1bit (&bb); + } + } + } + + return TRUE; +} + +static const gint slen_table[2][16] = { + {0, 0, 0, 0, 3, 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4}, + {0, 1, 2, 3, 0, 1, 2, 3, 1, 2, 3, 1, 2, 3, 2, 3} +}; + +struct +{ + gint l[23]; + gint s[15]; +} static const sfBandIndex[] = { + /* MPEG-1 */ + { + /* 44.1 khz */ + { 0, 4, 8, 12, 16, 20, 24, 30, 36, 44, 52, 62, 74, 90, 110, 134, 162, 196, + 238, 288, 342, 418, 576}, + { 0, 4, 8, 12, 16, 22, 30, 40, 52, 66, 84, 106, 136, 192, 192 } + }, { + /* 48khz */ + { 0, 4, 8, 12, 16, 20, 24, 30, 36, 42, 50, 60, 72, 88, 106, 128, 156, 190, + 230, 276, 330, 384, 576}, + { 0, 4, 8, 12, 16, 22, 28, 38, 50, 64, 80, 100, 126, 192, 192 } + }, { + /* 32khz */ + { 0, 4, 8, 12, 16, 20, 24, 30, 36, 44, 54, 66, 82, 102, 126, 156, 194, 240, + 296, 364, 448, 550, 576 }, + { 0, 4, 8, 12, 16, 22, 30, 42, 58, 78, 104, 138, 180, 192, 192 } + }, + /* MPEG-2 */ + { + /* 22.05 khz */ + { 0, 6, 12, 18, 24, 30, 36, 44, 54, 66, 80, 96, 116, 140, 168, 200, 238, + 284, 336, 396, 464, 522, 576 }, + { 0, 4, 8, 12, 18, 24, 32, 42, 56, 74, 100, 132, 174, 192, 192 } + }, { + /* 24khz */ + { 0, 6, 12, 18, 24, 30, 36, 44, 54, 66, 80, 96, 114, 136, 162, 194, 232, + 278, 330, 394, 464, 540, 576 }, + { 0, 4, 8, 12, 18, 26, 36, 48, 62, 80, 104, 136, 180, 192, 192 } + }, { + /* 16 khz */ + { 0, 6, 12, 18, 24, 30, 36, 44, 54, 66, 80, 96, 116, 140, 168, 200, 238, + 284, 336, 396, 464, 522, 576 }, + { 0, 4, 8, 12, 18, 26, 36, 48, 62, 80, 104, 134, 174, 192, 192 } + }, + /* MPEG-2.5 */ + { + /* 11025 */ + { 0, 6, 12, 18, 24, 30, 36, 44, 54, 66, 80, 96, 116, 140, 168, 200, 238, + 284, 336, 396, 464, 522, 576 }, + { 0, 4, 8, 12, 18, 26, 36, 48, 62, 80, 104, 134, 174, 192, 192 } + }, { + /* 12khz */ + { 0, 6, 12, 18, 24, 30, 36, 44, 54, 66, 80, 96, 116, 140, 168, 200, 238, + 284, 336, 396, 464, 522, 576 }, + { 0, 4, 8, 12, 18, 26, 36, 48, 62, 80, 104, 134, 174, 192, 192 } + }, { + /* 8khz */ + { 0, 12, 24, 36, 48, 60, 72, 88, 108, 132, 160, 192, 232, 280, 336, 400, + 476, 566, 568, 570, 572, 574, 576 }, + { 0, 8, 16, 24, 36, 52, 72, 96, 124, 160, 162, 164, 166, 192, 192 } + } +}; + +/* Offset into the sfBand table for each MPEG version */ +static const guint sfb_offset[] = { 6, 0 /* invalid */ , 3, 0 }; + +static void +III_get_scale_factors (III_scalefac_t * scalefac, III_side_info_t * si, + int gr, int ch, mp3tl * tl) +{ + int sfb, window; + gr_info_t *gr_info = &(si->gr[gr][ch]); + huffdec_bitbuf *bb = &tl->c_impl.bb; + gint slen0, slen1; + + slen0 = slen_table[0][gr_info->scalefac_compress]; + slen1 = slen_table[1][gr_info->scalefac_compress]; + if (gr_info->window_switching_flag && (gr_info->block_type == 2)) { + if (gr_info->mixed_block_flag) { /* MIXED *//* NEW - ag 11/25 */ + for (sfb = 0; sfb < 8; sfb++) + (*scalefac)[ch].l[sfb] = h_getbits (bb, slen0); + + for (sfb = 3; sfb < 6; sfb++) + for (window = 0; window < 3; window++) + (*scalefac)[ch].s[window][sfb] = h_getbits (bb, slen0); + + for ( /* sfb = 6 */ ; sfb < 12; sfb++) + for (window = 0; window < 3; window++) + (*scalefac)[ch].s[window][sfb] = h_getbits (bb, slen1); + + for (sfb = 12, window = 0; window < 3; window++) + (*scalefac)[ch].s[window][sfb] = 0; + } else { + /* SHORT block */ + for (sfb = 0; sfb < 6; sfb++) + for (window = 0; window < 3; window++) + (*scalefac)[ch].s[window][sfb] = h_getbits (bb, slen0); + for ( /* sfb = 6 */ ; sfb < 12; sfb++) + for (window = 0; window < 3; window++) + (*scalefac)[ch].s[window][sfb] = h_getbits (bb, slen1); + + for (window = 0; window < 3; window++) + (*scalefac)[ch].s[window][12] = 0; + } + } else { + gint i; + const gint l_sfbtable[5] = { 0, 6, 11, 16, 21 }; + /* LONG types 0,1,3 */ + if (gr == 0) { + for (sfb = 0; sfb < 11; sfb++) { + (*scalefac)[ch].l[sfb] = h_getbits (bb, slen0); + } + for (sfb = 11; sfb < 21; sfb++) { + (*scalefac)[ch].l[sfb] = h_getbits (bb, slen1); + } + } else { + for (i = 0; i < 2; i++) { + if (si->scfsi[i][ch] == 0) { + for (sfb = l_sfbtable[i]; sfb < l_sfbtable[i + 1]; sfb++) { + (*scalefac)[ch].l[sfb] = h_getbits (bb, slen0); + } + } + } + for ( /* i = 2 */ ; i < 4; i++) { + if (si->scfsi[i][ch] == 0) { + for (sfb = l_sfbtable[i]; sfb < l_sfbtable[i + 1]; sfb++) { + (*scalefac)[ch].l[sfb] = h_getbits (bb, slen1); + } + } + } + } + (*scalefac)[ch].l[21] = 0; + } +} + +/*** new MPEG2 stuff ***/ + +static const guint nr_of_sfb_block[6][3][4] = { + {{6, 5, 5, 5}, {9, 9, 9, 9}, {6, 9, 9, 9}}, + {{6, 5, 7, 3}, {9, 9, 12, 6}, {6, 9, 12, 6}}, + {{11, 10, 0, 0}, {18, 18, 0, 0}, {15, 18, 0, 0}}, + {{7, 7, 7, 0}, {12, 12, 12, 0}, {6, 15, 12, 0}}, + {{6, 6, 6, 3}, {12, 9, 9, 6}, {6, 12, 9, 6}}, + {{8, 8, 5, 0}, {15, 12, 9, 0}, {6, 18, 9, 0}} +}; + +static void +III_get_LSF_scale_data (guint * scalefac_buffer, III_side_info_t * si, + gint gr, gint ch, mp3tl * tl) +{ + short i, j, k; + short blocktypenumber; + short blocknumber = -1; + + gr_info_t *gr_info = &(si->gr[gr][ch]); + guint scalefac_comp, int_scalefac_comp, new_slen[4]; + + huffdec_bitbuf *bb = &tl->c_impl.bb; + fr_header *hdr = &tl->fr_ps.header; + + scalefac_comp = gr_info->scalefac_compress; + + blocktypenumber = 0; + if ((gr_info->block_type == 2) && (gr_info->mixed_block_flag == 0)) + blocktypenumber = 1; + + if ((gr_info->block_type == 2) && (gr_info->mixed_block_flag == 1)) + blocktypenumber = 2; + + if (!(((hdr->mode_ext == 1) || (hdr->mode_ext == 3)) && (ch == 1))) { + if (scalefac_comp < 400) { + new_slen[0] = (scalefac_comp >> 4) / 5; + new_slen[1] = (scalefac_comp >> 4) % 5; + new_slen[2] = (scalefac_comp % 16) >> 2; + new_slen[3] = (scalefac_comp % 4); + gr_info->preflag = 0; + blocknumber = 0; + } else if (scalefac_comp < 500) { + new_slen[0] = ((scalefac_comp - 400) >> 2) / 5; + new_slen[1] = ((scalefac_comp - 400) >> 2) % 5; + new_slen[2] = (scalefac_comp - 400) % 4; + new_slen[3] = 0; + gr_info->preflag = 0; + blocknumber = 1; + } else if (scalefac_comp < 512) { + new_slen[0] = (scalefac_comp - 500) / 3; + new_slen[1] = (scalefac_comp - 500) % 3; + new_slen[2] = 0; + new_slen[3] = 0; + gr_info->preflag = 1; + blocknumber = 2; + } + } + + if ((((hdr->mode_ext == 1) || (hdr->mode_ext == 3)) && (ch == 1))) { + /* intensity_scale = scalefac_comp %2; */ + int_scalefac_comp = scalefac_comp >> 1; + + if (int_scalefac_comp < 180) { + new_slen[0] = int_scalefac_comp / 36; + new_slen[1] = (int_scalefac_comp % 36) / 6; + new_slen[2] = (int_scalefac_comp % 36) % 6; + new_slen[3] = 0; + gr_info->preflag = 0; + blocknumber = 3; + } else if (int_scalefac_comp < 244) { + new_slen[0] = ((int_scalefac_comp - 180) % 64) >> 4; + new_slen[1] = ((int_scalefac_comp - 180) % 16) >> 2; + new_slen[2] = (int_scalefac_comp - 180) % 4; + new_slen[3] = 0; + gr_info->preflag = 0; + blocknumber = 4; + } else if (int_scalefac_comp < 255) { + new_slen[0] = (int_scalefac_comp - 244) / 3; + new_slen[1] = (int_scalefac_comp - 244) % 3; + new_slen[2] = 0; + new_slen[3] = 0; + gr_info->preflag = 0; + blocknumber = 5; + } + } + + if (blocknumber < 0) { + GST_WARNING ("Invalid block number"); + return; + } + + k = 0; + for (i = 0; i < 4; i++) { + guint slen = new_slen[i]; + if (slen == 0) { + for (j = nr_of_sfb_block[blocknumber][blocktypenumber][i]; j > 0; j--) { + scalefac_buffer[k] = 0; + k++; + } + } else { + for (j = nr_of_sfb_block[blocknumber][blocktypenumber][i]; j > 0; j--) { + scalefac_buffer[k] = h_getbits (bb, slen); + k++; + } + } + } + for (; k < 45; k++) + scalefac_buffer[k] = 0; +} + +static void +III_get_LSF_scale_factors (III_scalefac_t * scalefac, III_side_info_t * si, + int gr, int ch, mp3tl * tl) +{ + int sfb, k = 0, window; + gr_info_t *gr_info = &(si->gr[gr][ch]); + guint *scalefac_buffer; + + scalefac_buffer = tl->c_impl.scalefac_buffer; + III_get_LSF_scale_data (scalefac_buffer, si, gr, ch, tl); + + if (gr_info->window_switching_flag && (gr_info->block_type == 2)) { + if (gr_info->mixed_block_flag) { /* MIXED *//* NEW - ag 11/25 */ + for (sfb = 0; sfb < 8; sfb++) { + (*scalefac)[ch].l[sfb] = scalefac_buffer[k]; + k++; + } + for (sfb = 3; sfb < 12; sfb++) + for (window = 0; window < 3; window++) { + (*scalefac)[ch].s[window][sfb] = scalefac_buffer[k]; + k++; + } + for (sfb = 12, window = 0; window < 3; window++) + (*scalefac)[ch].s[window][sfb] = 0; + } else { /* SHORT */ + for (sfb = 0; sfb < 12; sfb++) + for (window = 0; window < 3; window++) { + (*scalefac)[ch].s[window][sfb] = scalefac_buffer[k]; + k++; + } + for (sfb = 12, window = 0; window < 3; window++) + (*scalefac)[ch].s[window][sfb] = 0; + } + } else { /* LONG types 0,1,3 */ + for (sfb = 0; sfb < 21; sfb++) { + (*scalefac)[ch].l[sfb] = scalefac_buffer[k]; + k++; + } + (*scalefac)[ch].l[21] = 0; + } +} + +#define HUFFBITS guint32 +#define HTSIZE 34 +#define MXOFF 250 + +/* do the huffman-decoding */ +/* note! for counta,countb -the 4 bit value is returned in y, discard x */ +static inline gboolean +huffman_decoder (huffdec_bitbuf * bb, gint tnum, int *x, int *y, int *v, int *w) +{ + HUFFBITS level; + guint point = 0; + gboolean error = TRUE; + const struct huffcodetab *h; + + g_return_val_if_fail (tnum >= 0 && tnum <= HTSIZE, FALSE); + + /* Grab a ptr to the huffman table to use */ + h = huff_tables + tnum; + + level = (guint32) (1) << (sizeof (HUFFBITS) * 8 - 1); + + /* table 0 needs no bits */ + if (h->treelen == 0) { + *x = *y = *v = *w = 0; + return TRUE; + } + + /* Lookup in Huffman table. */ + do { + if (h->val[point][0] == 0) { /*end of tree */ + *x = h->val[point][1] >> 4; + *y = h->val[point][1] & 0xf; + + error = FALSE; + break; + } + if (h_get1bit (bb)) { + while (h->val[point][1] >= MXOFF) + point += h->val[point][1]; + point += h->val[point][1]; + } else { + while (h->val[point][0] >= MXOFF) + point += h->val[point][0]; + point += h->val[point][0]; + } + level >>= 1; + } while (level || (point < h->treelen)); + + /* Check for error. */ + if (error) { + /* set x and y to a medium value as a simple concealment */ + GST_WARNING ("Illegal Huffman code in data."); + *x = (h->xlen - 1) << 1; + *y = (h->ylen - 1) << 1; + } + + /* Process sign encodings for quadruples tables. */ + if (h->quad_table) { + *v = (*y >> 3) & 1; + *w = (*y >> 2) & 1; + *x = (*y >> 1) & 1; + *y = *y & 1; + + if (*v && (h_get1bit (bb) == 1)) + *v = -*v; + if (*w && (h_get1bit (bb) == 1)) + *w = -*w; + if (*x && (h_get1bit (bb) == 1)) + *x = -*x; + if (*y && (h_get1bit (bb) == 1)) + *y = -*y; + } + /* Process sign and escape encodings for dual tables. */ + else { + /* x and y are reversed in the test bitstream. + Reverse x and y here to make test bitstream work. */ + + if (h->linbits && ((h->xlen - 1) == *x)) + *x += h_getbits (bb, h->linbits); + if (*x && (h_get1bit (bb) == 1)) + *x = -*x; + + if (h->linbits && ((h->ylen - 1) == *y)) + *y += h_getbits (bb, h->linbits); + if (*y && (h_get1bit (bb) == 1)) + *y = -*y; + } + + return !error; +} + +static gboolean +III_huffman_decode (gint is[SBLIMIT][SSLIMIT], III_side_info_t * si, + gint ch, gint gr, gint part2_start, mp3tl * tl) +{ + guint i; + int x, y; + int v = 0, w = 0; + gint h; /* Index of the huffman table to use */ + guint region1Start; + guint region2Start; + int sfreq; + guint grBits; + gr_info_t *gi = &(si->gr[gr][ch]); + huffdec_bitbuf *bb = &tl->c_impl.bb; + frame_params *fr_ps = &tl->fr_ps; + + /* Calculate index. */ + sfreq = sfb_offset[fr_ps->header.version] + fr_ps->header.srate_idx; + + /* Find region boundary for short block case. */ + if ((gi->window_switching_flag) && (gi->block_type == 2)) { + /* Region2. */ + if (fr_ps->header.version == MPEG_VERSION_2_5 + && fr_ps->header.srate_idx == 2) { + region1Start = 72; + } else { + region1Start = 36; /* sfb[9/3]*3=36 */ + } + region2Start = 576; /* No Region2 for short block case. */ + } else { /* Find region boundary for long block case. */ + region1Start = sfBandIndex[sfreq].l[gi->region0_count + 1]; /* MI */ + region2Start = sfBandIndex[sfreq].l[gi->region0_count + gi->region1_count + 2]; /* MI */ + } + + /* Read bigvalues area. */ + /* i < SSLIMIT * SBLIMIT => gi->big_values < SSLIMIT * SBLIMIT/2 */ + for (i = 0; i < gi->big_values * 2; i += 2) { + if (i < region1Start) + h = gi->table_select[0]; + else if (i < region2Start) + h = gi->table_select[1]; + else + h = gi->table_select[2]; + + if (!huffman_decoder (bb, h, &x, &y, &v, &w)) + return FALSE; + is[i / SSLIMIT][i % SSLIMIT] = x; + is[(i + 1) / SSLIMIT][(i + 1) % SSLIMIT] = y; + } + + /* Read count1 area. */ + h = gi->count1table_select + 32; + grBits = part2_start + gi->part2_3_length; + + while ((h_sstell (bb) < grBits) && (i + 3) < (SBLIMIT * SSLIMIT)) { + if (!huffman_decoder (bb, h, &x, &y, &v, &w)) + return FALSE; + + is[i / SSLIMIT][i % SSLIMIT] = v; + is[(i + 1) / SSLIMIT][(i + 1) % SSLIMIT] = w; + is[(i + 2) / SSLIMIT][(i + 2) % SSLIMIT] = x; + is[(i + 3) / SSLIMIT][(i + 3) % SSLIMIT] = y; + i += 4; + } + + if (h_sstell (bb) > grBits) { + /* Didn't end exactly at the grBits boundary. Rewind one entry. */ + if (i >= 4) + i -= 4; + h_rewindNbits (bb, h_sstell (bb) - grBits); + } + + /* Dismiss any stuffing Bits */ + if (h_sstell (bb) < grBits) + h_flushbits (bb, grBits - h_sstell (bb)); + + g_assert (i <= SSLIMIT * SBLIMIT); + + /* Zero out rest. */ + for (; i < SSLIMIT * SBLIMIT; i++) + is[i / SSLIMIT][i % SSLIMIT] = 0; + + return TRUE; +} + +static const gint pretab[22] = + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 3, 3, 3, 2, 0 }; + +static void +III_dequantize_sample (gint is[SBLIMIT][SSLIMIT], + gfloat xr[SBLIMIT][SSLIMIT], + III_scalefac_t * scalefac, + gr_info_t * gr_info, gint ch, gint gr, frame_params * fr_ps) +{ + int ss, sb, cb = 0, sfreq; + +// int stereo = fr_ps->stereo; + int next_cb_boundary; + int cb_begin = 0; + int cb_width = 0; + gint tmp; + gint16 pow_factor; + gboolean is_short_blk; + + /* Calculate index. */ + sfreq = sfb_offset[fr_ps->header.version] + fr_ps->header.srate_idx; + + /* choose correct scalefactor band per block type, initalize boundary */ + if (gr_info->window_switching_flag && (gr_info->block_type == 2)) { + if (gr_info->mixed_block_flag) { + next_cb_boundary = sfBandIndex[sfreq].l[1]; /* LONG blocks: 0,1,3 */ + } else { + next_cb_boundary = sfBandIndex[sfreq].s[1] * 3; /* pure SHORT block */ + cb_width = sfBandIndex[sfreq].s[1]; + cb_begin = 0; + } + } else { + next_cb_boundary = sfBandIndex[sfreq].l[1]; /* LONG blocks: 0,1,3 */ + } + + /* apply formula per block type */ + for (sb = 0; sb < SBLIMIT; sb++) { + gint sb_off = sb * 18; + is_short_blk = gr_info->window_switching_flag && + (((gr_info->block_type == 2) && (gr_info->mixed_block_flag == 0)) || + ((gr_info->block_type == 2) && gr_info->mixed_block_flag && (sb >= 2))); + + for (ss = 0; ss < SSLIMIT; ss++) { + if (sb_off + ss == next_cb_boundary) { /* Adjust critical band boundary */ + if (gr_info->window_switching_flag && (gr_info->block_type == 2)) { + if (gr_info->mixed_block_flag) { + if ((sb_off + ss) == sfBandIndex[sfreq].l[8]) { + next_cb_boundary = sfBandIndex[sfreq].s[4] * 3; + cb = 3; + cb_width = sfBandIndex[sfreq].s[cb + 1] - + sfBandIndex[sfreq].s[cb]; + cb_begin = sfBandIndex[sfreq].s[cb] * 3; + } else if ((sb_off + ss) < sfBandIndex[sfreq].l[8]) + next_cb_boundary = sfBandIndex[sfreq].l[(++cb) + 1]; + else { + next_cb_boundary = sfBandIndex[sfreq].s[(++cb) + 1] * 3; + cb_width = sfBandIndex[sfreq].s[cb + 1] - + sfBandIndex[sfreq].s[cb]; + cb_begin = sfBandIndex[sfreq].s[cb] * 3; + } + } else { + next_cb_boundary = sfBandIndex[sfreq].s[(++cb) + 1] * 3; + cb_width = sfBandIndex[sfreq].s[cb + 1] - sfBandIndex[sfreq].s[cb]; + cb_begin = sfBandIndex[sfreq].s[cb] * 3; + } + } else /* long blocks */ + next_cb_boundary = sfBandIndex[sfreq].l[(++cb) + 1]; + } + + /* Compute overall (global) scaling. */ + pow_factor = gr_info->global_gain; + + /* Do long/short dependent scaling operations. */ + if (is_short_blk) { + pow_factor -= + 8 * gr_info->subblock_gain[((sb_off + ss) - cb_begin) / cb_width]; + pow_factor -= gr_info->scalefac_scale * + (*scalefac)[ch].s[(sb_off + ss - cb_begin) / cb_width][cb]; + } else { + /* LONG block types 0,1,3 & 1st 2 subbands of switched blocks */ + pow_factor -= gr_info->scalefac_scale * + ((*scalefac)[ch].l[cb] + gr_info->preflag * pretab[cb]); + } + +#if 1 + /* g_assert (pow_factor >= 0 && pow_factor < + (sizeof (pow_2_table) / sizeof (pow_2_table[0]))); */ + xr[sb][ss] = pow_2_table[pow_factor]; +#else + /* Old method using powf */ + pow_factor -= 326; + if (pow_factor >= (-140)) + xr[sb][ss] = powf (2.0, 0.25 * (pow_factor)); + else + xr[sb][ss] = 0; +#endif + + /* Scale quantized value. */ + tmp = is[sb][ss]; + if (tmp >= 0) { + xr[sb][ss] *= pow_43_table[tmp]; + } else { + xr[sb][ss] *= -1.0f * pow_43_table[-tmp]; + } + } + } +} + +static void +III_reorder (gfloat xr[SBLIMIT][SSLIMIT], gfloat ro[SBLIMIT][SSLIMIT], + gr_info_t * gr_info, frame_params * fr_ps) +{ + int sfreq; + int sfb, sfb_start, sfb_lines; + int sb, ss, window, freq, src_line, des_line; + + /* Calculate index. */ + sfreq = sfb_offset[fr_ps->header.version] + fr_ps->header.srate_idx; + + if (gr_info->window_switching_flag && (gr_info->block_type == 2)) { + for (sb = 0; sb < SBLIMIT; sb++) + for (ss = 0; ss < SSLIMIT; ss++) + ro[sb][ss] = 0; + + if (gr_info->mixed_block_flag) { + /* NO REORDER FOR LOW 2 SUBBANDS */ + for (sb = 0; sb < 2; sb++) + for (ss = 0; ss < SSLIMIT; ss++) { + ro[sb][ss] = xr[sb][ss]; + } + /* REORDERING FOR REST SWITCHED SHORT */ + for (sfb = 3, sfb_start = sfBandIndex[sfreq].s[3], + sfb_lines = sfBandIndex[sfreq].s[4] - sfb_start; + sfb < 13; sfb++, sfb_start = sfBandIndex[sfreq].s[sfb], + (sfb_lines = sfBandIndex[sfreq].s[sfb + 1] - sfb_start)) + for (window = 0; window < 3; window++) + for (freq = 0; freq < sfb_lines; freq++) { + src_line = sfb_start * 3 + window * sfb_lines + freq; + des_line = (sfb_start * 3) + window + (freq * 3); + ro[des_line / SSLIMIT][des_line % SSLIMIT] = + xr[src_line / SSLIMIT][src_line % SSLIMIT]; + } + } else { /* pure short */ + for (sfb = 0, sfb_start = 0, sfb_lines = sfBandIndex[sfreq].s[1]; + sfb < 13; sfb++, sfb_start = sfBandIndex[sfreq].s[sfb], + (sfb_lines = sfBandIndex[sfreq].s[sfb + 1] - sfb_start)) + for (window = 0; window < 3; window++) + for (freq = 0; freq < sfb_lines; freq++) { + src_line = sfb_start * 3 + window * sfb_lines + freq; + des_line = (sfb_start * 3) + window + (freq * 3); + ro[des_line / SSLIMIT][des_line % SSLIMIT] = + xr[src_line / SSLIMIT][src_line % SSLIMIT]; + } + } + } else { /*long blocks */ + for (sb = 0; sb < SBLIMIT; sb++) + for (ss = 0; ss < SSLIMIT; ss++) + ro[sb][ss] = xr[sb][ss]; + } +} + +static void +III_i_stereo_k_values (gint is_pos, gfloat io, gint i, gfloat k[2][576]) +{ + if (is_pos == 0) { + k[0][i] = 1; + k[1][i] = 1; + } else if ((is_pos % 2) == 1) { + k[0][i] = powf (io, ((is_pos + 1) / 2.0f)); + k[1][i] = 1; + } else { + k[0][i] = 1; + k[1][i] = powf (io, (is_pos / 2.0f)); + } +} + +static void +III_stereo (gfloat xr[2][SBLIMIT][SSLIMIT], gfloat lr[2][SBLIMIT][SSLIMIT], + III_scalefac_t * scalefac, gr_info_t * gr_info, frame_params * fr_ps) +{ + int sfreq; + int stereo = fr_ps->stereo; + int ms_stereo = (fr_ps->header.mode == MPG_MD_JOINT_STEREO) && + (fr_ps->header.mode_ext & 0x2); + int i_stereo = (fr_ps->header.mode == MPG_MD_JOINT_STEREO) && + (fr_ps->header.mode_ext & 0x1); + int sfb; + int i, j, sb, ss; + short is_pos[SBLIMIT * SSLIMIT]; + gfloat is_ratio[SBLIMIT * SSLIMIT]; + gfloat io; + gfloat k[2][SBLIMIT * SSLIMIT]; + + int lsf = (fr_ps->header.version != MPEG_VERSION_1); + + if ((gr_info->scalefac_compress % 2) == 1) { + io = 0.707106781188f; + } else { + io = 0.840896415256f; + } + + /* Calculate index. */ + sfreq = sfb_offset[fr_ps->header.version] + fr_ps->header.srate_idx; + + /* intialization */ + for (i = 0; i < SBLIMIT * SSLIMIT; i++) + is_pos[i] = 7; + + if ((stereo == 2) && i_stereo) { + if (gr_info->window_switching_flag && (gr_info->block_type == 2)) { + if (gr_info->mixed_block_flag) { + int max_sfb = 0; + + for (j = 0; j < 3; j++) { + int sfbcnt; + + sfbcnt = 2; + for (sfb = 12; sfb >= 3; sfb--) { + int lines; + + lines = sfBandIndex[sfreq].s[sfb + 1] - sfBandIndex[sfreq].s[sfb]; + i = 3 * sfBandIndex[sfreq].s[sfb] + (j + 1) * lines - 1; + while (lines > 0) { + if (xr[1][i / SSLIMIT][i % SSLIMIT] != 0.0f) { + sfbcnt = sfb; + sfb = -10; + lines = -10; + } + lines--; + i--; + } + } + sfb = sfbcnt + 1; + + if (sfb > max_sfb) + max_sfb = sfb; + + while (sfb < 12) { + sb = sfBandIndex[sfreq].s[sfb + 1] - sfBandIndex[sfreq].s[sfb]; + i = 3 * sfBandIndex[sfreq].s[sfb] + j * sb; + for (; sb > 0; sb--) { + is_pos[i] = (*scalefac)[1].s[j][sfb]; + if (is_pos[i] != 7) { + if (lsf) { + III_i_stereo_k_values (is_pos[i], io, i, k); + } else { + is_ratio[i] = tanf (is_pos[i] * (float)(PI / 12)); + } + } + i++; + } + sfb++; + } + + sb = sfBandIndex[sfreq].s[12] - sfBandIndex[sfreq].s[11]; + sfb = 3 * sfBandIndex[sfreq].s[11] + j * sb; + sb = sfBandIndex[sfreq].s[13] - sfBandIndex[sfreq].s[12]; + + i = 3 * sfBandIndex[sfreq].s[11] + j * sb; + for (; sb > 0; sb--) { + is_pos[i] = is_pos[sfb]; + is_ratio[i] = is_ratio[sfb]; + k[0][i] = k[0][sfb]; + k[1][i] = k[1][sfb]; + i++; + } + } + if (max_sfb <= 3) { + i = 2; + ss = 17; + sb = -1; + while (i >= 0) { + if (xr[1][i][ss] != 0.0f) { + sb = i * 18 + ss; + i = -1; + } else { + ss--; + if (ss < 0) { + i--; + ss = 17; + } + } + } + i = 0; + while (sfBandIndex[sfreq].l[i] <= sb) + i++; + sfb = i; + i = sfBandIndex[sfreq].l[i]; + for (; sfb < 8; sfb++) { + sb = sfBandIndex[sfreq].l[sfb + 1] - sfBandIndex[sfreq].l[sfb]; + for (; sb > 0; sb--) { + is_pos[i] = (*scalefac)[1].l[sfb]; + if (is_pos[i] != 7) { + if (lsf) { + III_i_stereo_k_values (is_pos[i], io, i, k); + } else { + is_ratio[i] = tanf (is_pos[i] * (float)(PI / 12)); + } + } + i++; + } + } + } + } else { + for (j = 0; j < 3; j++) { + int sfbcnt; + + sfbcnt = -1; + for (sfb = 12; sfb >= 0; sfb--) { + int lines; + + lines = sfBandIndex[sfreq].s[sfb + 1] - sfBandIndex[sfreq].s[sfb]; + i = 3 * sfBandIndex[sfreq].s[sfb] + (j + 1) * lines - 1; + while (lines > 0) { + if (xr[1][i / SSLIMIT][i % SSLIMIT] != 0.0f) { + sfbcnt = sfb; + sfb = -10; + lines = -10; + } + lines--; + i--; + } + } + sfb = sfbcnt + 1; + while (sfb < 12) { + sb = sfBandIndex[sfreq].s[sfb + 1] - sfBandIndex[sfreq].s[sfb]; + i = 3 * sfBandIndex[sfreq].s[sfb] + j * sb; + for (; sb > 0; sb--) { + is_pos[i] = (*scalefac)[1].s[j][sfb]; + if (is_pos[i] != 7) { + if (lsf) { + III_i_stereo_k_values (is_pos[i], io, i, k); + } else { + is_ratio[i] = tanf (is_pos[i] * (float)(PI / 12)); + } + } + i++; + } + sfb++; + } + + sb = sfBandIndex[sfreq].s[12] - sfBandIndex[sfreq].s[11]; + sfb = 3 * sfBandIndex[sfreq].s[11] + j * sb; + sb = sfBandIndex[sfreq].s[13] - sfBandIndex[sfreq].s[12]; + + i = 3 * sfBandIndex[sfreq].s[11] + j * sb; + for (; sb > 0; sb--) { + is_pos[i] = is_pos[sfb]; + is_ratio[i] = is_ratio[sfb]; + k[0][i] = k[0][sfb]; + k[1][i] = k[1][sfb]; + i++; + } + } + } + } else { + i = 31; + ss = 17; + sb = 0; + while (i >= 0) { + if (xr[1][i][ss] != 0.0f) { + sb = i * 18 + ss; + i = -1; + } else { + ss--; + if (ss < 0) { + i--; + ss = 17; + } + } + } + i = 0; + while (sfBandIndex[sfreq].l[i] <= sb) + i++; + sfb = i; + i = sfBandIndex[sfreq].l[i]; + for (; sfb < 21; sfb++) { + sb = sfBandIndex[sfreq].l[sfb + 1] - sfBandIndex[sfreq].l[sfb]; + for (; sb > 0; sb--) { + is_pos[i] = (*scalefac)[1].l[sfb]; + if (is_pos[i] != 7) { + if (lsf) { + III_i_stereo_k_values (is_pos[i], io, i, k); + } else { + is_ratio[i] = tanf (is_pos[i] * (float)(PI / 12)); + } + } + i++; + } + } + sfb = sfBandIndex[sfreq].l[20]; + if (i > sfBandIndex[sfreq].l[21]) + sb = 576 - i; + else + sb = 576 - sfBandIndex[sfreq].l[21]; + + for (; sb > 0; sb--) { + is_pos[i] = is_pos[sfb]; + is_ratio[i] = is_ratio[sfb]; + k[0][i] = k[0][sfb]; + k[1][i] = k[1][sfb]; + i++; + } + } + } +#if 0 + for (ch = 0; ch < 2; ch++) + for (sb = 0; sb < SBLIMIT; sb++) + for (ss = 0; ss < SSLIMIT; ss++) + lr[ch][sb][ss] = 0; +#else + memset (lr, 0, sizeof (gfloat) * 2 * SBLIMIT * SSLIMIT); +#endif + + if (stereo == 2) + for (sb = 0; sb < SBLIMIT; sb++) + for (ss = 0; ss < SSLIMIT; ss++) { + i = (sb * 18) + ss; + if (is_pos[i] == 7) { + if (ms_stereo) { + lr[0][sb][ss] = (xr[0][sb][ss] + xr[1][sb][ss]) * 0.707106781188f; + lr[1][sb][ss] = (xr[0][sb][ss] - xr[1][sb][ss]) * 0.707106781188f; + } else { + lr[0][sb][ss] = xr[0][sb][ss]; + lr[1][sb][ss] = xr[1][sb][ss]; + } + } else if (i_stereo) { + if (lsf) { + lr[0][sb][ss] = xr[0][sb][ss] * k[0][i]; + lr[1][sb][ss] = xr[0][sb][ss] * k[1][i]; + } else { + lr[0][sb][ss] = xr[0][sb][ss] * (is_ratio[i] / (1 + is_ratio[i])); + lr[1][sb][ss] = xr[0][sb][ss] * (1 / (1 + is_ratio[i])); + } + } else { + GST_WARNING ("Error in stereo processing"); + } + } else /* mono , bypass xr[0][][] to lr[0][][] */ + for (sb = 0; sb < SBLIMIT; sb++) + for (ss = 0; ss < SSLIMIT; ss++) + lr[0][sb][ss] = xr[0][sb][ss]; + +} + +static const gfloat cs_table[8] = { + 0.85749292571f, 0.88174199732f, 0.94962864910f, 0.98331459249f, + 0.99551781607f, 0.99916055818f, 0.99989919524f, 0.99999315507f +}; + +static const gfloat ca_table[8] = { + -0.51449575543f, -0.47173196857f, -0.31337745420f, -0.18191319961f, + -0.09457419253f, -0.04096558289f, -0.01419856857f, -0.00369997467f +}; + +static void +III_antialias (gfloat xr[SBLIMIT][SSLIMIT], + gfloat hybridIn[SBLIMIT][SSLIMIT], gr_info_t * gr_info) +{ + gfloat bu, bd; /* upper and lower butterfly inputs */ + int ss, sb, sblim; + + /* clear all inputs */ + for (sb = 0; sb < SBLIMIT; sb++) + for (ss = 0; ss < SSLIMIT; ss++) + hybridIn[sb][ss] = xr[sb][ss]; + + if (gr_info->window_switching_flag && (gr_info->block_type == 2) && + !gr_info->mixed_block_flag) + return; + + if (gr_info->window_switching_flag && gr_info->mixed_block_flag && + (gr_info->block_type == 2)) + sblim = 1; + else + sblim = SBLIMIT - 1; + + /* 31 alias-reduction operations between each pair of sub-bands */ + /* with 8 butterflies between each pair */ + + for (sb = 0; sb < sblim; sb++) + for (ss = 0; ss < 8; ss++) { + bu = xr[sb][17 - ss]; + bd = xr[sb + 1][ss]; + hybridIn[sb][17 - ss] = (bu * cs_table[ss]) - (bd * ca_table[ss]); + hybridIn[sb + 1][ss] = (bd * cs_table[ss]) + (bu * ca_table[ss]); + } +} + +static inline void imdct_9pt (gfloat invec[9], gfloat outvec[9]); + +#define ICOS24(i) (cos24_table[(i)]) +#define COS18(i) (cos18_table[(i)]) +#define ICOS36_A(i) (icos72_table[4*(i)+1]) +#define ICOS72_A(i) (icos72_table[2*(i)]) + +/* Short (12 point) version of the IMDCT performed + as 2 x 3-point IMDCT */ +static inline void +inv_mdct_s (gfloat invec[6], gfloat outvec[12]) +{ + int i; + gfloat H[6], h[6], even_idct[3], odd_idct[3], *tmp; + gfloat t0, t1, t2; + /* sqrt (3) / 2.0 */ + const gfloat sqrt32 = 0.8660254037844385965883020617184229195117950439453125f; + + /* Preprocess the input to the two 3-point IDCT's */ + tmp = invec; + for (i = 1; i < 6; i++) { + H[i] = tmp[0] + tmp[1]; + tmp++; + } + + /* 3-point IMDCT */ + t0 = H[4] / 2.0f + invec[0]; + t1 = H[2] * sqrt32; + even_idct[0] = t0 + t1; + even_idct[1] = invec[0] - H[4]; + even_idct[2] = t0 - t1; + /* END 3-point IMDCT */ + + /* 3-point IMDCT */ + t2 = H[3] + H[5]; + + t0 = (t2) / 2.0f + H[1]; + t1 = (H[1] + H[3]) * sqrt32; + odd_idct[0] = t0 + t1; + odd_idct[1] = H[1] - t2; + odd_idct[2] = t0 - t1; + /* END 3-point IMDCT */ + + /* Post-Twiddle */ + odd_idct[0] *= ICOS24 (1); + odd_idct[1] *= ICOS24 (5); + odd_idct[2] *= ICOS24 (9); + + h[0] = (even_idct[0] + odd_idct[0]) * ICOS24 (0); + h[1] = (even_idct[1] + odd_idct[1]) * ICOS24 (2); + h[2] = (even_idct[2] + odd_idct[2]) * ICOS24 (4); + + h[3] = (even_idct[2] - odd_idct[2]) * ICOS24 (6); + h[4] = (even_idct[1] - odd_idct[1]) * ICOS24 (8); + h[5] = (even_idct[0] - odd_idct[0]) * ICOS24 (10); + + /* Rearrange the 6 values from the IDCT to the output vector */ + outvec[0] = h[3]; + outvec[1] = h[4]; + outvec[2] = h[5]; + outvec[3] = -h[5]; + outvec[4] = -h[4]; + outvec[5] = -h[3]; + outvec[6] = -h[2]; + outvec[7] = -h[1]; + outvec[8] = -h[0]; + outvec[9] = -h[0]; + outvec[10] = -h[1]; + outvec[11] = -h[2]; +} + +static inline void +inv_mdct_l (gfloat invec[18], gfloat outvec[36]) +{ + int i; + gfloat H[17], h[18], even[9], odd[9], even_idct[9], odd_idct[9], *tmp; + + for (i = 0; i < 17; i++) + H[i] = invec[i] + invec[i + 1]; + + even[0] = invec[0]; + odd[0] = H[0]; + tmp = H; + for (i = 1; i < 9; i++) { + even[i] = tmp[1]; + odd[i] = tmp[0] + tmp[2]; + tmp += 2; + } + + imdct_9pt (even, even_idct); + imdct_9pt (odd, odd_idct); + + for (i = 0; i < 9; i++) { + odd_idct[i] *= ICOS36_A (i); + h[i] = (even_idct[i] + odd_idct[i]) * ICOS72_A (i); + } + for ( /* i = 9 */ ; i < 18; i++) { + h[i] = (even_idct[17 - i] - odd_idct[17 - i]) * ICOS72_A (i); + } + + /* Rearrange the 18 values from the IDCT to the output vector */ + outvec[0] = h[9]; + outvec[1] = h[10]; + outvec[2] = h[11]; + outvec[3] = h[12]; + outvec[4] = h[13]; + outvec[5] = h[14]; + outvec[6] = h[15]; + outvec[7] = h[16]; + outvec[8] = h[17]; + + outvec[9] = -h[17]; + outvec[10] = -h[16]; + outvec[11] = -h[15]; + outvec[12] = -h[14]; + outvec[13] = -h[13]; + outvec[14] = -h[12]; + outvec[15] = -h[11]; + outvec[16] = -h[10]; + outvec[17] = -h[9]; + + outvec[35] = outvec[18] = -h[8]; + outvec[34] = outvec[19] = -h[7]; + outvec[33] = outvec[20] = -h[6]; + outvec[32] = outvec[21] = -h[5]; + outvec[31] = outvec[22] = -h[4]; + outvec[30] = outvec[23] = -h[3]; + outvec[29] = outvec[24] = -h[2]; + outvec[28] = outvec[25] = -h[1]; + outvec[27] = outvec[26] = -h[0]; +} + +static inline void +imdct_9pt (gfloat invec[9], gfloat outvec[9]) +{ + int i; + gfloat even_idct[5], odd_idct[4]; + gfloat t0, t1, t2; + + /* BEGIN 5 Point IMDCT */ + t0 = invec[6] / 2.0f + invec[0]; + t1 = invec[0] - invec[6]; + t2 = invec[2] - invec[4] - invec[8]; + + even_idct[0] = t0 + invec[2] * COS18 (2) + + invec[4] * COS18 (4) + invec[8] * COS18 (8); + + even_idct[1] = t2 / 2.0f + t1; + even_idct[2] = t0 - invec[2] * COS18 (8) + - invec[4] * COS18 (2) + invec[8] * COS18 (4); + + even_idct[3] = t0 - invec[2] * COS18 (4) + + invec[4] * COS18 (8) - invec[8] * COS18 (2); + + even_idct[4] = t1 - t2; + /* END 5 Point IMDCT */ + + /* BEGIN 4 Point IMDCT */ + { + gfloat odd1, odd2; + odd1 = invec[1] + invec[3]; + odd2 = invec[3] + invec[5]; + t0 = (invec[5] + invec[7]) * 0.5f + invec[1]; + + odd_idct[0] = t0 + odd1 * COS18 (2) + odd2 * COS18 (4); + odd_idct[1] = (invec[1] - invec[5]) * 1.5f - invec[7]; + odd_idct[2] = t0 - odd1 * COS18 (8) - odd2 * COS18 (2); + odd_idct[3] = t0 - odd1 * COS18 (4) + odd2 * COS18 (8); + } + /* END 4 Point IMDCT */ + + /* Adjust for non power of 2 IDCT */ + odd_idct[0] += invec[7] * COS18 (8); + odd_idct[1] -= invec[7] * COS18 (6); + odd_idct[2] += invec[7] * COS18 (4); + odd_idct[3] -= invec[7] * COS18 (2); + + /* Post-Twiddle */ + odd_idct[0] *= 0.5f / COS18 (1); + odd_idct[1] *= 0.5f / COS18 (3); + odd_idct[2] *= 0.5f / COS18 (5); + odd_idct[3] *= 0.5f / COS18 (7); + + for (i = 0; i < 4; i++) { + outvec[i] = even_idct[i] + odd_idct[i]; + } + outvec[4] = even_idct[4]; + /* Mirror into the other half of the vector */ + for (i = 5; i < 9; i++) { + outvec[i] = even_idct[8 - i] - odd_idct[8 - i]; + } +} + +static void +inv_mdct (gfloat in[18], gfloat out[36], gint block_type) +{ + int i, j; + + if (block_type == 2) { + gfloat tmp[12], tin[18], *tmpptr; + for (i = 0; i < 36; i++) { + out[i] = 0.0f; + } + + /* The short blocks input vector has to be re-arranged */ + tmpptr = tin; + for (i = 0; i < 3; i++) { + gfloat *v = &(in[i]); /* Input vector */ + for (j = 0; j < 6; j++) { + tmpptr[j] = *v; + v += 3; + } + tmpptr += 6; + } + + for (i = 0; i < 18; i += 6) { + tmpptr = &(out[i + 6]); + + inv_mdct_s (&(tin[i]), tmp); + + /* The three short blocks must be windowed and overlapped added + * with each other */ + for (j = 0; j < 12; j++) { + tmpptr[j] += tmp[j] * mdct_swin[2][j]; + } + } /* end for (i... */ + } else { /* block_type != 2 */ + inv_mdct_l (in, out); + + /* Window the imdct result */ + for (i = 0; i < 36; i++) + out[i] = out[i] * mdct_swin[block_type][i]; + } +} +static void +init_hybrid (mp3cimpl_info * c_impl) +{ + int i, j, k; + + for (i = 0; i < 2; i++) + for (j = 0; j < SBLIMIT; j++) + for (k = 0; k < SSLIMIT; k++) + c_impl->prevblck[i][j][k] = 0.0f; +} + +/* III_hybrid + * Parameters: + * double fsIn[SSLIMIT] - freq samples per subband in + * double tsOut[SSLIMIT] - time samples per subband out + * int sb, ch + * gr_info_t *gr_info + * frame_params *fr_ps + */ +static void +III_hybrid (gfloat fsIn[SSLIMIT], gfloat tsOut[SSLIMIT], int sb, int ch, + gr_info_t * gr_info, mp3tl * tl) +{ + gfloat rawout[36]; + int i; + i = (gr_info->window_switching_flag && gr_info->mixed_block_flag && + (sb < 2)) ? 0 : gr_info->block_type; + + inv_mdct (fsIn, rawout, i); + + /* overlap addition */ + for (i = 0; i < SSLIMIT; i++) { + tsOut[i] = rawout[i] + tl->c_impl.prevblck[ch][sb][i]; + tl->c_impl.prevblck[ch][sb][i] = rawout[i + 18]; + } +} + +/* Invert the odd frequencies for odd subbands in preparation for polyphase + * filtering */ +static void +III_frequency_inversion (gfloat hybridOut[SBLIMIT][SSLIMIT], + mp3tl * tl ATTR_UNUSED) +{ + guint ss, sb; + + for (ss = 1; ss < 18; ss += 2) { + for (sb = 1; sb < SBLIMIT; sb += 2) { + hybridOut[sb][ss] = -hybridOut[sb][ss]; + } + } +} + +static void +III_subband_synthesis (mp3tl * tl, frame_params * fr_ps, + gfloat hybridOut[SBLIMIT][SSLIMIT], gint channel, + short samples[SSLIMIT][SBLIMIT]) +{ + gint ss, sb; + gfloat polyPhaseIn[SBLIMIT]; /* PolyPhase Input. */ + + for (ss = 0; ss < 18; ss++) { + /* Each of the 32 subbands has 18 samples. On each iteration, we take + * one sample from each subband, (32 samples), and use a 32 point DCT + * to perform matrixing, and copy the result into the synthesis + * buffer fifo. */ + for (sb = 0; sb < SBLIMIT; sb++) { + polyPhaseIn[sb] = hybridOut[sb][ss]; + } + + mp3_SubBandSynthesis (tl, fr_ps, polyPhaseIn, channel, + &(tl->pcm_sample[channel][ss][0])); + } +} + +static Mp3TlRetcode +c_decode_mp3 (mp3tl * tl) +{ + III_scalefac_t III_scalefac; + III_side_info_t III_side_info; + huffdec_bitbuf *bb; + guint gr, ch, sb; + gint diff; + fr_header *hdr; + guint8 side_info[32]; /* At most 32 bytes side info for MPEG-1 stereo */ + gboolean MainDataOK; + + hdr = &tl->fr_ps.header; + bb = &tl->c_impl.bb; + + /* Check enough side_info data available */ + if (bs_bits_avail (tl->bs) < hdr->side_info_slots * 8) + return MP3TL_ERR_NEED_DATA; + + bs_getbytes (tl->bs, side_info, hdr->side_info_slots); + if (!III_get_side_info (side_info, &III_side_info, &tl->fr_ps)) { + GST_DEBUG ("Bad side info"); + return MP3TL_ERR_BAD_FRAME; + } + + /* Check enough main_data available */ + if (bs_bits_avail (tl->bs) < hdr->main_slots * 8) + return MP3TL_ERR_NEED_DATA; + + /* Verify that sufficient main_data was extracted from */ + /* the previous sync interval */ + diff = tl->c_impl.main_data_end - III_side_info.main_data_begin; + MainDataOK = (diff >= 0); + if (!MainDataOK) { + GST_DEBUG ("MainDataEnd: %d MainDataBegin: %d delta: %d", + tl->c_impl.main_data_end, III_side_info.main_data_begin, diff); + } + + /* Copy the remaining main data in the bit reservoir to the start of the + * huffman bit buffer, and then append the incoming bytes */ + if (MainDataOK) { + if (diff > 0) { + memmove (tl->c_impl.hb_buf, tl->c_impl.hb_buf + diff, + III_side_info.main_data_begin); + tl->c_impl.main_data_end = III_side_info.main_data_begin; + } + } + /* And append the incoming bytes to the reservoir */ + bs_getbytes (tl->bs, tl->c_impl.hb_buf + tl->c_impl.main_data_end, + hdr->main_slots); + tl->c_impl.main_data_end += hdr->main_slots; + + if (!MainDataOK) { + GST_DEBUG ("Bad frame - not enough main data bits"); + return MP3TL_ERR_BAD_FRAME; + } + + /* And setup the huffman bitstream reader for this data */ + h_setbuf (bb, tl->c_impl.hb_buf, tl->c_impl.main_data_end); + + /* Clear the scale factors to avoid problems with badly coded files + * that try to reuse scalefactors from the first granule when they didn't + * supply them. */ + memset (III_scalefac, 0, sizeof (III_scalefac_t)); + + for (gr = 0; gr < tl->n_granules; gr++) { + gfloat lr[2][SBLIMIT][SSLIMIT], ro[2][SBLIMIT][SSLIMIT]; + + for (ch = 0; ch < hdr->channels; ch++) { + gint is[SBLIMIT][SSLIMIT]; /* Quantized samples. */ + int part2_start; + + part2_start = h_sstell (bb); + if (hdr->version == MPEG_VERSION_1) { + III_get_scale_factors (&III_scalefac, &III_side_info, gr, ch, tl); + } else { + III_get_LSF_scale_factors (&III_scalefac, &III_side_info, gr, ch, tl); + } + + if (III_side_info.gr[gr][ch].big_values > ((SBLIMIT * SSLIMIT) / 2)) { + GST_DEBUG ("Bad side info decoding frame: big_values"); + return MP3TL_ERR_BAD_FRAME; + } + + if (!III_huffman_decode (is, &III_side_info, ch, gr, part2_start, tl)) { + GST_DEBUG ("Failed to decode huffman info"); + return MP3TL_ERR_BAD_FRAME; + } + +#ifdef HUFFMAN_DEBUG + { + gint i, j; + fprintf (stderr, "\nFrame %" G_GUINT64_FORMAT ", granule %d, channel %d\n", + tl->frame_num, gr, ch); + for (i = 0 ; i < 32; i++) { + fprintf (stderr, "SB %02d: ", i); + for (j = 0; j < 18; j++) { + fprintf (stderr, "%4d ", is[i][j]); + } + fprintf (stderr, "\n"); + } + } +#endif + + III_dequantize_sample (is, ro[ch], &III_scalefac, + &(III_side_info.gr[gr][ch]), ch, gr, &tl->fr_ps); + +#ifdef DEQUANT_DEBUG + { + gint i, j; + fprintf (stderr, "\nFrame %" G_GUINT64_FORMAT ", granule %d, channel %d\n", + tl->frame_num, gr, ch); + for (i = 0 ; i < 32; i++) { + fprintf (stderr, "SB %02d: ", i); + for (j = 0; j < 18; j++) { + fprintf (stderr, "%+f ", ro[ch][i][j]); + } + fprintf (stderr, "\n"); + } + } +#endif + } + + III_stereo (ro, lr, &III_scalefac, &(III_side_info.gr[gr][0]), &tl->fr_ps); + + for (ch = 0; ch < hdr->channels; ch++) { + gfloat re[SBLIMIT][SSLIMIT]; + gfloat hybridIn[SBLIMIT][SSLIMIT]; /* Hybrid filter input */ + gfloat hybridOut[SBLIMIT][SSLIMIT]; /* Hybrid filter out */ + gr_info_t *gi = &(III_side_info.gr[gr][ch]); + + III_reorder (lr[ch], re, gi, &tl->fr_ps); + + /* Antialias butterflies. */ + III_antialias (re, hybridIn, gi); +#if 0 + int i; + g_print ("HybridIn\n"); + for (sb = 0; sb < SBLIMIT; sb++) { + g_print ("SB %02d: ", sb); + for (i = 0; i < SSLIMIT; i++) { + g_print ("%06f ", hybridIn[sb][i]); + } + g_print ("\n"); + } +#endif + + for (sb = 0; sb < SBLIMIT; sb++) { + /* Hybrid synthesis. */ + III_hybrid (hybridIn[sb], hybridOut[sb], sb, ch, gi, tl); + } + + /* Frequency inversion for polyphase. Invert odd numbered indices */ + III_frequency_inversion (hybridOut, tl); + +#if 0 + g_print ("HybridOut\n"); + for (sb = 0; sb < SBLIMIT; sb++) { + g_print ("SB %02d: ", sb); + for (i = 0; i < SSLIMIT; i++) { + g_print ("%06f ", hybridOut[sb][i]); + } + g_print ("\n"); + } +#endif + + /* Polyphase synthesis */ + III_subband_synthesis (tl, &tl->fr_ps, hybridOut, ch, tl->pcm_sample[ch]); +#if 0 + if (ch == 0) { + g_print ("synth\n"); + + for (i = 0; i < SSLIMIT; i++) { + g_print ("SS %02d: ", i); + for (sb = 0; sb < SBLIMIT; sb++) { + g_print ("%04d ", tl->pcm_sample[ch][sb][i]); + } + g_print ("\n"); + } + } +#endif + + } + /* Output PCM sample points for one granule. */ + out_fifo (tl->pcm_sample, 18, &tl->fr_ps, tl->sample_buf, + &tl->sample_w, SAMPLE_BUF_SIZE); + } + + return MP3TL_ERR_OK; +} + +static gboolean +mp3_c_init (mp3tl * tl) +{ + init_hybrid (&tl->c_impl); + return TRUE; +} + +static void +mp3_c_flush (mp3tl * tl) +{ + init_hybrid (&tl->c_impl); + h_reset (&tl->c_impl.bb); + memset (tl->c_impl.hb_buf, 0, HDBB_BUFSIZE); + tl->c_impl.main_data_end = 0; +} + +/* Minimum size in bytes of an MP3 frame */ +#define MIN_FRAME_SIZE 24 + +mp3tl * +mp3tl_new (Bit_stream_struc * bs, Mp3TlMode mode) +{ + mp3tl *tl; + void *alloc_memory; + + g_return_val_if_fail (bs != NULL, NULL); + g_return_val_if_fail (mode == MP3TL_MODE_16BIT, NULL); + + alloc_memory = calloc(1, (sizeof (mp3tl) + __CACHE_LINE_BYTES)); + + tl = (mp3tl *) __CACHE_LINE_ALIGN(alloc_memory); + g_return_val_if_fail (tl != NULL, NULL); + + tl->alloc_memory = alloc_memory; + tl->bs = bs; + tl->need_sync = TRUE; + tl->need_header = TRUE; + tl->at_eos = FALSE; + tl->lost_sync = TRUE; + + tl->sample_size = 16; + tl->sample_buf = NULL; + tl->sample_w = 0; + tl->stream_layer = 0; + tl->error_count = 0; + + tl->fr_ps.alloc = NULL; + init_syn_filter (&tl->fr_ps); + + tl->free_first = TRUE; + + if (!mp3_c_init (tl)) { + free (tl); + return NULL; + } + + return tl; +} + +void +mp3tl_free (mp3tl * tl) +{ + g_return_if_fail (tl != NULL); + + free (tl->alloc_memory); +}; + +void +mp3tl_set_eos (mp3tl * tl, gboolean is_eos) +{ + tl->at_eos = is_eos; +} + +Mp3TlRetcode +mp3tl_sync (mp3tl * tl) +{ + g_return_val_if_fail (tl != NULL, MP3TL_ERR_PARAM); + + if (tl->need_sync) { + guint64 sync_start ATTR_UNUSED; + + /* Find a sync word, with valid header */ + bs_reset (tl->bs); + + /* Need at least sync word + header bits */ + if (bs_bits_avail (tl->bs) < SYNC_WORD_LNGTH + HEADER_LNGTH) + return MP3TL_ERR_NO_SYNC; + + sync_start = bs_pos (tl->bs); + GST_LOG ("Starting sync search at %" G_GUINT64_FORMAT " (byte %" + G_GUINT64_FORMAT ")", sync_start, sync_start / 8); + + do { + gboolean sync; + guint64 offset; + guint64 frame_start; + fr_header *hdr = &tl->fr_ps.header; + gboolean valid = TRUE; + guint64 total_offset ATTR_UNUSED; + + sync = bs_seek_sync (tl->bs); + offset = bs_read_pos (tl->bs) - bs_pos (tl->bs); + total_offset = bs_read_pos (tl->bs) - sync_start; + + if (!sync) { + /* Leave the last byte in the stream, as it might be the first byte + * of our sync word later */ + if (offset > 8) + bs_consume (tl->bs, (guint32) (offset - 8)); + + tl->lost_sync = TRUE; + GST_LOG ("Not enough data in buffer for a sync sequence"); + return MP3TL_ERR_NO_SYNC; + } + g_assert (offset >= SYNC_WORD_LNGTH); + + /* Check if we skipped any data to find the sync word */ + if (offset != SYNC_WORD_LNGTH) { + GST_DEBUG ("Skipped %" G_GUINT64_FORMAT " bits to find sync", offset); + tl->lost_sync = TRUE; + } + + /* Remember the start of frame */ + frame_start = bs_read_pos (tl->bs) - SYNC_WORD_LNGTH; + + /* Look ahead and check the header details */ + if (bs_bits_avail (tl->bs) < HEADER_LNGTH) { + /* Consume bytes to the start of sync word and go get more data */ + bs_consume (tl->bs, (guint32) (offset - SYNC_WORD_LNGTH)); + tl->lost_sync = TRUE; + + GST_LOG ("Not enough data in buffer to read header"); + return MP3TL_ERR_NO_SYNC; + } + + /* Read header bits */ + GST_LOG ("Reading header at %" G_GUINT64_FORMAT " (byte %" + G_GUINT64_FORMAT ")", bs_read_pos (tl->bs), bs_read_pos (tl->bs) / 8); + if (!read_header (tl, hdr)) { + valid = FALSE; + GST_LOG ("Bad header"); + } else { + /* Fill out the derived header details */ + hdr->sample_size = tl->sample_size; + if (!set_hdr_data_slots (hdr)) { + GST_LOG ("Bad header (slots)"); + valid = FALSE; + } + + /* Data is not allowed to suddenly change to a different layer */ + if (tl->stream_layer != 0 && hdr->layer != tl->stream_layer) { + GST_LOG ("Bad header (layer changed)"); + valid = FALSE; + } + } + + /* FIXME: Could check the CRC to confirm a sync point */ + + /* If we skipped any to find the sync, and we have enough data, + * jump ahead to where we expect the next frame to be and confirm + * that there is a sync word there */ + if (valid && tl->lost_sync) { + gint64 remain; + + remain = hdr->frame_bits - (bs_read_pos (tl->bs) - frame_start); + if (hdr->frame_bits < (8 * MIN_FRAME_SIZE)) { + GST_LOG ("Header indicates a frame too small to be correct"); + valid = FALSE; + } else if (bs_bits_avail (tl->bs) >= hdr->frame_bits) { + guint32 sync_word; + fr_header next_hdr; + + GST_DEBUG ("Peeking ahead %u bits to check sync (%" + G_GINT64_FORMAT ", %" G_GUINT64_FORMAT ", %" + G_GUINT64_FORMAT ")", hdr->frame_bits, remain, + (guint64) bs_read_pos (tl->bs), (guint64) frame_start); + + /* Skip 'remain' bits */ + bs_skipbits (tl->bs, (guint32) (remain - 1)); + + /* Read a sync word and check */ + sync_word = bs_getbits_aligned (tl->bs, SYNC_WORD_LNGTH); + + if (sync_word != SYNC_WORD) { + valid = FALSE; + GST_LOG ("No next sync word %u bits later @ %" G_GUINT64_FORMAT + ". Got 0x%03x", hdr->frame_bits, + bs_read_pos (tl->bs) - SYNC_WORD_LNGTH, sync_word); + } else if (!read_header (tl, &next_hdr)) { + GST_LOG ("Invalid header at next indicated frame"); + valid = FALSE; + } else { + /* Check that the samplerate and layer for the next header is + * the same */ + if ((hdr->layer != next_hdr.layer) || + (hdr->sample_rate != next_hdr.sample_rate) || + (hdr->copyright != next_hdr.copyright) || + (hdr->original != next_hdr.original) || + (hdr->emphasis != next_hdr.emphasis)) { + valid = FALSE; + GST_LOG ("Invalid header at next indicated frame"); + } + } + + if (valid) + GST_LOG ("Good - found a valid frame %u bits later.", + hdr->frame_bits); + } else if (!tl->at_eos) { + GST_LOG ("Not enough data in buffer to test next header"); + + /* Not at the end of stream, so wait for more data to validate the + * frame with */ + /* Consume bytes to the start of sync word and go get more data */ + bs_consume (tl->bs, (guint32) (offset - SYNC_WORD_LNGTH)); + return MP3TL_ERR_NO_SYNC; + } + } + + if (!valid) { + /* Move past the first byte of the sync word and keep looking */ + bs_consume (tl->bs, (guint32) (offset - SYNC_WORD_LNGTH + 8)); + } else { + /* Consume everything up to the start of sync word */ + if (offset > SYNC_WORD_LNGTH) + bs_consume (tl->bs, (guint32) (offset - SYNC_WORD_LNGTH)); + + tl->need_sync = FALSE; + GST_DEBUG ("OK after %" G_GUINT64_FORMAT " offset", + total_offset - SYNC_WORD_LNGTH); + } + } while (tl->need_sync); + + if (bs_pos (tl->bs) != sync_start) + GST_DEBUG ("Skipped %" G_GUINT64_FORMAT " bits, found sync", + bs_pos (tl->bs) - sync_start); + } + + return MP3TL_ERR_OK; +} + +Mp3TlRetcode +mp3tl_decode_header (mp3tl * tl, const fr_header ** ret_hdr) +{ + fr_header *hdr; + Mp3TlRetcode ret; + + g_return_val_if_fail (tl != NULL, MP3TL_ERR_PARAM); + hdr = &tl->fr_ps.header; + if (G_LIKELY (ret_hdr != NULL)) + *ret_hdr = hdr; + + if (!tl->need_header) + return MP3TL_ERR_OK; + + if ((ret = mp3tl_sync (tl)) != MP3TL_ERR_OK) + return ret; + + /* Restart the read ptr and move past the sync word */ + bs_reset (tl->bs); + bs_getbits (tl->bs, SYNC_WORD_LNGTH); + + /* If there are less than header bits available, something went + * wrong in the sync */ + g_assert (bs_bits_avail (tl->bs) >= HEADER_LNGTH); + + GST_DEBUG ("Frame is %d bytes (%d bits)", + hdr->frame_bits / 8, hdr->frame_bits); + + /* Consume the header and sync word */ + bs_consume (tl->bs, SYNC_WORD_LNGTH + HEADER_LNGTH); + + tl->need_header = FALSE; + return MP3TL_ERR_OK; +} + +Mp3TlRetcode +mp3tl_gather_frame (mp3tl * tl, guint64 * _offset, gint * _length) +{ + guint64 sync_start ATTR_UNUSED; + gboolean sync; + guint64 offset; + guint64 frame_start; + fr_header *hdr; + gboolean valid = TRUE; + + /* Find a sync word, with valid header */ + bs_reset (tl->bs); + + /* Need at least sync word + header bits */ + if (bs_bits_avail (tl->bs) < SYNC_WORD_LNGTH + HEADER_LNGTH) + return MP3TL_ERR_NO_SYNC; + + sync_start = bs_pos (tl->bs); + GST_LOG ("Starting sync search at %" G_GUINT64_FORMAT " (byte %" + G_GUINT64_FORMAT ")", sync_start, sync_start / 8); + + hdr = &tl->fr_ps.header; + sync = bs_seek_sync (tl->bs); + offset = bs_read_pos (tl->bs) - bs_pos (tl->bs); + + if (!sync) { + GST_LOG ("Not enough data for a sync sequence"); + return MP3TL_ERR_NO_SYNC; + } + + /* Check if we skipped any data to find the sync word */ + if (offset != SYNC_WORD_LNGTH) { + GST_DEBUG ("Skipped %" G_GUINT64_FORMAT " bits to find sync", offset); + } + + /* Remember the start of frame */ + frame_start = bs_read_pos (tl->bs) - SYNC_WORD_LNGTH; + + /* Look ahead and check the header details */ + if (bs_bits_avail (tl->bs) < HEADER_LNGTH) { + GST_LOG ("Not enough data to read header"); + return MP3TL_ERR_NO_SYNC; + } + + /* Read header bits */ + GST_LOG ("Reading header at %" G_GUINT64_FORMAT " (byte %" + G_GUINT64_FORMAT ")", bs_read_pos (tl->bs), bs_read_pos (tl->bs) / 8); + if (!read_header (tl, hdr)) { + valid = FALSE; + GST_LOG ("Bad header"); + } else { + /* Fill out the derived header details */ + hdr->sample_size = tl->sample_size; + if (!set_hdr_data_slots (hdr)) { + GST_LOG ("Bad header (slots)"); + valid = FALSE; + } + + /* Data is not allowed to suddenly change to a different layer */ + if (tl->stream_layer != 0 && hdr->layer != tl->stream_layer) { + GST_LOG ("Bad header (layer changed)"); + valid = FALSE; + } + } + + /* If we skipped any to find the sync, and we have enough data, + * jump ahead to where we expect the next frame to be and confirm + * that there is a sync word there */ + if (valid) { + gint64 remain; + + remain = hdr->frame_bits - (bs_read_pos (tl->bs) - frame_start); + if (hdr->frame_bits < (8 * MIN_FRAME_SIZE)) { + GST_LOG ("Header indicates a frame too small to be correct"); + } else if (bs_bits_avail (tl->bs) >= hdr->frame_bits) { + guint32 sync_word; + fr_header next_hdr; + + GST_DEBUG ("Peeking ahead %u bits to check sync (%" + G_GINT64_FORMAT ", %" G_GUINT64_FORMAT ", %" + G_GUINT64_FORMAT ")", hdr->frame_bits, remain, + (guint64) bs_read_pos (tl->bs), (guint64) frame_start); + + /* Skip 'remain' bits */ + bs_skipbits (tl->bs, (guint32) (remain - 1)); + + /* Read a sync word and check */ + sync_word = bs_getbits_aligned (tl->bs, SYNC_WORD_LNGTH); + + if (sync_word != SYNC_WORD) { + valid = FALSE; + GST_LOG ("No next sync word %u bits later @ %" G_GUINT64_FORMAT + ". Got 0x%03x", hdr->frame_bits, + bs_read_pos (tl->bs) - SYNC_WORD_LNGTH, sync_word); + } else if (!read_header (tl, &next_hdr)) { + GST_LOG ("Invalid header at next indicated frame"); + valid = FALSE; + } else { + /* Check that the samplerate and layer for the next header is + * the same */ + if ((hdr->layer != next_hdr.layer) || + (hdr->sample_rate != next_hdr.sample_rate) || + (hdr->copyright != next_hdr.copyright) || + (hdr->original != next_hdr.original) || + (hdr->emphasis != next_hdr.emphasis)) { + valid = FALSE; + GST_LOG ("Invalid header at next indicated frame"); + } + } + + if (valid) { + GST_LOG ("Good - found a valid frame %u bits later.", + hdr->frame_bits); + } + } else if (!tl->at_eos) { + GST_LOG ("Not enough data in buffer to test next header"); + /* Not at the end of stream, so wait for more data to validate the + * frame with */ + return MP3TL_ERR_NO_SYNC; + } + *_offset = frame_start >> 3; + *_length = hdr->frame_bits >> 3; + tl->lost_sync = FALSE; + } else { + *_offset = (frame_start + SYNC_WORD_LNGTH) >> 3; + return MP3TL_ERR_NO_SYNC; + } + + return MP3TL_ERR_OK; +} + +/********************************************************************* + * Decode the current frame into the samples buffer + *********************************************************************/ +Mp3TlRetcode +mp3tl_decode_frame (mp3tl * tl, guint8 * samples, guint bufsize) +{ + fr_header *hdr; + int i, j; + int error_protection; + guint new_crc; + Mp3TlRetcode ret; + gint64 frame_start_pos; + + g_return_val_if_fail (tl != NULL, MP3TL_ERR_PARAM); + g_return_val_if_fail (samples != NULL, MP3TL_ERR_PARAM); + + hdr = &tl->fr_ps.header; + + if ((ret = mp3tl_decode_header (tl, NULL)) != MP3TL_ERR_OK) + return ret; + + /* Check that the buffer is big enough to hold the decoded samples */ + if (bufsize < hdr->frame_samples * (hdr->sample_size / 8) * hdr->channels) + return MP3TL_ERR_PARAM; + + bs_reset (tl->bs); + + GST_LOG ("Starting decode of frame size %u bits, with %u bits in buffer", + hdr->frame_bits, (guint) bs_bits_avail (tl->bs)); + + /* Got enough bits for the decode? (exclude the header) */ + if (bs_bits_avail (tl->bs) < + hdr->frame_bits - (SYNC_WORD_LNGTH + HEADER_LNGTH)) + return MP3TL_ERR_NEED_DATA; + + hdr_to_frps (&tl->fr_ps); + + if (hdr->version == MPEG_VERSION_1) + tl->n_granules = 2; + else + tl->n_granules = 1; + + tl->stream_layer = hdr->layer; + + error_protection = hdr->error_protection; + + /* We're about to start reading bits out of the stream, + * after which we'll need a new sync and header + */ + tl->need_sync = TRUE; + tl->need_header = TRUE; + + /* Set up the output buffer */ + tl->sample_w = 0; + tl->sample_buf = (gint16 *) samples; + + /* Remember the start of the frame */ + frame_start_pos = bs_read_pos (tl->bs) - (SYNC_WORD_LNGTH + HEADER_LNGTH); + + /* Retrieve the CRC from the stream */ + if (error_protection) + tl->old_crc = bs_getbits (tl->bs, 16); + + switch (hdr->layer) { + case 1:{ + guint bit_alloc[2][SBLIMIT], scale_index[2][3][SBLIMIT]; + guint ch; + + I_decode_bitalloc (tl->bs, bit_alloc, &tl->fr_ps); + I_decode_scale (tl->bs, bit_alloc, scale_index, &tl->fr_ps); + + /* Compute and check the CRC */ + if (error_protection) { + I_CRC_calc (&tl->fr_ps, bit_alloc, &new_crc); + if (new_crc != tl->old_crc) { + tl->error_count++; + GST_DEBUG ("CRC mismatch - Bad frame"); + return MP3TL_ERR_BAD_FRAME; + } + } + + for (i = 0; i < SCALE_BLOCK; i++) { + I_buffer_sample (tl->bs, tl->sample, bit_alloc, &tl->fr_ps); + I_dequant_and_scale_sample (tl->sample, tl->fraction, bit_alloc, + scale_index, &tl->fr_ps); + + for (ch = 0; ch < hdr->channels; ch++) { + mp3_SubBandSynthesis (tl, &tl->fr_ps, &(tl->fraction[ch][0][0]), ch, + &((tl->pcm_sample)[ch][0][0])); + } + out_fifo (tl->pcm_sample, 1, &tl->fr_ps, tl->sample_buf, &tl->sample_w, + SAMPLE_BUF_SIZE); + } + break; + } + + case 2:{ + guint bit_alloc[2][SBLIMIT], scfsi[2][SBLIMIT], + scale_index[2][3][SBLIMIT]; + guint ch; + + /* Choose bit allocations table */ + II_pick_table (&tl->fr_ps); + + /* Read band bit allocations from the data and scale */ + II_decode_bitalloc (tl->bs, bit_alloc, &tl->fr_ps); + II_decode_scale (tl->bs, scfsi, bit_alloc, scale_index, &tl->fr_ps); + + if (error_protection) { + II_CRC_calc (&tl->fr_ps, bit_alloc, scfsi, &new_crc); + if (new_crc != tl->old_crc) { + tl->error_count++; + GST_DEBUG ("CRC mismatch - Bad frame"); + return MP3TL_ERR_BAD_FRAME; + } + } + + for (i = 0; i < SCALE_BLOCK; i++) { + II_buffer_sample (tl->bs, (tl->sample), bit_alloc, &tl->fr_ps); + II_dequant_and_scale_sample (tl->sample, bit_alloc, tl->fraction, + scale_index, i >> 2, &tl->fr_ps); + + for (j = 0; j < 3; j++) + for (ch = 0; ch < hdr->channels; ch++) { + mp3_SubBandSynthesis (tl, &tl->fr_ps, &((tl->fraction)[ch][j][0]), + ch, &((tl->pcm_sample)[ch][j][0])); + } + out_fifo (tl->pcm_sample, 3, &tl->fr_ps, tl->sample_buf, &tl->sample_w, + SAMPLE_BUF_SIZE); + } + break; + } + case 3: + ret = c_decode_mp3 (tl); + if (ret == MP3TL_ERR_BAD_FRAME) { + /* Consume the data from our bitreader */ + bs_consume (tl->bs, hdr->frame_bits - (SYNC_WORD_LNGTH + HEADER_LNGTH)); + return ret; + } + + if (ret != MP3TL_ERR_OK) + return ret; + break; + default: + GST_WARNING ("Unknown layer %d, invalid bitstream.", hdr->layer); + return MP3TL_ERR_STREAM; + } + + /* skip ancillary data HP 22-nov-95 */ + if (hdr->bitrate_idx != 0) { /* if not free-format */ + /* Ancillary bits are any left in the frame that didn't get used */ + gint64 anc_len = hdr->frame_slots * hdr->bits_per_slot; + + anc_len -= bs_read_pos (tl->bs) - frame_start_pos; + + if (anc_len > 0) { + GST_DEBUG ("Skipping %" G_GINT64_FORMAT " ancillary bits", anc_len); + do { + bs_getbits (tl->bs, (guint32) MIN (anc_len, MAX_LENGTH)); + anc_len -= MAX_LENGTH; + } while (anc_len > 0); + } + } + + tl->frame_num++; + tl->bits_used += hdr->frame_bits; + + /* Consume the data */ + bs_consume (tl->bs, hdr->frame_bits - (SYNC_WORD_LNGTH + HEADER_LNGTH)); + + GST_DEBUG ("Used %u bits = %u slots plus %u", hdr->frame_bits, + hdr->frame_slots, hdr->frame_bits % hdr->bits_per_slot); + + GST_DEBUG ("Avg slots/frame so far = %.3f; b/smp = %.2f; br = %.3f kbps", + (float) tl->bits_used / (tl->frame_num * hdr->bits_per_slot), + (float) tl->bits_used / (tl->frame_num * hdr->frame_samples), + (float) (1.0/1000 * tl->bits_used) / (tl->frame_num * hdr->frame_samples) * + s_rates[hdr->version][hdr->srate_idx]); + + /* Correctly decoded a frame, so assume we're synchronised */ + tl->lost_sync = FALSE; + + return MP3TL_ERR_OK; +} + +void +mp3tl_flush (mp3tl * tl) +{ + GST_LOG ("Flush"); + /* Clear out the bytestreams */ + bs_flush (tl->bs); + + tl->need_header = TRUE; + tl->need_sync = TRUE; + tl->lost_sync = TRUE; + + init_syn_filter (&tl->fr_ps); + + tl->sample_buf = NULL; + tl->sample_w = 0; + memset (tl->pcm_sample, 0, sizeof (tl->pcm_sample)); + + mp3_c_flush (tl); +} + +Mp3TlRetcode +mp3tl_skip_frame (mp3tl * tl) +{ + fr_header *hdr; + Mp3TlRetcode ret; + + g_return_val_if_fail (tl != NULL, MP3TL_ERR_PARAM); + + hdr = &tl->fr_ps.header; + + if ((ret = mp3tl_decode_header (tl, NULL)) != MP3TL_ERR_OK) + return ret; + + bs_reset (tl->bs); + + /* Got enough bits to consume? (exclude the header) */ + if (bs_bits_avail (tl->bs) < + hdr->frame_bits - (SYNC_WORD_LNGTH + HEADER_LNGTH)) + return MP3TL_ERR_NEED_DATA; + + hdr_to_frps (&tl->fr_ps); + + if (hdr->version == MPEG_VERSION_1) + tl->n_granules = 2; + else + tl->n_granules = 1; + + tl->stream_layer = hdr->layer; + + /* We're about to start reading bits out of the stream, + * after which we'll need a new sync and header + */ + tl->need_sync = TRUE; + tl->need_header = TRUE; + + tl->frame_num++; + tl->bits_used += hdr->frame_bits; + + /* Consume the data */ + bs_consume (tl->bs, hdr->frame_bits - (SYNC_WORD_LNGTH + HEADER_LNGTH)); + + GST_DEBUG ("Skipped %u bits = %u slots plus %u", hdr->frame_bits, + hdr->frame_slots, hdr->frame_bits % hdr->bits_per_slot); + + GST_DEBUG ("Avg slots/frame so far = %.3f; b/smp = %.2f; br = %.3f kbps", + (float) tl->bits_used / (tl->frame_num * hdr->bits_per_slot), + (float) tl->bits_used / (tl->frame_num * hdr->frame_samples), + (float) (1.0/1000 * tl->bits_used) / (tl->frame_num * hdr->frame_samples) * + s_rates[hdr->version][hdr->srate_idx]); + + return MP3TL_ERR_OK; +} + +const char * +mp3tl_get_err_reason (mp3tl * tl) +{ + return tl->reason; +} + +#define ID3_HDR_MIN 10 + +Mp3TlRetcode +mp3tl_skip_id3(mp3tl * tl) +{ + guint8 buf[ID3_HDR_MIN]; + + bs_reset(tl->bs); + + if (bs_bits_avail(tl->bs) < 8 * ID3_HDR_MIN) { + GST_DEBUG("Not enough data to read ID3 header"); + return MP3TL_ERR_NEED_DATA; + } + + bs_getbytes(tl->bs, buf, ID3_HDR_MIN); + + /* skip ID3 tag, if present */ + if (!memcmp(buf, "ID3", 3)) { + guint32 bytes_needed = (buf[6] << (7*3)) | (buf[7] << (7*2)) | (buf[8] << 7) | buf[9]; + + if (bs_bits_avail(tl->bs) < 8 * bytes_needed) { + GST_DEBUG("Not enough data to read ID3 tag (need %d)", bytes_needed); + return MP3TL_ERR_NEED_DATA; + } + + bs_consume(tl->bs, 8 * (ID3_HDR_MIN + bytes_needed)); + GST_DEBUG("ID3 tag found, skipping %d bytes", (ID3_HDR_MIN + bytes_needed)); + } + + bs_reset(tl->bs); + return MP3TL_ERR_OK; +} + +#define XING_HDR_MIN 8 + +Mp3TlRetcode +mp3tl_skip_xing(mp3tl * tl, const fr_header * hdr) +{ + const guint32 xing_id = 0x58696e67; /* 'Xing' in hex */ + const guint32 info_id = 0x496e666f; /* 'Info' in hex - found in LAME CBR files */ + guint32 xing_offset; + guint32 read_id; + + if (hdr->version == MPEG_VERSION_1) { /* MPEG-1 file */ + if (hdr->channels == 1) + xing_offset = 0x11; + else + xing_offset = 0x20; + } else { /* MPEG-2 header */ + if (hdr->channels == 1) + xing_offset = 0x09; + else + xing_offset = 0x11; + } + + bs_reset(tl->bs); + + if (bs_bits_avail(tl->bs) < 8 * (xing_offset + XING_HDR_MIN)) { + GST_DEBUG("Not enough data to read Xing header"); + return MP3TL_ERR_NEED_DATA; + } + + /* Read 4 bytes from the frame at the specified location */ + bs_skipbits(tl->bs, 8 * xing_offset); + read_id = bs_getbits(tl->bs, 32); + + /* skip Xing header, if present */ + if (read_id == xing_id || read_id == info_id) { + + bs_consume(tl->bs, hdr->frame_bits); + GST_DEBUG("Xing header found, skipping %d bytes", hdr->frame_bits / 8); + return MP3TL_ERR_STREAM; + + } else { + GST_DEBUG("No Xing header found"); + } + + bs_reset(tl->bs); + return MP3TL_ERR_OK; +} + +} // namespace flump3dec diff --git a/libraries/audio/src/flump3dec.h b/libraries/audio/src/flump3dec.h new file mode 100644 index 0000000000..721e092b0a --- /dev/null +++ b/libraries/audio/src/flump3dec.h @@ -0,0 +1,428 @@ +/* + * FLUENDO S.A. + * Copyright (C) <2005 - 2011> + * + * This Source Code is licensed under MIT license and the explanations attached + * in MIT License Statements. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is furnished to do + * so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * MIT license Statements for Fluendo's mp3 plug-in Source Code + * ------------------------------------------------------------ + * + * Fluendo's mp3 software Source Code (the "Source Code") is licensed under the + * MIT license provisions. + * + * The MIT license is an open source license that permits the User to operate and + * use in many forms the Source Code, which would be governed under its + * regulations. + * + * The purpose of this note is to clarify the intellectual property rights granted + * over the Source Code by Fluendo, as well as other legal issues that concern + * your use of it. + * + * MIT license contents and provisions + * ----------------------------------- + * + * The MIT license allows you to do the following things with the Source Code: + * + * - Copy and use the Source Code alone or jointly with other code for any + * purposes. + * Copy of the Source Code is not limited and is royalty-free. + * + * - Merge the Source Code with other code for developing new applications with no + * limits. + * + * - Modifying the Source Code for developing the plug-in or for implementing the + * plug-in in other applications for any purposes. The MIT License does not + * require you to share these modifications with anyone. + * + * - Publish, distribute, sublicense and sell copies of the Source Code to third + * parties. + * + * - Permit anyone to whom the Source Code is licensed to enjoy the rights above + * subject to the MIT license provisions. + * + * By licensing this Source Code under the MIT License, Fluendo is offering to the + * community the rights set out above without restriction and without any + * obligation for the User of the Source Code to release his/her modifications + * back to the community. Anyone operating with the Source Code released from + * Fluendo must grant the same MIT license rights to the community, except for any + * modifications operated on the Source Code which can be granted under a + * different license (even a proprietary license). + * + * All these rights granted to the User for the Source Code hold a limitation + * which is to include MIT permission notice and the following copyright notice: + * "Copyright 2005 Fluendo, S.L. This Source Code is licensed under MIT license + * and the explanations attached in MIT License Statements". These notices shall + * be included in all copies of the Source Code or in substantial parts of the + * Source Code which may be released separately or with modifications. + * + * Patents over the plug-in and/or Source Code + * ------------------------------------------- + * + * The binaries that can be created by compiling this Source Code released by + * Fluendo might be covered by patents in various parts of the world. Fluendo + * does not own or claim to own any patents on the techniques used in the code. + * (Such patents are owned or claimed to be owned by Thompson Licensing, S.A. and + * some other entities as the case may be). + * + * Fluendo has got the relevant licenses to cover its own activities with the + * Source Code but it is not authorized to sublicense nor to grant the rights + * which it has acquired over the patents. In this sense, you can work and deal + * freely with the Source Code under MIT provisions set out above, bearing in mind + * that some activities might not be allowed under applicable patent regulations + * and that Fluendo is not granting any rights in relation to such patents. + * + * The patent license granted to Fluendo only covers Fluendo's own Software and + * Source Code activities. In any case, this software license does not allow you + * to redistribute or copy complete, ready to use mp3 software decoder binaries + * made from the Source Code as made available by Fluendo. You can of course + * distribute binaries you make yourself under any terms allowed by the MIT + * license and whatever necessary rights you have or have acquired according to + * applicable patent regulations. + * + * As Fluendo can not assure that any of the activities you undertake do not + * infringe any patents or other industrial or intellectual property rights, + * Fluendo hereby disclaims any liability for any patent infringement that may be + * claimed to you or to any other person from any legitimate right’s owner, as + * stated in MIT license. So it is your responsibility to get information and to + * acquire the necessary patent licenses to undertake your activities legally. + */ + +// +// Modifications and bug fixes copyright 2018 High Fidelity, Inc. +// Now passes ISO/IEC 11172-4 "full accuracy" compliance testing. +// + +#ifndef __FLUMP3DEC_H__ +#define __FLUMP3DEC_H__ + +#include +#include +#include + +#if 0 +#include +#define G_GINT64_FORMAT "lld" +#define G_GUINT64_FORMAT "llu" + +#define GST_LOG(f, ...) do { printf(f "\n", __VA_ARGS__); } while (0) +#define GST_DEBUG(f, ...) do { printf(f "\n", __VA_ARGS__); } while (0) +#define GST_WARNING(f, ...) do { printf(f "\n", __VA_ARGS__); } while (0) +#else +#define GST_LOG(f, ...) do {} while (0) +#define GST_DEBUG(f, ...) do {} while (0) +#define GST_WARNING(f, ...) do {} while (0) +#endif + +#ifndef TRUE +#define TRUE 1 +#endif + +#ifndef FALSE +#define FALSE 0 +#endif + +#ifndef MIN +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +#endif + +#define g_assert(cond) assert(cond) +#define g_return_if_fail(cond) { if (!(cond)) return; } +#define g_return_val_if_fail(cond, val) { if (!(cond)) return (val); } + +namespace flump3dec { + +typedef char gchar; +typedef unsigned char guchar; +typedef int gint; +typedef unsigned int guint; +typedef float gfloat; +typedef double gdouble; +typedef int gboolean; +typedef size_t gsize; + +typedef int8_t gint8; +typedef uint8_t guint8; +typedef int16_t gint16; +typedef uint16_t guint16; +typedef int32_t gint32; +typedef uint32_t guint32; +typedef int64_t gint64; +typedef uint64_t guint64; + +/* Accumulator optimization on bitstream management */ +#define ENABLE_OPT_BS 1 + +/* Bit stream reader definitions */ +#define MAX_LENGTH 32 /* Maximum length of word written or + read from bit stream */ +#define BS_BYTE_SIZE 8 + +#if ENABLE_OPT_BS +#define BS_ACUM_SIZE 32 +#else +#define BS_ACUM_SIZE 8 +#endif + +typedef struct BSReader +{ + guint64 bitpos; /* Number of bits read so far */ + + gsize size; /* Number of bytes in the buffer list */ + const guint8 *data; /* Current data buffer */ + guint8 *cur_byte; /* ptr to the current byte */ + guint8 cur_bit; /* the next bit to be used in the current byte, + * numbered from 8 down to 1 */ + gsize cur_used; /* Number of bytes _completely_ consumed out of + * the 'cur buffer' */ +} BSReader; + +typedef struct Bit_stream_struc +{ + BSReader master; /* Master tracking position, advanced + * by bs_consume() */ + BSReader read; /* Current read position, set back to the + * master by bs_reset() */ +} Bit_stream_struc; + +/* Create and initialise a new bitstream reader */ +Bit_stream_struc *bs_new (); + +/* Release a bitstream reader */ +void bs_free (Bit_stream_struc * bs); + +/* Reset the current read position to the master position */ +static inline void +bs_reset (Bit_stream_struc * bs) +{ + memcpy (&bs->read, &bs->master, sizeof (BSReader)); +} + +/* Reset master and read states */ +static inline void +bs_flush (Bit_stream_struc * bs) +{ + g_return_if_fail (bs != NULL); + + bs->master.cur_bit = 8; + bs->master.size = 0; + bs->master.cur_used = 0; + bs->master.cur_byte = NULL; + bs->master.data = NULL; + bs->master.bitpos = 0; + + bs_reset (bs); +} + +/* Set data as the stream for processing */ +gboolean bs_set_data (Bit_stream_struc * bs, const guint8 * data, gsize size); + +/* Advance the master position by Nbits */ +void bs_consume (Bit_stream_struc * bs, guint32 Nbits); + +/* Number of bits available for reading */ +static inline gsize bs_bits_avail (Bit_stream_struc * bs) +{ + return ((bs->read.size - bs->read.cur_used) * 8 + (bs->read.cur_bit - 8)); +} + +/* Extract N bytes from the bitstream into the out array. */ +void bs_getbytes (Bit_stream_struc * bs, guint8 * out, guint32 N); + +/* Advance the read pointer by N bits */ +void bs_skipbits (Bit_stream_struc * bs, guint32 N); + +/* give number of consumed bytes */ +static inline gsize bs_get_consumed (Bit_stream_struc * bs) +{ + return bs->master.cur_used; +} + +/* Current bitstream position in bits */ +static inline guint64 +bs_pos (Bit_stream_struc * bs) +{ + return bs->master.bitpos; +} + +/* Current read bitstream position in bits */ +static inline guint64 +bs_read_pos (Bit_stream_struc * bs) +{ + return bs->read.bitpos; +} + +/* Advances the read position to the first bit of next frame or + * last byte in the buffer when the sync code is not found */ +gboolean bs_seek_sync (Bit_stream_struc * bs); + +/* Read N bits from the stream */ +/* bs - bit stream structure */ +/* N - number of bits to read from the bit stream */ +/* v - output value */ +static inline guint32 +bs_getbits (Bit_stream_struc * bs, guint32 N) +{ + guint32 val = 0; + gint j = N; + + g_assert (N <= MAX_LENGTH); + + while (j > 0) { + gint tmp; + gint k; + gint mask; + + /* Move to the next byte if we consumed the current one */ + if (bs->read.cur_bit == 0) { + bs->read.cur_bit = 8; + bs->read.cur_used++; + bs->read.cur_byte++; + } + + /* Protect against data limit */ + if ((bs->read.cur_used >= bs->read.size)) { + GST_WARNING ("Attempted to read beyond data"); + /* Return the bits we got so far */ + return val; + } + /* Take as many bits as we can from the current byte */ + k = MIN (j, bs->read.cur_bit); + + /* We want the k bits from the current byte, starting from + * the cur_bit. Mask out the top 'already used' bits, then shift + * the bits we want down to the bottom */ + mask = (1 << bs->read.cur_bit) - 1; + tmp = bs->read.cur_byte[0] & mask; + + /* Trim off the bits we're leaving for next time */ + tmp = tmp >> (bs->read.cur_bit - k); + + /* Adjust our tracking vars */ + bs->read.cur_bit -= k; + j -= k; + bs->read.bitpos += k; + + /* Put these bits in the right spot in the output */ + val |= tmp << j; + } + + return val; +} + +/* Read 1 bit from the stream */ +static inline guint32 +bs_get1bit (Bit_stream_struc * bs) +{ + return bs_getbits (bs, 1); +} + +/* read the next byte aligned N bits from the bit stream */ +static inline guint32 +bs_getbits_aligned (Bit_stream_struc * bs, guint32 N) +{ + guint32 align; + + align = bs->read.cur_bit; + if (align != 8 && align != 0) + bs_getbits (bs, align); + + return bs_getbits (bs, N); +} + +/* MPEG Header Definitions - ID Bit Values */ +#define MPEG_VERSION_1 0x03 +#define MPEG_VERSION_2 0x02 +#define MPEG_VERSION_2_5 0x00 + +/* Header Information Structure */ +typedef struct +{ + /* Stuff read straight from the MPEG header */ + guint version; + guint layer; + gboolean error_protection; + + gint bitrate_idx; /* Index into the bitrate tables */ + guint srate_idx; /* Index into the sample rate table */ + + gboolean padding; + gboolean extension; + guint mode; + guint mode_ext; + gboolean copyright; + gboolean original; + guint emphasis; + + /* Derived attributes */ + guint bitrate; /* Bitrate of the frame, kbps */ + guint sample_rate; /* sample rate in Hz */ + guint sample_size; /* in bits */ + guint frame_samples; /* Number of samples per channels in this + frame */ + guint channels; /* Number of channels in the frame */ + + guint bits_per_slot; /* Number of bits per slot */ + guint frame_slots; /* Total number of data slots in this frame */ + guint main_slots; /* Slots of main data in this frame */ + guint frame_bits; /* Number of bits in the frame, including header + and sync word */ + guint side_info_slots; /* Number of slots of side info in the frame */ +} fr_header; + +typedef struct mp3tl mp3tl; +typedef enum +{ + MP3TL_ERR_OK = 0, /* Successful return code */ + MP3TL_ERR_NO_SYNC, /* There was no sync word in the data buffer */ + MP3TL_ERR_NEED_DATA, /* Not enough data in the buffer for the requested op */ + MP3TL_ERR_BAD_FRAME, /* The frame data was corrupt and skipped */ + MP3TL_ERR_STREAM, /* Encountered invalid data in the stream */ + MP3TL_ERR_UNSUPPORTED_STREAM, /* Encountered valid but unplayable data in + * the stream */ + MP3TL_ERR_PARAM, /* Invalid parameter was passed in */ + MP3TL_ERR_UNKNOWN /* Unspecified internal decoder error (bug) */ +} Mp3TlRetcode; + +typedef enum +{ + MP3TL_MODE_16BIT = 0 /* Decoder mode to use */ +} Mp3TlMode; + +mp3tl *mp3tl_new (Bit_stream_struc * bs, Mp3TlMode mode); + +void mp3tl_free (mp3tl * tl); + +void mp3tl_set_eos (mp3tl * tl, gboolean more_data); +Mp3TlRetcode mp3tl_sync (mp3tl * tl); +Mp3TlRetcode mp3tl_gather_frame (mp3tl * tl, guint64 * _offset, gint * _length); +Mp3TlRetcode mp3tl_decode_header (mp3tl * tl, const fr_header ** ret_hdr); +Mp3TlRetcode mp3tl_skip_frame (mp3tl * tl); +Mp3TlRetcode mp3tl_decode_frame (mp3tl * tl, guint8 * samples, guint bufsize); +const char *mp3tl_get_err_reason (mp3tl * tl); +void mp3tl_flush (mp3tl * tl); + +Mp3TlRetcode mp3tl_skip_id3 (mp3tl * tl); +Mp3TlRetcode mp3tl_skip_xing (mp3tl * tl, const fr_header * hdr); + +} // namespace flump3dec + +#endif //__FLUMP3DEC_H__ diff --git a/libraries/auto-updater/src/AutoUpdater.cpp b/libraries/auto-updater/src/AutoUpdater.cpp index e58ac067a6..300a22983a 100644 --- a/libraries/auto-updater/src/AutoUpdater.cpp +++ b/libraries/auto-updater/src/AutoUpdater.cpp @@ -11,11 +11,16 @@ #include "AutoUpdater.h" -#include -#include #include -AutoUpdater::AutoUpdater() { +#include +#include +#include +#include + +AutoUpdater::AutoUpdater() : + _currentVersion(BuildInfo::BUILD_TYPE == BuildInfo::BuildType::Stable ? BuildInfo::VERSION : BuildInfo::BUILD_NUMBER) +{ #if defined Q_OS_WIN32 _operatingSystem = "windows"; #elif defined Q_OS_MAC @@ -31,9 +36,22 @@ void AutoUpdater::checkForUpdate() { this->getLatestVersionData(); } +const QUrl BUILDS_XML_URL("https://highfidelity.com/builds.xml"); +const QUrl MASTER_BUILDS_XML_URL("https://highfidelity.com/dev-builds.xml"); + void AutoUpdater::getLatestVersionData() { QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); - QNetworkRequest latestVersionRequest(BUILDS_XML_URL); + + QUrl buildsURL; + + if (BuildInfo::BUILD_TYPE == BuildInfo::BuildType::Stable) { + buildsURL = BUILDS_XML_URL; + } else if (BuildInfo::BUILD_TYPE == BuildInfo::BuildType::Master) { + buildsURL = MASTER_BUILDS_XML_URL; + } + + QNetworkRequest latestVersionRequest(buildsURL); + latestVersionRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); latestVersionRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); QNetworkReply* reply = networkAccessManager.get(latestVersionRequest); @@ -50,12 +68,22 @@ void AutoUpdater::parseLatestVersionData() { QString clientOnly; }; - int version { 0 }; + QString version; QString downloadUrl; QString releaseTime; QString releaseNotes; QString commitSha; QString pullRequestNumber; + + QString versionKey; + + // stable builds look at the stable_version node (semantic version) + // master builds look at the version node (build number) + if (BuildInfo::BUILD_TYPE == BuildInfo::BuildType::Stable) { + versionKey = "stable_version"; + } else if (BuildInfo::BUILD_TYPE == BuildInfo::BuildType::Master) { + versionKey = "version"; + } while (xml.readNextStartElement()) { if (xml.name() == "projects") { @@ -75,8 +103,8 @@ void AutoUpdater::parseLatestVersionData() { QHash campaignInstallers; while (xml.readNextStartElement()) { - if (xml.name() == "version") { - version = xml.readElementText().toInt(); + if (xml.name() == versionKey) { + version = xml.readElementText(); } else if (xml.name() == "url") { downloadUrl = xml.readElementText(); } else if (xml.name() == "installers") { @@ -157,33 +185,31 @@ void AutoUpdater::parseLatestVersionData() { } void AutoUpdater::checkVersionAndNotify() { - if (QCoreApplication::applicationVersion() == "dev" || - QCoreApplication::applicationVersion().contains("PR") || - _builds.empty()) { - // No version checking is required in dev builds or when no build - // data was found for the platform + if (_builds.empty()) { + // no build data was found for this platform return; } - int latestVersionAvailable = _builds.lastKey(); - if (QCoreApplication::applicationVersion().toInt() < latestVersionAvailable) { + + qDebug() << "Checking if update version" << _builds.lastKey().versionString + << "is newer than current version" << _currentVersion.versionString; + + if (_builds.lastKey() > _currentVersion) { emit newVersionIsAvailable(); } } -void AutoUpdater::performAutoUpdate(int version) { - // NOTE: This is not yet auto updating - however this is a checkpoint towards that end - // Next PR will handle the automatic download, upgrading and application restart - const QMap& chosenVersion = _builds.value(version); +void AutoUpdater::openLatestUpdateURL() { + const QMap& chosenVersion = _builds.last(); const QUrl& downloadUrl = chosenVersion.value("downloadUrl"); QDesktopServices::openUrl(downloadUrl); QCoreApplication::quit(); } -void AutoUpdater::downloadUpdateVersion(int version) { +void AutoUpdater::downloadUpdateVersion(const QString& version) { emit newVersionIsDownloaded(); } -void AutoUpdater::appendBuildData(int versionNumber, +void AutoUpdater::appendBuildData(const QString& versionNumber, const QString& downloadURL, const QString& releaseTime, const QString& releaseNotes, @@ -194,6 +220,6 @@ void AutoUpdater::appendBuildData(int versionNumber, thisBuildDetails.insert("releaseTime", releaseTime); thisBuildDetails.insert("releaseNotes", releaseNotes); thisBuildDetails.insert("pullRequestNumber", pullRequestNumber); - _builds.insert(versionNumber, thisBuildDetails); + _builds.insert(ApplicationVersion(versionNumber), thisBuildDetails); -} \ No newline at end of file +} diff --git a/libraries/auto-updater/src/AutoUpdater.h b/libraries/auto-updater/src/AutoUpdater.h index f56d7993e9..c788ac31d1 100644 --- a/libraries/auto-updater/src/AutoUpdater.h +++ b/libraries/auto-updater/src/AutoUpdater.h @@ -26,10 +26,9 @@ #include #include +#include #include -const QUrl BUILDS_XML_URL("https://highfidelity.com/builds.xml"); - class AutoUpdater : public QObject, public Dependency { Q_OBJECT SINGLETON_DEPENDENCY @@ -43,25 +42,29 @@ public: }; void checkForUpdate(); - const QMap>& getBuildData() { return _builds; } - void performAutoUpdate(int version); + const QMap>& getBuildData() { return _builds; } + void openLatestUpdateURL(); void setInstallerType(InstallerType type) { _installerType = type; } void setInstallerCampaign(QString campaign) { _installerCampaign = campaign; } + const ApplicationVersion& getCurrentVersion() const { return _currentVersion; } + signals: void latestVersionDataParsed(); void newVersionIsAvailable(); void newVersionIsDownloaded(); private: - QMap> _builds; + QMap> _builds; QString _operatingSystem; InstallerType _installerType { InstallerType::FULL }; QString _installerCampaign { "" }; + + ApplicationVersion _currentVersion; void getLatestVersionData(); - void downloadUpdateVersion(int version); - void appendBuildData(int versionNumber, + void downloadUpdateVersion(const QString& version); + void appendBuildData(const QString& versionNumber, const QString& downloadURL, const QString& releaseTime, const QString& releaseNotes, diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index da829b23e4..9682c81697 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -52,10 +52,15 @@ const glm::vec3 HAND_TO_PALM_OFFSET(0.0f, 0.12f, 0.08f); namespace render { template <> const ItemKey payloadGetKey(const AvatarSharedPointer& avatar) { - return ItemKey::Builder::opaqueShape().withTypeMeta().withTagBits(ItemKey::TAG_BITS_0 | ItemKey::TAG_BITS_1).withMetaCullGroup(); + ItemKey::Builder keyBuilder = ItemKey::Builder::opaqueShape().withTypeMeta().withTagBits(render::hifi::TAG_ALL_VIEWS).withMetaCullGroup(); + auto avatarPtr = static_pointer_cast(avatar); + if (!avatarPtr->getEnableMeshVisible()) { + keyBuilder.withInvisible(); + } + return keyBuilder.build(); } template <> const Item::Bound payloadGetBound(const AvatarSharedPointer& avatar) { - return static_pointer_cast(avatar)->getBounds(); + return static_pointer_cast(avatar)->getRenderBounds(); } template <> void payloadRender(const AvatarSharedPointer& avatar, RenderArgs* args) { auto avatarPtr = static_pointer_cast(avatar); @@ -164,6 +169,11 @@ AABox Avatar::getBounds() const { return _skeletonModel->getRenderableMeshBound(); } + +AABox Avatar::getRenderBounds() const { + return _renderBound; +} + void Avatar::animateScaleChanges(float deltaTime) { if (_isAnimatingScale) { @@ -569,16 +579,26 @@ static TextRenderer3D* textRenderer(TextRendererType type) { void Avatar::addToScene(AvatarSharedPointer self, const render::ScenePointer& scene, render::Transaction& transaction) { auto avatarPayload = new render::Payload(self); - auto avatarPayloadPointer = Avatar::PayloadPointer(avatarPayload); - + auto avatarPayloadPointer = std::shared_ptr>(avatarPayload); if (_renderItemID == render::Item::INVALID_ITEM_ID) { _renderItemID = scene->allocateID(); } + // INitialize the _render bound as we are creating the avatar render item + _renderBound = getBounds(); transaction.resetItem(_renderItemID, avatarPayloadPointer); _skeletonModel->addToScene(scene, transaction); + _skeletonModel->setTagMask(render::hifi::TAG_ALL_VIEWS); + _skeletonModel->setGroupCulled(true); + _skeletonModel->setCanCastShadow(true); + _skeletonModel->setVisibleInScene(_isMeshVisible, scene); + processMaterials(); for (auto& attachmentModel : _attachmentModels) { attachmentModel->addToScene(scene, transaction); + attachmentModel->setTagMask(render::hifi::TAG_ALL_VIEWS); + attachmentModel->setGroupCulled(false); + attachmentModel->setCanCastShadow(true); + attachmentModel->setVisibleInScene(_isMeshVisible, scene); } _mustFadeIn = true; @@ -637,7 +657,15 @@ void Avatar::removeFromScene(AvatarSharedPointer self, const render::ScenePointe void Avatar::updateRenderItem(render::Transaction& transaction) { if (render::Item::isValidID(_renderItemID)) { - transaction.updateItem>(_renderItemID, [](render::Payload& p) {}); + auto renderBound = getBounds(); + transaction.updateItem(_renderItemID, + [renderBound](AvatarData& avatar) { + auto avatarPtr = dynamic_cast(&avatar); + if (avatarPtr) { + avatarPtr->_renderBound = renderBound; + } + } + ); } } @@ -759,6 +787,18 @@ void Avatar::render(RenderArgs* renderArgs) { } } + +void Avatar::setEnableMeshVisible(bool isEnabled) { + if (_isMeshVisible != isEnabled) { + _isMeshVisible = isEnabled; + _needMeshVisibleSwitch = true; + } +} + +bool Avatar::getEnableMeshVisible() const { + return _isMeshVisible; +} + void Avatar::fixupModelsInScene(const render::ScenePointer& scene) { bool canTryFade{ false }; @@ -770,6 +810,12 @@ void Avatar::fixupModelsInScene(const render::ScenePointer& scene) { if (_skeletonModel->isRenderable() && _skeletonModel->needsFixupInScene()) { _skeletonModel->removeFromScene(scene, transaction); _skeletonModel->addToScene(scene, transaction); + + _skeletonModel->setTagMask(render::hifi::TAG_ALL_VIEWS); + _skeletonModel->setGroupCulled(true); + _skeletonModel->setCanCastShadow(true); + _skeletonModel->setVisibleInScene(_isMeshVisible, scene); + processMaterials(); canTryFade = true; _isAnimatingScale = true; @@ -778,9 +824,25 @@ void Avatar::fixupModelsInScene(const render::ScenePointer& scene) { if (attachmentModel->isRenderable() && attachmentModel->needsFixupInScene()) { attachmentModel->removeFromScene(scene, transaction); attachmentModel->addToScene(scene, transaction); + + attachmentModel->setTagMask(render::hifi::TAG_ALL_VIEWS); + attachmentModel->setGroupCulled(false); + attachmentModel->setCanCastShadow(true); + attachmentModel->setVisibleInScene(_isMeshVisible, scene); } } + if (_needMeshVisibleSwitch) { + _skeletonModel->setVisibleInScene(_isMeshVisible, scene); + for (auto attachmentModel : _attachmentModels) { + if (attachmentModel->isRenderable()) { + attachmentModel->setVisibleInScene(_isMeshVisible, scene); + } + } + updateRenderItem(transaction); + _needMeshVisibleSwitch = false; + } + if (_mustFadeIn && canTryFade) { // Do it now to be sure all the sub items are ready and the fade is sent to them too fade(transaction, render::Transition::USER_ENTER_DOMAIN); @@ -799,6 +861,7 @@ bool Avatar::shouldRenderHead(const RenderArgs* renderArgs) const { return true; } +// virtual void Avatar::simulateAttachments(float deltaTime) { assert(_attachmentModels.size() == _attachmentModelsTexturesLoaded.size()); PerformanceTimer perfTimer("attachments"); @@ -1481,14 +1544,12 @@ void Avatar::updateDisplayNameAlpha(bool showDisplayName) { } } +// virtual void Avatar::computeShapeInfo(ShapeInfo& shapeInfo) { float uniformScale = getModelScale(); - float radius = uniformScale * _skeletonModel->getBoundingCapsuleRadius(); - float height = uniformScale * _skeletonModel->getBoundingCapsuleHeight(); - shapeInfo.setCapsuleY(radius, 0.5f * height); - - glm::vec3 offset = uniformScale * _skeletonModel->getBoundingCapsuleOffset(); - shapeInfo.setOffset(offset); + shapeInfo.setCapsuleY(uniformScale * _skeletonModel->getBoundingCapsuleRadius(), + 0.5f * uniformScale * _skeletonModel->getBoundingCapsuleHeight()); + shapeInfo.setOffset(uniformScale * _skeletonModel->getBoundingCapsuleOffset()); } void Avatar::getCapsule(glm::vec3& start, glm::vec3& end, float& radius) { @@ -1511,8 +1572,9 @@ float Avatar::computeMass() { return _density * TWO_PI * radius * radius * (glm::length(end - start) + 2.0f * radius / 3.0f); } +// virtual void Avatar::rebuildCollisionShape() { - addPhysicsFlags(Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS); + addPhysicsFlags(Simulation::DIRTY_SHAPE); } void Avatar::setPhysicsCallback(AvatarPhysicsCallback cb) { diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h index 0f48e03e55..10c1d9ead2 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h @@ -74,7 +74,6 @@ public: virtual void instantiableAvatar() = 0; typedef render::Payload Payload; - typedef std::shared_ptr PayloadPointer; void init(); void updateAvatarEntities(); @@ -322,6 +321,7 @@ public: bool hasNewJointData() const { return _hasNewJointData; } float getBoundingRadius() const; + AABox getRenderBounds() const; // THis call is accessible from rendering thread only to report the bounding box of the avatar during the frame. void addToScene(AvatarSharedPointer self, const render::ScenePointer& scene); void ensureInScene(AvatarSharedPointer self, const render::ScenePointer& scene); @@ -356,6 +356,10 @@ public: virtual void setAvatarEntityDataChanged(bool value) override; + // Show hide the model representation of the avatar + virtual void setEnableMeshVisible(bool isEnabled); + virtual bool getEnableMeshVisible() const; + void addMaterial(graphics::MaterialLayer material, const std::string& parentMaterialName) override; void removeMaterial(graphics::MaterialPointer material, const std::string& parentMaterialName) override; @@ -532,6 +536,10 @@ protected: std::mutex _materialsLock; void processMaterials(); + + AABox _renderBound; + bool _isMeshVisible{ true }; + bool _needMeshVisibleSwitch{ true }; }; #endif // hifi_Avatar_h diff --git a/libraries/avatars-renderer/src/avatars-renderer/Head.cpp b/libraries/avatars-renderer/src/avatars-renderer/Head.cpp index 256b3bf8a6..5800c1404b 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Head.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Head.cpp @@ -20,6 +20,7 @@ #include #include #include +#include "Logging.h" #include "Avatar.h" @@ -58,25 +59,30 @@ void Head::simulate(float deltaTime) { _longTermAverageLoudness = glm::mix(_longTermAverageLoudness, _averageLoudness, glm::min(deltaTime / AUDIO_LONG_TERM_AVERAGING_SECS, 1.0f)); } - if (!_isFaceTrackerConnected) { - if (!_isEyeTrackerConnected) { - // Update eye saccades - const float AVERAGE_MICROSACCADE_INTERVAL = 1.0f; - const float AVERAGE_SACCADE_INTERVAL = 6.0f; - const float MICROSACCADE_MAGNITUDE = 0.002f; - const float SACCADE_MAGNITUDE = 0.04f; - const float NOMINAL_FRAME_RATE = 60.0f; + if (!_isEyeTrackerConnected) { + // Update eye saccades + const float AVERAGE_MICROSACCADE_INTERVAL = 1.0f; + const float AVERAGE_SACCADE_INTERVAL = 6.0f; + const float MICROSACCADE_MAGNITUDE = 0.002f; + const float SACCADE_MAGNITUDE = 0.04f; + const float NOMINAL_FRAME_RATE = 60.0f; - if (randFloat() < deltaTime / AVERAGE_MICROSACCADE_INTERVAL) { - _saccadeTarget = MICROSACCADE_MAGNITUDE * randVector(); - } else if (randFloat() < deltaTime / AVERAGE_SACCADE_INTERVAL) { - _saccadeTarget = SACCADE_MAGNITUDE * randVector(); - } - _saccade += (_saccadeTarget - _saccade) * pow(0.5f, NOMINAL_FRAME_RATE * deltaTime); - } else { - _saccade = glm::vec3(); + if (randFloat() < deltaTime / AVERAGE_MICROSACCADE_INTERVAL) { + _saccadeTarget = MICROSACCADE_MAGNITUDE * randVector(); + } else if (randFloat() < deltaTime / AVERAGE_SACCADE_INTERVAL) { + _saccadeTarget = SACCADE_MAGNITUDE * randVector(); } + _saccade += (_saccadeTarget - _saccade) * pow(0.5f, NOMINAL_FRAME_RATE * deltaTime); + } else { + _saccade = glm::vec3(); + } + const float BLINK_SPEED = 10.0f; + const float BLINK_SPEED_VARIABILITY = 1.0f; + const float BLINK_START_VARIABILITY = 0.25f; + const float FULLY_OPEN = 0.0f; + const float FULLY_CLOSED = 1.0f; + if (getHasProceduralBlinkFaceMovement()) { // Detect transition from talking to not; force blink after that and a delay bool forceBlink = false; const float TALKING_LOUDNESS = 100.0f; @@ -88,29 +94,12 @@ void Head::simulate(float deltaTime) { forceBlink = true; } - // Update audio attack data for facial animation (eyebrows and mouth) - float audioAttackAveragingRate = (10.0f - deltaTime * NORMAL_HZ) / 10.0f; // --> 0.9 at 60 Hz - _audioAttack = audioAttackAveragingRate * _audioAttack + - (1.0f - audioAttackAveragingRate) * fabs((audioLoudness - _longTermAverageLoudness) - _lastLoudness); - _lastLoudness = (audioLoudness - _longTermAverageLoudness); - - const float BROW_LIFT_THRESHOLD = 100.0f; - if (_audioAttack > BROW_LIFT_THRESHOLD) { - _browAudioLift += sqrtf(_audioAttack) * 0.01f; - } - _browAudioLift = glm::clamp(_browAudioLift *= 0.7f, 0.0f, 1.0f); - - const float BLINK_SPEED = 10.0f; - const float BLINK_SPEED_VARIABILITY = 1.0f; - const float BLINK_START_VARIABILITY = 0.25f; - const float FULLY_OPEN = 0.0f; - const float FULLY_CLOSED = 1.0f; if (_leftEyeBlinkVelocity == 0.0f && _rightEyeBlinkVelocity == 0.0f) { // no blinking when brows are raised; blink less with increasing loudness const float BASE_BLINK_RATE = 15.0f / 60.0f; const float ROOT_LOUDNESS_TO_BLINK_INTERVAL = 0.25f; if (forceBlink || (_browAudioLift < EPSILON && shouldDo(glm::max(1.0f, sqrt(fabs(_averageLoudness - _longTermAverageLoudness)) * - ROOT_LOUDNESS_TO_BLINK_INTERVAL) / BASE_BLINK_RATE, deltaTime))) { + ROOT_LOUDNESS_TO_BLINK_INTERVAL) / BASE_BLINK_RATE, deltaTime))) { _leftEyeBlinkVelocity = BLINK_SPEED + randFloat() * BLINK_SPEED_VARIABILITY; _rightEyeBlinkVelocity = BLINK_SPEED + randFloat() * BLINK_SPEED_VARIABILITY; if (randFloat() < 0.5f) { @@ -136,22 +125,45 @@ void Head::simulate(float deltaTime) { _rightEyeBlinkVelocity = 0.0f; } } + } else { + _rightEyeBlink = FULLY_OPEN; + _leftEyeBlink = FULLY_OPEN; + } // use data to update fake Faceshift blendshape coefficients + if (getHasAudioEnabledFaceMovement()) { + // Update audio attack data for facial animation (eyebrows and mouth) + float audioAttackAveragingRate = (10.0f - deltaTime * NORMAL_HZ) / 10.0f; // --> 0.9 at 60 Hz + _audioAttack = audioAttackAveragingRate * _audioAttack + + (1.0f - audioAttackAveragingRate) * fabs((audioLoudness - _longTermAverageLoudness) - _lastLoudness); + _lastLoudness = (audioLoudness - _longTermAverageLoudness); + const float BROW_LIFT_THRESHOLD = 100.0f; + if (_audioAttack > BROW_LIFT_THRESHOLD) { + _browAudioLift += sqrtf(_audioAttack) * 0.01f; + } + _browAudioLift = glm::clamp(_browAudioLift *= 0.7f, 0.0f, 1.0f); calculateMouthShapes(deltaTime); - FaceTracker::updateFakeCoefficients(_leftEyeBlink, - _rightEyeBlink, - _browAudioLift, - _audioJawOpen, - _mouth2, - _mouth3, - _mouth4, - _transientBlendshapeCoefficients); - - applyEyelidOffset(getOrientation()); } else { - _saccade = glm::vec3(); + _audioJawOpen = 0.0f; + _browAudioLift = 0.0f; + _mouth2 = 0.0f; + _mouth3 = 0.0f; + _mouth4 = 0.0f; + _mouthTime = 0.0f; + } + + FaceTracker::updateFakeCoefficients(_leftEyeBlink, + _rightEyeBlink, + _browAudioLift, + _audioJawOpen, + _mouth2, + _mouth3, + _mouth4, + _transientBlendshapeCoefficients); + + if (getHasProceduralEyeFaceMovement()) { + applyEyelidOffset(getOrientation()); } _leftEyePosition = _rightEyePosition = getPosition(); diff --git a/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp b/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp index 428f86f0ab..de8c02f10e 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp @@ -35,7 +35,7 @@ SkeletonModel::SkeletonModel(Avatar* owningAvatar, QObject* parent) : _useDualQuaternionSkinning = true; // Avatars all cast shadow - _canCastShadow = true; + setCanCastShadow(true); assert(_owningAvatar); } diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 48ef1fb881..b5186ba8f4 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -300,14 +300,15 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent tranlationChangedSince(lastSentTime) || parentInfoChangedSince(lastSentTime)); - hasFaceTrackerInfo = !dropFaceTracking && hasFaceTracker() && (sendAll || faceTrackerInfoChangedSince(lastSentTime)); + hasFaceTrackerInfo = !dropFaceTracking && (hasFaceTracker() || getHasScriptedBlendshapes()) && + (sendAll || faceTrackerInfoChangedSince(lastSentTime)); hasJointData = sendAll || !sendMinimum; hasJointDefaultPoseFlags = hasJointData; } const size_t byteArraySize = AvatarDataPacket::MAX_CONSTANT_HEADER_SIZE + - (hasFaceTrackerInfo ? AvatarDataPacket::maxFaceTrackerInfoSize(_headData->getNumSummedBlendshapeCoefficients()) : 0) + + (hasFaceTrackerInfo ? AvatarDataPacket::maxFaceTrackerInfoSize(_headData->getBlendshapeCoefficients().size()) : 0) + (hasJointData ? AvatarDataPacket::maxJointDataSize(_jointData.size()) : 0) + (hasJointDefaultPoseFlags ? AvatarDataPacket::maxJointDefaultPoseFlagsSize(_jointData.size()) : 0); @@ -442,7 +443,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent auto startSection = destinationBuffer; auto data = reinterpret_cast(destinationBuffer); - uint8_t flags { 0 }; + uint16_t flags { 0 }; setSemiNibbleAt(flags, KEY_STATE_START_BIT, _keyState); @@ -450,20 +451,33 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent bool isFingerPointing = _handState & IS_FINGER_POINTING_FLAG; setSemiNibbleAt(flags, HAND_STATE_START_BIT, _handState & ~IS_FINGER_POINTING_FLAG); if (isFingerPointing) { - setAtBit(flags, HAND_STATE_FINGER_POINTING_BIT); + setAtBit16(flags, HAND_STATE_FINGER_POINTING_BIT); } // face tracker state if (_headData->_isFaceTrackerConnected) { - setAtBit(flags, IS_FACE_TRACKER_CONNECTED); + setAtBit16(flags, IS_FACE_TRACKER_CONNECTED); } // eye tracker state if (_headData->_isEyeTrackerConnected) { - setAtBit(flags, IS_EYE_TRACKER_CONNECTED); + setAtBit16(flags, IS_EYE_TRACKER_CONNECTED); } // referential state if (!parentID.isNull()) { - setAtBit(flags, HAS_REFERENTIAL); + setAtBit16(flags, HAS_REFERENTIAL); } + // audio face movement + if (_headData->getHasAudioEnabledFaceMovement()) { + setAtBit16(flags, AUDIO_ENABLED_FACE_MOVEMENT); + } + // procedural eye face movement + if (_headData->getHasProceduralEyeFaceMovement()) { + setAtBit16(flags, PROCEDURAL_EYE_FACE_MOVEMENT); + } + // procedural blink face movement + if (_headData->getHasProceduralBlinkFaceMovement()) { + setAtBit16(flags, PROCEDURAL_BLINK_FACE_MOVEMENT); + } + data->flags = flags; destinationBuffer += sizeof(AvatarDataPacket::AdditionalFlags); @@ -506,8 +520,9 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent if (hasFaceTrackerInfo) { auto startSection = destinationBuffer; auto faceTrackerInfo = reinterpret_cast(destinationBuffer); - const auto& blendshapeCoefficients = _headData->getSummedBlendshapeCoefficients(); - + const auto& blendshapeCoefficients = _headData->getBlendshapeCoefficients(); + // note: we don't use the blink and average loudness, we just use the numBlendShapes and + // compute the procedural info on the client side. faceTrackerInfo->leftEyeBlink = _headData->_leftEyeBlink; faceTrackerInfo->rightEyeBlink = _headData->_rightEyeBlink; faceTrackerInfo->averageLoudness = _headData->_averageLoudness; @@ -972,7 +987,7 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { PACKET_READ_CHECK(AdditionalFlags, sizeof(AvatarDataPacket::AdditionalFlags)); auto data = reinterpret_cast(sourceBuffer); - uint8_t bitItems = data->flags; + uint16_t bitItems = data->flags; // key state, stored as a semi-nibble in the bitItems auto newKeyState = (KeyState)getSemiNibbleAt(bitItems, KEY_STATE_START_BIT); @@ -980,26 +995,38 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { // hand state, stored as a semi-nibble plus a bit in the bitItems // we store the hand state as well as other items in a shared bitset. The hand state is an octal, but is split // into two sections to maintain backward compatibility. The bits are ordered as such (0-7 left to right). - // +---+-----+-----+--+ - // |x,x|H0,H1|x,x,x|H2| - // +---+-----+-----+--+ + // AA 6/1/18 added three more flags bits 8,9, and 10 for procedural audio, blink, and eye saccade enabled + // +---+-----+-----+--+--+--+--+-----+ + // |x,x|H0,H1|x,x,x|H2|Au|Bl|Ey|xxxxx| + // +---+-----+-----+--+--+--+--+-----+ // Hand state - H0,H1,H2 is found in the 3rd, 4th, and 8th bits auto newHandState = getSemiNibbleAt(bitItems, HAND_STATE_START_BIT) - + (oneAtBit(bitItems, HAND_STATE_FINGER_POINTING_BIT) ? IS_FINGER_POINTING_FLAG : 0); + + (oneAtBit16(bitItems, HAND_STATE_FINGER_POINTING_BIT) ? IS_FINGER_POINTING_FLAG : 0); - auto newFaceTrackerConnected = oneAtBit(bitItems, IS_FACE_TRACKER_CONNECTED); - auto newEyeTrackerConnected = oneAtBit(bitItems, IS_EYE_TRACKER_CONNECTED); + auto newFaceTrackerConnected = oneAtBit16(bitItems, IS_FACE_TRACKER_CONNECTED); + auto newEyeTrackerConnected = oneAtBit16(bitItems, IS_EYE_TRACKER_CONNECTED); + auto newHasAudioEnabledFaceMovement = oneAtBit16(bitItems, AUDIO_ENABLED_FACE_MOVEMENT); + auto newHasProceduralEyeFaceMovement = oneAtBit16(bitItems, PROCEDURAL_EYE_FACE_MOVEMENT); + auto newHasProceduralBlinkFaceMovement = oneAtBit16(bitItems, PROCEDURAL_BLINK_FACE_MOVEMENT); + + bool keyStateChanged = (_keyState != newKeyState); bool handStateChanged = (_handState != newHandState); bool faceStateChanged = (_headData->_isFaceTrackerConnected != newFaceTrackerConnected); bool eyeStateChanged = (_headData->_isEyeTrackerConnected != newEyeTrackerConnected); - bool somethingChanged = keyStateChanged || handStateChanged || faceStateChanged || eyeStateChanged; + bool audioEnableFaceMovementChanged = (_headData->getHasAudioEnabledFaceMovement() != newHasAudioEnabledFaceMovement); + bool proceduralEyeFaceMovementChanged = (_headData->getHasProceduralEyeFaceMovement() != newHasProceduralEyeFaceMovement); + bool proceduralBlinkFaceMovementChanged = (_headData->getHasProceduralBlinkFaceMovement() != newHasProceduralBlinkFaceMovement); + bool somethingChanged = keyStateChanged || handStateChanged || faceStateChanged || eyeStateChanged || audioEnableFaceMovementChanged || proceduralEyeFaceMovementChanged || proceduralBlinkFaceMovementChanged; _keyState = newKeyState; _handState = newHandState; _headData->_isFaceTrackerConnected = newFaceTrackerConnected; _headData->_isEyeTrackerConnected = newEyeTrackerConnected; + _headData->setHasAudioEnabledFaceMovement(newHasAudioEnabledFaceMovement); + _headData->setHasProceduralEyeFaceMovement(newHasProceduralEyeFaceMovement); + _headData->setHasProceduralBlinkFaceMovement(newHasProceduralBlinkFaceMovement); sourceBuffer += sizeof(AvatarDataPacket::AdditionalFlags); @@ -1060,23 +1087,21 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { PACKET_READ_CHECK(FaceTrackerInfo, sizeof(AvatarDataPacket::FaceTrackerInfo)); auto faceTrackerInfo = reinterpret_cast(sourceBuffer); - sourceBuffer += sizeof(AvatarDataPacket::FaceTrackerInfo); - - _headData->_leftEyeBlink = faceTrackerInfo->leftEyeBlink; - _headData->_rightEyeBlink = faceTrackerInfo->rightEyeBlink; - _headData->_averageLoudness = faceTrackerInfo->averageLoudness; - _headData->_browAudioLift = faceTrackerInfo->browAudioLift; - int numCoefficients = faceTrackerInfo->numBlendshapeCoefficients; const int coefficientsSize = sizeof(float) * numCoefficients; + sourceBuffer += sizeof(AvatarDataPacket::FaceTrackerInfo); + PACKET_READ_CHECK(FaceTrackerCoefficients, coefficientsSize); _headData->_blendshapeCoefficients.resize(numCoefficients); // make sure there's room for the copy! - _headData->_transientBlendshapeCoefficients.resize(numCoefficients); + //only copy the blendshapes to headData, not the procedural face info memcpy(_headData->_blendshapeCoefficients.data(), sourceBuffer, coefficientsSize); sourceBuffer += coefficientsSize; + int numBytesRead = sourceBuffer - startSection; _faceTrackerRate.increment(numBytesRead); _faceTrackerUpdateRate.increment(); + } else { + _headData->_blendshapeCoefficients.fill(0, _headData->_blendshapeCoefficients.size()); } if (hasJointData) { diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 4946ce45b9..51b3257ba2 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -79,20 +79,30 @@ const quint32 AVATAR_MOTION_SCRIPTABLE_BITS = // Bitset of state flags - we store the key state, hand state, Faceshift, eye tracking, and existence of // referential data in this bit set. The hand state is an octal, but is split into two sections to maintain // backward compatibility. The bits are ordered as such (0-7 left to right). -// +-----+-----+-+-+-+--+ -// |K0,K1|H0,H1|F|E|R|H2| -// +-----+-----+-+-+-+--+ +// AA 6/1/18 added three more flags bits 8,9, and 10 for procedural audio, blink, and eye saccade enabled +// +// +-----+-----+-+-+-+--+--+--+--+-----+ +// |K0,K1|H0,H1|F|E|R|H2|Au|Bl|Ey|xxxxx| +// +-----+-----+-+-+-+--+--+--+--+-----+ +// // Key state - K0,K1 is found in the 1st and 2nd bits // Hand state - H0,H1,H2 is found in the 3rd, 4th, and 8th bits // Face tracker - F is found in the 5th bit // Eye tracker - E is found in the 6th bit // Referential Data - R is found in the 7th bit +// Procedural audio to mouth movement is enabled 8th bit +// Procedural Blink is enabled 9th bit +// Procedural Eyelid is enabled 10th bit + const int KEY_STATE_START_BIT = 0; // 1st and 2nd bits const int HAND_STATE_START_BIT = 2; // 3rd and 4th bits const int IS_FACE_TRACKER_CONNECTED = 4; // 5th bit const int IS_EYE_TRACKER_CONNECTED = 5; // 6th bit (was CHAT_CIRCLING) const int HAS_REFERENTIAL = 6; // 7th bit const int HAND_STATE_FINGER_POINTING_BIT = 7; // 8th bit +const int AUDIO_ENABLED_FACE_MOVEMENT = 8; // 9th bit +const int PROCEDURAL_EYE_FACE_MOVEMENT = 9; // 10th bit +const int PROCEDURAL_BLINK_FACE_MOVEMENT = 10; // 11th bit const char HAND_STATE_NULL = 0; @@ -200,9 +210,9 @@ namespace AvatarDataPacket { static_assert(sizeof(SensorToWorldMatrix) == SENSOR_TO_WORLD_SIZE, "AvatarDataPacket::SensorToWorldMatrix size doesn't match."); PACKED_BEGIN struct AdditionalFlags { - uint8_t flags; // additional flags: hand state, key state, eye tracking + uint16_t flags; // additional flags: hand state, key state, eye tracking } PACKED_END; - const size_t ADDITIONAL_FLAGS_SIZE = 1; + const size_t ADDITIONAL_FLAGS_SIZE = 2; static_assert(sizeof(AdditionalFlags) == ADDITIONAL_FLAGS_SIZE, "AvatarDataPacket::AdditionalFlags size doesn't match."); // only present if HAS_REFERENTIAL flag is set in AvatarInfo.flags @@ -501,6 +511,11 @@ public: float getDomainLimitedScale() const; + virtual bool getHasScriptedBlendshapes() const { return false; } + virtual bool getHasProceduralBlinkFaceMovement() const { return true; } + virtual bool getHasProceduralEyeFaceMovement() const { return true; } + virtual bool getHasAudioEnabledFaceMovement() const { return false; } + /**jsdoc * Returns the minimum scale allowed for this avatar in the current domain. * This value can change as the user changes avatars or when changing domains. diff --git a/libraries/avatars/src/AvatarHashMap.cpp b/libraries/avatars/src/AvatarHashMap.cpp index 829c98a418..974ae92432 100644 --- a/libraries/avatars/src/AvatarHashMap.cpp +++ b/libraries/avatars/src/AvatarHashMap.cpp @@ -82,6 +82,7 @@ AvatarSharedPointer AvatarHashMap::addAvatar(const QUuid& sessionUUID, const QWe avatar->setSessionUUID(sessionUUID); avatar->setOwningAvatarMixer(mixerWeakPointer); + // addAvatar is only called from newOrExistingAvatar, which already locks _hashLock _avatarHash.insert(sessionUUID, avatar); emit avatarAddedEvent(sessionUUID); diff --git a/libraries/avatars/src/AvatarHashMap.h b/libraries/avatars/src/AvatarHashMap.h index 6747025de0..ef6f7845eb 100644 --- a/libraries/avatars/src/AvatarHashMap.h +++ b/libraries/avatars/src/AvatarHashMap.h @@ -46,7 +46,7 @@ class AvatarHashMap : public QObject, public Dependency { public: AvatarHash getHashCopy() { QReadLocker lock(&_hashLock); return _avatarHash; } const AvatarHash getHashCopy() const { QReadLocker lock(&_hashLock); return _avatarHash; } - int size() { return _avatarHash.size(); } + int size() { QReadLocker lock(&_hashLock); return _avatarHash.size(); } // Currently, your own avatar will be included as the null avatar id. @@ -152,8 +152,6 @@ protected: virtual void handleRemovedAvatar(const AvatarSharedPointer& removedAvatar, KillAvatarReason removalReason = KillAvatarReason::NoReason); AvatarHash _avatarHash; - // "Case-based safety": Most access to the _avatarHash is on the same thread. Write access is protected by a write-lock. - // If you read from a different thread, you must read-lock the _hashLock. (Scripted write access is not supported). mutable QReadWriteLock _hashLock; private: diff --git a/libraries/avatars/src/HeadData.h b/libraries/avatars/src/HeadData.h index bcc2cacde5..f9c4b52139 100644 --- a/libraries/avatars/src/HeadData.h +++ b/libraries/avatars/src/HeadData.h @@ -69,6 +69,24 @@ public: } bool lookAtPositionChangedSince(quint64 time) { return _lookAtPositionChanged >= time; } + bool getHasProceduralEyeFaceMovement() const { return _hasProceduralEyeFaceMovement; } + + void setHasProceduralEyeFaceMovement(const bool hasProceduralEyeFaceMovement) { + _hasProceduralEyeFaceMovement = hasProceduralEyeFaceMovement; + } + + bool getHasProceduralBlinkFaceMovement() const { return _hasProceduralBlinkFaceMovement; } + + void setHasProceduralBlinkFaceMovement(const bool hasProceduralBlinkFaceMovement) { + _hasProceduralBlinkFaceMovement = hasProceduralBlinkFaceMovement; + } + + bool getHasAudioEnabledFaceMovement() const { return _hasAudioEnabledFaceMovement; } + + void setHasAudioEnabledFaceMovement(const bool hasAudioEnabledFaceMovement) { + _hasAudioEnabledFaceMovement = hasAudioEnabledFaceMovement; + } + friend class AvatarData; QJsonObject toJson() const; @@ -83,6 +101,9 @@ protected: glm::vec3 _lookAtPosition; quint64 _lookAtPositionChanged { 0 }; + bool _hasAudioEnabledFaceMovement { true }; + bool _hasProceduralBlinkFaceMovement { true }; + bool _hasProceduralEyeFaceMovement { true }; bool _isFaceTrackerConnected { false }; bool _isEyeTrackerConnected { false }; float _leftEyeBlink { 0.0f }; diff --git a/libraries/baking/src/TextureBaker.cpp b/libraries/baking/src/TextureBaker.cpp index b6957a2712..ecfe724441 100644 --- a/libraries/baking/src/TextureBaker.cpp +++ b/libraries/baking/src/TextureBaker.cpp @@ -22,12 +22,16 @@ #include #include +#include + #include "ModelBakingLoggingCategory.h" const QString BAKED_TEXTURE_KTX_EXT = ".ktx"; const QString BAKED_TEXTURE_BCN_SUFFIX = "_bcn.ktx"; const QString BAKED_META_TEXTURE_SUFFIX = ".texmeta.json"; +bool TextureBaker::_compressionEnabled = true; + TextureBaker::TextureBaker(const QUrl& textureURL, image::TextureUsage::Type textureType, const QDir& outputDirectory, const QString& metaTexturePathPrefix, const QString& baseFilename, const QByteArray& textureContent) : @@ -124,51 +128,55 @@ void TextureBaker::processTexture() { TextureMeta meta; + auto originalCopyFilePath = _outputDirectory.absoluteFilePath(_textureURL.fileName()); { - auto filePath = _outputDirectory.absoluteFilePath(_textureURL.fileName()); - QFile file { filePath }; + QFile file { originalCopyFilePath }; if (!file.open(QIODevice::WriteOnly) || file.write(_originalTexture) == -1) { handleError("Could not write original texture for " + _textureURL.toString()); return; } - _outputFiles.push_back(filePath); + // IMPORTANT: _originalTexture is empty past this point + _originalTexture.clear(); + _outputFiles.push_back(originalCopyFilePath); meta.original = _metaTexturePathPrefix +_textureURL.fileName(); } - // IMPORTANT: _originalTexture is empty past this point - auto processedTexture = image::processImage(std::move(_originalTexture), _textureURL.toString().toStdString(), - ABSOLUTE_MAX_TEXTURE_NUM_PIXELS, _textureType, _abortProcessing); - processedTexture->setSourceHash(hash); - - if (shouldStop()) { + auto buffer = std::static_pointer_cast(std::make_shared(originalCopyFilePath)); + if (!buffer->open(QIODevice::ReadOnly)) { + handleError("Could not open original file at " + originalCopyFilePath); return; } - if (!processedTexture) { - handleError("Could not process texture " + _textureURL.toString()); - return; - } + // Compressed KTX + if (_compressionEnabled) { + auto processedTexture = image::processImage(buffer, _textureURL.toString().toStdString(), + ABSOLUTE_MAX_TEXTURE_NUM_PIXELS, _textureType, true, _abortProcessing); + if (!processedTexture) { + handleError("Could not process texture " + _textureURL.toString()); + return; + } + processedTexture->setSourceHash(hash); - - auto memKTX = gpu::Texture::serialize(*processedTexture); + if (shouldStop()) { + return; + } - if (!memKTX) { - handleError("Could not serialize " + _textureURL.toString() + " to KTX"); - return; - } + auto memKTX = gpu::Texture::serialize(*processedTexture); + if (!memKTX) { + handleError("Could not serialize " + _textureURL.toString() + " to KTX"); + return; + } - const char* name = khronos::gl::texture::toString(memKTX->_header.getGLInternaFormat()); - if (name == nullptr) { - handleError("Could not determine internal format for compressed KTX: " + _textureURL.toString()); - return; - } + const char* name = khronos::gl::texture::toString(memKTX->_header.getGLInternaFormat()); + if (name == nullptr) { + handleError("Could not determine internal format for compressed KTX: " + _textureURL.toString()); + return; + } - // attempt to write the baked texture to the destination file path - { const char* data = reinterpret_cast(memKTX->_storage->data()); const size_t length = memKTX->_storage->size(); - auto fileName = _baseFilename + BAKED_TEXTURE_BCN_SUFFIX; + auto fileName = _baseFilename + "_" + name + ".ktx"; auto filePath = _outputDirectory.absoluteFilePath(fileName); QFile bakedTextureFile { filePath }; if (!bakedTextureFile.open(QIODevice::WriteOnly) || bakedTextureFile.write(data, length) == -1) { @@ -179,6 +187,42 @@ void TextureBaker::processTexture() { meta.availableTextureTypes[memKTX->_header.getGLInternaFormat()] = _metaTexturePathPrefix + fileName; } + // Uncompressed KTX + if (_textureType == image::TextureUsage::Type::CUBE_TEXTURE) { + buffer->reset(); + auto processedTexture = image::processImage(std::move(buffer), _textureURL.toString().toStdString(), + ABSOLUTE_MAX_TEXTURE_NUM_PIXELS, _textureType, false, _abortProcessing); + if (!processedTexture) { + handleError("Could not process texture " + _textureURL.toString()); + return; + } + processedTexture->setSourceHash(hash); + + if (shouldStop()) { + return; + } + + auto memKTX = gpu::Texture::serialize(*processedTexture); + if (!memKTX) { + handleError("Could not serialize " + _textureURL.toString() + " to KTX"); + return; + } + + const char* data = reinterpret_cast(memKTX->_storage->data()); + const size_t length = memKTX->_storage->size(); + + auto fileName = _baseFilename + ".ktx"; + auto filePath = _outputDirectory.absoluteFilePath(fileName); + QFile bakedTextureFile { filePath }; + if (!bakedTextureFile.open(QIODevice::WriteOnly) || bakedTextureFile.write(data, length) == -1) { + handleError("Could not write baked texture for " + _textureURL.toString()); + return; + } + _outputFiles.push_back(filePath); + meta.uncompressed = _metaTexturePathPrefix + fileName; + } else { + buffer.reset(); + } { auto data = meta.serialize(); diff --git a/libraries/baking/src/TextureBaker.h b/libraries/baking/src/TextureBaker.h index 54839c001a..c8c4fb73b8 100644 --- a/libraries/baking/src/TextureBaker.h +++ b/libraries/baking/src/TextureBaker.h @@ -41,6 +41,8 @@ public: virtual void setWasAborted(bool wasAborted) override; + static void setCompressionEnabled(bool enabled) { _compressionEnabled = enabled; } + public slots: virtual void bake() override; virtual void abort() override; @@ -65,6 +67,8 @@ private: QString _metaTexturePathPrefix; std::atomic _abortProcessing { false }; + + static bool _compressionEnabled; }; #endif // hifi_TextureBaker_h diff --git a/libraries/controllers/src/controllers/ScriptingInterface.cpp b/libraries/controllers/src/controllers/ScriptingInterface.cpp index 16db22401f..f49b41cbe6 100644 --- a/libraries/controllers/src/controllers/ScriptingInterface.cpp +++ b/libraries/controllers/src/controllers/ScriptingInterface.cpp @@ -92,28 +92,16 @@ namespace controller { return userInputMapper->getValue(Input((uint32_t)source)); } - float ScriptingInterface::getButtonValue(StandardButtonChannel source, uint16_t device) const { - return getValue(Input(device, source, ChannelType::BUTTON).getID()); - } - float ScriptingInterface::getAxisValue(int source) const { auto userInputMapper = DependencyManager::get(); return userInputMapper->getValue(Input((uint32_t)source)); } - float ScriptingInterface::getAxisValue(StandardAxisChannel source, uint16_t device) const { - return getValue(Input(device, source, ChannelType::AXIS).getID()); - } - Pose ScriptingInterface::getPoseValue(const int& source) const { auto userInputMapper = DependencyManager::get(); return userInputMapper->getPose(Input((uint32_t)source)); } - Pose ScriptingInterface::getPoseValue(StandardPoseChannel source, uint16_t device) const { - return getPoseValue(Input(device, source, ChannelType::POSE).getID()); - } - QVector ScriptingInterface::getAllActions() { return DependencyManager::get()->getAllActions(); } diff --git a/libraries/controllers/src/controllers/ScriptingInterface.h b/libraries/controllers/src/controllers/ScriptingInterface.h index dacb0c8568..b0004bc12d 100644 --- a/libraries/controllers/src/controllers/ScriptingInterface.h +++ b/libraries/controllers/src/controllers/ScriptingInterface.h @@ -206,43 +206,6 @@ namespace controller { */ Q_INVOKABLE Pose getPoseValue(const int& source) const; - /**jsdoc - * Get the value of a button on a particular device. - * @function Controller.getButtonValue - * @param {StandardButtonChannel} source - The button to get the value of. - * @param {number} [device=0] - The ID of the hardware device to get the value from. The default value of - * 0 corresponds to Standard. - * @returns {number} The current value of the button if the parameters are valid, otherwise 0. - * @deprecated This function no longer works. - */ - // FIXME: This function causes a JavaScript crash: https://highfidelity.manuscript.com/f/cases/edit/14139 - Q_INVOKABLE float getButtonValue(StandardButtonChannel source, uint16_t device = 0) const; - - /**jsdoc - * Get the value of an axis control on a particular device. - * @function Controller.getAxisValue - * @variation 0 - * @param {StandardAxisChannel} source - The axis to get the value of. - * @param {number} [device=0] - The ID of the hardware device to get the value from. The default value of - * 0 corresponds to Standard. - * @returns {number} The current value of the axis if the parameters are valid, otherwise 0. - * @deprecated This function no longer works. - */ - Q_INVOKABLE float getAxisValue(StandardAxisChannel source, uint16_t device = 0) const; - - /**jsdoc - * Get the value of an pose control on a particular device. - * @function Controller.getPoseValue - * @variation 0 - * @param {StandardPoseChannel} source - The pose to get the value of. - * @param {number} [device=0] - The ID of the hardware device to get the value from. The default value of - * 0 corresponds to Standard. - * @returns {Pose} The current value of the controller pose output if the parameters are valid, otherwise an invalid - * pose with Pose.valid == false. - * @deprecated This function no longer works. - */ - Q_INVOKABLE Pose getPoseValue(StandardPoseChannel source, uint16_t device = 0) const; - /**jsdoc * Triggers a haptic pulse on connected and enabled devices that have the capability. * @function Controller.triggerHapticPulse diff --git a/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.cpp index 09b9b7f8f9..d8b8cbd54a 100644 --- a/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.cpp @@ -151,11 +151,9 @@ void Basic2DWindowOpenGLDisplayPlugin::compositeExtra() { batch.setModelTransform(stickTransform); batch.draw(gpu::TRIANGLE_STRIP, 4); - if (!virtualPadManager.getLeftVirtualPad()->isBeingTouched()) { - batch.setResourceTexture(0, _virtualPadJumpBtnTexture); - batch.setModelTransform(jumpTransform); - batch.draw(gpu::TRIANGLE_STRIP, 4); - } + batch.setResourceTexture(0, _virtualPadJumpBtnTexture); + batch.setModelTransform(jumpTransform); + batch.draw(gpu::TRIANGLE_STRIP, 4); }); } #endif diff --git a/libraries/embedded-webserver/src/HTTPConnection.cpp b/libraries/embedded-webserver/src/HTTPConnection.cpp index 12da599575..f65cd87f6e 100644 --- a/libraries/embedded-webserver/src/HTTPConnection.cpp +++ b/libraries/embedded-webserver/src/HTTPConnection.cpp @@ -11,6 +11,8 @@ #include "HTTPConnection.h" +#include + #include #include #include @@ -29,11 +31,92 @@ const char* HTTPConnection::StatusCode404 = "404 Not Found"; const char* HTTPConnection::StatusCode500 = "500 Internal server error"; const char* HTTPConnection::DefaultContentType = "text/plain; charset=ISO-8859-1"; -HTTPConnection::HTTPConnection (QTcpSocket* socket, HTTPManager* parentManager) : + +class MemoryStorage : public HTTPConnection::Storage { +public: + static std::unique_ptr make(qint64 size); + virtual ~MemoryStorage() = default; + + const QByteArray& content() const override { return _array; } + qint64 bytesLeftToWrite() const override { return _array.size() - _bytesWritten; } + void write(const QByteArray& data) override; + +private: + MemoryStorage(qint64 size) { _array.resize(size); } + + QByteArray _array; + qint64 _bytesWritten { 0 }; +}; + +std::unique_ptr MemoryStorage::make(qint64 size) { + return std::unique_ptr(new MemoryStorage(size)); +} + +void MemoryStorage::write(const QByteArray& data) { + assert(data.size() <= bytesLeftToWrite()); + memcpy(_array.data() + _bytesWritten, data.data(), data.size()); + _bytesWritten += data.size(); +} + + +class FileStorage : public HTTPConnection::Storage { +public: + static std::unique_ptr make(qint64 size); + virtual ~FileStorage(); + + const QByteArray& content() const override { return _wrapperArray; }; + qint64 bytesLeftToWrite() const override { return _mappedMemorySize - _bytesWritten; } + void write(const QByteArray& data) override; + +private: + FileStorage(std::unique_ptr file, uchar* mapped, qint64 size); + + // Byte array is const because any edit will trigger a deep copy + // and pull all the data we want to keep on disk in memory. + const QByteArray _wrapperArray; + std::unique_ptr _file; + + uchar* const _mappedMemoryAddress { nullptr }; + const qint64 _mappedMemorySize { 0 }; + qint64 _bytesWritten { 0 }; +}; + +std::unique_ptr FileStorage::make(qint64 size) { + auto file = std::unique_ptr(new QTemporaryFile()); + file->open(); // Open for resize + file->resize(size); + auto mapped = file->map(0, size); // map the entire file + + return std::unique_ptr(new FileStorage(std::move(file), mapped, size)); +} + +// Use QByteArray::fromRawData to avoid a new allocation and access the already existing +// memory directly as long as all operations on the array are const. +FileStorage::FileStorage(std::unique_ptr file, uchar* mapped, qint64 size) : + _wrapperArray(QByteArray::fromRawData(reinterpret_cast(mapped), size)), + _file(std::move(file)), + _mappedMemoryAddress(mapped), + _mappedMemorySize(size) +{ +} + +FileStorage::~FileStorage() { + _file->unmap(_mappedMemoryAddress); + _file->close(); +} + +void FileStorage::write(const QByteArray& data) { + assert(data.size() <= bytesLeftToWrite()); + // We write directly to the mapped memory + memcpy(_mappedMemoryAddress + _bytesWritten, data.data(), data.size()); + _bytesWritten += data.size(); +} + + +HTTPConnection::HTTPConnection(QTcpSocket* socket, HTTPManager* parentManager) : QObject(parentManager), _parentManager(parentManager), _socket(socket), - _stream(socket), _address(socket->peerAddress()) { // take over ownership of the socket @@ -62,7 +145,7 @@ QHash HTTPConnection::parseUrlEncodedForm() { return QHash(); } - QUrlQuery form { _requestContent }; + QUrlQuery form { _requestContent->content() }; QHash pairs; for (auto pair : form.queryItems()) { auto key = QUrl::fromPercentEncoding(pair.first.toLatin1().replace('+', ' ')); @@ -97,7 +180,7 @@ QList HTTPConnection::parseFormData() const { QByteArray end = "\r\n--" + boundary + "--\r\n"; QList data; - QBuffer buffer(const_cast(&_requestContent)); + QBuffer buffer(const_cast(&_requestContent->content())); buffer.open(QIODevice::ReadOnly); while (buffer.canReadLine()) { QByteArray line = buffer.readLine().trimmed(); @@ -107,12 +190,13 @@ QList HTTPConnection::parseFormData() const { QByteArray line = buffer.readLine().trimmed(); if (line.isEmpty()) { // content starts after this line - int idx = _requestContent.indexOf(end, buffer.pos()); + int idx = _requestContent->content().indexOf(end, buffer.pos()); if (idx == -1) { qWarning() << "Missing end boundary." << _address; return data; } - datum.second = _requestContent.mid(buffer.pos(), idx - buffer.pos()); + datum.second = QByteArray::fromRawData(_requestContent->content().data() + buffer.pos(), + idx - buffer.pos()); data.append(datum); buffer.seek(idx + end.length()); @@ -256,7 +340,24 @@ void HTTPConnection::readHeaders() { _parentManager->handleHTTPRequest(this, _requestUrl); } else { - _requestContent.resize(clength.toInt()); + bool success = false; + auto length = clength.toInt(&success); + if (!success) { + qWarning() << "Invalid header." << _address << trimmed; + respond("400 Bad Request", "The header was malformed."); + return; + } + + // Storing big requests in memory gets expensive, especially on servers + // with limited memory. So we store big requests in a temporary file on disk + // and map it to faster read/write access. + static const int MAX_CONTENT_SIZE_IN_MEMORY = 10 * 1000 * 1000; + if (length < MAX_CONTENT_SIZE_IN_MEMORY) { + _requestContent = MemoryStorage::make(length); + } else { + _requestContent = FileStorage::make(length); + } + connect(_socket, SIGNAL(readyRead()), SLOT(readContent())); // read any content immediately available @@ -285,12 +386,13 @@ void HTTPConnection::readHeaders() { } void HTTPConnection::readContent() { - int size = _requestContent.size(); - if (_socket->bytesAvailable() < size) { - return; - } - _socket->read(_requestContent.data(), size); - _socket->disconnect(this, SLOT(readContent())); + auto size = std::min(_socket->bytesAvailable(), _requestContent->bytesLeftToWrite()); - _parentManager->handleHTTPRequest(this, _requestUrl.path()); + _requestContent->write(_socket->read(size)); + + if (_requestContent->bytesLeftToWrite() == 0) { + _socket->disconnect(this, SLOT(readContent())); + + _parentManager->handleHTTPRequest(this, _requestUrl.path()); + } } diff --git a/libraries/embedded-webserver/src/HTTPConnection.h b/libraries/embedded-webserver/src/HTTPConnection.h index e4d23e3c90..4b42acf296 100644 --- a/libraries/embedded-webserver/src/HTTPConnection.h +++ b/libraries/embedded-webserver/src/HTTPConnection.h @@ -16,14 +16,14 @@ #ifndef hifi_HTTPConnection_h #define hifi_HTTPConnection_h -#include #include -#include #include #include +#include #include #include #include +#include #include #include @@ -57,52 +57,63 @@ public: /// WebSocket close status codes. enum ReasonCode { NoReason = 0, NormalClosure = 1000, GoingAway = 1001 }; + class Storage { + public: + Storage() = default; + virtual ~Storage() = default; + + virtual const QByteArray& content() const = 0; + + virtual qint64 bytesLeftToWrite() const = 0; + virtual void write(const QByteArray& data) = 0; + }; + /// Initializes the connection. - HTTPConnection (QTcpSocket* socket, HTTPManager* parentManager); + HTTPConnection(QTcpSocket* socket, HTTPManager* parentManager); /// Destroys the connection. - virtual ~HTTPConnection (); + virtual ~HTTPConnection(); /// Returns a pointer to the underlying socket, to which WebSocket message bodies should be written. - QTcpSocket* socket () const { return _socket; } + QTcpSocket* socket() const { return _socket; } /// Returns the request operation. - QNetworkAccessManager::Operation requestOperation () const { return _requestOperation; } + QNetworkAccessManager::Operation requestOperation() const { return _requestOperation; } /// Returns a reference to the request URL. - const QUrl& requestUrl () const { return _requestUrl; } + const QUrl& requestUrl() const { return _requestUrl; } /// Returns a copy of the request header value. If it does not exist, it will return a default constructed QByteArray. QByteArray requestHeader(const QString& key) const { return _requestHeaders.value(key.toLower().toLocal8Bit()); } /// Returns a reference to the request content. - const QByteArray& requestContent () const { return _requestContent; } + const QByteArray& requestContent() const { return _requestContent->content(); } /// Parses the request content as form data, returning a list of header/content pairs. - QList parseFormData () const; + QList parseFormData() const; /// Parses the request content as a url encoded form, returning a hash of key/value pairs. /// Duplicate keys are not supported. QHash parseUrlEncodedForm(); /// Sends a response and closes the connection. - void respond (const char* code, const QByteArray& content = QByteArray(), + void respond(const char* code, const QByteArray& content = QByteArray(), const char* contentType = DefaultContentType, const Headers& headers = Headers()); - void respond (const char* code, std::unique_ptr device, + void respond(const char* code, std::unique_ptr device, const char* contentType = DefaultContentType, const Headers& headers = Headers()); protected slots: /// Reads the request line. - void readRequest (); + void readRequest(); /// Reads the headers. - void readHeaders (); + void readHeaders(); /// Reads the content. - void readContent (); + void readContent(); protected: void respondWithStatusAndHeaders(const char* code, const char* contentType, const Headers& headers, qint64 size); @@ -112,9 +123,6 @@ protected: /// The underlying socket. QTcpSocket* _socket; - - /// The data stream for writing to the socket. - QDataStream _stream; /// The stored address. QHostAddress _address; @@ -132,7 +140,7 @@ protected: QByteArray _lastRequestHeader; /// The content of the request. - QByteArray _requestContent; + std::unique_ptr _requestContent; /// Response content std::unique_ptr _responseDevice; diff --git a/libraries/embedded-webserver/src/HTTPManager.cpp b/libraries/embedded-webserver/src/HTTPManager.cpp index db6fc8e084..4a75994e12 100644 --- a/libraries/embedded-webserver/src/HTTPManager.cpp +++ b/libraries/embedded-webserver/src/HTTPManager.cpp @@ -24,8 +24,7 @@ const int SOCKET_ERROR_EXIT_CODE = 2; const int SOCKET_CHECK_INTERVAL_IN_MS = 30000; -HTTPManager::HTTPManager(const QHostAddress& listenAddress, quint16 port, const QString& documentRoot, HTTPRequestHandler* requestHandler, QObject* parent) : - QTcpServer(parent), +HTTPManager::HTTPManager(const QHostAddress& listenAddress, quint16 port, const QString& documentRoot, HTTPRequestHandler* requestHandler) : _listenAddress(listenAddress), _documentRoot(documentRoot), _requestHandler(requestHandler), diff --git a/libraries/embedded-webserver/src/HTTPManager.h b/libraries/embedded-webserver/src/HTTPManager.h index cb76eed9f2..597f6921cc 100644 --- a/libraries/embedded-webserver/src/HTTPManager.h +++ b/libraries/embedded-webserver/src/HTTPManager.h @@ -33,7 +33,7 @@ class HTTPManager : public QTcpServer, public HTTPRequestHandler { Q_OBJECT public: /// Initializes the manager. - HTTPManager(const QHostAddress& listenAddress, quint16 port, const QString& documentRoot, HTTPRequestHandler* requestHandler = NULL, QObject* parent = 0); + HTTPManager(const QHostAddress& listenAddress, quint16 port, const QString& documentRoot, HTTPRequestHandler* requestHandler = nullptr); bool handleHTTPRequest(HTTPConnection* connection, const QUrl& url, bool skipSubHandler = false) override; diff --git a/libraries/embedded-webserver/src/HTTPSConnection.h b/libraries/embedded-webserver/src/HTTPSConnection.h index 7b53dc0063..3e6c752a08 100644 --- a/libraries/embedded-webserver/src/HTTPSConnection.h +++ b/libraries/embedded-webserver/src/HTTPSConnection.h @@ -23,4 +23,4 @@ protected slots: void handleSSLErrors(const QList& errors); }; -#endif // hifi_HTTPSConnection_h \ No newline at end of file +#endif // hifi_HTTPSConnection_h diff --git a/libraries/embedded-webserver/src/HTTPSManager.cpp b/libraries/embedded-webserver/src/HTTPSManager.cpp index 8ba44f98ac..95339a0dd6 100644 --- a/libraries/embedded-webserver/src/HTTPSManager.cpp +++ b/libraries/embedded-webserver/src/HTTPSManager.cpp @@ -16,8 +16,8 @@ #include "HTTPSConnection.h" HTTPSManager::HTTPSManager(QHostAddress listenAddress, quint16 port, const QSslCertificate& certificate, const QSslKey& privateKey, - const QString& documentRoot, HTTPSRequestHandler* requestHandler, QObject* parent) : - HTTPManager(listenAddress, port, documentRoot, requestHandler, parent), + const QString& documentRoot, HTTPSRequestHandler* requestHandler) : + HTTPManager(listenAddress, port, documentRoot, requestHandler), _certificate(certificate), _privateKey(privateKey), _sslRequestHandler(requestHandler) diff --git a/libraries/embedded-webserver/src/HTTPSManager.h b/libraries/embedded-webserver/src/HTTPSManager.h index 2d3cc9ed62..9cea32862c 100644 --- a/libraries/embedded-webserver/src/HTTPSManager.h +++ b/libraries/embedded-webserver/src/HTTPSManager.h @@ -31,7 +31,7 @@ public: const QSslCertificate& certificate, const QSslKey& privateKey, const QString& documentRoot, - HTTPSRequestHandler* requestHandler = NULL, QObject* parent = 0); + HTTPSRequestHandler* requestHandler = nullptr); void setCertificate(const QSslCertificate& certificate) { _certificate = certificate; } void setPrivateKey(const QSslKey& privateKey) { _privateKey = privateKey; } @@ -48,4 +48,4 @@ private: HTTPSRequestHandler* _sslRequestHandler; }; -#endif // hifi_HTTPSManager_h \ No newline at end of file +#endif // hifi_HTTPSManager_h diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index 600d1c32a8..afd6149d92 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -42,7 +42,19 @@ size_t std::hash::operator()(const EntityItemID& id) const { return qHash(id); } std::function EntityTreeRenderer::_entitiesShouldFadeFunction; -std::function EntityTreeRenderer::_renderDebugHullsOperator = [] { return false; }; + +QString resolveScriptURL(const QString& scriptUrl) { + auto normalizedScriptUrl = DependencyManager::get()->normalizeURL(scriptUrl); + QUrl url { normalizedScriptUrl }; + if (url.isLocalFile()) { + // Outside of the ScriptEngine, /~/ resolves to the /resources directory. + // Inside of the ScriptEngine, /~/ resolves to the /scripts directory. + // Here we expand local paths in case they are /~/ paths, so they aren't + // incorrectly recognized as being located in /scripts when utilized in ScriptEngine. + return PathUtils::expandToLocalDataAbsolutePath(url).toString(); + } + return normalizedScriptUrl; +} EntityTreeRenderer::EntityTreeRenderer(bool wantScripts, AbstractViewStateInterface* viewState, AbstractScriptingServicesInterface* scriptingServices) : @@ -221,7 +233,7 @@ void EntityTreeRenderer::reloadEntityScripts() { const auto& renderer = entry.second; const auto& entity = renderer->getEntity(); if (!entity->getScript().isEmpty()) { - _entitiesScriptEngine->loadEntityScript(entity->getEntityItemID(), entity->getScript(), true); + _entitiesScriptEngine->loadEntityScript(entity->getEntityItemID(), resolveScriptURL(entity->getScript()), true); } } } @@ -914,8 +926,7 @@ void EntityTreeRenderer::checkAndCallPreload(const EntityItemID& entityID, bool entity->scriptHasUnloaded(); } if (shouldLoad) { - scriptUrl = DependencyManager::get()->normalizeURL(scriptUrl); - _entitiesScriptEngine->loadEntityScript(entityID, scriptUrl, reload); + _entitiesScriptEngine->loadEntityScript(entityID, resolveScriptURL(scriptUrl), reload); entity->scriptHasPreloaded(); } } diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.h b/libraries/entities-renderer/src/EntityTreeRenderer.h index 882ec2fd5b..de3033936a 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.h +++ b/libraries/entities-renderer/src/EntityTreeRenderer.h @@ -116,14 +116,10 @@ public: EntityItemPointer getEntity(const EntityItemID& id); void onEntityChanged(const EntityItemID& id); - static void setRenderDebugHullsOperator(std::function renderDebugHullsOperator) { _renderDebugHullsOperator = renderDebugHullsOperator; } - static bool shouldRenderDebugHulls() { return _renderDebugHullsOperator(); } - signals: void enterEntity(const EntityItemID& entityItemID); void leaveEntity(const EntityItemID& entityItemID); void collisionWithEntity(const EntityItemID& idA, const EntityItemID& idB, const Collision& collision); - void setRenderDebugHulls(); public slots: void addingEntity(const EntityItemID& entityID); @@ -259,8 +255,6 @@ private: static int _entitiesScriptEngineCount; static CalculateEntityLoadingPriority _calculateEntityLoadingPriorityFunc; static std::function _entitiesShouldFadeFunction; - - static std::function _renderDebugHullsOperator; }; diff --git a/libraries/entities-renderer/src/RenderableEntityItem.cpp b/libraries/entities-renderer/src/RenderableEntityItem.cpp index b61f24972a..ae4c13d96f 100644 --- a/libraries/entities-renderer/src/RenderableEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableEntityItem.cpp @@ -157,16 +157,20 @@ Item::Bound EntityRenderer::getBound() { return _bound; } +render::hifi::Tag EntityRenderer::getTagMask() const { + return _isVisibleInSecondaryCamera ? render::hifi::TAG_ALL_VIEWS : render::hifi::TAG_MAIN_VIEW; +} + ItemKey EntityRenderer::getKey() { if (isTransparent()) { - return ItemKey::Builder::transparentShape().withTypeMeta().withTagBits(render::ItemKey::TAG_BITS_0 | render::ItemKey::TAG_BITS_1); + return ItemKey::Builder::transparentShape().withTypeMeta().withTagBits(getTagMask()); } // This allows shapes to cast shadows if (_canCastShadow) { - return ItemKey::Builder::opaqueShape().withTypeMeta().withTagBits(render::ItemKey::TAG_BITS_0 | render::ItemKey::TAG_BITS_1).withShadowCaster(); + return ItemKey::Builder::opaqueShape().withTypeMeta().withTagBits(getTagMask()).withShadowCaster(); } else { - return ItemKey::Builder::opaqueShape().withTypeMeta().withTagBits(render::ItemKey::TAG_BITS_0 | render::ItemKey::TAG_BITS_1); + return ItemKey::Builder::opaqueShape().withTypeMeta().withTagBits(getTagMask()); } } @@ -380,6 +384,7 @@ void EntityRenderer::doRenderUpdateSynchronous(const ScenePointer& scene, Transa _moving = entity->isMovingRelativeToParent(); _visible = entity->getVisible(); + setIsVisibleInSecondaryCamera(entity->isVisibleInSecondaryCamera()); _canCastShadow = entity->getCanCastShadow(); _cauterized = entity->getCauterized(); _needsRenderUpdate = false; diff --git a/libraries/entities-renderer/src/RenderableEntityItem.h b/libraries/entities-renderer/src/RenderableEntityItem.h index ada57c8ab0..e1ce2ed39e 100644 --- a/libraries/entities-renderer/src/RenderableEntityItem.h +++ b/libraries/entities-renderer/src/RenderableEntityItem.h @@ -18,6 +18,7 @@ #include "AbstractViewStateInterface.h" #include "EntitiesRendererLogging.h" #include +#include class EntityTreeRenderer; @@ -74,6 +75,7 @@ protected: virtual Item::Bound getBound() override; virtual void render(RenderArgs* args) override final; virtual uint32_t metaFetchMetaSubItems(ItemIDs& subItems) override; + virtual render::hifi::Tag getTagMask() const; // Returns true if the item in question needs to have updateInScene called because of internal rendering state changes virtual bool needsRenderUpdate() const; @@ -97,6 +99,8 @@ protected: bool isFading() const { return _isFading; } virtual bool isTransparent() const { return _isFading ? Interpolate::calculateFadeRatio(_fadeStartTime) < 1.0f : false; } inline bool isValidRenderItem() const { return _renderItemID != Item::INVALID_ITEM_ID; } + + virtual void setIsVisibleInSecondaryCamera(bool value) { _isVisibleInSecondaryCamera = value; } template T withReadLockResult(const std::function& f) { @@ -129,6 +133,7 @@ protected: bool _isFading{ _entitiesShouldFadeFunction() }; bool _prevIsTransparent { false }; bool _visible { false }; + bool _isVisibleInSecondaryCamera { false }; bool _canCastShadow { false }; bool _cauterized { false }; bool _moving { false }; diff --git a/libraries/entities-renderer/src/RenderableMaterialEntityItem.cpp b/libraries/entities-renderer/src/RenderableMaterialEntityItem.cpp index 7cea841bf0..eabcb68e4f 100644 --- a/libraries/entities-renderer/src/RenderableMaterialEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableMaterialEntityItem.cpp @@ -43,7 +43,7 @@ void MaterialEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& ItemKey MaterialEntityRenderer::getKey() { ItemKey::Builder builder; - builder.withTypeShape().withTagBits(render::ItemKey::TAG_BITS_0 | render::ItemKey::TAG_BITS_1); + builder.withTypeShape().withTagBits(getTagMask()); if (!_visible) { builder.withInvisible(); diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index c4fa71a488..be385008a3 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -23,7 +23,6 @@ #include #include -#include #include #include #include @@ -35,8 +34,6 @@ #include "EntitiesRendererLogging.h" -static CollisionRenderMeshCache collisionMeshCache; - void ModelEntityWrapper::setModel(const ModelPointer& model) { withWriteLock([&] { if (_model != model) { @@ -1052,17 +1049,14 @@ using namespace render; using namespace render::entities; ModelEntityRenderer::ModelEntityRenderer(const EntityItemPointer& entity) : Parent(entity) { - connect(DependencyManager::get().data(), &EntityTreeRenderer::setRenderDebugHulls, this, [&] { - _needsCollisionGeometryUpdate = true; - emit requestRenderUpdate(); - }); + } void ModelEntityRenderer::setKey(bool didVisualGeometryRequestSucceed) { if (didVisualGeometryRequestSucceed) { - _itemKey = ItemKey::Builder().withTypeMeta().withTagBits(render::ItemKey::TAG_BITS_0 | render::ItemKey::TAG_BITS_1); + _itemKey = ItemKey::Builder().withTypeMeta().withTagBits(getTagMask()); } else { - _itemKey = ItemKey::Builder().withTypeMeta().withTypeShape().withTagBits(render::ItemKey::TAG_BITS_0 | render::ItemKey::TAG_BITS_1); + _itemKey = ItemKey::Builder().withTypeMeta().withTypeShape().withTagBits(getTagMask()); } } @@ -1070,6 +1064,13 @@ ItemKey ModelEntityRenderer::getKey() { return _itemKey; } +render::hifi::Tag ModelEntityRenderer::getTagMask() const { + // Default behavior for model is to not be visible in main view if cauterized (aka parented to the avatar's neck joint) + return _cauterized ? + (_isVisibleInSecondaryCamera ? render::hifi::TAG_SECONDARY_VIEW : render::hifi::TAG_NONE) : + Parent::getTagMask(); // calculate which views to be shown in +} + uint32_t ModelEntityRenderer::metaFetchMetaSubItems(ItemIDs& subItems) { if (_model) { auto metaSubItems = _subRenderItemIDs; @@ -1208,10 +1209,6 @@ bool ModelEntityRenderer::needsRenderUpdate() const { if (model->getRenderItemsNeedUpdate()) { return true; } - - if (_needsCollisionGeometryUpdate) { - return true; - } } return Parent::needsRenderUpdate(); } @@ -1278,12 +1275,7 @@ bool ModelEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPoin } void ModelEntityRenderer::setCollisionMeshKey(const void*key) { - if (key != _collisionMeshKey) { - if (_collisionMeshKey) { - collisionMeshCache.releaseMesh(_collisionMeshKey); - } - _collisionMeshKey = key; - } + _collisionMeshKey = key; } void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) { @@ -1329,9 +1321,9 @@ void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce emit DependencyManager::get()-> modelAddedToScene(entity->getEntityItemID(), NestableType::Entity, _model); } + _didLastVisualGeometryRequestSucceed = didVisualGeometryRequestSucceed; }); connect(model.get(), &Model::requestRenderUpdate, this, &ModelEntityRenderer::requestRenderUpdate); - connect(entity.get(), &RenderableModelEntityItem::requestCollisionGeometryUpdate, this, &ModelEntityRenderer::flagForCollisionGeometryUpdate); model->setLoadingPriority(EntityTreeRenderer::getEntityLoadingPriority(*entity)); entity->setModel(model); withWriteLock([&] { _model = model; }); @@ -1386,41 +1378,22 @@ void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce entity->updateModelBounds(); entity->stopModelOverrideIfNoParent(); - // Default behavior for model is to not be visible in main view if cauterized (aka parented to the avatar's neck joint) - uint32_t viewTaskBits = _cauterized ? - render::ItemKey::TAG_BITS_1 : // draw in every view except the main one (view zero) - render::ItemKey::TAG_BITS_ALL; // draw in all views - - if (model->isVisible() != _visible || model->getViewTagBits() != viewTaskBits) { + render::hifi::Tag tagMask = getTagMask(); + if (model->isVisible() != _visible) { // FIXME: this seems like it could be optimized if we tracked our last known visible state in // the renderable item. As it stands now the model checks it's visible/invisible state // so most of the time we don't do anything in this function. - model->setVisibleInScene(_visible, scene, viewTaskBits, false); + model->setVisibleInScene(_visible, scene); } + + if (model->getTagMask() != tagMask) { + model->setTagMask(tagMask, scene); + } + // TODO? early exit here when not visible? if (model->canCastShadow() != _canCastShadow) { - model->setCanCastShadow(_canCastShadow, scene, viewTaskBits, false); - } - - if (_needsCollisionGeometryUpdate) { - setCollisionMeshKey(entity->getCollisionMeshKey()); - _needsCollisionGeometryUpdate = false; - ShapeType type = entity->getShapeType(); - if (DependencyManager::get()->shouldRenderDebugHulls() && type != SHAPE_TYPE_STATIC_MESH && type != SHAPE_TYPE_NONE) { - // NOTE: it is OK if _collisionMeshKey is nullptr - graphics::MeshPointer mesh = collisionMeshCache.getMesh(_collisionMeshKey); - // NOTE: the model will render the collisionGeometry if it has one - _model->setCollisionMesh(mesh); - } else { - if (_collisionMeshKey) { - // release mesh - collisionMeshCache.releaseMesh(_collisionMeshKey); - } - // clear model's collision geometry - graphics::MeshPointer mesh = nullptr; - _model->setCollisionMesh(mesh); - } + model->setCanCastShadow(_canCastShadow, scene); } { @@ -1473,9 +1446,9 @@ void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce } } -void ModelEntityRenderer::flagForCollisionGeometryUpdate() { - _needsCollisionGeometryUpdate = true; - emit requestRenderUpdate(); +void ModelEntityRenderer::setIsVisibleInSecondaryCamera(bool value) { + Parent::setIsVisibleInSecondaryCamera(value); + setKey(_didLastVisualGeometryRequestSucceed); } // NOTE: this only renders the "meta" portion of the Model, namely it renders debugging items diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.h b/libraries/entities-renderer/src/RenderableModelEntityItem.h index 68bc70c8a9..50d8801363 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.h +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.h @@ -161,9 +161,12 @@ protected: virtual bool needsRenderUpdate() const override; virtual void doRender(RenderArgs* args) override; virtual void doRenderUpdateSynchronousTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) override; - void flagForCollisionGeometryUpdate(); void setCollisionMeshKey(const void* key); + render::hifi::Tag getTagMask() const override; + + void setIsVisibleInSecondaryCamera(bool value) override; + private: void animate(const TypedEntityPointer& entity); void mapJoints(const TypedEntityPointer& entity, const QStringList& modelJointNames); @@ -185,7 +188,6 @@ private: #endif bool _needsJointSimulation { false }; - bool _needsCollisionGeometryUpdate { false }; const void* _collisionMeshKey { nullptr }; // used on client side @@ -202,6 +204,8 @@ private: render::ItemKey _itemKey { render::ItemKey::Builder().withTypeMeta() }; + bool _didLastVisualGeometryRequestSucceed { true }; + void processMaterials(); }; diff --git a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp index ee77646920..881c39c0bd 100644 --- a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp @@ -147,9 +147,9 @@ void ParticleEffectEntityRenderer::doRenderUpdateAsynchronousTyped(const TypedEn ItemKey ParticleEffectEntityRenderer::getKey() { if (_visible) { - return ItemKey::Builder::transparentShape().withTagBits(render::ItemKey::TAG_BITS_0 | render::ItemKey::TAG_BITS_1); + return ItemKey::Builder::transparentShape().withTagBits(getTagMask()); } else { - return ItemKey::Builder().withInvisible().withTagBits(render::ItemKey::TAG_BITS_0 | render::ItemKey::TAG_BITS_1).build(); + return ItemKey::Builder().withInvisible().withTagBits(getTagMask()).build(); } } diff --git a/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp index d571eac35c..7cab57123d 100644 --- a/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp @@ -112,7 +112,7 @@ PolyLineEntityRenderer::PolyLineEntityRenderer(const EntityItemPointer& entity) } ItemKey PolyLineEntityRenderer::getKey() { - return ItemKey::Builder::transparentShape().withTypeMeta().withTagBits(render::ItemKey::TAG_BITS_0 | render::ItemKey::TAG_BITS_1); + return ItemKey::Builder::transparentShape().withTypeMeta().withTagBits(getTagMask()); } ShapeKey PolyLineEntityRenderer::getShapeKey() { diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h index 70c87dca6f..7077ae799b 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h @@ -169,7 +169,7 @@ public: } protected: - virtual ItemKey getKey() override { return ItemKey::Builder::opaqueShape().withTagBits(render::ItemKey::TAG_BITS_0 | render::ItemKey::TAG_BITS_1); } + virtual ItemKey getKey() override { return ItemKey::Builder::opaqueShape().withTagBits(getTagMask()); } virtual ShapeKey getShapeKey() override; virtual bool needsRenderUpdateFromTypedEntity(const TypedEntityPointer& entity) const override; virtual void doRenderUpdateSynchronousTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) override; diff --git a/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp b/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp index 1d34837a58..69068b81d2 100644 --- a/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp @@ -139,7 +139,7 @@ bool ShapeEntityRenderer::isTransparent() const { ItemKey ShapeEntityRenderer::getKey() { ItemKey::Builder builder; - builder.withTypeShape().withTypeMeta().withTagBits(render::ItemKey::TAG_BITS_0 | render::ItemKey::TAG_BITS_1); + builder.withTypeShape().withTypeMeta().withTagBits(getTagMask()); withReadLock([&] { if (isTransparent()) { diff --git a/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp b/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp index 5062162b6e..c5035431f6 100644 --- a/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp @@ -269,7 +269,7 @@ void ZoneEntityRenderer::doRenderUpdateAsynchronousTyped(const TypedEntityPointe ItemKey ZoneEntityRenderer::getKey() { - return ItemKey::Builder().withTypeMeta().withTagBits(render::ItemKey::TAG_BITS_0 | render::ItemKey::TAG_BITS_1).build(); + return ItemKey::Builder().withTypeMeta().withTagBits(getTagMask()).build(); } bool ZoneEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPointer& entity) const { diff --git a/libraries/entities-renderer/src/textured_particle.slv b/libraries/entities-renderer/src/textured_particle.slv index cab76227c4..1d4261b1cc 100644 --- a/libraries/entities-renderer/src/textured_particle.slv +++ b/libraries/entities-renderer/src/textured_particle.slv @@ -37,8 +37,8 @@ layout(std140) uniform particleBuffer { ParticleUniforms particle; }; -in vec3 inPosition; -in vec2 inColor; // This is actual Lifetime + Seed +layout(location=0) in vec3 inPosition; +layout(location=2) in vec2 inColor; // This is actual Lifetime + Seed out vec4 varColor; out vec2 varTexcoord; diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index d9365b516f..70881fbc40 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -1383,6 +1383,7 @@ bool EntityItem::setProperties(const EntityItemProperties& properties) { SET_ENTITY_PROPERTY_FROM_PROPERTIES(visible, setVisible); SET_ENTITY_PROPERTY_FROM_PROPERTIES(canCastShadow, setCanCastShadow); SET_ENTITY_PROPERTY_FROM_PROPERTIES(userData, setUserData); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(isVisibleInSecondaryCamera, setIsVisibleInSecondaryCamera); // Certifiable Properties SET_ENTITY_PROPERTY_FROM_PROPERTIES(itemName, setItemName); @@ -2760,6 +2761,28 @@ void EntityItem::setVisible(bool value) { } } +bool EntityItem::isVisibleInSecondaryCamera() const { + bool result; + withReadLock([&] { + result = _isVisibleInSecondaryCamera; + }); + return result; +} + +void EntityItem::setIsVisibleInSecondaryCamera(bool value) { + bool changed = false; + withWriteLock([&] { + if (_isVisibleInSecondaryCamera != value) { + changed = true; + _isVisibleInSecondaryCamera = value; + } + }); + + if (changed) { + emit requestRenderUpdate(); + } +} + bool EntityItem::getCanCastShadow() const { bool result; withReadLock([&] { diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index bc4767d3db..0acf8dbbc1 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -277,6 +277,9 @@ public: bool getVisible() const; void setVisible(bool value); + bool isVisibleInSecondaryCamera() const; + void setIsVisibleInSecondaryCamera(bool value); + bool getCanCastShadow() const; void setCanCastShadow(bool value); @@ -578,6 +581,7 @@ protected: glm::vec3 _registrationPoint { ENTITY_ITEM_DEFAULT_REGISTRATION_POINT }; float _angularDamping { ENTITY_ITEM_DEFAULT_ANGULAR_DAMPING }; bool _visible { ENTITY_ITEM_DEFAULT_VISIBLE }; + bool _isVisibleInSecondaryCamera { ENTITY_ITEM_DEFAULT_VISIBLE_IN_SECONDARY_CAMERA }; bool _canCastShadow{ ENTITY_ITEM_DEFAULT_CAN_CAST_SHADOW }; bool _collisionless { ENTITY_ITEM_DEFAULT_COLLISIONLESS }; uint16_t _collisionMask { ENTITY_COLLISION_MASK_DEFAULT }; diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 3ada45f4a0..84e248b74d 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -368,6 +368,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { CHECK_PROPERTY_CHANGE(PROP_MATERIAL_MAPPING_SCALE, materialMappingScale); CHECK_PROPERTY_CHANGE(PROP_MATERIAL_MAPPING_ROT, materialMappingRot); CHECK_PROPERTY_CHANGE(PROP_MATERIAL_DATA, materialData); + CHECK_PROPERTY_CHANGE(PROP_VISIBLE_IN_SECONDARY_CAMERA, isVisibleInSecondaryCamera); // Certifiable Properties CHECK_PROPERTY_CHANGE(PROP_ITEM_NAME, itemName); @@ -490,6 +491,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { * {@link Entities.EntityType|Model} and {@link Entities.EntityType|Shape} entities. Shadows are cast if inside a * {@link Entities.EntityType|Zone} entity with castShadows enabled in its * {@link Entities.EntityProperties-Zone|keyLight} property. + * @property {boolean} isVisibleInSecondaryCamera=true - Whether or not the entity is rendered in the secondary camera. If true then the entity is rendered. * * @property {Vec3} position=0,0,0 - The position of the entity. * @property {Quat} rotation=0,0,0,1 - The orientation of the entity with respect to world coordinates. @@ -595,6 +597,15 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { * @property {Entities.RenderInfo} renderInfo - Information on the cost of rendering the entity. Currently information is only * provided for Model entities. Read-only. * + * @property {boolean} cloneable=false - If true then the entity can be cloned via {@link Entities.cloneEntity}. + * @property {number} cloneLifetime=300 - The entity lifetime for clones created from this entity. + * @property {number} cloneLimit=0 - The total number of clones of this entity that can exist in the domain at any given time. + * @property {boolean} cloneDynamic=false - If true then clones created from this entity will have their + * dynamic property set to true. + * @property {boolean} cloneAvatarEntity=false - If true then clones created from this entity will be created as + * avatar entities: their clientOnly property will be set to true. + * @property {Uuid} cloneOriginID - The ID of the entity that this entity was cloned from. + * * @property {string} itemName="" - Certifiable name of the Marketplace item. * @property {string} itemDescription="" - Certifiable description of the Marketplace item. * @property {string} itemCategories="" - Certifiable category of the Marketplace item. @@ -1226,6 +1237,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_LOCKED, locked); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_USER_DATA, userData); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ALPHA, alpha); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_VISIBLE_IN_SECONDARY_CAMERA, isVisibleInSecondaryCamera); // Certifiable Properties COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ITEM_NAME, itemName); @@ -1565,6 +1577,7 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool COPY_PROPERTY_FROM_QSCRIPTVALUE(materialMappingScale, glmVec2, setMaterialMappingScale); COPY_PROPERTY_FROM_QSCRIPTVALUE(materialMappingRot, float, setMaterialMappingRot); COPY_PROPERTY_FROM_QSCRIPTVALUE(materialData, QString, setMaterialData); + COPY_PROPERTY_FROM_QSCRIPTVALUE(isVisibleInSecondaryCamera, bool, setIsVisibleInSecondaryCamera); // Certifiable Properties COPY_PROPERTY_FROM_QSCRIPTVALUE(itemName, QString, setItemName); @@ -1896,7 +1909,7 @@ void EntityItemProperties::entityPropertyFlagsFromScriptValue(const QScriptValue ADD_PROPERTY_TO_MAP(PROP_ANGULAR_VELOCITY, AngularVelocity, angularVelocity, glm::vec3); ADD_PROPERTY_TO_MAP(PROP_ANGULAR_DAMPING, AngularDamping, angularDamping, float); ADD_PROPERTY_TO_MAP(PROP_COLLISIONLESS, Collisionless, collisionless, bool); - ADD_PROPERTY_TO_MAP(PROP_DYNAMIC, unused, ignoreForCollisions, unused); // legacy support + ADD_PROPERTY_TO_MAP(PROP_COLLISIONLESS, unused, ignoreForCollisions, unused); // legacy support ADD_PROPERTY_TO_MAP(PROP_COLLISION_MASK, unused, collisionMask, unused); ADD_PROPERTY_TO_MAP(PROP_COLLISION_MASK, unused, collidesWith, unused); ADD_PROPERTY_TO_MAP(PROP_DYNAMIC, unused, collisionsWillMove, unused); // legacy support @@ -1944,6 +1957,8 @@ void EntityItemProperties::entityPropertyFlagsFromScriptValue(const QScriptValue ADD_PROPERTY_TO_MAP(PROP_MATERIAL_MAPPING_ROT, MaterialMappingRot, materialMappingRot, float); ADD_PROPERTY_TO_MAP(PROP_MATERIAL_DATA, MaterialData, materialData, QString); + ADD_PROPERTY_TO_MAP(PROP_VISIBLE_IN_SECONDARY_CAMERA, IsVisibleInSecondaryCamera, isVisibleInSecondaryCamera, bool); + // Certifiable Properties ADD_PROPERTY_TO_MAP(PROP_ITEM_NAME, ItemName, itemName, QString); ADD_PROPERTY_TO_MAP(PROP_ITEM_DESCRIPTION, ItemDescription, itemDescription, QString); @@ -3041,6 +3056,8 @@ void EntityItemProperties::markAllChanged() { _cloneDynamicChanged = true; _cloneAvatarEntityChanged = true; _cloneOriginIDChanged = true; + + _isVisibleInSecondaryCameraChanged = true; } // The minimum bounding box for the entity. @@ -3319,6 +3336,9 @@ QList EntityItemProperties::listChangedProperties() { if (materialDataChanged()) { out += "materialData"; } + if (isVisibleInSecondaryCameraChanged()) { + out += "isVisibleInSecondaryCamera"; + } // Certifiable Properties if (itemNameChanged()) { diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index 01c43a46b3..e46eb73910 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -233,6 +233,8 @@ public: DEFINE_PROPERTY_REF(PROP_MATERIAL_MAPPING_ROT, MaterialMappingRot, materialMappingRot, float, 0); DEFINE_PROPERTY_REF(PROP_MATERIAL_DATA, MaterialData, materialData, QString, ""); + DEFINE_PROPERTY(PROP_VISIBLE_IN_SECONDARY_CAMERA, IsVisibleInSecondaryCamera, isVisibleInSecondaryCamera, bool, ENTITY_ITEM_DEFAULT_VISIBLE_IN_SECONDARY_CAMERA); + // Certifiable Properties - related to Proof of Purchase certificates DEFINE_PROPERTY_REF(PROP_ITEM_NAME, ItemName, itemName, QString, ENTITY_ITEM_DEFAULT_ITEM_NAME); DEFINE_PROPERTY_REF(PROP_ITEM_DESCRIPTION, ItemDescription, itemDescription, QString, ENTITY_ITEM_DEFAULT_ITEM_DESCRIPTION); diff --git a/libraries/entities/src/EntityItemPropertiesDefaults.h b/libraries/entities/src/EntityItemPropertiesDefaults.h index 0fd926e677..6c39e90c91 100644 --- a/libraries/entities/src/EntityItemPropertiesDefaults.h +++ b/libraries/entities/src/EntityItemPropertiesDefaults.h @@ -46,6 +46,7 @@ const quint32 ENTITY_ITEM_DEFAULT_STATIC_CERTIFICATE_VERSION = 0; const float ENTITY_ITEM_DEFAULT_ALPHA = 1.0f; const float ENTITY_ITEM_DEFAULT_LOCAL_RENDER_ALPHA = 1.0f; const bool ENTITY_ITEM_DEFAULT_VISIBLE = true; +const bool ENTITY_ITEM_DEFAULT_VISIBLE_IN_SECONDARY_CAMERA = true; const bool ENTITY_ITEM_DEFAULT_CAN_CAST_SHADOW { true }; const QString ENTITY_ITEM_DEFAULT_SCRIPT = QString(""); diff --git a/libraries/entities/src/EntityPropertyFlags.h b/libraries/entities/src/EntityPropertyFlags.h index 2fdcd62949..d43a991f22 100644 --- a/libraries/entities/src/EntityPropertyFlags.h +++ b/libraries/entities/src/EntityPropertyFlags.h @@ -249,6 +249,8 @@ enum EntityPropertyList { PROP_MATERIAL_MAPPING_ROT, PROP_MATERIAL_DATA, + PROP_VISIBLE_IN_SECONDARY_CAMERA, // not sent over the wire, only used locally + //////////////////////////////////////////////////////////////////////////////////////////////////// // ATTENTION: add new properties to end of list just ABOVE this line PROP_AFTER_LAST_ITEM, diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index f751a4f8ed..98407a74c6 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -1688,15 +1688,21 @@ QVector EntityScriptingInterface::getChildrenIDs(const QUuid& parentID) { if (!_entityTree) { return result; } - - EntityItemPointer entity = _entityTree->findEntityByEntityItemID(parentID); - if (!entity) { - qCDebug(entities) << "EntityScriptingInterface::getChildrenIDs - no entity with ID" << parentID; - return result; - } - _entityTree->withReadLock([&] { - entity->forEachChild([&](SpatiallyNestablePointer child) { + QSharedPointer parentFinder = DependencyManager::get(); + if (!parentFinder) { + return; + } + bool success; + SpatiallyNestableWeakPointer parentWP = parentFinder->find(parentID, success); + if (!success) { + return; + } + SpatiallyNestablePointer parent = parentWP.lock(); + if (!parent) { + return; + } + parent->forEachChild([&](SpatiallyNestablePointer child) { result.push_back(child->getID()); }); }); diff --git a/libraries/entities/src/EntityScriptingInterface.h b/libraries/entities/src/EntityScriptingInterface.h index 9acbc13251..50df825e5f 100644 --- a/libraries/entities/src/EntityScriptingInterface.h +++ b/libraries/entities/src/EntityScriptingInterface.h @@ -225,12 +225,12 @@ public slots: bool collisionless, const glm::vec3& position, const glm::vec3& gravity); /**jsdoc - * Request a clone of an entity. Only entities that have been marked as 'cloneable' will be able to be cloned using this method. - * A cloned entity has most of the properties of the orignal entity, and can be requested from clients that do not have rez permissions. - * The client requests a clone from the entity server, which returns back the entityID of a valid clone if the operation was allowed. + * Create a clone of an entity. A clone can be created by a client that doesn't have rez permissions in the current domain. + * The entity must have its cloneable property set to true. The clone has a modified name, other + * properties set per its clone related-properties, and its clone-related properties are set to defaults. * @function Entities.cloneEntity - * @param {Uuid} entityIDToClone - the ID of the entity to clone - * @returns {Entities.EntityID} The ID of the newly created clone + * @param {Uuid} entityID - The ID of the entity to clone. + * @returns {Uuid} The ID of the new entity if successfully cloned, otherwise {@link Uuid|Uuid.NULL}. */ Q_INVOKABLE QUuid cloneEntity(QUuid entityIDToClone); @@ -1226,12 +1226,11 @@ public slots: /**jsdoc - * Get the IDs of entities, overlays, and avatars that are directly parented to an entity. To get all descendants of an - * entity, recurse on the IDs returned by the function. + * Get the IDs of entities, overlays, and avatars that are directly parented to an entity, overlay, or avatar model. Recurse on the IDs returned by the function to get all descendants of an entity, overlay, or avatar. * @function Entities.getChildrenIDs - * @param {Uuid} parentID - The ID of the entity to get the children IDs of. + * @param {Uuid} parentID - The ID of the entity, overlay, or avatar to get the children IDs of. * @returns {Uuid[]} An array of entity, overlay, and avatar IDs that are parented directly to the parentID - * entity. Does not include children's children, etc. The array is empty if no children can be found or + * entity, overlay, or avatar. Does not include children's children, etc. The array is empty if no children can be found or * parentID cannot be found. * @example
    * function createEntity(description, position, parent) { diff --git a/libraries/gpu/src/gpu/Texture.cpp b/libraries/gpu/src/gpu/Texture.cpp index ed9505766b..a92243f808 100755 --- a/libraries/gpu/src/gpu/Texture.cpp +++ b/libraries/gpu/src/gpu/Texture.cpp @@ -684,9 +684,9 @@ bool sphericalHarmonicsFromTexture(const gpu::Texture& cubeTexture, std::vector< PROFILE_RANGE(render_gpu, "sphericalHarmonicsFromTexture"); +#ifndef USE_GLES auto mipFormat = cubeTexture.getStoredMipFormat(); std::function unpackFunc; - switch (mipFormat.getSemantic()) { case gpu::R11G11B10: unpackFunc = glm::unpackF2x11_1x10; @@ -698,6 +698,7 @@ bool sphericalHarmonicsFromTexture(const gpu::Texture& cubeTexture, std::vector< assert(false); break; } +#endif const uint sqOrder = order*order; @@ -732,7 +733,11 @@ bool sphericalHarmonicsFromTexture(const gpu::Texture& cubeTexture, std::vector< for(int face=0; face < gpu::Texture::NUM_CUBE_FACES; face++) { PROFILE_RANGE(render_gpu, "ProcessFace"); +#ifndef USE_GLES auto data = reinterpret_cast( cubeTexture.accessStoredMipFace(0, face)->readData() ); +#else + auto data = cubeTexture.accessStoredMipFace(0, face)->readData(); +#endif if (data == nullptr) { continue; } @@ -816,8 +821,15 @@ bool sphericalHarmonicsFromTexture(const gpu::Texture& cubeTexture, std::vector< glm::vec3 color{ 0.0f, 0.0f, 0.0f }; for (int i = 0; i < stride; ++i) { for (int j = 0; j < stride; ++j) { +#ifndef USE_GLES int k = (int)(x + i - halfStride + (y + j - halfStride) * width); color += unpackFunc(data[k]); +#else + const int NUM_COMPONENTS_PER_PIXEL = 4; + int k = NUM_COMPONENTS_PER_PIXEL * (int)(x + i - halfStride + (y + j - halfStride) * width); + // BGRA -> RGBA + color += glm::pow(glm::vec3(data[k + 2], data[k + 1], data[k]) / 255.0f, glm::vec3(2.2f)); +#endif } } diff --git a/libraries/gpu/src/gpu/Texture_ktx.cpp b/libraries/gpu/src/gpu/Texture_ktx.cpp index 129c125411..839cb915e2 100644 --- a/libraries/gpu/src/gpu/Texture_ktx.cpp +++ b/libraries/gpu/src/gpu/Texture_ktx.cpp @@ -352,7 +352,7 @@ ktx::KTXUniquePointer Texture::serialize(const Texture& texture) { if (!Texture::evalKTXFormat(mipFormat, texelFormat, header)) { return nullptr; } - + // Set Dimensions uint32_t numFaces = 1; switch (texture.getType()) { diff --git a/libraries/image/CMakeLists.txt b/libraries/image/CMakeLists.txt index 8073cc2b5e..6bc5c762f5 100644 --- a/libraries/image/CMakeLists.txt +++ b/libraries/image/CMakeLists.txt @@ -2,3 +2,4 @@ set(TARGET_NAME image) setup_hifi_library() link_hifi_libraries(shared gpu) target_nvtt() +target_etc2comp() diff --git a/libraries/image/src/image/Image.cpp b/libraries/image/src/image/Image.cpp index c1c07838c7..7fc3a73f87 100644 --- a/libraries/image/src/image/Image.cpp +++ b/libraries/image/src/image/Image.cpp @@ -31,18 +31,24 @@ using namespace gpu; #define CPU_MIPMAPS 1 #include +#ifdef USE_GLES +#include +#include +#endif + static const glm::uvec2 SPARSE_PAGE_SIZE(128); +#ifdef Q_OS_ANDROID +static const glm::uvec2 MAX_TEXTURE_SIZE(2048); +#else static const glm::uvec2 MAX_TEXTURE_SIZE(4096); +#endif bool DEV_DECIMATE_TEXTURES = false; std::atomic DECIMATED_TEXTURE_COUNT{ 0 }; std::atomic RECTIFIED_TEXTURE_COUNT{ 0 }; -static const auto HDR_FORMAT = gpu::Element::COLOR_R11G11B10; - -static std::atomic compressColorTextures { false }; -static std::atomic compressNormalTextures { false }; -static std::atomic compressGrayscaleTextures { false }; -static std::atomic compressCubeTextures { false }; +// we use a ref here to work around static order initialization +// possibly causing the element not to be constructed yet +static const auto& HDR_FORMAT = gpu::Element::COLOR_R11G11B10; uint rectifyDimension(const uint& dimension) { if (dimension == 0) { @@ -75,7 +81,13 @@ const QStringList getSupportedFormats() { return stringFormats; } + +// On GLES, we don't use HDR skyboxes +#ifndef USE_GLES QImage::Format QIMAGE_HDR_FORMAT = QImage::Format_RGB30; +#else +QImage::Format QIMAGE_HDR_FORMAT = QImage::Format_RGB32; +#endif TextureUsage::TextureLoader TextureUsage::getTextureLoaderForType(Type type, const QVariantMap& options) { switch (type) { @@ -111,112 +123,63 @@ TextureUsage::TextureLoader TextureUsage::getTextureLoaderForType(Type type, con } gpu::TexturePointer TextureUsage::createStrict2DTextureFromImage(QImage&& srcImage, const std::string& srcImageName, - const std::atomic& abortProcessing) { - return process2DTextureColorFromImage(std::move(srcImage), srcImageName, true, abortProcessing); + bool compress, const std::atomic& abortProcessing) { + return process2DTextureColorFromImage(std::move(srcImage), srcImageName, compress, true, abortProcessing); } gpu::TexturePointer TextureUsage::create2DTextureFromImage(QImage&& srcImage, const std::string& srcImageName, - const std::atomic& abortProcessing) { - return process2DTextureColorFromImage(std::move(srcImage), srcImageName, false, abortProcessing); + bool compress, const std::atomic& abortProcessing) { + return process2DTextureColorFromImage(std::move(srcImage), srcImageName, compress, false, abortProcessing); } gpu::TexturePointer TextureUsage::createAlbedoTextureFromImage(QImage&& srcImage, const std::string& srcImageName, - const std::atomic& abortProcessing) { - return process2DTextureColorFromImage(std::move(srcImage), srcImageName, false, abortProcessing); + bool compress, const std::atomic& abortProcessing) { + return process2DTextureColorFromImage(std::move(srcImage), srcImageName, compress, false, abortProcessing); } gpu::TexturePointer TextureUsage::createEmissiveTextureFromImage(QImage&& srcImage, const std::string& srcImageName, - const std::atomic& abortProcessing) { - return process2DTextureColorFromImage(std::move(srcImage), srcImageName, false, abortProcessing); + bool compress, const std::atomic& abortProcessing) { + return process2DTextureColorFromImage(std::move(srcImage), srcImageName, compress, false, abortProcessing); } gpu::TexturePointer TextureUsage::createLightmapTextureFromImage(QImage&& srcImage, const std::string& srcImageName, - const std::atomic& abortProcessing) { - return process2DTextureColorFromImage(std::move(srcImage), srcImageName, false, abortProcessing); + bool compress, const std::atomic& abortProcessing) { + return process2DTextureColorFromImage(std::move(srcImage), srcImageName, compress, false, abortProcessing); } gpu::TexturePointer TextureUsage::createNormalTextureFromNormalImage(QImage&& srcImage, const std::string& srcImageName, - const std::atomic& abortProcessing) { - return process2DTextureNormalMapFromImage(std::move(srcImage), srcImageName, false, abortProcessing); + bool compress, const std::atomic& abortProcessing) { + return process2DTextureNormalMapFromImage(std::move(srcImage), srcImageName, compress, false, abortProcessing); } gpu::TexturePointer TextureUsage::createNormalTextureFromBumpImage(QImage&& srcImage, const std::string& srcImageName, - const std::atomic& abortProcessing) { - return process2DTextureNormalMapFromImage(std::move(srcImage), srcImageName, true, abortProcessing); + bool compress, const std::atomic& abortProcessing) { + return process2DTextureNormalMapFromImage(std::move(srcImage), srcImageName, compress, true, abortProcessing); } gpu::TexturePointer TextureUsage::createRoughnessTextureFromImage(QImage&& srcImage, const std::string& srcImageName, - const std::atomic& abortProcessing) { - return process2DTextureGrayscaleFromImage(std::move(srcImage), srcImageName, false, abortProcessing); + bool compress, const std::atomic& abortProcessing) { + return process2DTextureGrayscaleFromImage(std::move(srcImage), srcImageName, compress, false, abortProcessing); } gpu::TexturePointer TextureUsage::createRoughnessTextureFromGlossImage(QImage&& srcImage, const std::string& srcImageName, - const std::atomic& abortProcessing) { - return process2DTextureGrayscaleFromImage(std::move(srcImage), srcImageName, true, abortProcessing); + bool compress, const std::atomic& abortProcessing) { + return process2DTextureGrayscaleFromImage(std::move(srcImage), srcImageName, compress, true, abortProcessing); } gpu::TexturePointer TextureUsage::createMetallicTextureFromImage(QImage&& srcImage, const std::string& srcImageName, - const std::atomic& abortProcessing) { - return process2DTextureGrayscaleFromImage(std::move(srcImage), srcImageName, false, abortProcessing); + bool compress, const std::atomic& abortProcessing) { + return process2DTextureGrayscaleFromImage(std::move(srcImage), srcImageName, compress, false, abortProcessing); } gpu::TexturePointer TextureUsage::createCubeTextureFromImage(QImage&& srcImage, const std::string& srcImageName, - const std::atomic& abortProcessing) { - return processCubeTextureColorFromImage(std::move(srcImage), srcImageName, true, abortProcessing); + bool compress, const std::atomic& abortProcessing) { + return processCubeTextureColorFromImage(std::move(srcImage), srcImageName, compress, true, abortProcessing); } gpu::TexturePointer TextureUsage::createCubeTextureFromImageWithoutIrradiance(QImage&& srcImage, const std::string& srcImageName, - const std::atomic& abortProcessing) { - return processCubeTextureColorFromImage(std::move(srcImage), srcImageName, false, abortProcessing); -} - - -bool isColorTexturesCompressionEnabled() { -#if CPU_MIPMAPS - return compressColorTextures.load(); -#else - return false; -#endif -} - -bool isNormalTexturesCompressionEnabled() { -#if CPU_MIPMAPS - return compressNormalTextures.load(); -#else - return false; -#endif -} - -bool isGrayscaleTexturesCompressionEnabled() { -#if CPU_MIPMAPS - return compressGrayscaleTextures.load(); -#else - return false; -#endif -} - -bool isCubeTexturesCompressionEnabled() { -#if CPU_MIPMAPS - return compressCubeTextures.load(); -#else - return false; -#endif -} - -void setColorTexturesCompressionEnabled(bool enabled) { - compressColorTextures.store(enabled); -} - -void setNormalTexturesCompressionEnabled(bool enabled) { - compressNormalTextures.store(enabled); -} - -void setGrayscaleTexturesCompressionEnabled(bool enabled) { - compressGrayscaleTextures.store(enabled); -} - -void setCubeTexturesCompressionEnabled(bool enabled) { - compressCubeTextures.store(enabled); + bool compress, const std::atomic& abortProcessing) { + return processCubeTextureColorFromImage(std::move(srcImage), srcImageName, compress, false, abortProcessing); } static float denormalize(float value, const float minValue) { @@ -238,17 +201,11 @@ uint32 packR11G11B10F(const glm::vec3& color) { return glm::packF2x11_1x10(ucolor); } -QImage processRawImageData(QByteArray&& content, const std::string& filename) { - // Take a local copy to force move construction - // https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#f18-for-consume-parameters-pass-by-x-and-stdmove-the-parameter - QByteArray localCopy = std::move(content); - +QImage processRawImageData(QIODevice& content, const std::string& filename) { // Help the QImage loader by extracting the image file format from the url filename ext. // Some tga are not created properly without it. auto filenameExtension = filename.substr(filename.find_last_of('.') + 1); - QBuffer buffer; - buffer.setData(localCopy); - QImageReader imageReader(&buffer, filenameExtension.c_str()); + QImageReader imageReader(&content, filenameExtension.c_str()); if (imageReader.canRead()) { return imageReader.read(); @@ -256,8 +213,8 @@ QImage processRawImageData(QByteArray&& content, const std::string& filename) { // Extension could be incorrect, try to detect the format from the content QImageReader newImageReader; newImageReader.setDecideFormatFromContent(true); - buffer.setData(localCopy); - newImageReader.setDevice(&buffer); + content.reset(); + newImageReader.setDevice(&content); if (newImageReader.canRead()) { qCWarning(imagelogging) << "Image file" << filename.c_str() << "has extension" << filenameExtension.c_str() @@ -269,11 +226,14 @@ QImage processRawImageData(QByteArray&& content, const std::string& filename) { return QImage(); } -gpu::TexturePointer processImage(QByteArray&& content, const std::string& filename, +gpu::TexturePointer processImage(std::shared_ptr content, const std::string& filename, int maxNumPixels, TextureUsage::Type textureType, - const std::atomic& abortProcessing) { + bool compress, const std::atomic& abortProcessing) { - QImage image = processRawImageData(std::move(content), filename); + QImage image = processRawImageData(*content.get(), filename); + // Texture content can take up a lot of memory. Here we release our ownership of that content + // in case it can be released. + content.reset(); int imageWidth = image.width(); int imageHeight = image.height(); @@ -299,7 +259,7 @@ gpu::TexturePointer processImage(QByteArray&& content, const std::string& filena } auto loader = TextureUsage::getTextureLoaderForType(textureType); - auto texture = loader(std::move(image), filename, abortProcessing); + auto texture = loader(std::move(image), filename, compress, abortProcessing); return texture; } @@ -548,13 +508,15 @@ void generateLDRMips(gpu::Texture* texture, QImage&& image, const std::atomic(localCopy.constBits()); + auto mipFormat = texture->getStoredMipFormat(); +#ifndef USE_GLES + const void* data = static_cast(localCopy.constBits()); nvtt::TextureType textureType = nvtt::TextureType_2D; nvtt::InputFormat inputFormat = nvtt::InputFormat_BGRA_8UB; nvtt::WrapMode wrapMode = nvtt::WrapMode_Mirror; @@ -584,8 +546,6 @@ void generateLDRMips(gpu::Texture* texture, QImage&& image, const std::atomicgetStoredMipFormat(); if (mipFormat == gpu::Element::COLOR_COMPRESSED_BCX_SRGB) { compressionOptions.setFormat(nvtt::Format_BC1); } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_BCX_SRGBA_MASK) { @@ -669,21 +629,94 @@ void generateLDRMips(gpu::Texture* texture, QImage&& image, const std::atomic floatData; + floatData.resize(width * height); + for (int y = 0; y < height; y++) { + QRgb *line = (QRgb *) localCopy.scanLine(y); + for (int x = 0; x < width; x++) { + QRgb &pixel = line[x]; + floatData[x + y * width] = vec4(qRed(pixel), qGreen(pixel), qBlue(pixel), qAlpha(pixel)) / MAX_COLOR; + } + } + + // free up the memory afterward to avoid bloating the heap + localCopy = QImage(); // QImage doesn't have a clear function, so override it with an empty one. + + Etc::EncodeMipmaps( + (float *)floatData.data(), width, height, + etcFormat, errorMetric, effort, + numEncodeThreads, numEncodeThreads, + numMips, Etc::FILTER_WRAP_NONE, + mipMaps, &encodingTime + ); + + for (int i = 0; i < numMips; i++) { + if (mipMaps[i].paucEncodingBits.get()) { + if (face >= 0) { + texture->assignStoredMipFace(i, face, mipMaps[i].uiEncodingBitsBytes, static_cast(mipMaps[i].paucEncodingBits.get())); + } else { + texture->assignStoredMip(i, mipMaps[i].uiEncodingBitsBytes, static_cast(mipMaps[i].paucEncodingBits.get())); + } + } + } + + delete[] mipMaps; +#endif } #endif - - void generateMips(gpu::Texture* texture, QImage&& image, const std::atomic& abortProcessing = false, int face = -1) { #if CPU_MIPMAPS PROFILE_RANGE(resource_parse, "generateMips"); +#ifndef USE_GLES if (image.format() == QIMAGE_HDR_FORMAT) { generateHDRMips(texture, std::move(image), abortProcessing, face); } else { generateLDRMips(texture, std::move(image), abortProcessing, face); } +#else + generateLDRMips(texture, std::move(image), abortProcessing, face); +#endif #else texture->setAutoGenerateMips(true); #endif @@ -716,7 +749,7 @@ void processTextureAlpha(const QImage& srcImage, bool& validAlpha, bool& alphaAs validAlpha = (numOpaques != NUM_PIXELS); } -gpu::TexturePointer TextureUsage::process2DTextureColorFromImage(QImage&& srcImage, const std::string& srcImageName, +gpu::TexturePointer TextureUsage::process2DTextureColorFromImage(QImage&& srcImage, const std::string& srcImageName, bool compress, bool isStrict, const std::atomic& abortProcessing) { PROFILE_RANGE(resource_parse, "process2DTextureColorFromImage"); QImage image = processSourceImage(std::move(srcImage), false); @@ -737,8 +770,7 @@ gpu::TexturePointer TextureUsage::process2DTextureColorFromImage(QImage&& srcIma if ((image.width() > 0) && (image.height() > 0)) { gpu::Element formatMip; gpu::Element formatGPU; - if (isColorTexturesCompressionEnabled()) { -#ifndef USE_GLES + if (compress) { if (validAlpha) { // NOTE: This disables BC1a compression because it was producing odd artifacts on text textures // for the tutorial. Instead we use BC3 (which is larger) but doesn't produce the same artifacts). @@ -746,22 +778,16 @@ gpu::TexturePointer TextureUsage::process2DTextureColorFromImage(QImage&& srcIma } else { formatGPU = gpu::Element::COLOR_COMPRESSED_BCX_SRGB; } -#else - if (validAlpha) { - formatGPU = gpu::Element::COLOR_COMPRESSED_ETC2_SRGBA; - } else { - formatGPU = gpu::Element::COLOR_COMPRESSED_ETC2_SRGB; - } -#endif formatMip = formatGPU; } else { #ifdef USE_GLES // GLES does not support GL_BGRA - formatMip = gpu::Element::COLOR_SRGBA_32; + formatGPU = gpu::Element::COLOR_COMPRESSED_ETC2_SRGBA; + formatMip = formatGPU; #else + formatGPU = gpu::Element::COLOR_SRGBA_32; formatMip = gpu::Element::COLOR_SBGRA_32; #endif - formatGPU = gpu::Element::COLOR_SRGBA_32; } if (isStrict) { @@ -861,7 +887,8 @@ QImage processBumpMap(QImage&& image) { return result; } gpu::TexturePointer TextureUsage::process2DTextureNormalMapFromImage(QImage&& srcImage, const std::string& srcImageName, - bool isBumpMap, const std::atomic& abortProcessing) { + bool compress, bool isBumpMap, + const std::atomic& abortProcessing) { PROFILE_RANGE(resource_parse, "process2DTextureNormalMapFromImage"); QImage image = processSourceImage(std::move(srcImage), false); @@ -876,16 +903,18 @@ gpu::TexturePointer TextureUsage::process2DTextureNormalMapFromImage(QImage&& sr gpu::TexturePointer theTexture = nullptr; if ((image.width() > 0) && (image.height() > 0)) { - gpu::Element formatMip = gpu::Element::VEC2NU8_XY; - gpu::Element formatGPU = gpu::Element::VEC2NU8_XY; - if (isNormalTexturesCompressionEnabled()) { -#ifndef USE_GLES + gpu::Element formatMip; + gpu::Element formatGPU; + if (compress) { formatGPU = gpu::Element::COLOR_COMPRESSED_BCX_XY; -#else + } else { +#ifdef USE_GLES formatGPU = gpu::Element::COLOR_COMPRESSED_EAC_XY; +#else + formatGPU = gpu::Element::VEC2NU8_XY; #endif - formatMip = formatGPU; } + formatMip = formatGPU; theTexture = gpu::Texture::create2D(formatGPU, image.width(), image.height(), gpu::Texture::MAX_NUM_MIPS, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR)); theTexture->setSource(srcImageName); @@ -898,7 +927,7 @@ gpu::TexturePointer TextureUsage::process2DTextureNormalMapFromImage(QImage&& sr } gpu::TexturePointer TextureUsage::process2DTextureGrayscaleFromImage(QImage&& srcImage, const std::string& srcImageName, - bool isInvertedPixels, + bool compress, bool isInvertedPixels, const std::atomic& abortProcessing) { PROFILE_RANGE(resource_parse, "process2DTextureGrayscaleFromImage"); QImage image = processSourceImage(std::move(srcImage), false); @@ -916,17 +945,16 @@ gpu::TexturePointer TextureUsage::process2DTextureGrayscaleFromImage(QImage&& sr if ((image.width() > 0) && (image.height() > 0)) { gpu::Element formatMip; gpu::Element formatGPU; - if (isGrayscaleTexturesCompressionEnabled()) { -#ifndef USE_GLES + if (compress) { formatGPU = gpu::Element::COLOR_COMPRESSED_BCX_RED; -#else - formatGPU = gpu::Element::COLOR_COMPRESSED_EAC_RED; -#endif - formatMip = formatGPU; } else { - formatMip = gpu::Element::COLOR_R_8; +#ifdef USE_GLES + formatGPU = gpu::Element::COLOR_COMPRESSED_EAC_RED; +#else formatGPU = gpu::Element::COLOR_R_8; +#endif } + formatMip = formatGPU; theTexture = gpu::Texture::create2D(formatGPU, image.width(), image.height(), gpu::Texture::MAX_NUM_MIPS, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR)); theTexture->setSource(srcImageName); @@ -1264,7 +1292,7 @@ QImage convertToHDRFormat(QImage&& srcImage, gpu::Element format) { } gpu::TexturePointer TextureUsage::processCubeTextureColorFromImage(QImage&& srcImage, const std::string& srcImageName, - bool generateIrradiance, + bool compress, bool generateIrradiance, const std::atomic& abortProcessing) { PROFILE_RANGE(resource_parse, "processCubeTextureColorFromImage"); @@ -1283,19 +1311,25 @@ gpu::TexturePointer TextureUsage::processCubeTextureColorFromImage(QImage&& srcI QImage image = processSourceImage(std::move(localCopy), true); if (image.format() != QIMAGE_HDR_FORMAT) { +#ifndef USE_GLES image = convertToHDRFormat(std::move(image), HDR_FORMAT); +#else + image = image.convertToFormat(QImage::Format_RGB32); +#endif } gpu::Element formatMip; gpu::Element formatGPU; - if (isCubeTexturesCompressionEnabled()) { - // TODO: gles: pick HDR ETC format - formatMip = gpu::Element::COLOR_COMPRESSED_BCX_HDR_RGB; + if (compress) { formatGPU = gpu::Element::COLOR_COMPRESSED_BCX_HDR_RGB; } else { - formatMip = HDR_FORMAT; +#ifdef USE_GLES + formatGPU = gpu::Element::COLOR_COMPRESSED_ETC2_SRGB; +#else formatGPU = HDR_FORMAT; +#endif } + formatMip = formatGPU; // Find the layout of the cubemap in the 2D image // Use the original image size since processSourceImage may have altered the size / aspect ratio @@ -1342,9 +1376,16 @@ gpu::TexturePointer TextureUsage::processCubeTextureColorFromImage(QImage&& srcI // Generate irradiance while we are at it if (generateIrradiance) { PROFILE_RANGE(resource_parse, "generateIrradiance"); - auto irradianceTexture = gpu::Texture::createCube(HDR_FORMAT, faces[0].width(), gpu::Texture::MAX_NUM_MIPS, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR, gpu::Sampler::WRAP_CLAMP)); + gpu::Element irradianceFormat; + // TODO: we could locally compress the irradiance texture on Android, but we don't need to +#ifndef USE_GLES + irradianceFormat = HDR_FORMAT; +#else + irradianceFormat = gpu::Element::COLOR_SRGBA_32; +#endif + auto irradianceTexture = gpu::Texture::createCube(irradianceFormat, faces[0].width(), gpu::Texture::MAX_NUM_MIPS, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR, gpu::Sampler::WRAP_CLAMP)); irradianceTexture->setSource(srcImageName); - irradianceTexture->setStoredMipFormat(HDR_FORMAT); + irradianceTexture->setStoredMipFormat(irradianceFormat); for (uint8 face = 0; face < faces.size(); ++face) { irradianceTexture->assignStoredMipFace(0, face, faces[face].byteCount(), faces[face].constBits()); } diff --git a/libraries/image/src/image/Image.h b/libraries/image/src/image/Image.h index 39f5ea3bab..ccf4845fca 100644 --- a/libraries/image/src/image/Image.h +++ b/libraries/image/src/image/Image.h @@ -41,60 +41,50 @@ enum Type { UNUSED_TEXTURE }; -using TextureLoader = std::function&)>; +using TextureLoader = std::function&)>; TextureLoader getTextureLoaderForType(Type type, const QVariantMap& options = QVariantMap()); gpu::TexturePointer create2DTextureFromImage(QImage&& image, const std::string& srcImageName, - const std::atomic& abortProcessing); + bool compress, const std::atomic& abortProcessing); gpu::TexturePointer createStrict2DTextureFromImage(QImage&& image, const std::string& srcImageName, - const std::atomic& abortProcessing); + bool compress, const std::atomic& abortProcessing); gpu::TexturePointer createAlbedoTextureFromImage(QImage&& image, const std::string& srcImageName, - const std::atomic& abortProcessing); + bool compress, const std::atomic& abortProcessing); gpu::TexturePointer createEmissiveTextureFromImage(QImage&& image, const std::string& srcImageName, - const std::atomic& abortProcessing); + bool compress, const std::atomic& abortProcessing); gpu::TexturePointer createNormalTextureFromNormalImage(QImage&& image, const std::string& srcImageName, - const std::atomic& abortProcessing); + bool compress, const std::atomic& abortProcessing); gpu::TexturePointer createNormalTextureFromBumpImage(QImage&& image, const std::string& srcImageName, - const std::atomic& abortProcessing); + bool compress, const std::atomic& abortProcessing); gpu::TexturePointer createRoughnessTextureFromImage(QImage&& image, const std::string& srcImageName, - const std::atomic& abortProcessing); + bool compress, const std::atomic& abortProcessing); gpu::TexturePointer createRoughnessTextureFromGlossImage(QImage&& image, const std::string& srcImageName, - const std::atomic& abortProcessing); + bool compress, const std::atomic& abortProcessing); gpu::TexturePointer createMetallicTextureFromImage(QImage&& image, const std::string& srcImageName, - const std::atomic& abortProcessing); + bool compress, const std::atomic& abortProcessing); gpu::TexturePointer createCubeTextureFromImage(QImage&& image, const std::string& srcImageName, - const std::atomic& abortProcessing); + bool compress, const std::atomic& abortProcessing); gpu::TexturePointer createCubeTextureFromImageWithoutIrradiance(QImage&& image, const std::string& srcImageName, - const std::atomic& abortProcessing); + bool compress, const std::atomic& abortProcessing); gpu::TexturePointer createLightmapTextureFromImage(QImage&& image, const std::string& srcImageName, - const std::atomic& abortProcessing); + bool compress, const std::atomic& abortProcessing); -gpu::TexturePointer process2DTextureColorFromImage(QImage&& srcImage, const std::string& srcImageName, bool isStrict, - const std::atomic& abortProcessing); -gpu::TexturePointer process2DTextureNormalMapFromImage(QImage&& srcImage, const std::string& srcImageName, bool isBumpMap, - const std::atomic& abortProcessing); -gpu::TexturePointer process2DTextureGrayscaleFromImage(QImage&& srcImage, const std::string& srcImageName, bool isInvertedPixels, - const std::atomic& abortProcessing); -gpu::TexturePointer processCubeTextureColorFromImage(QImage&& srcImage, const std::string& srcImageName, bool generateIrradiance, - const std::atomic& abortProcessing); +gpu::TexturePointer process2DTextureColorFromImage(QImage&& srcImage, const std::string& srcImageName, bool compress, + bool isStrict, const std::atomic& abortProcessing); +gpu::TexturePointer process2DTextureNormalMapFromImage(QImage&& srcImage, const std::string& srcImageName, bool compress, + bool isBumpMap, const std::atomic& abortProcessing); +gpu::TexturePointer process2DTextureGrayscaleFromImage(QImage&& srcImage, const std::string& srcImageName, bool compress, + bool isInvertedPixels, const std::atomic& abortProcessing); +gpu::TexturePointer processCubeTextureColorFromImage(QImage&& srcImage, const std::string& srcImageName, bool compress, + bool generateIrradiance, const std::atomic& abortProcessing); } // namespace TextureUsage const QStringList getSupportedFormats(); -bool isColorTexturesCompressionEnabled(); -bool isNormalTexturesCompressionEnabled(); -bool isGrayscaleTexturesCompressionEnabled(); -bool isCubeTexturesCompressionEnabled(); - -void setColorTexturesCompressionEnabled(bool enabled); -void setNormalTexturesCompressionEnabled(bool enabled); -void setGrayscaleTexturesCompressionEnabled(bool enabled); -void setCubeTexturesCompressionEnabled(bool enabled); - -gpu::TexturePointer processImage(QByteArray&& content, const std::string& url, +gpu::TexturePointer processImage(std::shared_ptr content, const std::string& url, int maxNumPixels, TextureUsage::Type textureType, - const std::atomic& abortProcessing = false); + bool compress = false, const std::atomic& abortProcessing = false); } // namespace image diff --git a/libraries/input-plugins/src/input-plugins/TouchscreenVirtualPadDevice.cpp b/libraries/input-plugins/src/input-plugins/TouchscreenVirtualPadDevice.cpp index 8d63b82911..32194e1b84 100644 --- a/libraries/input-plugins/src/input-plugins/TouchscreenVirtualPadDevice.cpp +++ b/libraries/input-plugins/src/input-plugins/TouchscreenVirtualPadDevice.cpp @@ -350,6 +350,8 @@ void TouchscreenVirtualPadDevice::touchUpdateEvent(const QTouchEvent* event) { if (idxMoveStartingPointCandidate != -1) { _moveCurrentTouchId = tPoints[idxMoveStartingPointCandidate].id(); _unusedTouches.erase(_moveCurrentTouchId); + thisPoint.x = tPoints[idxMoveStartingPointCandidate].pos().x(); + thisPoint.y = tPoints[idxMoveStartingPointCandidate].pos().y(); moveTouchBegin(thisPoint); } else { moveTouchEnd(); @@ -359,6 +361,8 @@ void TouchscreenVirtualPadDevice::touchUpdateEvent(const QTouchEvent* event) { if (idxViewStartingPointCandidate != -1) { _viewCurrentTouchId = tPoints[idxViewStartingPointCandidate].id(); _unusedTouches.erase(_viewCurrentTouchId); + thisPoint.x = tPoints[idxViewStartingPointCandidate].pos().x(); + thisPoint.y = tPoints[idxViewStartingPointCandidate].pos().y(); viewTouchBegin(thisPoint); } else { viewTouchEnd(); @@ -368,6 +372,8 @@ void TouchscreenVirtualPadDevice::touchUpdateEvent(const QTouchEvent* event) { if (idxJumpStartingPointCandidate != -1) { _jumpCurrentTouchId = tPoints[idxJumpStartingPointCandidate].id(); _unusedTouches.erase(_jumpCurrentTouchId); + thisPoint.x = tPoints[idxJumpStartingPointCandidate].pos().x(); + thisPoint.y = tPoints[idxJumpStartingPointCandidate].pos().y(); jumpTouchBegin(thisPoint); } else { if (_jumpHasValidTouch) { @@ -424,6 +430,7 @@ void TouchscreenVirtualPadDevice::moveTouchBegin(glm::vec2 touchPoint) { } else { _moveRefTouchPoint = touchPoint; } + _moveCurrentTouchPoint = touchPoint; _moveHasValidTouch = true; } } diff --git a/libraries/ktx/src/TextureMeta.cpp b/libraries/ktx/src/TextureMeta.cpp index 3a2abf24c4..c8427c1f60 100644 --- a/libraries/ktx/src/TextureMeta.cpp +++ b/libraries/ktx/src/TextureMeta.cpp @@ -33,6 +33,9 @@ bool TextureMeta::deserialize(const QByteArray& data, TextureMeta* meta) { if (root.contains("original")) { meta->original = root["original"].toString(); } + if (root.contains("uncompressed")) { + meta->uncompressed = root["uncompressed"].toString(); + } if (root.contains("compressed")) { auto compressed = root["compressed"].toObject(); for (auto it = compressed.constBegin(); it != compressed.constEnd(); it++) { @@ -57,6 +60,7 @@ QByteArray TextureMeta::serialize() { compressed[name] = kv.second.toString(); } root["original"] = original.toString(); + root["uncompressed"] = uncompressed.toString(); root["compressed"] = compressed; doc.setObject(root); diff --git a/libraries/ktx/src/TextureMeta.h b/libraries/ktx/src/TextureMeta.h index 6582c29e70..5450fee110 100644 --- a/libraries/ktx/src/TextureMeta.h +++ b/libraries/ktx/src/TextureMeta.h @@ -35,6 +35,7 @@ struct TextureMeta { QByteArray serialize(); QUrl original; + QUrl uncompressed; std::unordered_map availableTextureTypes; }; diff --git a/libraries/model-networking/src/model-networking/TextureCache.cpp b/libraries/model-networking/src/model-networking/TextureCache.cpp index ed21fd35bc..40b31cac53 100644 --- a/libraries/model-networking/src/model-networking/TextureCache.cpp +++ b/libraries/model-networking/src/model-networking/TextureCache.cpp @@ -50,6 +50,8 @@ #include +#include + Q_LOGGING_CATEGORY(trace_resource_parse_image, "trace.resource.parse.image") Q_LOGGING_CATEGORY(trace_resource_parse_image_raw, "trace.resource.parse.image.raw") Q_LOGGING_CATEGORY(trace_resource_parse_image_ktx, "trace.resource.parse.image.ktx") @@ -277,7 +279,7 @@ gpu::TexturePointer TextureCache::getImageTexture(const QString& path, image::Te return nullptr; } auto loader = image::TextureUsage::getTextureLoaderForType(type, options); - return gpu::TexturePointer(loader(std::move(image), path.toStdString(), false)); + return gpu::TexturePointer(loader(std::move(image), path.toStdString(), false, false)); } QSharedPointer TextureCache::createResource(const QUrl& url, const QSharedPointer& fallback, @@ -964,7 +966,6 @@ void NetworkTexture::loadMetaContent(const QByteArray& content) { return; } - auto& backend = DependencyManager::get()->getGPUContext()->getBackend(); for (auto pair : meta.availableTextureTypes) { gpu::Element elFormat; @@ -990,6 +991,21 @@ void NetworkTexture::loadMetaContent(const QByteArray& content) { } } +#ifndef Q_OS_ANDROID + if (!meta.uncompressed.isEmpty()) { + _currentlyLoadingResourceType = ResourceType::KTX; + _activeUrl = _activeUrl.resolved(meta.uncompressed); + + auto textureCache = DependencyManager::get(); + auto self = _self.lock(); + if (!self) { + return; + } + QMetaObject::invokeMethod(this, "attemptRequest", Qt::QueuedConnection); + return; + } +#endif + if (!meta.original.isEmpty()) { _currentlyLoadingResourceType = ResourceType::ORIGINAL; _activeUrl = _activeUrl.resolved(meta.original); @@ -1143,7 +1159,8 @@ void ImageReader::read() { PROFILE_RANGE_EX(resource_parse_image_raw, __FUNCTION__, 0xffff0000, 0); // IMPORTANT: _content is empty past this point - texture = image::processImage(std::move(_content), _url.toString().toStdString(), _maxNumPixels, networkTexture->getTextureType()); + auto buffer = std::shared_ptr((QIODevice*)new OwningBuffer(std::move(_content))); + texture = image::processImage(std::move(buffer), _url.toString().toStdString(), _maxNumPixels, networkTexture->getTextureType()); if (!texture) { qCWarning(modelnetworking) << "Could not process:" << _url; diff --git a/libraries/networking/src/AccountManager.cpp b/libraries/networking/src/AccountManager.cpp index e70e3e26d0..05f0ec12b5 100644 --- a/libraries/networking/src/AccountManager.cpp +++ b/libraries/networking/src/AccountManager.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -743,6 +744,9 @@ void AccountManager::generateNewKeypair(bool isUserKeypair, const QUuid& domainI return; } + // Ensure openssl/Qt config is set up. + QSslConfiguration::defaultConfiguration(); + // make sure we don't already have an outbound keypair generation request if (!_isWaitingForKeypairResponse) { _isWaitingForKeypairResponse = true; @@ -751,94 +755,75 @@ void AccountManager::generateNewKeypair(bool isUserKeypair, const QUuid& domainI qCDebug(networking) << "Clearing current private key in DataServerAccountInfo"; _accountInfo.setPrivateKey(QByteArray()); - // setup a new QThread to generate the keypair on, in case it takes a while - QThread* generateThread = new QThread(this); - generateThread->setObjectName("Account Manager Generator Thread"); - - // setup a keypair generator + // Create a runnable keypair generated to create an RSA pair and exit. RSAKeypairGenerator* keypairGenerator = new RSAKeypairGenerator; if (!isUserKeypair) { - keypairGenerator->setDomainID(domainID); _accountInfo.setDomainID(domainID); } - // start keypair generation when the thread starts - connect(generateThread, &QThread::started, keypairGenerator, &RSAKeypairGenerator::generateKeypair); - // handle success or failure of keypair generation - connect(keypairGenerator, &RSAKeypairGenerator::generatedKeypair, this, &AccountManager::processGeneratedKeypair); - connect(keypairGenerator, &RSAKeypairGenerator::errorGeneratingKeypair, - this, &AccountManager::handleKeypairGenerationError); - - connect(keypairGenerator, &QObject::destroyed, generateThread, &QThread::quit); - connect(generateThread, &QThread::finished, generateThread, &QThread::deleteLater); - - keypairGenerator->moveToThread(generateThread); + connect(keypairGenerator, &RSAKeypairGenerator::generatedKeypair, this, + &AccountManager::processGeneratedKeypair); + connect(keypairGenerator, &RSAKeypairGenerator::errorGeneratingKeypair, this, + &AccountManager::handleKeypairGenerationError); qCDebug(networking) << "Starting worker thread to generate 2048-bit RSA keypair."; - generateThread->start(); + // Start on Qt's global thread pool. + QThreadPool::globalInstance()->start(keypairGenerator); } } -void AccountManager::processGeneratedKeypair() { +void AccountManager::processGeneratedKeypair(QByteArray publicKey, QByteArray privateKey) { qCDebug(networking) << "Generated 2048-bit RSA keypair. Uploading public key now."; - RSAKeypairGenerator* keypairGenerator = qobject_cast(sender()); + // hold the private key to later set our metaverse API account info if upload succeeds + _pendingPrivateKey = privateKey; - if (keypairGenerator) { - // hold the private key to later set our metaverse API account info if upload succeeds - _pendingPrivateKey = keypairGenerator->getPrivateKey(); + // upload the public key so data-web has an up-to-date key + const QString USER_PUBLIC_KEY_UPDATE_PATH = "api/v1/user/public_key"; + const QString DOMAIN_PUBLIC_KEY_UPDATE_PATH = "api/v1/domains/%1/public_key"; - // upload the public key so data-web has an up-to-date key - const QString USER_PUBLIC_KEY_UPDATE_PATH = "api/v1/user/public_key"; - const QString DOMAIN_PUBLIC_KEY_UPDATE_PATH = "api/v1/domains/%1/public_key"; - - QString uploadPath; - const auto& domainID = keypairGenerator->getDomainID(); - if (domainID.isNull()) { - uploadPath = USER_PUBLIC_KEY_UPDATE_PATH; - } else { - uploadPath = DOMAIN_PUBLIC_KEY_UPDATE_PATH.arg(uuidStringWithoutCurlyBraces(domainID)); - } - - // setup a multipart upload to send up the public key - QHttpMultiPart* requestMultiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType); - - QHttpPart publicKeyPart; - publicKeyPart.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/octet-stream")); - - publicKeyPart.setHeader(QNetworkRequest::ContentDispositionHeader, - QVariant("form-data; name=\"public_key\"; filename=\"public_key\"")); - publicKeyPart.setBody(keypairGenerator->getPublicKey()); - requestMultiPart->append(publicKeyPart); - - if (!domainID.isNull()) { - const auto& key = getTemporaryDomainKey(domainID); - QHttpPart apiKeyPart; - publicKeyPart.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/octet-stream")); - apiKeyPart.setHeader(QNetworkRequest::ContentDispositionHeader, - QVariant("form-data; name=\"api_key\"")); - apiKeyPart.setBody(key.toUtf8()); - requestMultiPart->append(apiKeyPart); - } - - // setup callback parameters so we know once the keypair upload has succeeded or failed - JSONCallbackParameters callbackParameters; - callbackParameters.jsonCallbackReceiver = this; - callbackParameters.jsonCallbackMethod = "publicKeyUploadSucceeded"; - callbackParameters.errorCallbackReceiver = this; - callbackParameters.errorCallbackMethod = "publicKeyUploadFailed"; - - sendRequest(uploadPath, AccountManagerAuth::Optional, QNetworkAccessManager::PutOperation, - callbackParameters, QByteArray(), requestMultiPart); - - keypairGenerator->deleteLater(); + QString uploadPath; + const auto& domainID = _accountInfo.getDomainID(); + if (domainID.isNull()) { + uploadPath = USER_PUBLIC_KEY_UPDATE_PATH; } else { - qCWarning(networking) << "Expected processGeneratedKeypair to be called by a live RSAKeypairGenerator" - << "but the casted sender is NULL. Will not process generated keypair."; + uploadPath = DOMAIN_PUBLIC_KEY_UPDATE_PATH.arg(uuidStringWithoutCurlyBraces(domainID)); } + + // setup a multipart upload to send up the public key + QHttpMultiPart* requestMultiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType); + + QHttpPart publicKeyPart; + publicKeyPart.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/octet-stream")); + + publicKeyPart.setHeader(QNetworkRequest::ContentDispositionHeader, + QVariant("form-data; name=\"public_key\"; filename=\"public_key\"")); + publicKeyPart.setBody(publicKey); + requestMultiPart->append(publicKeyPart); + + // Currently broken? We don't have the temporary domain key. + if (!domainID.isNull()) { + const auto& key = getTemporaryDomainKey(domainID); + QHttpPart apiKeyPart; + publicKeyPart.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/octet-stream")); + apiKeyPart.setHeader(QNetworkRequest::ContentDispositionHeader, + QVariant("form-data; name=\"api_key\"")); + apiKeyPart.setBody(key.toUtf8()); + requestMultiPart->append(apiKeyPart); + } + + // setup callback parameters so we know once the keypair upload has succeeded or failed + JSONCallbackParameters callbackParameters; + callbackParameters.jsonCallbackReceiver = this; + callbackParameters.jsonCallbackMethod = "publicKeyUploadSucceeded"; + callbackParameters.errorCallbackReceiver = this; + callbackParameters.errorCallbackMethod = "publicKeyUploadFailed"; + + sendRequest(uploadPath, AccountManagerAuth::Optional, QNetworkAccessManager::PutOperation, + callbackParameters, QByteArray(), requestMultiPart); } void AccountManager::publicKeyUploadSucceeded(QNetworkReply& reply) { @@ -877,6 +862,4 @@ void AccountManager::handleKeypairGenerationError() { // reset our waiting state for keypair response _isWaitingForKeypairResponse = false; - - sender()->deleteLater(); } diff --git a/libraries/networking/src/AccountManager.h b/libraries/networking/src/AccountManager.h index 88ebaf5656..9068966512 100644 --- a/libraries/networking/src/AccountManager.h +++ b/libraries/networking/src/AccountManager.h @@ -128,7 +128,7 @@ signals: private slots: void processReply(); void handleKeypairGenerationError(); - void processGeneratedKeypair(); + void processGeneratedKeypair(QByteArray publicKey, QByteArray privateKey); void publicKeyUploadSucceeded(QNetworkReply& reply); void publicKeyUploadFailed(QNetworkReply& reply); void generateNewKeypair(bool isUserKeypair = true, const QUuid& domainID = QUuid()); diff --git a/libraries/networking/src/AddressManager.cpp b/libraries/networking/src/AddressManager.cpp index 977cabb57a..317be194b8 100644 --- a/libraries/networking/src/AddressManager.cpp +++ b/libraries/networking/src/AddressManager.cpp @@ -157,7 +157,11 @@ void AddressManager::storeCurrentAddress() { // be loaded over http(s) // url.scheme() == URL_SCHEME_HTTP || // url.scheme() == URL_SCHEME_HTTPS || - currentAddressHandle.set(url); + if (isConnected()) { + currentAddressHandle.set(url); + } else { + qCWarning(networking) << "Ignoring attempt to save current address because not connected to domain:" << url; + } } else { qCWarning(networking) << "Ignoring attempt to save current address with an invalid url:" << url; } diff --git a/libraries/networking/src/LimitedNodeList.cpp b/libraries/networking/src/LimitedNodeList.cpp index 012a891698..502874fbfb 100644 --- a/libraries/networking/src/LimitedNodeList.cpp +++ b/libraries/networking/src/LimitedNodeList.cpp @@ -815,8 +815,13 @@ unsigned int LimitedNodeList::broadcastToNodes(std::unique_ptr packet, eachNode([&](const SharedNodePointer& node){ if (node && destinationNodeTypes.contains(node->getType())) { - sendUnreliablePacket(*packet, *node); - ++n; + if (packet->isReliable()) { + auto packetCopy = NLPacket::createCopy(*packet); + sendPacket(std::move(packetCopy), *node); + } else { + sendUnreliablePacket(*packet, *node); + } + ++n; } }); diff --git a/libraries/networking/src/RSAKeypairGenerator.cpp b/libraries/networking/src/RSAKeypairGenerator.cpp index 8ca8b81ea3..e83615e3df 100644 --- a/libraries/networking/src/RSAKeypairGenerator.cpp +++ b/libraries/networking/src/RSAKeypairGenerator.cpp @@ -25,7 +25,10 @@ RSAKeypairGenerator::RSAKeypairGenerator(QObject* parent) : QObject(parent) { - +} + +void RSAKeypairGenerator::run() { + generateKeypair(); } void RSAKeypairGenerator::generateKeypair() { @@ -92,5 +95,5 @@ void RSAKeypairGenerator::generateKeypair() { OPENSSL_free(publicKeyDER); OPENSSL_free(privateKeyDER); - emit generatedKeypair(); + emit generatedKeypair(_publicKey, _privateKey); } diff --git a/libraries/networking/src/RSAKeypairGenerator.h b/libraries/networking/src/RSAKeypairGenerator.h index 36f4a9550b..552f12395b 100644 --- a/libraries/networking/src/RSAKeypairGenerator.h +++ b/libraries/networking/src/RSAKeypairGenerator.h @@ -13,25 +13,20 @@ #define hifi_RSAKeypairGenerator_h #include +#include #include -class RSAKeypairGenerator : public QObject { +class RSAKeypairGenerator : public QObject, public QRunnable { Q_OBJECT public: - RSAKeypairGenerator(QObject* parent = 0); + RSAKeypairGenerator(QObject* parent = nullptr); - void setDomainID(const QUuid& domainID) { _domainID = domainID; } - const QUuid& getDomainID() const { return _domainID; } - - const QByteArray& getPublicKey() const { return _publicKey; } - const QByteArray& getPrivateKey() const { return _privateKey; } - -public slots: + virtual void run() override; void generateKeypair(); signals: void errorGeneratingKeypair(); - void generatedKeypair(); + void generatedKeypair(QByteArray publicKey, QByteArray privateKey); private: QUuid _domainID; diff --git a/libraries/networking/src/ResourceCache.cpp b/libraries/networking/src/ResourceCache.cpp index 18e60ef5ef..0b1334daba 100644 --- a/libraries/networking/src/ResourceCache.cpp +++ b/libraries/networking/src/ResourceCache.cpp @@ -636,7 +636,10 @@ void Resource::attemptRequest() { << "- retrying asset load - attempt" << _attempts << " of " << MAX_ATTEMPTS; } - ResourceCache::attemptRequest(_self); + auto self = _self.lock(); + if (self) { + ResourceCache::attemptRequest(self); + } } void Resource::finishedLoading(bool success) { diff --git a/libraries/networking/src/ResourceManager.cpp b/libraries/networking/src/ResourceManager.cpp index d06b74b724..6df15129a2 100644 --- a/libraries/networking/src/ResourceManager.cpp +++ b/libraries/networking/src/ResourceManager.cpp @@ -38,6 +38,11 @@ ResourceManager::ResourceManager(bool atpSupportEnabled) : _atpSupportEnabled(at _thread.start(); } +ResourceManager::~ResourceManager() { + _thread.terminate(); + _thread.wait(); +} + void ResourceManager::setUrlPrefixOverride(const QString& prefix, const QString& replacement) { QMutexLocker locker(&_prefixMapLock); if (replacement.isEmpty()) { diff --git a/libraries/networking/src/ResourceManager.h b/libraries/networking/src/ResourceManager.h index 9fc636f5fe..a79222d2d8 100644 --- a/libraries/networking/src/ResourceManager.h +++ b/libraries/networking/src/ResourceManager.h @@ -28,6 +28,7 @@ class ResourceManager: public QObject, public Dependency { public: ResourceManager(bool atpSupportEnabled = true); + ~ResourceManager(); void setUrlPrefixOverride(const QString& prefix, const QString& replacement); QString normalizeURL(const QString& urlString); diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index b69733c18d..c342ecffc5 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -40,7 +40,7 @@ PacketVersion versionForPacketType(PacketType packetType) { case PacketType::AvatarData: case PacketType::BulkAvatarData: case PacketType::KillAvatar: - return static_cast(AvatarMixerPacketVersion::FBXReaderNodeReparenting); + return static_cast(AvatarMixerPacketVersion::ProceduralFaceMovementFlagsAndBlendshapes); case PacketType::MessagesData: return static_cast(MessageDataVersion::TextOrBinaryData); // ICE packets diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index 5203a9d178..64054fd080 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -282,7 +282,9 @@ enum class AvatarMixerPacketVersion : PacketVersion { AvatarIdentityLookAtSnapping, UpdatedMannequinDefaultAvatar, AvatarJointDefaultPoseFlags, - FBXReaderNodeReparenting + FBXReaderNodeReparenting, + FixMannequinDefaultAvatarFeet, + ProceduralFaceMovementFlagsAndBlendshapes }; enum class DomainConnectRequestVersion : PacketVersion { diff --git a/libraries/octree/src/Octree.cpp b/libraries/octree/src/Octree.cpp index 5f943fabf2..f0e41d59ea 100644 --- a/libraries/octree/src/Octree.cpp +++ b/libraries/octree/src/Octree.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -972,12 +973,19 @@ bool Octree::writeToJSONFile(const char* fileName, const OctreeElementPointer& e return false; } - QFile persistFile(fileName); + QSaveFile persistFile(fileName); bool success = false; if (persistFile.open(QIODevice::WriteOnly)) { - success = persistFile.write(jsonDataForFile) != -1; + if (persistFile.write(jsonDataForFile) != -1) { + success = persistFile.commit(); + if (!success) { + qCritical() << "Failed to commit to JSON save file:" << persistFile.errorString(); + } + } else { + qCritical("Failed to write to JSON file."); + } } else { - qCritical("Could not write to JSON description of entities."); + qCritical("Failed to open JSON file for writing."); } return success; diff --git a/libraries/octree/src/OctreeDataUtils.cpp b/libraries/octree/src/OctreeDataUtils.cpp index b57ab8db31..44a56fe97a 100644 --- a/libraries/octree/src/OctreeDataUtils.cpp +++ b/libraries/octree/src/OctreeDataUtils.cpp @@ -40,9 +40,10 @@ bool readOctreeFile(QString path, QJsonDocument* doc) { } bool OctreeUtils::RawOctreeData::readOctreeDataInfoFromJSON(QJsonObject root) { - if (root.contains("Id") && root.contains("DataVersion")) { + if (root.contains("Id") && root.contains("DataVersion") && root.contains("Version")) { id = root["Id"].toVariant().toUuid(); - version = root["DataVersion"].toInt(); + dataVersion = root["DataVersion"].toInt(); + version = root["Version"].toInt(); } readSubclassData(root); return true; @@ -76,11 +77,10 @@ bool OctreeUtils::RawOctreeData::readOctreeDataInfoFromFile(QString path) { } QByteArray OctreeUtils::RawOctreeData::toByteArray() { - const auto protocolVersion = (int)versionForPacketType((PacketTypeEnum::Value)dataPacketType()); QJsonObject obj { - { "DataVersion", QJsonValue((qint64)version) }, + { "DataVersion", QJsonValue((qint64)dataVersion) }, { "Id", QJsonValue(id.toString()) }, - { "Version", protocolVersion }, + { "Version", QJsonValue((qint64)version) }, }; writeSubclassData(obj); @@ -111,8 +111,8 @@ PacketType OctreeUtils::RawOctreeData::dataPacketType() const { void OctreeUtils::RawOctreeData::resetIdAndVersion() { id = QUuid::createUuid(); - version = OctreeUtils::INITIAL_VERSION; - qDebug() << "Reset octree data to: " << id << version; + dataVersion = OctreeUtils::INITIAL_VERSION; + qDebug() << "Reset octree data to: " << id << dataVersion; } void OctreeUtils::RawEntityData::readSubclassData(const QJsonObject& root) { diff --git a/libraries/octree/src/OctreeDataUtils.h b/libraries/octree/src/OctreeDataUtils.h index 485599096d..9060e7b460 100644 --- a/libraries/octree/src/OctreeDataUtils.h +++ b/libraries/octree/src/OctreeDataUtils.h @@ -28,6 +28,7 @@ constexpr Version INITIAL_VERSION = 0; class RawOctreeData { public: QUuid id { QUuid() }; + Version dataVersion { -1 }; Version version { -1 }; virtual PacketType dataPacketType() const; diff --git a/libraries/octree/src/OctreePersistThread.cpp b/libraries/octree/src/OctreePersistThread.cpp index e6afccab47..8b1d766418 100644 --- a/libraries/octree/src/OctreePersistThread.cpp +++ b/libraries/octree/src/OctreePersistThread.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include @@ -35,32 +36,152 @@ #include "OctreeUtils.h" #include "OctreeDataUtils.h" -const int OctreePersistThread::DEFAULT_PERSIST_INTERVAL = 1000 * 30; // every 30 seconds +constexpr std::chrono::seconds OctreePersistThread::DEFAULT_PERSIST_INTERVAL { 30 }; +constexpr std::chrono::milliseconds TIME_BETWEEN_PROCESSING { 10 }; -OctreePersistThread::OctreePersistThread(OctreePointer tree, const QString& filename, const QString& backupDirectory, int persistInterval, - bool wantBackup, const QJsonObject& settings, bool debugTimestampNow, - QString persistAsFileType, const QByteArray& replacementData) : +constexpr int MAX_OCTREE_REPLACEMENT_BACKUP_FILES_COUNT { 20 }; +constexpr int64_t MAX_OCTREE_REPLACEMENT_BACKUP_FILES_SIZE_BYTES { 50 * 1000 * 1000 }; + +OctreePersistThread::OctreePersistThread(OctreePointer tree, const QString& filename, std::chrono::milliseconds persistInterval, + bool debugTimestampNow, QString persistAsFileType) : _tree(tree), _filename(filename), - _backupDirectory(backupDirectory), _persistInterval(persistInterval), + _lastPersistCheck(std::chrono::steady_clock::now()), _initialLoadComplete(false), - _replacementData(replacementData), _loadTimeUSecs(0), - _lastCheck(0), - _wantBackup(wantBackup), _debugTimestampNow(debugTimestampNow), _lastTimeDebug(0), _persistAsFileType(persistAsFileType) { - parseSettings(settings); - - // in case the persist filename has an extension that doesn't match the file type QString sansExt = fileNameWithoutExtension(_filename, PERSIST_EXTENSIONS); _filename = sansExt + "." + _persistAsFileType; } +void OctreePersistThread::start() { + cleanupOldReplacementBackups(); + + auto& packetReceiver = DependencyManager::get()->getPacketReceiver(); + packetReceiver.registerListener(PacketType::OctreeDataFileReply, this, "handleOctreeDataFileReply"); + + auto nodeList = DependencyManager::get(); + const DomainHandler& domainHandler = nodeList->getDomainHandler(); + + auto packet = NLPacket::create(PacketType::OctreeDataFileRequest, -1, true, false); + + OctreeUtils::RawOctreeData data; + qCDebug(octree) << "Reading octree data from" << _filename; + if (data.readOctreeDataInfoFromFile(_filename)) { + qCDebug(octree) << "Current octree data: ID(" << data.id << ") DataVersion(" << data.version << ")"; + packet->writePrimitive(true); + auto id = data.id.toRfc4122(); + packet->write(id); + packet->writePrimitive(data.version); + } else { + qCWarning(octree) << "No octree data found"; + packet->writePrimitive(false); + } + + qCDebug(octree) << "Sending OctreeDataFileRequest to DS"; + nodeList->sendPacket(std::move(packet), domainHandler.getSockAddr()); +} + +void OctreePersistThread::handleOctreeDataFileReply(QSharedPointer message) { + if (_initialLoadComplete) { + qCWarning(octree) << "Received OctreeDataFileReply after initial load had completed"; + return; + } + + bool includesNewData; + message->readPrimitive(&includesNewData); + QByteArray replacementData; + OctreeUtils::RawOctreeData data; + bool hasValidOctreeData { false }; + if (includesNewData) { + replacementData = message->readAll(); + replaceData(replacementData); + hasValidOctreeData = data.readOctreeDataInfoFromFile(_filename); + qDebug() << "Got OctreeDataFileReply, new data sent"; + } else { + qDebug() << "Got OctreeDataFileReply, current entity data is sufficient"; + + OctreeUtils::RawEntityData data; + qCDebug(octree) << "Reading octree data from" << _filename; + if (data.readOctreeDataInfoFromFile(_filename)) { + hasValidOctreeData = true; + if (data.id.isNull()) { + qCDebug(octree) << "Current octree data has a null id, updating"; + data.resetIdAndVersion(); + + QFile file(_filename); + if (file.open(QIODevice::WriteOnly)) { + auto entityData = data.toGzippedByteArray(); + file.write(entityData); + file.close(); + } else { + qCDebug(octree) << "Failed to update octree data"; + } + } + } + } + + quint64 loadStarted = usecTimestampNow(); + qCDebug(octree) << "loading Octrees from file: " << _filename << "..."; + + if (hasValidOctreeData) { + qDebug() << "Setting entity version info to: " << data.id << data.version; + _tree->setOctreeVersionInfo(data.id, data.version); + } + + bool persistentFileRead; + + _tree->withWriteLock([&] { + PerformanceWarning warn(true, "Loading Octree File", true); + + persistentFileRead = _tree->readFromFile(_filename.toLocal8Bit().constData()); + _tree->pruneTree(); + }); + + quint64 loadDone = usecTimestampNow(); + _loadTimeUSecs = loadDone - loadStarted; + + _tree->clearDirtyBit(); // the tree is clean since we just loaded it + qCDebug(octree, "DONE loading Octrees from file... fileRead=%s", debug::valueOf(persistentFileRead)); + + unsigned long nodeCount = OctreeElement::getNodeCount(); + unsigned long internalNodeCount = OctreeElement::getInternalNodeCount(); + unsigned long leafNodeCount = OctreeElement::getLeafNodeCount(); + qCDebug(octree, "Nodes after loading scene %lu nodes %lu internal %lu leaves", nodeCount, internalNodeCount, leafNodeCount); + + bool wantDebug = false; + if (wantDebug) { + double usecPerGet = (double)OctreeElement::getGetChildAtIndexTime() + / (double)OctreeElement::getGetChildAtIndexCalls(); + qCDebug(octree) << "getChildAtIndexCalls=" << OctreeElement::getGetChildAtIndexCalls() + << " getChildAtIndexTime=" << OctreeElement::getGetChildAtIndexTime() << " perGet=" << usecPerGet; + + double usecPerSet = (double)OctreeElement::getSetChildAtIndexTime() + / (double)OctreeElement::getSetChildAtIndexCalls(); + qCDebug(octree) << "setChildAtIndexCalls=" << OctreeElement::getSetChildAtIndexCalls() + << " setChildAtIndexTime=" << OctreeElement::getSetChildAtIndexTime() << " perSet=" << usecPerSet; + } + + _initialLoadComplete = true; + + // Since we just loaded the persistent file, we can consider ourselves as having just persisted + _lastPersistCheck = std::chrono::steady_clock::now(); + + if (replacementData.isNull()) { + sendLatestEntityDataToDS(); + } + + QTimer::singleShot(TIME_BETWEEN_PROCESSING.count(), this, &OctreePersistThread::process); + + emit loadCompleted(); +} + + QString OctreePersistThread::getPersistFileMimeType() const { if (_persistAsFileType == "json") { return "application/json"; @@ -70,72 +191,6 @@ QString OctreePersistThread::getPersistFileMimeType() const { return ""; } -void OctreePersistThread::parseSettings(const QJsonObject& settings) { - if (settings["backups"].isArray()) { - const QJsonArray& backupRules = settings["backups"].toArray(); - qCDebug(octree) << "BACKUP RULES:"; - - foreach (const QJsonValue& value, backupRules) { - - QJsonObject obj = value.toObject(); - - int interval = 0; - int count = 0; - - QJsonValue intervalVal = obj["backupInterval"]; - if (intervalVal.isString()) { - interval = intervalVal.toString().toInt(); - } else { - interval = intervalVal.toInt(); - } - - QJsonValue countVal = obj["maxBackupVersions"]; - if (countVal.isString()) { - count = countVal.toString().toInt(); - } else { - count = countVal.toInt(); - } - - qCDebug(octree) << " Name:" << obj["Name"].toString(); - qCDebug(octree) << " format:" << obj["format"].toString(); - qCDebug(octree) << " interval:" << interval; - qCDebug(octree) << " count:" << count; - - BackupRule newRule = { obj["Name"].toString(), interval, obj["format"].toString(), count, 0}; - - newRule.lastBackup = getMostRecentBackupTimeInUsecs(obj["format"].toString()); - - if (newRule.lastBackup > 0) { - quint64 now = usecTimestampNow(); - quint64 sinceLastBackup = now - newRule.lastBackup; - qCDebug(octree) << " lastBackup:" << qPrintable(formatUsecTime(sinceLastBackup)) << "ago"; - } else { - qCDebug(octree) << " lastBackup: NEVER"; - } - - _backupRules << newRule; - } - } else { - qCDebug(octree) << "BACKUP RULES: NONE"; - } -} - -quint64 OctreePersistThread::getMostRecentBackupTimeInUsecs(const QString& format) { - - quint64 mostRecentBackupInUsecs = 0; - - QString mostRecentBackupFileName; - QDateTime mostRecentBackupTime; - - bool recentBackup = getMostRecentBackup(format, mostRecentBackupFileName, mostRecentBackupTime); - - if (recentBackup) { - mostRecentBackupInUsecs = mostRecentBackupTime.toMSecsSinceEpoch() * USECS_PER_MSEC; - } - - return mostRecentBackupInUsecs; -} - void OctreePersistThread::replaceData(QByteArray data) { backupCurrentFile(); @@ -167,123 +222,24 @@ bool OctreePersistThread::backupCurrentFile() { return true; } -bool OctreePersistThread::process() { +void OctreePersistThread::process() { + _tree->update(); - if (!_initialLoadComplete) { - quint64 loadStarted = usecTimestampNow(); - qCDebug(octree) << "loading Octrees from file: " << _filename << "..."; + auto now = std::chrono::steady_clock::now(); + auto timeSinceLastPersist = now - _lastPersistCheck; - if (!_replacementData.isNull()) { - replaceData(_replacementData); - } - - OctreeUtils::RawOctreeData data; - if (data.readOctreeDataInfoFromFile(_filename)) { - qDebug() << "Setting entity version info to: " << data.id << data.version; - _tree->setOctreeVersionInfo(data.id, data.version); - } - - bool persistentFileRead; - - _tree->withWriteLock([&] { - PerformanceWarning warn(true, "Loading Octree File", true); - - // First check to make sure "lock" file doesn't exist. If it does exist, then - // our last save crashed during the save, and we want to load our most recent backup. - QString lockFileName = _filename + ".lock"; - std::ifstream lockFile(qPrintable(lockFileName), std::ios::in | std::ios::binary | std::ios::ate); - if (lockFile.is_open()) { - qCDebug(octree) << "WARNING: Octree lock file detected at startup:" << lockFileName; - - lockFile.close(); - qCDebug(octree) << "Removing lock file:" << lockFileName; - remove(qPrintable(lockFileName)); - qCDebug(octree) << "Lock file removed:" << lockFileName; - } - - persistentFileRead = _tree->readFromFile(qPrintable(_filename.toLocal8Bit())); - _tree->pruneTree(); - }); - - quint64 loadDone = usecTimestampNow(); - _loadTimeUSecs = loadDone - loadStarted; - - _tree->clearDirtyBit(); // the tree is clean since we just loaded it - qCDebug(octree, "DONE loading Octrees from file... fileRead=%s", debug::valueOf(persistentFileRead)); - - unsigned long nodeCount = OctreeElement::getNodeCount(); - unsigned long internalNodeCount = OctreeElement::getInternalNodeCount(); - unsigned long leafNodeCount = OctreeElement::getLeafNodeCount(); - qCDebug(octree, "Nodes after loading scene %lu nodes %lu internal %lu leaves", nodeCount, internalNodeCount, leafNodeCount); - - bool wantDebug = false; - if (wantDebug) { - double usecPerGet = (double)OctreeElement::getGetChildAtIndexTime() - / (double)OctreeElement::getGetChildAtIndexCalls(); - qCDebug(octree) << "getChildAtIndexCalls=" << OctreeElement::getGetChildAtIndexCalls() - << " getChildAtIndexTime=" << OctreeElement::getGetChildAtIndexTime() << " perGet=" << usecPerGet; - - double usecPerSet = (double)OctreeElement::getSetChildAtIndexTime() - / (double)OctreeElement::getSetChildAtIndexCalls(); - qCDebug(octree) << "setChildAtIndexCalls=" << OctreeElement::getSetChildAtIndexCalls() - << " setChildAtIndexTime=" << OctreeElement::getSetChildAtIndexTime() << " perSet=" << usecPerSet; - } - - _initialLoadComplete = true; - - // Since we just loaded the persistent file, we can consider ourselves as having "just checked" for persistance. - _lastCheck = usecTimestampNow(); // we just loaded, no need to save again - - // This last persist time is not really used until the file is actually persisted. It is only - // used in formatting the backup filename in cases of non-rolling backup names. However, we don't - // want an uninitialized value for this, so we set it to the current time (startup of the server) - time(&_lastPersistTime); - - if (_replacementData.isNull()) { - sendLatestEntityDataToDS(); - } - _replacementData.clear(); - - emit loadCompleted(); + if (timeSinceLastPersist > _persistInterval) { + _lastPersistCheck = now; + persist(); } - if (isStillRunning()) { - quint64 MSECS_TO_USECS = 1000; - quint64 USECS_TO_SLEEP = 10 * MSECS_TO_USECS; // every 10ms - std::this_thread::sleep_for(std::chrono::microseconds(USECS_TO_SLEEP)); - - // do our updates then check to save... - _tree->update(); - - quint64 now = usecTimestampNow(); - quint64 sinceLastSave = now - _lastCheck; - quint64 intervalToCheck = _persistInterval * MSECS_TO_USECS; - - if (sinceLastSave > intervalToCheck) { - _lastCheck = now; - persist(); - } - } - - // if we were asked to debugTimestampNow do that now... - if (_debugTimestampNow) { - quint64 now = usecTimestampNow(); - quint64 sinceLastDebug = now - _lastTimeDebug; - quint64 DEBUG_TIMESTAMP_INTERVAL = 600000000; // every 10 minutes - - if (sinceLastDebug > DEBUG_TIMESTAMP_INTERVAL) { - _lastTimeDebug = usecTimestampNow(true); // ask for debug output - } - - } - return isStillRunning(); // keep running till they terminate us + QTimer::singleShot(TIME_BETWEEN_PROCESSING.count(), this, &OctreePersistThread::process); } void OctreePersistThread::aboutToFinish() { qCDebug(octree) << "Persist thread about to finish..."; persist(); qCDebug(octree) << "Persist thread done with about to finish..."; - _stopThread = true; } QByteArray OctreePersistThread::getPersistFileContents() const { @@ -295,6 +251,36 @@ QByteArray OctreePersistThread::getPersistFileContents() const { return fileContents; } +void OctreePersistThread::cleanupOldReplacementBackups() { + QRegExp filenameRegex { ".*\\.backup\\.\\d{8}-\\d{6}$" }; + QFileInfo persistFile { _filename }; + QDir backupDir { persistFile.absolutePath() }; + backupDir.setSorting(QDir::SortFlag::Time); + backupDir.setFilter(QDir::Filter::Files); + qDebug() << "Scanning backups for cleanup:" << backupDir.absolutePath(); + + int count = 0; + int64_t totalSize = 0; + for (auto fileInfo : backupDir.entryInfoList()) { + auto absPath = fileInfo.absoluteFilePath(); + qDebug() << " Found:" << absPath; + if (filenameRegex.exactMatch(absPath)) { + if (count >= MAX_OCTREE_REPLACEMENT_BACKUP_FILES_COUNT || totalSize > MAX_OCTREE_REPLACEMENT_BACKUP_FILES_SIZE_BYTES) { + qDebug() << " Removing:" << absPath; + QFile backup(absPath); + if (backup.remove()) { + qDebug() << " Removed backup:" << absPath; + } else { + qWarning() << " Failed to remove backup:" << absPath; + } + } + totalSize += fileInfo.size(); + count++; + } + } + qDebug() << "Found" << count << "backups"; +} + void OctreePersistThread::persist() { if (_tree->isDirty() && _initialLoadComplete) { @@ -304,27 +290,14 @@ void OctreePersistThread::persist() { qCDebug(octree) << "DONE pruning Octree before saving..."; }); - qCDebug(octree) << "persist operation calling backup..."; - backup(); // handle backup if requested - qCDebug(octree) << "persist operation DONE with backup..."; - _tree->incrementPersistDataVersion(); - // create our "lock" file to indicate we're saving. - QString lockFileName = _filename + ".lock"; - std::ofstream lockFile(qPrintable(lockFileName), std::ios::out|std::ios::binary); - if(lockFile.is_open()) { - qCDebug(octree) << "saving Octree lock file created at:" << lockFileName; - - _tree->writeToFile(qPrintable(_filename), NULL, _persistAsFileType); - time(&_lastPersistTime); + qCDebug(octree) << "Saving Octree data to:" << _filename; + if (_tree->writeToFile(_filename.toLocal8Bit().constData(), nullptr, _persistAsFileType)) { _tree->clearDirtyBit(); // tree is clean after saving - qCDebug(octree) << "DONE saving Octree to file..."; - - lockFile.close(); - qCDebug(octree) << "saving Octree lock file closed:" << lockFileName; - remove(qPrintable(lockFileName)); - qCDebug(octree) << "saving Octree lock file removed:" << lockFileName; + qCDebug(octree) << "DONE persisting Octree data to" << _filename; + } else { + qCWarning(octree) << "Failed to persist Octree data to" << _filename; } sendLatestEntityDataToDS(); @@ -345,197 +318,3 @@ void OctreePersistThread::sendLatestEntityDataToDS() { qCWarning(octree) << "Failed to persist octree to DS"; } } - -void OctreePersistThread::restoreFromMostRecentBackup() { - qCDebug(octree) << "Restoring from most recent backup..."; - - QString mostRecentBackupFileName; - QDateTime mostRecentBackupTime; - - bool recentBackup = getMostRecentBackup(QString(""), mostRecentBackupFileName, mostRecentBackupTime); - - // If we found a backup file, restore from that file. - if (recentBackup) { - qCDebug(octree) << "BEST backup file:" << mostRecentBackupFileName << " last modified:" << mostRecentBackupTime.toString(); - - qCDebug(octree) << "Removing old file:" << _filename; - remove(qPrintable(_filename)); - - qCDebug(octree) << "Restoring backup file " << mostRecentBackupFileName << "..."; - bool result = QFile::copy(mostRecentBackupFileName, _filename); - if (result) { - qCDebug(octree) << "DONE restoring backup file " << mostRecentBackupFileName << "to" << _filename << "..."; - } else { - qCDebug(octree) << "ERROR while restoring backup file " << mostRecentBackupFileName << "to" << _filename << "..."; - perror("ERROR while restoring backup file"); - } - } else { - qCDebug(octree) << "NO BEST backup file found."; - } -} - -bool OctreePersistThread::getMostRecentBackup(const QString& format, - QString& mostRecentBackupFileName, QDateTime& mostRecentBackupTime) { - - // Based on our backup file name, determine the path and file name pattern for backup files - QFileInfo persistFileInfo(_filename); - QString path = _backupDirectory; - QString fileNamePart = persistFileInfo.fileName(); - - QStringList filters; - - if (format.isEmpty()) { - // Create a file filter that will find all backup files of this extension format - foreach(const BackupRule& rule, _backupRules) { - QString backupExtension = rule.extensionFormat; - backupExtension.replace(QRegExp("%."), "*"); - QString backupFileNamePart = fileNamePart + backupExtension; - filters << backupFileNamePart; - } - } else { - QString backupExtension = format; - backupExtension.replace(QRegExp("%."), "*"); - QString backupFileNamePart = fileNamePart + backupExtension; - filters << backupFileNamePart; - } - - bool bestBackupFound = false; - QString bestBackupFile; - QDateTime bestBackupFileTime; - - // Iterate over all of the backup files in the persist location - QDirIterator dirIterator(path, filters, QDir::Files|QDir::NoSymLinks, QDirIterator::NoIteratorFlags); - while(dirIterator.hasNext()) { - - dirIterator.next(); - QDateTime lastModified = dirIterator.fileInfo().lastModified(); - - // Based on last modified date, track the most recently modified file as the best backup - if (lastModified > bestBackupFileTime) { - bestBackupFound = true; - bestBackupFile = dirIterator.filePath(); - bestBackupFileTime = lastModified; - } - } - - // If we found a backup then return the results - if (bestBackupFound) { - mostRecentBackupFileName = bestBackupFile; - mostRecentBackupTime = bestBackupFileTime; - } - return bestBackupFound; -} - -void OctreePersistThread::rollOldBackupVersions(const BackupRule& rule) { - - if (rule.extensionFormat.contains("%N")) { - if (rule.maxBackupVersions > 0) { - qCDebug(octree) << "Rolling old backup versions for rule" << rule.name << "..."; - - QString backupFileName = _backupDirectory + "/" + QUrl(_filename).fileName(); - - // Delete maximum rolling file because rename() fails on Windows if target exists - QString backupMaxExtensionN = rule.extensionFormat; - backupMaxExtensionN.replace(QString("%N"), QString::number(rule.maxBackupVersions)); - QString backupMaxFilenameN = backupFileName + backupMaxExtensionN; - QFile backupMaxFileN(backupMaxFilenameN); - if (backupMaxFileN.exists()) { - int result = remove(qPrintable(backupMaxFilenameN)); - if (result != 0) { - qCDebug(octree) << "ERROR deleting old backup file " << backupMaxFilenameN; - } - } - - for(int n = rule.maxBackupVersions - 1; n > 0; n--) { - QString backupExtensionN = rule.extensionFormat; - QString backupExtensionNplusOne = rule.extensionFormat; - backupExtensionN.replace(QString("%N"), QString::number(n)); - backupExtensionNplusOne.replace(QString("%N"), QString::number(n+1)); - - QString backupFilenameN = findMostRecentFileExtension(backupFileName, PERSIST_EXTENSIONS) + backupExtensionN; - QString backupFilenameNplusOne = backupFileName + backupExtensionNplusOne; - - QFile backupFileN(backupFilenameN); - - if (backupFileN.exists()) { - qCDebug(octree) << "rolling backup file " << backupFilenameN << "to" << backupFilenameNplusOne << "..."; - int result = rename(qPrintable(backupFilenameN), qPrintable(backupFilenameNplusOne)); - if (result == 0) { - qCDebug(octree) << "DONE rolling backup file " << backupFilenameN << "to" << backupFilenameNplusOne << "..."; - } else { - qCDebug(octree) << "ERROR in rolling backup file " << backupFilenameN << "to" << backupFilenameNplusOne << "..."; - perror("ERROR in rolling backup file"); - } - } - } - qCDebug(octree) << "Done rolling old backup versions..."; - } else { - qCDebug(octree) << "Rolling backups for rule" << rule.name << "." - << " Max Rolled Backup Versions less than 1 [" << rule.maxBackupVersions << "]." - << " No need to roll backups..."; - } - } -} - -void OctreePersistThread::backup() { - qCDebug(octree) << "backup operation wantBackup:" << _wantBackup; - if (_wantBackup) { - quint64 now = usecTimestampNow(); - - for(int i = 0; i < _backupRules.count(); i++) { - BackupRule& rule = _backupRules[i]; - - quint64 sinceLastBackup = now - rule.lastBackup; - quint64 SECS_TO_USECS = 1000 * 1000; - quint64 intervalToBackup = rule.interval * SECS_TO_USECS; - - qCDebug(octree) << "Checking [" << rule.name << "] - Time since last backup [" << sinceLastBackup << "] " << - "compared to backup interval [" << intervalToBackup << "]..."; - - if (sinceLastBackup > intervalToBackup) { - qCDebug(octree) << "Time since last backup [" << sinceLastBackup << "] for rule [" << rule.name - << "] exceeds backup interval [" << intervalToBackup << "] doing backup now..."; - - struct tm* localTime = localtime(&_lastPersistTime); - - QString backupFileName = _backupDirectory + "/" + QUrl(_filename).fileName(); - - // check to see if they asked for version rolling format - if (rule.extensionFormat.contains("%N")) { - rollOldBackupVersions(rule); // rename all the old backup files accordingly - QString backupExtension = rule.extensionFormat; - backupExtension.replace(QString("%N"), QString("1")); - backupFileName += backupExtension; - } else { - char backupExtension[256]; - strftime(backupExtension, sizeof(backupExtension), qPrintable(rule.extensionFormat), localTime); - backupFileName += backupExtension; - } - - if (rule.maxBackupVersions > 0) { - QFile persistFile(_filename); - if (persistFile.exists()) { - qCDebug(octree) << "backing up persist file " << _filename << "to" << backupFileName << "..."; - bool result = QFile::copy(_filename, backupFileName); - if (result) { - qCDebug(octree) << "DONE backing up persist file..."; - rule.lastBackup = now; // only record successful backup in this case. - } else { - qCDebug(octree) << "ERROR in backing up persist file..."; - perror("ERROR in backing up persist file"); - } - } else { - qCDebug(octree) << "persist file " << _filename << " does not exist. " << - "nothing to backup for this rule ["<< rule.name << "]..."; - } - } else { - qCDebug(octree) << "This backup rule" << rule.name - << " has Max Rolled Backup Versions less than 1 [" << rule.maxBackupVersions << "]." - << " There are no backups to be done..."; - } - } else { - qCDebug(octree) << "Backup not needed for this rule ["<< rule.name << "]..."; - } - } - } -} diff --git a/libraries/octree/src/OctreePersistThread.h b/libraries/octree/src/OctreePersistThread.h index bde207001f..0044a8fa5a 100644 --- a/libraries/octree/src/OctreePersistThread.h +++ b/libraries/octree/src/OctreePersistThread.h @@ -18,7 +18,7 @@ #include #include "Octree.h" -class OctreePersistThread : public GenericThread { +class OctreePersistThread : public QObject { Q_OBJECT public: class BackupRule { @@ -30,37 +30,37 @@ public: quint64 lastBackup; }; - static const int DEFAULT_PERSIST_INTERVAL; + static const std::chrono::seconds DEFAULT_PERSIST_INTERVAL; - OctreePersistThread(OctreePointer tree, const QString& filename, const QString& backupDirectory, - int persistInterval = DEFAULT_PERSIST_INTERVAL, bool wantBackup = false, - const QJsonObject& settings = QJsonObject(), bool debugTimestampNow = false, - QString persistAsFileType = "json.gz", const QByteArray& replacementData = QByteArray()); + OctreePersistThread(OctreePointer tree, + const QString& filename, + std::chrono::milliseconds persistInterval = DEFAULT_PERSIST_INTERVAL, + bool debugTimestampNow = false, + QString persistAsFileType = "json.gz"); bool isInitialLoadComplete() const { return _initialLoadComplete; } quint64 getLoadElapsedTime() const { return _loadTimeUSecs; } - void aboutToFinish(); /// call this to inform the persist thread that the owner is about to finish to support final persist - QString getPersistFilename() const { return _filename; } QString getPersistFileMimeType() const; QByteArray getPersistFileContents() const; + void aboutToFinish(); /// call this to inform the persist thread that the owner is about to finish to support final persist + +public slots: + void start(); + signals: void loadCompleted(); -protected: - /// Implements generic processing behavior for this thread. - virtual bool process() override; +protected slots: + void process(); + void handleOctreeDataFileReply(QSharedPointer message); +protected: void persist(); - void backup(); - void rollOldBackupVersions(const BackupRule& rule); - void restoreFromMostRecentBackup(); - bool getMostRecentBackup(const QString& format, QString& mostRecentBackupFileName, QDateTime& mostRecentBackupTime); - quint64 getMostRecentBackupTimeInUsecs(const QString& format); - void parseSettings(const QJsonObject& settings); bool backupCurrentFile(); + void cleanupOldReplacementBackups(); void replaceData(QByteArray data); void sendLatestEntityDataToDS(); @@ -68,18 +68,12 @@ protected: private: OctreePointer _tree; QString _filename; - QString _backupDirectory; - int _persistInterval; + std::chrono::milliseconds _persistInterval; + std::chrono::steady_clock::time_point _lastPersistCheck; bool _initialLoadComplete; - QByteArray _replacementData; quint64 _loadTimeUSecs; - time_t _lastPersistTime; - quint64 _lastCheck; - bool _wantBackup; - QVector _backupRules; - bool _debugTimestampNow; quint64 _lastTimeDebug; diff --git a/libraries/physics/src/EntityMotionState.cpp b/libraries/physics/src/EntityMotionState.cpp index deb779b1f8..594ea476f6 100644 --- a/libraries/physics/src/EntityMotionState.cpp +++ b/libraries/physics/src/EntityMotionState.cpp @@ -446,17 +446,13 @@ 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())); - // 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())) { + if (_entity->dynamicDataNeedsTransmit()) { return true; } - if (_entity->shouldSuppressLocationEdits()) { - return false; + // "shouldSuppressLocationEdits" really means: "the entity has a 'Hold' action therefore + // we don't need send an update unless the entity is not contained by its queryAACube" + return _entity->queryAACubeNeedsUpdate(); } return remoteSimulationOutOfSync(simulationStep); diff --git a/libraries/physics/src/ObjectDynamic.cpp b/libraries/physics/src/ObjectDynamic.cpp index 3341025a8f..28323a8b92 100644 --- a/libraries/physics/src/ObjectDynamic.cpp +++ b/libraries/physics/src/ObjectDynamic.cpp @@ -159,6 +159,13 @@ void ObjectDynamic::removeFromSimulation(EntitySimulationPointer simulation) con simulation->removeDynamic(myID); } +void ObjectDynamic::setOwnerEntity(const EntityItemPointer ownerEntity) { + if (!ownerEntity) { + activateBody(); + } + _ownerEntity = ownerEntity; +} + EntityItemPointer ObjectDynamic::getEntityByID(EntityItemID entityID) const { EntityItemPointer ownerEntity; withReadLock([&]{ diff --git a/libraries/physics/src/ObjectDynamic.h b/libraries/physics/src/ObjectDynamic.h index 7fdf2e323a..bfee79aca9 100644 --- a/libraries/physics/src/ObjectDynamic.h +++ b/libraries/physics/src/ObjectDynamic.h @@ -33,7 +33,7 @@ public: virtual void removeFromSimulation(EntitySimulationPointer simulation) const override; virtual EntityItemWeakPointer getOwnerEntity() const override { return _ownerEntity; } - virtual void setOwnerEntity(const EntityItemPointer ownerEntity) override { _ownerEntity = ownerEntity; } + virtual void setOwnerEntity(const EntityItemPointer ownerEntity) override; virtual void invalidate() {}; diff --git a/libraries/physics/src/ObjectMotionState.h b/libraries/physics/src/ObjectMotionState.h index bb3c00bd5d..0ca28d6b09 100644 --- a/libraries/physics/src/ObjectMotionState.h +++ b/libraries/physics/src/ObjectMotionState.h @@ -111,7 +111,7 @@ public: virtual PhysicsMotionType getMotionType() const { return _motionType; } void setMass(float mass); - virtual float getMass() const; + float getMass() const; void setBodyLinearVelocity(const glm::vec3& velocity) const; void setBodyAngularVelocity(const glm::vec3& velocity) const; diff --git a/libraries/physics/src/PhysicsEngine.cpp b/libraries/physics/src/PhysicsEngine.cpp index 21b5b38b13..ee87cab8e8 100644 --- a/libraries/physics/src/PhysicsEngine.cpp +++ b/libraries/physics/src/PhysicsEngine.cpp @@ -105,6 +105,10 @@ void PhysicsEngine::addObjectToDynamicsWorld(ObjectMotionState* motionState) { } case MOTION_TYPE_DYNAMIC: { mass = motionState->getMass(); + if (mass != mass || mass < 1.0f) { + qCDebug(physics) << "mass is too low, setting to 1.0 Kg --" << mass; + mass = 1.0f; + } btCollisionShape* shape = const_cast(motionState->getShape()); assert(shape); shape->calculateLocalInertia(mass, inertia); diff --git a/libraries/render-utils/src/CauterizedModel.cpp b/libraries/render-utils/src/CauterizedModel.cpp index 1596f7ba83..80a9c5ccae 100644 --- a/libraries/render-utils/src/CauterizedModel.cpp +++ b/libraries/render-utils/src/CauterizedModel.cpp @@ -47,7 +47,7 @@ bool CauterizedModel::updateGeometry() { return needsFullUpdate; } -void CauterizedModel::createVisibleRenderItemSet() { +void CauterizedModel::createRenderItemSet() { if (_isCauterized) { assert(isLoaded()); const auto& meshes = _renderGeometry->getMeshes(); @@ -87,21 +87,17 @@ void CauterizedModel::createVisibleRenderItemSet() { for (int partIndex = 0; partIndex < numParts; partIndex++) { auto ptr = std::make_shared(shared_from_this(), i, partIndex, shapeID, transform, offset); _modelMeshRenderItems << std::static_pointer_cast(ptr); - _modelMeshMaterialNames.push_back(getGeometry()->getShapeMaterial(shapeID)->getName()); + auto material = getGeometry()->getShapeMaterial(shapeID); + _modelMeshMaterialNames.push_back(material ? material->getName() : ""); _modelMeshRenderItemShapes.emplace_back(ShapeInfo{ (int)i }); shapeID++; } } } else { - Model::createVisibleRenderItemSet(); + Model::createRenderItemSet(); } } -void CauterizedModel::createCollisionRenderItemSet() { - // Temporary HACK: use base class method for now - Model::createCollisionRenderItemSet(); -} - void CauterizedModel::updateClusterMatrices() { PerformanceTimer perfTimer("CauterizedModel::updateClusterMatrices"); @@ -185,12 +181,6 @@ void CauterizedModel::updateRenderItems() { if (!_addedToScene) { return; } - - glm::vec3 scale = getScale(); - if (_collisionGeometry) { - // _collisionGeometry is already scaled - scale = glm::vec3(1.0f); - } _needsUpdateClusterMatrices = true; _renderItemsNeedUpdate = false; @@ -215,10 +205,7 @@ void CauterizedModel::updateRenderItems() { modelTransform.setRotation(self->getRotation()); bool isWireframe = self->isWireframe(); - bool isVisible = self->isVisible(); - bool canCastShadow = self->canCastShadow(); - bool isLayeredInFront = self->isLayeredInFront(); - bool isLayeredInHUD = self->isLayeredInHUD(); + auto renderItemKeyGlobalFlags = self->getRenderItemKeyGlobalFlags(); bool enableCauterization = self->getEnableCauterization(); render::Transaction transaction; @@ -234,7 +221,7 @@ void CauterizedModel::updateRenderItems() { bool useDualQuaternionSkinning = self->getUseDualQuaternionSkinning(); transaction.updateItem(itemID, [modelTransform, meshState, useDualQuaternionSkinning, cauterizedMeshState, invalidatePayloadShapeKey, - isWireframe, isVisible, isLayeredInFront, isLayeredInHUD, canCastShadow, enableCauterization](CauterizedMeshPartPayload& data) { + isWireframe, renderItemKeyGlobalFlags, enableCauterization](CauterizedMeshPartPayload& data) { if (useDualQuaternionSkinning) { data.updateClusterBuffer(meshState.clusterDualQuaternions, cauterizedMeshState.clusterDualQuaternions); @@ -276,8 +263,7 @@ void CauterizedModel::updateRenderItems() { data.updateTransformForCauterizedMesh(renderTransform); data.setEnableCauterization(enableCauterization); - data.updateKey(isVisible, isLayeredInFront || isLayeredInHUD, canCastShadow, render::ItemKey::TAG_BITS_ALL); - data.setLayer(isLayeredInFront, isLayeredInHUD); + data.updateKey(renderItemKeyGlobalFlags); data.setShapeKey(invalidatePayloadShapeKey, isWireframe, useDualQuaternionSkinning); }); } diff --git a/libraries/render-utils/src/CauterizedModel.h b/libraries/render-utils/src/CauterizedModel.h index d16c928ba6..36a96fb006 100644 --- a/libraries/render-utils/src/CauterizedModel.h +++ b/libraries/render-utils/src/CauterizedModel.h @@ -31,9 +31,8 @@ public: void deleteGeometry() override; bool updateGeometry() override; - void createVisibleRenderItemSet() override; - void createCollisionRenderItemSet() override; - + void createRenderItemSet() override; + virtual void updateClusterMatrices() override; void updateRenderItems() override; diff --git a/libraries/render-utils/src/MeshPartPayload.cpp b/libraries/render-utils/src/MeshPartPayload.cpp index 72a4b7e0a4..7cf8bc8297 100644 --- a/libraries/render-utils/src/MeshPartPayload.cpp +++ b/libraries/render-utils/src/MeshPartPayload.cpp @@ -79,28 +79,10 @@ void MeshPartPayload::removeMaterial(graphics::MaterialPointer material) { _drawMaterials.remove(material); } -void MeshPartPayload::updateKey(bool isVisible, bool isLayered, bool canCastShadow, uint8_t tagBits, bool isGroupCulled) { - ItemKey::Builder builder; +void MeshPartPayload::updateKey(const render::ItemKey& key) { + ItemKey::Builder builder(key); builder.withTypeShape(); - if (!isVisible) { - builder.withInvisible(); - } - - builder.withTagBits(tagBits); - - if (isLayered) { - builder.withLayered(); - } - - if (canCastShadow) { - builder.withShadowCaster(); - } - - if (isGroupCulled) { - builder.withSubMetaCulled(); - } - if (topMaterialExists()) { auto matKey = _drawMaterials.top().material->getKey(); if (matKey.isTranslucent()) { @@ -200,12 +182,6 @@ template <> const Item::Bound payloadGetBound(const ModelMeshPartPayload::Pointe } return Item::Bound(); } -template <> int payloadGetLayer(const ModelMeshPartPayload::Pointer& payload) { - if (payload) { - return payload->getLayer(); - } - return 0; -} template <> const ShapeKey shapeGetShapeKey(const ModelMeshPartPayload::Pointer& payload) { if (payload) { @@ -332,28 +308,10 @@ void ModelMeshPartPayload::updateTransformForSkinnedMesh(const Transform& render } // Note that this method is called for models but not for shapes -void ModelMeshPartPayload::updateKey(bool isVisible, bool isLayered, bool canCastShadow, uint8_t tagBits, bool isGroupCulled) { - ItemKey::Builder builder; +void ModelMeshPartPayload::updateKey(const render::ItemKey& key) { + ItemKey::Builder builder(key); builder.withTypeShape(); - if (!isVisible) { - builder.withInvisible(); - } - - builder.withTagBits(tagBits); - - if (isLayered) { - builder.withLayered(); - } - - if (canCastShadow) { - builder.withShadowCaster(); - } - - if (isGroupCulled) { - builder.withSubMetaCulled(); - } - if (_isBlendShaped || _isSkinned) { builder.withDeformed(); } @@ -368,20 +326,6 @@ void ModelMeshPartPayload::updateKey(bool isVisible, bool isLayered, bool canCas _itemKey = builder.build(); } -void ModelMeshPartPayload::setLayer(bool isLayeredInFront, bool isLayeredInHUD) { - if (isLayeredInFront) { - _layer = Item::LAYER_3D_FRONT; - } else if (isLayeredInHUD) { - _layer = Item::LAYER_3D_HUD; - } else { - _layer = Item::LAYER_3D; - } -} - -int ModelMeshPartPayload::getLayer() const { - return _layer; -} - void ModelMeshPartPayload::setShapeKey(bool invalidateShapeKey, bool isWireframe, bool useDualQuaternionSkinning) { if (invalidateShapeKey) { _shapeKey = ShapeKey::Builder::invalid(); diff --git a/libraries/render-utils/src/MeshPartPayload.h b/libraries/render-utils/src/MeshPartPayload.h index 08ad7a8311..5c7177e890 100644 --- a/libraries/render-utils/src/MeshPartPayload.h +++ b/libraries/render-utils/src/MeshPartPayload.h @@ -32,7 +32,7 @@ public: typedef render::Payload Payload; typedef Payload::DataPointer Pointer; - virtual void updateKey(bool isVisible, bool isLayered, bool canCastShadow, uint8_t tagBits, bool isGroupCulled = false); + virtual void updateKey(const render::ItemKey& key); virtual void updateMeshPart(const std::shared_ptr& drawMesh, int partIndex); @@ -95,7 +95,7 @@ public: void notifyLocationChanged() override; - void updateKey(bool isVisible, bool isLayered, bool canCastShadow, uint8_t tagBits, bool isGroupCulled = false) override; + void updateKey(const render::ItemKey& key) override; // matrix palette skinning void updateClusterBuffer(const std::vector& clusterMatrices); @@ -105,11 +105,9 @@ public: void updateTransformForSkinnedMesh(const Transform& renderTransform, const Transform& boundTransform); // Render Item interface - int getLayer() const; render::ShapeKey getShapeKey() const override; // shape interface void render(RenderArgs* args) override; - void setLayer(bool isLayeredInFront, bool isLayeredInHUD); void setShapeKey(bool invalidateShapeKey, bool isWireframe, bool useDualQuaternionSkinning); // ModelMeshPartPayload functions to perform render @@ -139,13 +137,11 @@ private: gpu::BufferPointer _blendedVertexBuffer; render::ShapeKey _shapeKey { render::ShapeKey::Builder::invalid() }; - int _layer { render::Item::LAYER_3D }; }; namespace render { template <> const ItemKey payloadGetKey(const ModelMeshPartPayload::Pointer& payload); template <> const Item::Bound payloadGetBound(const ModelMeshPartPayload::Pointer& payload); - template <> int payloadGetLayer(const ModelMeshPartPayload::Pointer& payload); template <> const ShapeKey shapeGetShapeKey(const ModelMeshPartPayload::Pointer& payload); template <> void payloadRender(const ModelMeshPartPayload::Pointer& payload, RenderArgs* args); } diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index 65b12ac0d4..178c00c4c7 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -47,51 +47,9 @@ int vec3VectorTypeId = qRegisterMetaType >(); float Model::FAKE_DIMENSION_PLACEHOLDER = -1.0f; #define HTTP_INVALID_COM "http://invalid.com" -const int NUM_COLLISION_HULL_COLORS = 24; -std::vector _collisionMaterials; - -void initCollisionMaterials() { - // generates bright colors in red, green, blue, yellow, magenta, and cyan spectrums - // (no browns, greys, or dark shades) - float component[NUM_COLLISION_HULL_COLORS] = { - 0.0f, 0.0f, 0.0f, 0.0f, - 0.0f, 0.0f, 0.0f, 0.0f, - 0.2f, 0.4f, 0.6f, 0.8f, - 1.0f, 1.0f, 1.0f, 1.0f, - 1.0f, 1.0f, 1.0f, 1.0f, - 0.8f, 0.6f, 0.4f, 0.2f - }; - _collisionMaterials.reserve(NUM_COLLISION_HULL_COLORS); - - // each component gets the same cuve - // but offset by a multiple of one third the full width - int numComponents = 3; - int sectionWidth = NUM_COLLISION_HULL_COLORS / numComponents; - int greenPhase = sectionWidth; - int bluePhase = 2 * sectionWidth; - - // we stride through the colors to scatter adjacent shades - // so they don't tend to group together for large models - for (int i = 0; i < sectionWidth; ++i) { - for (int j = 0; j < numComponents; ++j) { - graphics::MaterialPointer material; - material = std::make_shared(); - int index = j * sectionWidth + i; - float red = component[index % NUM_COLLISION_HULL_COLORS]; - float green = component[(index + greenPhase) % NUM_COLLISION_HULL_COLORS]; - float blue = component[(index + bluePhase) % NUM_COLLISION_HULL_COLORS]; - material->setAlbedo(glm::vec3(red, green, blue)); - material->setMetallic(0.02f); - material->setRoughness(0.5f); - _collisionMaterials.push_back(material); - } - } -} - Model::Model(QObject* parent, SpatiallyNestable* spatiallyNestableOverride) : QObject(parent), _renderGeometry(), - _collisionGeometry(), _renderWatcher(_renderGeometry), _spatiallyNestableOverride(spatiallyNestableOverride), _translation(0.0f), @@ -103,11 +61,10 @@ Model::Model(QObject* parent, SpatiallyNestable* spatiallyNestableOverride) : _snapModelToRegistrationPoint(false), _snappedToRegistrationPoint(false), _url(HTTP_INVALID_COM), - _isVisible(true), - _canCastShadow(false), _blendNumber(0), _appliedBlendNumber(0), - _isWireframe(false) + _isWireframe(false), + _renderItemKeyGlobalFlags(render::ItemKey::Builder().withVisible().withTagBits(render::hifi::TAG_ALL_VIEWS).build()) { // we may have been created in the network thread, but we live in the main thread if (_viewState) { @@ -268,12 +225,7 @@ void Model::updateRenderItems() { modelTransform.setScale(glm::vec3(1.0f)); bool isWireframe = self->isWireframe(); - bool isVisible = self->isVisible(); - bool canCastShadow = self->canCastShadow(); - uint8_t viewTagBits = self->getViewTagBits(); - bool isLayeredInFront = self->isLayeredInFront(); - bool isLayeredInHUD = self->isLayeredInHUD(); - bool isGroupCulled = self->isGroupCulled(); + auto renderItemKeyGlobalFlags = self->getRenderItemKeyGlobalFlags(); render::Transaction transaction; for (int i = 0; i < (int) self->_modelMeshRenderItemIDs.size(); i++) { @@ -287,9 +239,7 @@ void Model::updateRenderItems() { bool useDualQuaternionSkinning = self->getUseDualQuaternionSkinning(); transaction.updateItem(itemID, [modelTransform, meshState, useDualQuaternionSkinning, - invalidatePayloadShapeKey, isWireframe, isVisible, - canCastShadow, viewTagBits, isLayeredInFront, - isLayeredInHUD, isGroupCulled](ModelMeshPartPayload& data) { + invalidatePayloadShapeKey, isWireframe, renderItemKeyGlobalFlags](ModelMeshPartPayload& data) { if (useDualQuaternionSkinning) { data.updateClusterBuffer(meshState.clusterDualQuaternions); } else { @@ -313,21 +263,11 @@ void Model::updateRenderItems() { } data.updateTransformForSkinnedMesh(renderTransform, modelTransform); - data.updateKey(isVisible, isLayeredInFront || isLayeredInHUD, canCastShadow, viewTagBits, isGroupCulled); - data.setLayer(isLayeredInFront, isLayeredInHUD); + data.updateKey(renderItemKeyGlobalFlags); data.setShapeKey(invalidatePayloadShapeKey, isWireframe, useDualQuaternionSkinning); }); } - Transform collisionMeshOffset; - collisionMeshOffset.setIdentity(); - foreach(auto itemID, self->_collisionRenderItemsMap.keys()) { - transaction.updateItem(itemID, [modelTransform, collisionMeshOffset](MeshPartPayload& data) { - // update the model transform for this render item. - data.updateTransform(modelTransform, collisionMeshOffset); - }); - } - AbstractViewStateInterface::instance()->getMain3DScene()->enqueueTransaction(transaction); }); } @@ -773,156 +713,129 @@ void Model::calculateTriangleSets(const FBXGeometry& geometry) { } } -void Model::setVisibleInScene(bool isVisible, const render::ScenePointer& scene, uint8_t viewTagBits, bool isGroupCulled) { - if (_isVisible != isVisible || _viewTagBits != viewTagBits || _isGroupCulled != isGroupCulled) { - _isVisible = isVisible; - _viewTagBits = viewTagBits; - _isGroupCulled = isGroupCulled; +void Model::updateRenderItemsKey(const render::ScenePointer& scene) { + if (!scene) { + _needsFixupInScene = true; + return; + } + auto renderItemsKey = _renderItemKeyGlobalFlags; + render::Transaction transaction; + foreach(auto item, _modelMeshRenderItemsMap.keys()) { + transaction.updateItem(item, [renderItemsKey](ModelMeshPartPayload& data) { + data.updateKey(renderItemsKey); + }); + } + scene->enqueueTransaction(transaction); +} - bool isLayeredInFront = _isLayeredInFront; - bool isLayeredInHUD = _isLayeredInHUD; - bool canCastShadow = _canCastShadow; - render::Transaction transaction; - foreach (auto item, _modelMeshRenderItemsMap.keys()) { - transaction.updateItem(item, [isVisible, viewTagBits, isLayeredInFront, canCastShadow, - isLayeredInHUD, isGroupCulled](ModelMeshPartPayload& data) { - data.updateKey(isVisible, isLayeredInFront || isLayeredInHUD, canCastShadow, viewTagBits, isGroupCulled); - }); - } - foreach(auto item, _collisionRenderItemsMap.keys()) { - transaction.updateItem(item, [isVisible, viewTagBits, isLayeredInFront, canCastShadow, - isLayeredInHUD, isGroupCulled](ModelMeshPartPayload& data) { - data.updateKey(isVisible, isLayeredInFront || isLayeredInHUD, canCastShadow, viewTagBits, isGroupCulled); - }); - } - scene->enqueueTransaction(transaction); +void Model::setVisibleInScene(bool visible, const render::ScenePointer& scene) { + if (Model::isVisible() != visible) { + auto keyBuilder = render::ItemKey::Builder(_renderItemKeyGlobalFlags); + _renderItemKeyGlobalFlags = (visible ? keyBuilder.withVisible() : keyBuilder.withInvisible()); + updateRenderItemsKey(scene); } } -void Model::setCanCastShadow(bool canCastShadow, const render::ScenePointer& scene, uint8_t viewTagBits, bool isGroupCulled) { - if (_canCastShadow != canCastShadow) { - _canCastShadow = canCastShadow; +bool Model::isVisible() const { + return _renderItemKeyGlobalFlags.isVisible(); +} - bool isVisible = _isVisible; - bool isLayeredInFront = _isLayeredInFront; - bool isLayeredInHUD = _isLayeredInHUD; - - render::Transaction transaction; - foreach (auto item, _modelMeshRenderItemsMap.keys()) { - transaction.updateItem(item, - [isVisible, viewTagBits, canCastShadow, isLayeredInFront, isLayeredInHUD, isGroupCulled](ModelMeshPartPayload& data) { - data.updateKey(isVisible, viewTagBits, canCastShadow, isLayeredInFront || isLayeredInHUD, isGroupCulled); - }); - } - - scene->enqueueTransaction(transaction); +void Model::setCanCastShadow(bool castShadow, const render::ScenePointer& scene) { + if (Model::canCastShadow() != castShadow) { + auto keyBuilder = render::ItemKey::Builder(_renderItemKeyGlobalFlags); + _renderItemKeyGlobalFlags = (castShadow ? keyBuilder.withShadowCaster() : keyBuilder.withoutShadowCaster()); + updateRenderItemsKey(scene); } } -void Model::setLayeredInFront(bool isLayeredInFront, const render::ScenePointer& scene) { - if (_isLayeredInFront != isLayeredInFront) { - _isLayeredInFront = isLayeredInFront; +bool Model::canCastShadow() const { + return _renderItemKeyGlobalFlags.isShadowCaster(); +} - bool isVisible = _isVisible; - bool canCastShadow = _canCastShadow; - uint8_t viewTagBits = _viewTagBits; - bool isLayeredInHUD = _isLayeredInHUD; - bool isGroupCulled = _isGroupCulled; - - render::Transaction transaction; - foreach(auto item, _modelMeshRenderItemsMap.keys()) { - transaction.updateItem(item, [isVisible, viewTagBits, isLayeredInFront, canCastShadow, - isLayeredInHUD, isGroupCulled](ModelMeshPartPayload& data) { - data.updateKey(isVisible, isLayeredInFront || isLayeredInHUD, canCastShadow, viewTagBits, isGroupCulled); - data.setLayer(isLayeredInFront, isLayeredInHUD); - }); - } - foreach(auto item, _collisionRenderItemsMap.keys()) { - transaction.updateItem(item, [isVisible, viewTagBits, isLayeredInFront, canCastShadow, - isLayeredInHUD, isGroupCulled](ModelMeshPartPayload& data) { - data.updateKey(isVisible, isLayeredInFront || isLayeredInHUD, canCastShadow, viewTagBits, isGroupCulled); - data.setLayer(isLayeredInFront, isLayeredInHUD); - }); - } - scene->enqueueTransaction(transaction); +void Model::setLayeredInFront(bool layeredInFront, const render::ScenePointer& scene) { + if (Model::isLayeredInFront() != layeredInFront) { + auto keyBuilder = render::ItemKey::Builder(_renderItemKeyGlobalFlags); + _renderItemKeyGlobalFlags = (layeredInFront ? keyBuilder.withLayer(render::hifi::LAYER_3D_FRONT) : keyBuilder.withoutLayer()); + updateRenderItemsKey(scene); } } -void Model::setLayeredInHUD(bool isLayeredInHUD, const render::ScenePointer& scene) { - if (_isLayeredInHUD != isLayeredInHUD) { - _isLayeredInHUD = isLayeredInHUD; +bool Model::isLayeredInFront() const { + return _renderItemKeyGlobalFlags.isLayer(render::hifi::LAYER_3D_FRONT); +} - bool isVisible = _isVisible; - bool canCastShadow = _canCastShadow; - uint8_t viewTagBits = _viewTagBits; - bool isLayeredInFront = _isLayeredInFront; - bool isGroupCulled = _isGroupCulled; - - render::Transaction transaction; - foreach(auto item, _modelMeshRenderItemsMap.keys()) { - transaction.updateItem(item, [isVisible, viewTagBits, isLayeredInFront, canCastShadow, - isLayeredInHUD, isGroupCulled](ModelMeshPartPayload& data) { - data.updateKey(isVisible, isLayeredInFront || isLayeredInHUD, canCastShadow, viewTagBits, isGroupCulled); - data.setLayer(isLayeredInFront, isLayeredInHUD); - }); - } - foreach(auto item, _collisionRenderItemsMap.keys()) { - transaction.updateItem(item, [isVisible, viewTagBits, isLayeredInFront, canCastShadow, - isLayeredInHUD, isGroupCulled](ModelMeshPartPayload& data) { - data.updateKey(isVisible, isLayeredInFront || isLayeredInHUD, canCastShadow, viewTagBits, isGroupCulled); - data.setLayer(isLayeredInFront, isLayeredInHUD); - }); - } - scene->enqueueTransaction(transaction); +void Model::setLayeredInHUD(bool layeredInHUD, const render::ScenePointer& scene) { + if (Model::isLayeredInHUD() != layeredInHUD) { + auto keyBuilder = render::ItemKey::Builder(_renderItemKeyGlobalFlags); + _renderItemKeyGlobalFlags = (layeredInHUD ? keyBuilder.withLayer(render::hifi::LAYER_3D_HUD) : keyBuilder.withoutLayer()); + updateRenderItemsKey(scene); } } +bool Model::isLayeredInHUD() const { + return _renderItemKeyGlobalFlags.isLayer(render::hifi::LAYER_3D_HUD); +} + +void Model::setTagMask(uint8_t mask, const render::ScenePointer& scene) { + if (Model::getTagMask() != mask) { + auto keyBuilder = render::ItemKey::Builder(_renderItemKeyGlobalFlags); + _renderItemKeyGlobalFlags = keyBuilder.withTagBits(mask); + updateRenderItemsKey(scene); + } +} +render::hifi::Tag Model::getTagMask() const { + return (render::hifi::Tag) _renderItemKeyGlobalFlags.getTagBits(); +} + +void Model::setGroupCulled(bool groupCulled, const render::ScenePointer& scene) { + if (Model::isGroupCulled() != groupCulled) { + auto keyBuilder = render::ItemKey::Builder(_renderItemKeyGlobalFlags); + _renderItemKeyGlobalFlags = (groupCulled ? keyBuilder.withSubMetaCulled() : keyBuilder.withoutSubMetaCulled()); + updateRenderItemsKey(scene); + } +} +bool Model::isGroupCulled() const { + return _renderItemKeyGlobalFlags.isSubMetaCulled(); +} + +const render::ItemKey Model::getRenderItemKeyGlobalFlags() const { + return _renderItemKeyGlobalFlags; +} + bool Model::addToScene(const render::ScenePointer& scene, render::Transaction& transaction, render::Item::Status::Getters& statusGetters) { - bool readyToRender = _collisionGeometry || isLoaded(); - if (!_addedToScene && readyToRender) { - createRenderItemSet(); + if (!_addedToScene && isLoaded()) { + updateClusterMatrices(); + if (_modelMeshRenderItems.empty()) { + createRenderItemSet(); + } } bool somethingAdded = false; - if (_collisionGeometry) { - if (_collisionRenderItemsMap.empty()) { - foreach (auto renderItem, _collisionRenderItems) { - auto item = scene->allocateID(); - auto renderPayload = std::make_shared(renderItem); - if (_collisionRenderItems.empty() && statusGetters.size()) { - renderPayload->addStatusGetters(statusGetters); - } - transaction.resetItem(item, renderPayload); - _collisionRenderItemsMap.insert(item, renderPayload); + + if (_modelMeshRenderItemsMap.empty()) { + + bool hasTransparent = false; + size_t verticesCount = 0; + foreach(auto renderItem, _modelMeshRenderItems) { + auto item = scene->allocateID(); + auto renderPayload = std::make_shared(renderItem); + if (_modelMeshRenderItemsMap.empty() && statusGetters.size()) { + renderPayload->addStatusGetters(statusGetters); } - somethingAdded = !_collisionRenderItemsMap.empty(); + transaction.resetItem(item, renderPayload); + + hasTransparent = hasTransparent || renderItem.get()->getShapeKey().isTranslucent(); + verticesCount += renderItem.get()->getVerticesCount(); + _modelMeshRenderItemsMap.insert(item, renderPayload); + _modelMeshRenderItemIDs.emplace_back(item); } - } else { - if (_modelMeshRenderItemsMap.empty()) { + somethingAdded = !_modelMeshRenderItemsMap.empty(); - bool hasTransparent = false; - size_t verticesCount = 0; - foreach(auto renderItem, _modelMeshRenderItems) { - auto item = scene->allocateID(); - auto renderPayload = std::make_shared(renderItem); - if (_modelMeshRenderItemsMap.empty() && statusGetters.size()) { - renderPayload->addStatusGetters(statusGetters); - } - transaction.resetItem(item, renderPayload); - - hasTransparent = hasTransparent || renderItem.get()->getShapeKey().isTranslucent(); - verticesCount += renderItem.get()->getVerticesCount(); - _modelMeshRenderItemsMap.insert(item, renderPayload); - _modelMeshRenderItemIDs.emplace_back(item); - } - somethingAdded = !_modelMeshRenderItemsMap.empty(); - - _renderInfoVertexCount = verticesCount; - _renderInfoDrawCalls = _modelMeshRenderItemsMap.count(); - _renderInfoHasTransparent = hasTransparent; - } + _renderInfoVertexCount = verticesCount; + _renderInfoDrawCalls = _modelMeshRenderItemsMap.count(); + _renderInfoHasTransparent = hasTransparent; } if (somethingAdded) { @@ -944,11 +857,6 @@ void Model::removeFromScene(const render::ScenePointer& scene, render::Transacti _modelMeshMaterialNames.clear(); _modelMeshRenderItemShapes.clear(); - foreach(auto item, _collisionRenderItemsMap.keys()) { - transaction.removeItem(item); - } - _collisionRenderItems.clear(); - _collisionRenderItemsMap.clear(); _addedToScene = false; _renderInfoVertexCount = 0; @@ -1092,15 +1000,11 @@ int Model::getLastFreeJointIndex(int jointIndex) const { void Model::setTextures(const QVariantMap& textures) { if (isLoaded()) { - _needsUpdateTextures = true; + _pendingTextures.clear(); _needsFixupInScene = true; _renderGeometry->setTextures(textures); } else { - // FIXME(Huffman): Disconnect previously connected lambdas so we don't set textures multiple - // after the geometry has finished loading. - connect(&_renderWatcher, &GeometryResourceWatcher::finished, this, [this, textures]() { - _renderGeometry->setTextures(textures); - }); + _pendingTextures = textures; } } @@ -1124,7 +1028,8 @@ void Model::setURL(const QUrl& url) { } _needsReload = true; - _needsUpdateTextures = true; + // One might be tempted to _pendingTextures.clear(), thinking that a new URL means an old texture doesn't apply. + // But sometimes, particularly when first setting the values, the texture might be set first. So let's not clear here. _visualGeometryRequestFailed = false; _needsFixupInScene = true; invalidCalculatedMeshBoxes(); @@ -1141,6 +1046,8 @@ void Model::setURL(const QUrl& url) { void Model::loadURLFinished(bool success) { if (!success) { _visualGeometryRequestFailed = true; + } else if (!_pendingTextures.empty()) { + setTextures(_pendingTextures); } emit setURLFinished(success); } @@ -1523,7 +1430,6 @@ void Model::deleteGeometry() { _rig.destroyAnimGraph(); _blendedBlendshapeCoefficients.clear(); _renderGeometry.reset(); - _collisionGeometry.reset(); } void Model::overrideModelTransformAndOffset(const Transform& transform, const glm::vec3& offset) { @@ -1552,19 +1458,6 @@ const render::ItemIDs& Model::fetchRenderItemIDs() const { } void Model::createRenderItemSet() { - updateClusterMatrices(); - if (_collisionGeometry) { - if (_collisionRenderItems.empty()) { - createCollisionRenderItemSet(); - } - } else { - if (_modelMeshRenderItems.empty()) { - createVisibleRenderItemSet(); - } - } -}; - -void Model::createVisibleRenderItemSet() { assert(isLoaded()); const auto& meshes = _renderGeometry->getMeshes(); @@ -1610,41 +1503,6 @@ void Model::createVisibleRenderItemSet() { } } -void Model::createCollisionRenderItemSet() { - assert((bool)_collisionGeometry); - if (_collisionMaterials.empty()) { - initCollisionMaterials(); - } - - const auto& meshes = _collisionGeometry->getMeshes(); - - // We should not have any existing renderItems if we enter this section of code - Q_ASSERT(_collisionRenderItems.isEmpty()); - - Transform identity; - identity.setIdentity(); - Transform offset; - offset.postTranslate(_offset); - - // Run through all of the meshes, and place them into their segregated, but unsorted buckets - uint32_t numMeshes = (uint32_t)meshes.size(); - for (uint32_t i = 0; i < numMeshes; i++) { - const auto& mesh = meshes.at(i); - if (!mesh) { - continue; - } - - // Create the render payloads - int numParts = (int)mesh->getNumParts(); - for (int partIndex = 0; partIndex < numParts; partIndex++) { - graphics::MaterialPointer& material = _collisionMaterials[partIndex % NUM_COLLISION_HULL_COLORS]; - auto payload = std::make_shared(mesh, partIndex, material); - payload->updateTransform(identity, offset); - _collisionRenderItems << payload; - } - } -} - bool Model::isRenderable() const { return !_meshStates.empty() || (isLoaded() && _renderGeometry->getMeshes().empty()); } @@ -1676,20 +1534,16 @@ void Model::addMaterial(graphics::MaterialLayer material, const std::string& par for (auto shapeID : shapeIDs) { if (shapeID < _modelMeshRenderItemIDs.size()) { auto itemID = _modelMeshRenderItemIDs[shapeID]; - bool visible = isVisible(); - uint8_t viewTagBits = getViewTagBits(); - bool layeredInFront = isLayeredInFront(); - bool layeredInHUD = isLayeredInHUD(); - bool canCastShadow = _canCastShadow; + auto renderItemsKey = _renderItemKeyGlobalFlags; bool wireframe = isWireframe(); auto meshIndex = _modelMeshRenderItemShapes[shapeID].meshIndex; bool invalidatePayloadShapeKey = shouldInvalidatePayloadShapeKey(meshIndex); bool useDualQuaternionSkinning = _useDualQuaternionSkinning; - transaction.updateItem(itemID, [material, visible, layeredInFront, layeredInHUD, viewTagBits, canCastShadow, + transaction.updateItem(itemID, [material, renderItemsKey, invalidatePayloadShapeKey, wireframe, useDualQuaternionSkinning](ModelMeshPartPayload& data) { data.addMaterial(material); // if the material changed, we might need to update our item key or shape key - data.updateKey(visible, layeredInFront || layeredInHUD, canCastShadow, viewTagBits); + data.updateKey(renderItemsKey); data.setShapeKey(invalidatePayloadShapeKey, wireframe, useDualQuaternionSkinning); }); } @@ -1704,19 +1558,16 @@ void Model::removeMaterial(graphics::MaterialPointer material, const std::string if (shapeID < _modelMeshRenderItemIDs.size()) { auto itemID = _modelMeshRenderItemIDs[shapeID]; bool visible = isVisible(); - uint8_t viewTagBits = getViewTagBits(); - bool layeredInFront = isLayeredInFront(); - bool layeredInHUD = isLayeredInHUD(); - bool canCastShadow = _canCastShadow; + auto renderItemsKey = _renderItemKeyGlobalFlags; bool wireframe = isWireframe(); auto meshIndex = _modelMeshRenderItemShapes[shapeID].meshIndex; bool invalidatePayloadShapeKey = shouldInvalidatePayloadShapeKey(meshIndex); bool useDualQuaternionSkinning = _useDualQuaternionSkinning; - transaction.updateItem(itemID, [material, visible, layeredInFront, layeredInHUD, viewTagBits, canCastShadow, + transaction.updateItem(itemID, [material, visible, renderItemsKey, invalidatePayloadShapeKey, wireframe, useDualQuaternionSkinning](ModelMeshPartPayload& data) { data.removeMaterial(material); // if the material changed, we might need to update our item key or shape key - data.updateKey(visible, layeredInFront || layeredInHUD, canCastShadow, viewTagBits); + data.updateKey(renderItemsKey); data.setShapeKey(invalidatePayloadShapeKey, wireframe, useDualQuaternionSkinning); }); } @@ -1735,15 +1586,6 @@ public: } }; -void Model::setCollisionMesh(graphics::MeshPointer mesh) { - if (mesh) { - _collisionGeometry = std::make_shared(mesh); - } else { - _collisionGeometry.reset(); - } - _needsFixupInScene = true; -} - ModelBlender::ModelBlender() : _pendingBlenders(0) { } diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index 2b14a7c055..bc82a0d335 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -33,6 +33,7 @@ #include #include +#include "RenderHifi.h" #include "GeometryCache.h" #include "TextureCache.h" #include "Rig.h" @@ -87,13 +88,27 @@ public: const QUrl& getURL() const { return _url; } // new Scene/Engine rendering support - void setVisibleInScene(bool isVisible, const render::ScenePointer& scene, uint8_t viewTagBits, bool isGroupCulled); + void setVisibleInScene(bool isVisible, const render::ScenePointer& scene = nullptr); + bool isVisible() const; - bool canCastShadow() const { return _canCastShadow; } - void setCanCastShadow(bool canCastShadow, const render::ScenePointer& scene, uint8_t viewTagBits, bool isGroupCulled); + render::hifi::Tag getTagMask() const; + void setTagMask(uint8_t mask, const render::ScenePointer& scene = nullptr); + + bool isGroupCulled() const; + void setGroupCulled(bool isGroupCulled, const render::ScenePointer& scene = nullptr); + + bool canCastShadow() const; + void setCanCastShadow(bool canCastShadow, const render::ScenePointer& scene = nullptr); + + void setLayeredInFront(bool isLayeredInFront, const render::ScenePointer& scene = nullptr); + void setLayeredInHUD(bool isLayeredInHUD, const render::ScenePointer& scene = nullptr); + + bool isLayeredInFront() const; + bool isLayeredInHUD() const; + + // Access the current RenderItemKey Global Flags used by the model and applied to the render items representing the parts of the model. + const render::ItemKey getRenderItemKeyGlobalFlags() const; - void setLayeredInFront(bool isLayeredInFront, const render::ScenePointer& scene); - void setLayeredInHUD(bool isLayeredInHUD, const render::ScenePointer& scene); bool needsFixupInScene() const; bool needsReload() const { return _needsReload; } @@ -108,13 +123,7 @@ public: void removeFromScene(const render::ScenePointer& scene, render::Transaction& transaction); bool isRenderable() const; - bool isVisible() const { return _isVisible; } - uint8_t getViewTagBits() const { return _viewTagBits; } - - bool isLayeredInFront() const { return _isLayeredInFront; } - bool isLayeredInHUD() const { return _isLayeredInHUD; } - - bool isGroupCulled() const { return _isGroupCulled; } + void updateRenderItemsKey(const render::ScenePointer& scene); virtual void updateRenderItems(); void setRenderItemsNeedUpdate(); @@ -144,8 +153,6 @@ public: /// Returns a reference to the shared geometry. const Geometry::Pointer& getGeometry() const { return _renderGeometry; } - /// Returns a reference to the shared collision geometry. - const Geometry::Pointer& getCollisionGeometry() const { return _collisionGeometry; } const QVariantMap getTextures() const { assert(isLoaded()); return _renderGeometry->getTextures(); } Q_INVOKABLE virtual void setTextures(const QVariantMap& textures); @@ -251,7 +258,6 @@ public: // returns 'true' if needs fullUpdate after geometry change virtual bool updateGeometry(); - void setCollisionMesh(graphics::MeshPointer mesh); void setLoadingPriority(float priority) { _loadingPriority = priority; } @@ -353,7 +359,6 @@ protected: bool getJointPosition(int jointIndex, glm::vec3& position) const; Geometry::Pointer _renderGeometry; // only ever set by its watcher - Geometry::Pointer _collisionGeometry; GeometryResourceWatcher _renderWatcher; @@ -404,10 +409,6 @@ protected: QVector _blendshapeCoefficients; QUrl _url; - bool _isVisible; - uint8_t _viewTagBits{ render::ItemKey::TAG_BITS_ALL }; - - bool _canCastShadow; gpu::Buffers _blendedVertexBuffers; @@ -425,9 +426,7 @@ protected: QVector _modelSpaceMeshTriangleSets; // model space triangles for all sub meshes - void createRenderItemSet(); - virtual void createVisibleRenderItemSet(); - virtual void createCollisionRenderItemSet(); + virtual void createRenderItemSet(); bool _isWireframe; bool _useDualQuaternionSkinning { false }; @@ -438,9 +437,6 @@ protected: static AbstractViewStateInterface* _viewState; - QVector> _collisionRenderItems; - QMap _collisionRenderItemsMap; - QVector> _modelMeshRenderItems; QMap _modelMeshRenderItemsMap; render::ItemIDs _modelMeshRenderItemIDs; @@ -452,7 +448,7 @@ protected: bool _needsFixupInScene { true }; // needs to be removed/re-added to scene bool _needsReload { true }; bool _needsUpdateClusterMatrices { true }; - mutable bool _needsUpdateTextures { true }; + QVariantMap _pendingTextures { }; friend class ModelMeshPartPayload; Rig _rig; @@ -471,10 +467,16 @@ protected: int _renderInfoDrawCalls { 0 }; int _renderInfoHasTransparent { false }; - bool _isLayeredInFront { false }; - bool _isLayeredInHUD { false }; - - bool _isGroupCulled{ false }; + // This Render ItemKey Global Flags capture the Model wide global set of flags that should be communicated to all the render items representing the Model. + // The flags concerned are: + // - isVisible: if true the Model is visible globally in the scene, regardless of the other flags in the item keys (tags or layer or shadow caster). + // - TagBits: the view mask defined through the TagBits telling in which view the Model is rendered if visible. + // - Layer: In which Layer this Model lives. + // - CastShadow: if true and visible and rendered in the view, the Model cast shadows if in a Light volume casting shadows. + // - CullGroup: if true, the render items representing the parts of the Model are culled by a single Meta render item that knows about them, they are not culled individually. + // For this to work, a Meta RI must exists and knows about the RIs of this Model. + // + render::ItemKey _renderItemKeyGlobalFlags; bool shouldInvalidatePayloadShapeKey(int meshIndex); diff --git a/libraries/render-utils/src/RenderDeferredTask.cpp b/libraries/render-utils/src/RenderDeferredTask.cpp index 3ea56f8542..47cab54d09 100644 --- a/libraries/render-utils/src/RenderDeferredTask.cpp +++ b/libraries/render-utils/src/RenderDeferredTask.cpp @@ -27,6 +27,7 @@ #include #include +#include "RenderHifi.h" #include "RenderCommonTask.h" #include "LightingModel.h" #include "StencilMaskPass.h" @@ -200,8 +201,8 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren const auto overlaysInFrontRangeTimer = task.addJob("BeginOverlaysInFrontRangeTimer", "BeginOverlaysInFrontRangeTimer"); // Layered Overlays - const auto filteredOverlaysOpaque = task.addJob("FilterOverlaysLayeredOpaque", overlayOpaques, Item::LAYER_3D_FRONT); - const auto filteredOverlaysTransparent = task.addJob("FilterOverlaysLayeredTransparent", overlayTransparents, Item::LAYER_3D_FRONT); + const auto filteredOverlaysOpaque = task.addJob("FilterOverlaysLayeredOpaque", overlayOpaques, render::hifi::LAYER_3D_FRONT); + const auto filteredOverlaysTransparent = task.addJob("FilterOverlaysLayeredTransparent", overlayTransparents, render::hifi::LAYER_3D_FRONT); const auto overlaysInFrontOpaque = filteredOverlaysOpaque.getN(0); const auto overlaysInFrontTransparent = filteredOverlaysTransparent.getN(0); diff --git a/libraries/render-utils/src/RenderForwardTask.cpp b/libraries/render-utils/src/RenderForwardTask.cpp index 4388a39cf3..09a2afb711 100755 --- a/libraries/render-utils/src/RenderForwardTask.cpp +++ b/libraries/render-utils/src/RenderForwardTask.cpp @@ -23,6 +23,7 @@ #include +#include "RenderHifi.h" #include "StencilMaskPass.h" #include "ZoneRenderer.h" #include "FadeEffect.h" @@ -79,8 +80,8 @@ void RenderForwardTask::build(JobModel& task, const render::Varying& input, rend task.addJob("PrepareStencil", framebuffer); // Layered Overlays - const auto filteredOverlaysOpaque = task.addJob("FilterOverlaysLayeredOpaque", overlayOpaques, Item::LAYER_3D_FRONT); - const auto filteredOverlaysTransparent = task.addJob("FilterOverlaysLayeredTransparent", overlayTransparents, Item::LAYER_3D_FRONT); + const auto filteredOverlaysOpaque = task.addJob("FilterOverlaysLayeredOpaque", overlayOpaques, render::hifi::LAYER_3D_FRONT); + const auto filteredOverlaysTransparent = task.addJob("FilterOverlaysLayeredTransparent", overlayTransparents, render::hifi::LAYER_3D_FRONT); const auto overlaysInFrontOpaque = filteredOverlaysOpaque.getN(0); const auto overlaysInFrontTransparent = filteredOverlaysTransparent.getN(0); diff --git a/libraries/render-utils/src/RenderHifi.h b/libraries/render-utils/src/RenderHifi.h new file mode 100644 index 0000000000..c489a0ad1d --- /dev/null +++ b/libraries/render-utils/src/RenderHifi.h @@ -0,0 +1,43 @@ +// +// RenderHifi.h +// libraries/render-utils/src +// +// Created by Sam Gateau on 5/30/2018. +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#ifndef hifi_RenderHifi_h +#define hifi_RenderHifi_h + +#include + + + +// In the library render-utils we are specializing the generic components of the render library to create the custom hifi render engine +// Objects and types serving this goal are define in the namespace render.hifi +// TODO: extend the namespace to all the classes where it make sense in render-utils +namespace render { + namespace hifi { + + // Tag is the alias names of render::ItemKey::Tag combinations used in the Hifi Render Engine + enum Tag : uint8_t { + TAG_NONE = render::ItemKey::TAG_BITS_NONE, // No Tags at all + TAG_MAIN_VIEW = render::ItemKey::TAG_BITS_0, // Main view + TAG_SECONDARY_VIEW = render::ItemKey::TAG_BITS_1, // Secondary View + TAG_ALL_VIEWS = TAG_MAIN_VIEW | TAG_SECONDARY_VIEW, // All views + }; + + // Layer is the alias names of the render::ItemKey::Layer used in the Hifi Render Engine + enum Layer : uint8_t { + LAYER_3D = render::ItemKey::LAYER_DEFAULT, + LAYER_3D_FRONT = render::ItemKey::LAYER_1, + LAYER_3D_HUD = render::ItemKey::LAYER_2, + LAYER_2D = render::ItemKey::LAYER_3, + LAYER_BACKGROUND = render::ItemKey::LAYER_BACKGROUND, + }; + } +} + +#endif // hifi_RenderHifi_h diff --git a/libraries/render-utils/src/RenderViewTask.cpp b/libraries/render-utils/src/RenderViewTask.cpp index 122fc16e61..82426a3a1f 100644 --- a/libraries/render-utils/src/RenderViewTask.cpp +++ b/libraries/render-utils/src/RenderViewTask.cpp @@ -19,7 +19,9 @@ void RenderViewTask::build(JobModel& task, const render::Varying& input, render: // Warning : the cull functor passed to the shadow pass should only be testing for LOD culling. If frustum culling // is performed, then casters not in the view frustum will be removed, which is not what we wish. - task.addJob("RenderShadowTask", cullFunctor, tagBits, tagMask); + if (isDeferred) { + task.addJob("RenderShadowTask", cullFunctor, tagBits, tagMask); + } const auto items = task.addJob("FetchCullSort", cullFunctor, tagBits, tagMask); assert(items.canCast()); diff --git a/libraries/render/src/render/CullTask.cpp b/libraries/render/src/render/CullTask.cpp index b5819f114f..3f55e6dedc 100644 --- a/libraries/render/src/render/CullTask.cpp +++ b/libraries/render/src/render/CullTask.cpp @@ -368,9 +368,9 @@ void CullShapeBounds::run(const RenderContextPointer& renderContext, const Input RenderArgs* args = renderContext->args; const auto& inShapes = inputs.get0(); - const auto& cullFilter = inputs.get1(); - const auto& boundsFilter = inputs.get2(); - const auto& antiFrustum = inputs.get3(); + const auto& cullFilter = inputs.get1(); + const auto& boundsFilter = inputs.get2(); + const auto& antiFrustum = inputs.get3(); auto& outShapes = outputs.edit0(); auto& outBounds = outputs.edit1(); @@ -380,7 +380,7 @@ void CullShapeBounds::run(const RenderContextPointer& renderContext, const Input if (!cullFilter.selectsNothing() || !boundsFilter.selectsNothing()) { auto& details = args->_details.edit(_detailType); Test test(_cullFunctor, args, details, antiFrustum); - auto scene = args->_scene; + auto scene = args->_scene; for (auto& inItems : inShapes) { auto key = inItems.first; @@ -395,26 +395,26 @@ void CullShapeBounds::run(const RenderContextPointer& renderContext, const Input if (antiFrustum == nullptr) { for (auto& item : inItems.second) { if (test.solidAngleTest(item.bound) && test.frustumTest(item.bound)) { - const auto shapeKey = scene->getItem(item.id).getKey(); - if (cullFilter.test(shapeKey)) { - outItems->second.emplace_back(item); - } - if (boundsFilter.test(shapeKey)) { - outBounds += item.bound; - } + const auto shapeKey = scene->getItem(item.id).getKey(); + if (cullFilter.test(shapeKey)) { + outItems->second.emplace_back(item); + } + if (boundsFilter.test(shapeKey)) { + outBounds += item.bound; + } } } } else { for (auto& item : inItems.second) { if (test.solidAngleTest(item.bound) && test.frustumTest(item.bound) && test.antiFrustumTest(item.bound)) { - const auto shapeKey = scene->getItem(item.id).getKey(); - if (cullFilter.test(shapeKey)) { - outItems->second.emplace_back(item); - } - if (boundsFilter.test(shapeKey)) { - outBounds += item.bound; - } - } + const auto shapeKey = scene->getItem(item.id).getKey(); + if (cullFilter.test(shapeKey)) { + outItems->second.emplace_back(item); + } + if (boundsFilter.test(shapeKey)) { + outBounds += item.bound; + } + } } } details._rendered += (int)outItems->second.size(); @@ -487,6 +487,7 @@ void FetchSpatialSelection::run(const RenderContextPointer& renderContext, if (filter.test(item.getKey())) { ItemBound itemBound(id, item.getBound()); outItems.emplace_back(itemBound); + } } } diff --git a/libraries/render/src/render/Item.cpp b/libraries/render/src/render/Item.cpp index 9c5efb9fa7..532964777f 100644 --- a/libraries/render/src/render/Item.cpp +++ b/libraries/render/src/render/Item.cpp @@ -29,25 +29,9 @@ const float Item::Status::Value::CYAN = 180.0f; const float Item::Status::Value::BLUE = 240.0f; const float Item::Status::Value::MAGENTA = 300.0f; -const int Item::LAYER_2D = 0; -const int Item::LAYER_3D = 1; -const int Item::LAYER_3D_FRONT = 2; -const int Item::LAYER_3D_HUD = 3; - -const uint8_t ItemKey::TAG_BITS_ALL { 0xFF }; -const uint8_t ItemKey::TAG_BITS_NONE { 0x00 }; -const uint8_t ItemKey::TAG_BITS_0 { 0x01 }; -const uint8_t ItemKey::TAG_BITS_1 { 0x02 }; -const uint8_t ItemKey::TAG_BITS_2 { 0x04 }; -const uint8_t ItemKey::TAG_BITS_3 { 0x08 }; -const uint8_t ItemKey::TAG_BITS_4 { 0x10 }; -const uint8_t ItemKey::TAG_BITS_5 { 0x20 }; -const uint8_t ItemKey::TAG_BITS_6 { 0x40 }; -const uint8_t ItemKey::TAG_BITS_7 { 0x80 }; - const uint32_t ItemKey::KEY_TAG_BITS_MASK = ((uint32_t) ItemKey::TAG_BITS_ALL) << FIRST_TAG_BIT; - +const uint32_t ItemKey::KEY_LAYER_BITS_MASK = ((uint32_t)ItemKey::LAYER_BITS_ALL) << FIRST_LAYER_BIT; void Item::Status::Value::setScale(float scale) { _scale = (std::numeric_limits::max() -1) * 0.5f * (1.0f + std::max(std::min(scale, 1.0f), 0.0f)); diff --git a/libraries/render/src/render/Item.h b/libraries/render/src/render/Item.h index b8a3fbf0f8..28994d82b6 100644 --- a/libraries/render/src/render/Item.h +++ b/libraries/render/src/render/Item.h @@ -52,23 +52,45 @@ public: TAG_6, TAG_7, - NUM_TAGS + NUM_TAGS, + + // Tag bits are derived from the Tag enum + TAG_BITS_ALL = 0xFF, + TAG_BITS_NONE = 0x00, + TAG_BITS_0 = 0x01, + TAG_BITS_1 = 0x02, + TAG_BITS_2 = 0x04, + TAG_BITS_3 = 0x08, + TAG_BITS_4 = 0x10, + TAG_BITS_5 = 0x20, + TAG_BITS_6 = 0x40, + TAG_BITS_7 = 0x80, + }; + + // Items are organized in layers, an item belongs to one of the 8 Layers available. + // By default an item is in the 'LAYER_DEFAULT' meaning that it is NOT layered. + // THere is NO ordering relationship between layers. + enum Layer : uint8_t { + LAYER_DEFAULT = 0, // layer 0 aka Default is a 'NOT' layer, items are not considered layered, this is the default value + LAYER_1, + LAYER_2, + LAYER_3, + LAYER_4, + LAYER_5, + LAYER_6, + LAYER_BACKGROUND, // Last Layer is the background by convention + + NUM_LAYERS, + + // Layer bits are derived from the Layer enum, the number of bits needed to represent integer 0 to NUM_LAYERS + NUM_LAYER_BITS = 3, + LAYER_BITS_ALL = 0x07, }; - // Tag bits are derived from the Tag enum - const static uint8_t TAG_BITS_ALL; - const static uint8_t TAG_BITS_NONE; - const static uint8_t TAG_BITS_0; - const static uint8_t TAG_BITS_1; - const static uint8_t TAG_BITS_2; - const static uint8_t TAG_BITS_3; - const static uint8_t TAG_BITS_4; - const static uint8_t TAG_BITS_5; - const static uint8_t TAG_BITS_6; - const static uint8_t TAG_BITS_7; enum FlagBit : uint32_t { - TYPE_SHAPE = 0, // Item is a Shape - TYPE_LIGHT, // Item is a Light + TYPE_SHAPE = 0, // Item is a Shape: Implements the Shape Interface that draw a Geometry rendered with a Material + TYPE_LIGHT, // Item is a Light: Implements the Light Interface that + TYPE_CAMERA, // Item is a Camera: Implements the Camera Interface TYPE_META, // Item is a Meta: meanning it s used to represent a higher level object, potentially represented by other render items TRANSLUCENT, // Transparent and not opaque, for some odd reason TRANSPARENCY doesn't work... @@ -77,13 +99,15 @@ public: DEFORMED, // Deformed within bound, not solid INVISIBLE, // Visible or not in the scene? SHADOW_CASTER, // Item cast shadows - LAYERED, // Item belongs to one of the layers different from the default layer META_CULL_GROUP, // As a meta item, the culling of my sub items is based solely on my bounding box and my visibility in the view SUB_META_CULLED, // As a sub item of a meta render item set as cull group, need to be set to my culling to the meta render it FIRST_TAG_BIT, // 8 Tags available to organize the items and filter them against LAST_TAG_BIT = FIRST_TAG_BIT + NUM_TAGS, + FIRST_LAYER_BIT, // 8 Exclusive Layers (encoded in 3 bits) available to organize the items in layers, an item can only belong to ONE layer + LAST_LAYER_BIT = FIRST_LAYER_BIT + NUM_LAYER_BITS, + __SMALLER, // Reserved bit for spatialized item to indicate that it is smaller than expected in the cell in which it belongs (probably because it overlaps over several smaller cells) NUM_FLAGS, // Not a valid flag @@ -96,6 +120,12 @@ public: return (keyBits & ~KEY_TAG_BITS_MASK) | (((uint32_t)tagBits) << FIRST_TAG_BIT); } + // All the bits touching layer bits sets to true + const static uint32_t KEY_LAYER_BITS_MASK; + static uint32_t evalLayerBitsWithKeyBits(uint8_t layer, const uint32_t keyBits) { + return (keyBits & ~KEY_LAYER_BITS_MASK) | (((uint32_t)layer & LAYER_BITS_ALL) << FIRST_LAYER_BIT); + } + // The key is the Flags Flags _flags; @@ -119,24 +149,30 @@ public: Builder& withTypeMeta() { _flags.set(TYPE_META); return (*this); } Builder& withTransparent() { _flags.set(TRANSLUCENT); return (*this); } Builder& withViewSpace() { _flags.set(VIEW_SPACE); return (*this); } + Builder& withoutViewSpace() { _flags.reset(VIEW_SPACE); return (*this); } Builder& withDynamic() { _flags.set(DYNAMIC); return (*this); } Builder& withDeformed() { _flags.set(DEFORMED); return (*this); } Builder& withInvisible() { _flags.set(INVISIBLE); return (*this); } Builder& withVisible() { _flags.reset(INVISIBLE); return (*this); } Builder& withShadowCaster() { _flags.set(SHADOW_CASTER); return (*this); } - Builder& withLayered() { _flags.set(LAYERED); return (*this); } + Builder& withoutShadowCaster() { _flags.reset(SHADOW_CASTER); return (*this); } Builder& withMetaCullGroup() { _flags.set(META_CULL_GROUP); return (*this); } + Builder& withoutMetaCullGroup() { _flags.reset(META_CULL_GROUP); return (*this); } Builder& withSubMetaCulled() { _flags.set(SUB_META_CULLED); return (*this); } + Builder& withoutSubMetaCulled() { _flags.reset(SUB_META_CULLED); return (*this); } Builder& withTag(Tag tag) { _flags.set(FIRST_TAG_BIT + tag); return (*this); } // Set ALL the tags in one call using the Tag bits Builder& withTagBits(uint8_t tagBits) { _flags = evalTagBitsWithKeyBits(tagBits, _flags.to_ulong()); return (*this); } + Builder& withLayer(uint8_t layer) { _flags = evalLayerBitsWithKeyBits(layer, _flags.to_ulong()); return (*this); } + Builder& withoutLayer() { return withLayer(LAYER_DEFAULT); } + // Convenient standard keys that we will keep on using all over the place static Builder opaqueShape() { return Builder().withTypeShape(); } static Builder transparentShape() { return Builder().withTypeShape().withTransparent(); } static Builder light() { return Builder().withTypeLight(); } - static Builder background() { return Builder().withViewSpace().withLayered(); } + static Builder background() { return Builder().withViewSpace().withLayer(LAYER_BACKGROUND); } }; ItemKey(const Builder& builder) : ItemKey(builder._flags) {} @@ -161,9 +197,6 @@ public: bool isShadowCaster() const { return _flags[SHADOW_CASTER]; } - bool isLayered() const { return _flags[LAYERED]; } - bool isSpatial() const { return !isLayered(); } - bool isMetaCullGroup() const { return _flags[META_CULL_GROUP]; } void setMetaCullGroup(bool cullGroup) { (cullGroup ? _flags.set(META_CULL_GROUP) : _flags.reset(META_CULL_GROUP)); } @@ -173,6 +206,11 @@ public: bool isTag(Tag tag) const { return _flags[FIRST_TAG_BIT + tag]; } uint8_t getTagBits() const { return ((_flags.to_ulong() & KEY_TAG_BITS_MASK) >> FIRST_TAG_BIT); } + uint8_t getLayer() const { return ((_flags.to_ulong() & KEY_LAYER_BITS_MASK) >> FIRST_LAYER_BIT); } + bool isLayer(uint8_t layer) const { return getLayer() == layer; } + bool isLayered() const { return getLayer() != LAYER_DEFAULT; } + bool isSpatial() const { return !isLayered(); } + // Probably not public, flags used by the scene bool isSmall() const { return _flags[__SMALLER]; } void setSmaller(bool smaller) { (smaller ? _flags.set(__SMALLER) : _flags.reset(__SMALLER)); } @@ -230,9 +268,6 @@ public: Builder& withNoShadowCaster() { _value.reset(ItemKey::SHADOW_CASTER); _mask.set(ItemKey::SHADOW_CASTER); return (*this); } Builder& withShadowCaster() { _value.set(ItemKey::SHADOW_CASTER); _mask.set(ItemKey::SHADOW_CASTER); return (*this); } - Builder& withoutLayered() { _value.reset(ItemKey::LAYERED); _mask.set(ItemKey::LAYERED); return (*this); } - Builder& withLayered() { _value.set(ItemKey::LAYERED); _mask.set(ItemKey::LAYERED); return (*this); } - Builder& withoutMetaCullGroup() { _value.reset(ItemKey::META_CULL_GROUP); _mask.set(ItemKey::META_CULL_GROUP); return (*this); } Builder& withMetaCullGroup() { _value.set(ItemKey::META_CULL_GROUP); _mask.set(ItemKey::META_CULL_GROUP); return (*this); } @@ -244,6 +279,9 @@ public: // Set ALL the tags in one call using the Tag bits and the Tag bits touched Builder& withTagBits(uint8_t tagBits, uint8_t tagMask) { _value = ItemKey::evalTagBitsWithKeyBits(tagBits, _value.to_ulong()); _mask = ItemKey::evalTagBitsWithKeyBits(tagMask, _mask.to_ulong()); return (*this); } + Builder& withoutLayered() { _value = ItemKey::evalLayerBitsWithKeyBits(ItemKey::LAYER_DEFAULT, _value.to_ulong()); _mask |= ItemKey::KEY_LAYER_BITS_MASK; return (*this); } + Builder& withLayer(uint8_t layer) { _value = ItemKey::evalLayerBitsWithKeyBits(layer, _value.to_ulong()); _mask |= ItemKey::KEY_LAYER_BITS_MASK; return (*this); } + Builder& withNothing() { _value.reset(); _mask.reset(); return (*this); } // Convenient standard keys that we will keep on using all over the place @@ -252,9 +290,7 @@ public: static Builder transparentShape() { return Builder().withTypeShape().withTransparent().withWorldSpace(); } static Builder light() { return Builder().withTypeLight(); } static Builder meta() { return Builder().withTypeMeta(); } - static Builder background() { return Builder().withViewSpace().withLayered(); } - static Builder opaqueShapeLayered() { return Builder().withTypeShape().withOpaque().withWorldSpace().withLayered(); } - static Builder transparentShapeLayered() { return Builder().withTypeShape().withTransparent().withWorldSpace().withLayered(); } + static Builder background() { return Builder().withViewSpace().withLayer(ItemKey::LAYER_BACKGROUND); } static Builder nothing() { return Builder().withNothing(); } }; @@ -377,7 +413,6 @@ public: public: virtual const ItemKey getKey() const = 0; virtual const Bound getBound() const = 0; - virtual int getLayer() const = 0; virtual void render(RenderArgs* args) = 0; virtual const ShapeKey getShapeKey() const = 0; @@ -422,13 +457,8 @@ public: // Get the bound of the item expressed in world space (or eye space depending on the key.isWorldSpace()) const Bound getBound() const { return _payload->getBound(); } - // Get the layer where the item belongs. - int getLayer() const { return _payload->getLayer(); } - - static const int LAYER_2D; - static const int LAYER_3D; - static const int LAYER_3D_FRONT; - static const int LAYER_3D_HUD; + // Get the layer where the item belongs, simply reflecting the key. + int getLayer() const { return _key.getLayer(); } // Render call for the item void render(RenderArgs* args) const { _payload->render(args); } @@ -478,7 +508,6 @@ inline QDebug operator<<(QDebug debug, const Item& item) { // Item shared interface supported by the payload template const ItemKey payloadGetKey(const std::shared_ptr& payloadData) { return ItemKey(); } template const Item::Bound payloadGetBound(const std::shared_ptr& payloadData) { return Item::Bound(); } -template int payloadGetLayer(const std::shared_ptr& payloadData) { return 0; } template void payloadRender(const std::shared_ptr& payloadData, RenderArgs* args) { } // Shape type interface @@ -505,7 +534,6 @@ public: // Payload general interface virtual const ItemKey getKey() const override { return payloadGetKey(_data); } virtual const Item::Bound getBound() const override { return payloadGetBound(_data); } - virtual int getLayer() const override { return payloadGetLayer(_data); } virtual void render(RenderArgs* args) override { payloadRender(_data, args); } diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index f98bffb739..f8c99b192f 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -419,7 +419,7 @@ void ScriptEngine::waitTillDoneRunning() { // Wait for the scripting thread to stop running, as // flooding it with aborts/exceptions will persist it longer static const auto MAX_SCRIPT_QUITTING_TIME = 0.5 * MSECS_PER_SECOND; - if (workerThread->wait(MAX_SCRIPT_QUITTING_TIME)) { + if (!workerThread->wait(MAX_SCRIPT_QUITTING_TIME)) { workerThread->terminate(); } } diff --git a/libraries/script-engine/src/ScriptEngines.cpp b/libraries/script-engine/src/ScriptEngines.cpp index ad6e1debe9..b481a14f14 100644 --- a/libraries/script-engine/src/ScriptEngines.cpp +++ b/libraries/script-engine/src/ScriptEngines.cpp @@ -594,7 +594,7 @@ void ScriptEngines::onScriptFinished(const QString& rawScriptURL, ScriptEnginePo } } - if (removed) { + if (removed && !_isReloading) { // Update settings with removed script saveScripts(); emit scriptCountChanged(); diff --git a/libraries/shared/src/ApplicationVersion.cpp b/libraries/shared/src/ApplicationVersion.cpp new file mode 100644 index 0000000000..5c2d5ad11c --- /dev/null +++ b/libraries/shared/src/ApplicationVersion.cpp @@ -0,0 +1,94 @@ +// +// ApplicationVersion.cpp +// libraries/shared/src +// +// Created by Stephen Birarda on 6/8/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 "ApplicationVersion.h" + +#include + +#include +#include +#include + +ApplicationVersion::ApplicationVersion(const QString& versionString) : + versionString(versionString) +{ + // attempt to regex out a semantic version from the string + // handling both x.y.z and x.y formats + QRegExp semanticRegex("([\\d]+)\\.([\\d]+)(?:\\.([\\d]+))?"); + + int pos = semanticRegex.indexIn(versionString); + if (pos != -1) { + isSemantic = true; + auto captures = semanticRegex.capturedTexts(); + + major = captures[1].toInt(); + minor = captures[2].toInt(); + + if (captures.length() > 3) { + patch = captures[3].toInt(); + } else { + // the patch is implictly 0 if it was not included + patch = 0; + } + } else { + // if we didn't have a sematic style, we assume that we just have a build number + build = versionString.toInt(); + } +} + +bool ApplicationVersion::operator==(const ApplicationVersion& other) const { + if (isSemantic && other.isSemantic) { + return major == other.major && minor == other.minor && patch == other.patch; + } else if (!isSemantic && !other.isSemantic) { + return build == other.build; + } else { + assert(isSemantic == other.isSemantic); + return false; + } +} + +bool ApplicationVersion::operator<(const ApplicationVersion& other) const { + if (isSemantic && other.isSemantic) { + if (major == other.major) { + if (minor == other.minor) { + return patch < other.patch; + } else { + return minor < other.minor; + } + } else { + return major < other.major; + } + } else if (!isSemantic && !other.isSemantic) { + return build < other.build; + } else { + assert(isSemantic == other.isSemantic); + return false; + } +} + +bool ApplicationVersion::operator>(const ApplicationVersion& other) const { + if (isSemantic && other.isSemantic) { + if (major == other.major) { + if (minor == other.minor) { + return patch > other.patch; + } else { + return minor > other.minor; + } + } else { + return major > other.major; + } + } else if (!isSemantic && !other.isSemantic) { + return build > other.build; + } else { + assert(isSemantic == other.isSemantic); + return false; + } +} diff --git a/libraries/shared/src/ApplicationVersion.h b/libraries/shared/src/ApplicationVersion.h new file mode 100644 index 0000000000..5cb0a09a8d --- /dev/null +++ b/libraries/shared/src/ApplicationVersion.h @@ -0,0 +1,41 @@ +// +// ApplicationVersion.h +// libraries/shared/src +// +// Created by Stephen Birarda on 6/8/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_ApplicationVersion_h +#define hifi_ApplicationVersion_h + +#include + +class ApplicationVersion { +public: + ApplicationVersion(const QString& versionString); + + bool operator==(const ApplicationVersion& other) const; + bool operator!=(const ApplicationVersion& other) const { return !(*this == other); } + + bool operator <(const ApplicationVersion& other) const; + bool operator >(const ApplicationVersion& other) const; + + bool operator >=(const ApplicationVersion& other) const { return (*this == other) || (*this > other); } + bool operator <=(const ApplicationVersion& other) const { return (*this == other) || (*this < other); } + + int major = -1; + int minor = -1; + int patch = -1; + + int build = -1; + + bool isSemantic { false }; + + QString versionString; +}; + +#endif // hifi_ApplicationVersion_h diff --git a/libraries/shared/src/AvatarConstants.h b/libraries/shared/src/AvatarConstants.h index 930da6a494..e90e25d5b0 100644 --- a/libraries/shared/src/AvatarConstants.h +++ b/libraries/shared/src/AvatarConstants.h @@ -20,6 +20,16 @@ const float DEFAULT_AVATAR_EYE_TO_TOP_OF_HEAD = 0.11f; // meters const float DEFAULT_AVATAR_NECK_TO_TOP_OF_HEAD = 0.185f; // meters const float DEFAULT_AVATAR_NECK_HEIGHT = DEFAULT_AVATAR_HEIGHT - DEFAULT_AVATAR_NECK_TO_TOP_OF_HEAD; const float DEFAULT_AVATAR_EYE_HEIGHT = DEFAULT_AVATAR_HEIGHT - DEFAULT_AVATAR_EYE_TO_TOP_OF_HEAD; +const float DEFAULT_AVATAR_SUPPORT_BASE_LEFT = -0.25f; +const float DEFAULT_AVATAR_SUPPORT_BASE_RIGHT = 0.25f; +const float DEFAULT_AVATAR_SUPPORT_BASE_FRONT = -0.20f; +const float DEFAULT_AVATAR_SUPPORT_BASE_BACK = 0.10f; +const float DEFAULT_AVATAR_FORWARD_DAMPENING_FACTOR = 0.5f; +const float DEFAULT_AVATAR_LATERAL_DAMPENING_FACTOR = 2.0f; +const float DEFAULT_AVATAR_HIPS_MASS = 40.0f; +const float DEFAULT_AVATAR_HEAD_MASS = 20.0f; +const float DEFAULT_AVATAR_LEFTHAND_MASS = 2.0f; +const float DEFAULT_AVATAR_RIGHTHAND_MASS = 2.0f; // Used when avatar is missing joints... (avatar space) const glm::quat DEFAULT_AVATAR_MIDDLE_EYE_ROT { Quaternions::Y_180 }; diff --git a/libraries/shared/src/GLMHelpers.cpp b/libraries/shared/src/GLMHelpers.cpp index 75446754d5..4be8ad0e41 100644 --- a/libraries/shared/src/GLMHelpers.cpp +++ b/libraries/shared/src/GLMHelpers.cpp @@ -574,8 +574,9 @@ void generateBasisVectors(const glm::vec3& primaryAxis, const glm::vec3& seconda vAxisOut = glm::cross(wAxisOut, uAxisOut); } +// assumes z-forward and y-up glm::vec2 getFacingDir2D(const glm::quat& rot) { - glm::vec3 facing3D = rot * Vectors::UNIT_NEG_Z; + glm::vec3 facing3D = rot * Vectors::UNIT_Z; glm::vec2 facing2D(facing3D.x, facing3D.z); const float ALMOST_ZERO = 0.0001f; if (glm::length(facing2D) < ALMOST_ZERO) { @@ -585,8 +586,9 @@ glm::vec2 getFacingDir2D(const glm::quat& rot) { } } +// assumes z-forward and y-up glm::vec2 getFacingDir2D(const glm::mat4& m) { - glm::vec3 facing3D = transformVectorFast(m, Vectors::UNIT_NEG_Z); + glm::vec3 facing3D = transformVectorFast(m, Vectors::UNIT_Z); glm::vec2 facing2D(facing3D.x, facing3D.z); const float ALMOST_ZERO = 0.0001f; if (glm::length(facing2D) < ALMOST_ZERO) { diff --git a/libraries/shared/src/GLMHelpers.h b/libraries/shared/src/GLMHelpers.h index 0e1af27cd2..7e6ef4cb28 100644 --- a/libraries/shared/src/GLMHelpers.h +++ b/libraries/shared/src/GLMHelpers.h @@ -250,7 +250,10 @@ glm::vec3 transformVectorFull(const glm::mat4& m, const glm::vec3& v); void generateBasisVectors(const glm::vec3& primaryAxis, const glm::vec3& secondaryAxis, glm::vec3& uAxisOut, glm::vec3& vAxisOut, glm::vec3& wAxisOut); +// assumes z-forward and y-up glm::vec2 getFacingDir2D(const glm::quat& rot); + +// assumes z-forward and y-up glm::vec2 getFacingDir2D(const glm::mat4& m); inline bool isNaN(const glm::vec3& value) { return isNaN(value.x) || isNaN(value.y) || isNaN(value.z); } diff --git a/libraries/shared/src/OwningBuffer.h b/libraries/shared/src/OwningBuffer.h new file mode 100644 index 0000000000..80184286bc --- /dev/null +++ b/libraries/shared/src/OwningBuffer.h @@ -0,0 +1,29 @@ +// +// OwningBuffer.h +// shared/src +// +// Created by Ryan Huffman on 5/31/2018. +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_OwningBuffer_h +#define hifi_OwningBuffer_h + +#include +class OwningBuffer : public QBuffer { +public: + OwningBuffer(const QByteArray& content) : _content(content) { + setData(_content); + } + OwningBuffer(QByteArray&& content) : _content(std::move(content)) { + setData(_content); + } + +private: + QByteArray _content; +}; + +#endif // hifi_OwningBuffer_h diff --git a/libraries/shared/src/PathUtils.cpp b/libraries/shared/src/PathUtils.cpp index b5c76257ef..7fc94db6af 100644 --- a/libraries/shared/src/PathUtils.cpp +++ b/libraries/shared/src/PathUtils.cpp @@ -32,15 +32,13 @@ #include "shared/GlobalAppProperties.h" #include "SharedUtil.h" + // Format: AppName-PID-Timestamp // Example: ... QString TEMP_DIR_FORMAT { "%1-%2-%3" }; #if !defined(Q_OS_ANDROID) && defined(DEV_BUILD) static bool USE_SOURCE_TREE_RESOURCES() { -#if defined(Q_OS_OSX) - return true; -#else static bool result = false; static std::once_flag once; std::call_once(once, [&] { @@ -48,10 +46,28 @@ static bool USE_SOURCE_TREE_RESOURCES() { result = QProcessEnvironment::systemEnvironment().contains(USE_SOURCE_TREE_RESOURCES_FLAG); }); return result; -#endif } #endif +const QString& PathUtils::getRccPath() { + static QString rccLocation; + static std::once_flag once; + std::call_once(once, [&] { + static const QString rccName{ "/resources.rcc" }; +#if defined(Q_OS_OSX) + char buffer[8192]; + uint32_t bufferSize = sizeof(buffer); + _NSGetExecutablePath(buffer, &bufferSize); + rccLocation = QDir::cleanPath(QFileInfo(buffer).dir().absoluteFilePath("../Resources")) + rccName; +#elif defined(Q_OS_ANDROID) + rccLocation = QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + rccName; +#else + rccLocation = QCoreApplication::applicationDirPath() + rccName; +#endif + }); + return rccLocation; +} + #ifdef DEV_BUILD const QString& PathUtils::projectRootPath() { static QString sourceFolder; @@ -65,23 +81,9 @@ const QString& PathUtils::projectRootPath() { #endif const QString& PathUtils::resourcesPath() { - static QString staticResourcePath; + static QString staticResourcePath{ ":/" }; static std::once_flag once; std::call_once(once, [&]{ - -#if defined(Q_OS_OSX) - // FIXME fix the OSX installer to install the resources.rcc instead of the - // individual resource files - // FIXME the first call to fetch the resources location seems to return - // nothing for QCoreApplication::applicationDirPath() - char buffer[8192]; - uint32_t bufferSize = sizeof(buffer); - _NSGetExecutablePath(buffer, &bufferSize); - staticResourcePath = QDir::cleanPath(QFileInfo(buffer).dir().absoluteFilePath("../Resources")) + "/"; -#else - staticResourcePath = ":/"; -#endif - #if !defined(Q_OS_ANDROID) && defined(DEV_BUILD) if (USE_SOURCE_TREE_RESOURCES()) { // For dev builds, optionally load content from the Git source tree @@ -90,21 +92,13 @@ const QString& PathUtils::resourcesPath() { #endif qDebug() << "Resource path resolved to " << staticResourcePath; }); - return staticResourcePath; } const QString& PathUtils::resourcesUrl() { - static QString staticResourcePath; + static QString staticResourcePath{ "qrc:///" }; static std::once_flag once; std::call_once(once, [&]{ - -#if defined(Q_OS_OSX) - staticResourcePath = QUrl::fromLocalFile(resourcesPath()).toString(); -#else - staticResourcePath = "qrc:///"; -#endif - #if !defined(Q_OS_ANDROID) && defined(DEV_BUILD) if (USE_SOURCE_TREE_RESOURCES()) { // For dev builds, optionally load content from the Git source tree diff --git a/libraries/shared/src/PathUtils.h b/libraries/shared/src/PathUtils.h index fc933b6b8c..2247f4cc6a 100644 --- a/libraries/shared/src/PathUtils.h +++ b/libraries/shared/src/PathUtils.h @@ -37,6 +37,7 @@ class PathUtils : public QObject, public Dependency { Q_PROPERTY(QString resources READ resourcesPath CONSTANT) Q_PROPERTY(QUrl defaultScripts READ defaultScriptsLocation CONSTANT) public: + static const QString& getRccPath(); static const QString& resourcesUrl(); static QUrl resourcesUrl(const QString& relative); static const QString& resourcesPath(); diff --git a/libraries/shared/src/SharedUtil.cpp b/libraries/shared/src/SharedUtil.cpp index 7c4a9b6d6c..bb22a1e753 100644 --- a/libraries/shared/src/SharedUtil.cpp +++ b/libraries/shared/src/SharedUtil.cpp @@ -297,14 +297,23 @@ void setAtBit(unsigned char& byte, int bitIndex) { byte |= (1 << (7 - bitIndex)); } +bool oneAtBit16(unsigned short word, int bitIndex) { + return (word >> (15 - bitIndex) & 1); +} + +void setAtBit16(unsigned short& word, int bitIndex) { + word |= (1 << (15 - bitIndex)); +} + + void clearAtBit(unsigned char& byte, int bitIndex) { if (oneAtBit(byte, bitIndex)) { byte -= (1 << (7 - bitIndex)); } } -int getSemiNibbleAt(unsigned char byte, int bitIndex) { - return (byte >> (6 - bitIndex) & 3); // semi-nibbles store 00, 01, 10, or 11 +int getSemiNibbleAt(unsigned short word, int bitIndex) { + return (word >> (14 - bitIndex) & 3); // semi-nibbles store 00, 01, 10, or 11 } int getNthBit(unsigned char byte, int ordinal) { @@ -326,9 +335,9 @@ int getNthBit(unsigned char byte, int ordinal) { return ERROR_RESULT; } -void setSemiNibbleAt(unsigned char& byte, int bitIndex, int value) { +void setSemiNibbleAt(unsigned short& word, int bitIndex, int value) { //assert(value <= 3 && value >= 0); - byte |= ((value & 3) << (6 - bitIndex)); // semi-nibbles store 00, 01, 10, or 11 + word |= ((value & 3) << (14 - bitIndex)); // semi-nibbles store 00, 01, 10, or 11 } bool isInEnvironment(const char* environment) { diff --git a/libraries/shared/src/SharedUtil.h b/libraries/shared/src/SharedUtil.h index 9875314aa4..db9fff3e93 100644 --- a/libraries/shared/src/SharedUtil.h +++ b/libraries/shared/src/SharedUtil.h @@ -163,9 +163,11 @@ void printVoxelCode(unsigned char* voxelCode); int numberOfOnes(unsigned char byte); bool oneAtBit(unsigned char byte, int bitIndex); void setAtBit(unsigned char& byte, int bitIndex); +bool oneAtBit16(unsigned short word, int bitIndex); +void setAtBit16(unsigned short& word, int bitIndex); void clearAtBit(unsigned char& byte, int bitIndex); -int getSemiNibbleAt(unsigned char byte, int bitIndex); -void setSemiNibbleAt(unsigned char& byte, int bitIndex, int value); +int getSemiNibbleAt(unsigned short word, int bitIndex); +void setSemiNibbleAt(unsigned short& word, int bitIndex, int value); int getNthBit(unsigned char byte, int ordinal); /// determines the bit placement 0-7 of the ordinal set bit diff --git a/libraries/shared/src/shared/QtHelpers.h b/libraries/shared/src/shared/QtHelpers.h index 2133119324..3387be6909 100644 --- a/libraries/shared/src/shared/QtHelpers.h +++ b/libraries/shared/src/shared/QtHelpers.h @@ -48,6 +48,6 @@ bool blockingInvokeMethod( } } #define BLOCKING_INVOKE_METHOD(obj, member, ...) \ - hifi::qt::blockingInvokeMethod(__FUNCTION__, obj, member, ##__VA_ARGS__) + ::hifi::qt::blockingInvokeMethod(__FUNCTION__, obj, member, ##__VA_ARGS__) #endif diff --git a/libraries/task/src/task/Config.h b/libraries/task/src/task/Config.h index 20de5c850e..00fa01808b 100644 --- a/libraries/task/src/task/Config.h +++ b/libraries/task/src/task/Config.h @@ -127,9 +127,29 @@ public: double getCPURunTime() const { return _msCPURunTime; } // Describe the node graph data connections of the associated Job/Task + /**jsdoc + * @function Render.isTask + * @returns {boolean} + */ Q_INVOKABLE virtual bool isTask() const { return false; } + + /**jsdoc + * @function Render.getSubConfigs + * @returns {object[]} + */ Q_INVOKABLE virtual QObjectList getSubConfigs() const { return QObjectList(); } + + /**jsdoc + * @function Render.getNumSubs + * @returns {number} + */ Q_INVOKABLE virtual int getNumSubs() const { return 0; } + + /**jsdoc + * @function Render.getSubConfig + * @param {number} index + * @returns {object} + */ Q_INVOKABLE virtual QObject* getSubConfig(int i) const { return nullptr; } public slots: diff --git a/libraries/ui/src/ui/TabletScriptingInterface.cpp b/libraries/ui/src/ui/TabletScriptingInterface.cpp index 9070d87a3c..2c52e669a0 100644 --- a/libraries/ui/src/ui/TabletScriptingInterface.cpp +++ b/libraries/ui/src/ui/TabletScriptingInterface.cpp @@ -431,6 +431,19 @@ bool TabletProxy::isMessageDialogOpen() { return result.toBool(); } +void TabletProxy::closeDialog() { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "closeDialog"); + return; + } + + if (!_qmlTabletRoot) { + return; + } + + QMetaObject::invokeMethod(_qmlTabletRoot, "closeDialog"); +} + void TabletProxy::emitWebEvent(const QVariant& msg) { if (QThread::currentThread() != thread()) { QMetaObject::invokeMethod(this, "emitWebEvent", Q_ARG(QVariant, msg)); diff --git a/libraries/ui/src/ui/TabletScriptingInterface.h b/libraries/ui/src/ui/TabletScriptingInterface.h index 43d889f1d1..1ab29ca3fd 100644 --- a/libraries/ui/src/ui/TabletScriptingInterface.h +++ b/libraries/ui/src/ui/TabletScriptingInterface.h @@ -308,6 +308,12 @@ public: */ Q_INVOKABLE bool isMessageDialogOpen(); + /**jsdoc + * Close any open dialogs. + * @function TabletProxy#closeDialog + */ + Q_INVOKABLE void closeDialog(); + /**jsdoc * Creates a new button, adds it to this and returns it. * @function TabletProxy#addButton diff --git a/plugins/hifiSdl2/src/SDL2Manager.cpp b/plugins/hifiSdl2/src/SDL2Manager.cpp index 664e53d115..df0cef06c8 100644 --- a/plugins/hifiSdl2/src/SDL2Manager.cpp +++ b/plugins/hifiSdl2/src/SDL2Manager.cpp @@ -47,7 +47,7 @@ static_assert( const char* SDL2Manager::NAME = "SDL2"; const char* SDL2Manager::SDL2_ID_STRING = "SDL2"; -const bool DEFAULT_ENABLED = false; +const bool DEFAULT_ENABLED = true; SDL_JoystickID SDL2Manager::getInstanceId(SDL_GameController* controller) { SDL_Joystick* joystick = SDL_GameControllerGetJoystick(controller); diff --git a/plugins/oculus/src/OculusBaseDisplayPlugin.cpp b/plugins/oculus/src/OculusBaseDisplayPlugin.cpp index 7e337070d3..5aa1e45943 100644 --- a/plugins/oculus/src/OculusBaseDisplayPlugin.cpp +++ b/plugins/oculus/src/OculusBaseDisplayPlugin.cpp @@ -15,19 +15,25 @@ #include "OculusHelpers.h" +using namespace hifi; + void OculusBaseDisplayPlugin::resetSensors() { ovr_RecenterTrackingOrigin(_session); - _currentRenderFrameInfo.renderPose = glm::mat4(); // identity } bool OculusBaseDisplayPlugin::beginFrameRender(uint32_t frameIndex) { - handleOVREvents(); - if (quitRequested()) { + ovrSessionStatus status{}; + if (!OVR_SUCCESS(ovr_GetSessionStatus(_session, &status))) { + qCWarning(oculusLog) << "Unable to fetch Oculus session status" << ovr::getError(); + return false; + } + + if (ovr::quitRequested(status)) { QMetaObject::invokeMethod(qApp, "quit"); return false; } - if (reorientRequested()) { + if (ovr::reorientRequested(status)) { emit resetSensorsRequested(); } @@ -35,18 +41,18 @@ bool OculusBaseDisplayPlugin::beginFrameRender(uint32_t frameIndex) { _currentRenderFrameInfo.sensorSampleTime = ovr_GetTimeInSeconds(); _currentRenderFrameInfo.predictedDisplayTime = ovr_GetPredictedDisplayTime(_session, frameIndex); auto trackingState = ovr_GetTrackingState(_session, _currentRenderFrameInfo.predictedDisplayTime, ovrTrue); - _currentRenderFrameInfo.renderPose = toGlm(trackingState.HeadPose.ThePose); + _currentRenderFrameInfo.renderPose = ovr::toGlm(trackingState.HeadPose.ThePose); _currentRenderFrameInfo.presentPose = _currentRenderFrameInfo.renderPose; std::array handPoses; // Make controller poses available to the presentation thread - ovr_for_each_hand([&](ovrHandType hand) { + ovr::for_each_hand([&](ovrHandType hand) { static const auto REQUIRED_HAND_STATUS = ovrStatus_OrientationTracked | ovrStatus_PositionTracked; if (REQUIRED_HAND_STATUS != (trackingState.HandStatusFlags[hand] & REQUIRED_HAND_STATUS)) { return; } - auto correctedPose = ovrControllerPoseToHandPose(hand, trackingState.HandPoses[hand]); + auto correctedPose = ovr::toControllerPose(hand, trackingState.HandPoses[hand]); static const glm::quat HAND_TO_LASER_ROTATION = glm::rotation(Vectors::UNIT_Z, Vectors::UNIT_NEG_Y); handPoses[hand] = glm::translate(glm::mat4(), correctedPose.translation) * glm::mat4_cast(correctedPose.rotation * HAND_TO_LASER_ROTATION); }); @@ -58,7 +64,7 @@ bool OculusBaseDisplayPlugin::beginFrameRender(uint32_t frameIndex) { } bool OculusBaseDisplayPlugin::isSupported() const { - return oculusAvailable(); + return ovr::available(); } glm::mat4 OculusBaseDisplayPlugin::getEyeProjection(Eye eye, const glm::mat4& baseProjection) const { @@ -71,7 +77,7 @@ glm::mat4 OculusBaseDisplayPlugin::getEyeProjection(Eye eye, const glm::mat4& ba ovrFovPort fovPort = _hmdDesc.DefaultEyeFov[eye]; ovrEyeRenderDesc& erd = ovr_GetRenderDesc(_session, ovrEye, fovPort); ovrMatrix4f ovrPerspectiveProjection = ovrMatrix4f_Projection(erd.Fov, baseNearClip, baseFarClip, ovrProjection_ClipRangeOpenGL); - return toGlm(ovrPerspectiveProjection); + return ovr::toGlm(ovrPerspectiveProjection); } else { return baseProjection; } @@ -85,7 +91,7 @@ glm::mat4 OculusBaseDisplayPlugin::getCullingProjection(const glm::mat4& basePro float baseFarClip = baseFrustum.getFarClip(); auto combinedFov = _eyeFovs[0]; combinedFov.LeftTan = combinedFov.RightTan = std::max(combinedFov.LeftTan, combinedFov.RightTan); - return toGlm(ovrMatrix4f_Projection(combinedFov, baseNearClip, baseFarClip, ovrProjection_ClipRangeOpenGL)); + return ovr::toGlm(ovrMatrix4f_Projection(combinedFov, baseNearClip, baseFarClip, ovrProjection_ClipRangeOpenGL)); } else { return baseProjection; } @@ -102,7 +108,7 @@ void OculusBaseDisplayPlugin::uncustomizeContext() { } bool OculusBaseDisplayPlugin::internalActivate() { - _session = acquireOculusSession(); + _session = ovr::acquireRenderSession(); if (!_session) { return false; } @@ -113,21 +119,21 @@ bool OculusBaseDisplayPlugin::internalActivate() { _viewScaleDesc.HmdSpaceToWorldScaleInMeters = 1.0f; _ipd = 0; - ovr_for_each_eye([&](ovrEyeType eye) { + ovr::for_each_eye([&](ovrEyeType eye) { _eyeFovs[eye] = _hmdDesc.DefaultEyeFov[eye]; ovrEyeRenderDesc& erd = _eyeRenderDescs[eye] = ovr_GetRenderDesc(_session, eye, _eyeFovs[eye]); ovrMatrix4f ovrPerspectiveProjection = ovrMatrix4f_Projection(erd.Fov, DEFAULT_NEAR_CLIP, DEFAULT_FAR_CLIP, ovrProjection_ClipRangeOpenGL); - _eyeProjections[eye] = toGlm(ovrPerspectiveProjection); - _eyeOffsets[eye] = glm::translate(mat4(), toGlm(erd.HmdToEyeOffset)); - eyeSizes[eye] = toGlm(ovr_GetFovTextureSize(_session, eye, erd.Fov, 1.0f)); - _viewScaleDesc.HmdToEyeOffset[eye] = erd.HmdToEyeOffset; - _ipd += glm::abs(glm::length(toGlm(erd.HmdToEyeOffset))); + _eyeProjections[eye] = ovr::toGlm(ovrPerspectiveProjection); + _eyeOffsets[eye] = ovr::toGlm(erd.HmdToEyePose); + eyeSizes[eye] = ovr::toGlm(ovr_GetFovTextureSize(_session, eye, erd.Fov, 1.0f)); + _viewScaleDesc.HmdToEyePose[eye] = erd.HmdToEyePose; + _ipd += glm::abs(glm::length(ovr::toGlm(erd.HmdToEyePose.Position))); }); auto combinedFov = _eyeFovs[0]; combinedFov.LeftTan = combinedFov.RightTan = std::max(combinedFov.LeftTan, combinedFov.RightTan); - _cullingProjection = toGlm(ovrMatrix4f_Projection(combinedFov, DEFAULT_NEAR_CLIP, DEFAULT_FAR_CLIP, ovrProjection_ClipRangeOpenGL)); + _cullingProjection = ovr::toGlm(ovrMatrix4f_Projection(combinedFov, DEFAULT_NEAR_CLIP, DEFAULT_FAR_CLIP, ovrProjection_ClipRangeOpenGL)); _renderTargetSize = uvec2( eyeSizes[0].x + eyeSizes[1].x, @@ -136,7 +142,7 @@ bool OculusBaseDisplayPlugin::internalActivate() { memset(&_sceneLayer, 0, sizeof(ovrLayerEyeFov)); _sceneLayer.Header.Type = ovrLayerType_EyeFov; _sceneLayer.Header.Flags = ovrLayerFlag_TextureOriginAtBottomLeft; - ovr_for_each_eye([&](ovrEyeType eye) { + ovr::for_each_eye([&](ovrEyeType eye) { ovrFovPort & fov = _sceneLayer.Fov[eye] = _eyeRenderDescs[eye].Fov; ovrSizei & size = _sceneLayer.Viewport[eye].Size = ovr_GetFovTextureSize(_session, eye, fov, 1.0f); _sceneLayer.Viewport[eye].Pos = { eye == ovrEye_Left ? 0 : size.w, 0 }; @@ -150,28 +156,14 @@ bool OculusBaseDisplayPlugin::internalActivate() { void OculusBaseDisplayPlugin::internalDeactivate() { Parent::internalDeactivate(); + ovr::releaseRenderSession(_session); } -bool OculusBaseDisplayPlugin::activateStandBySession() { - if (!_session) { - _session = acquireOculusSession(); - } - return _session; -} -void OculusBaseDisplayPlugin::deactivateSession() { - // FIXME - // Switching to Qt 5.9 exposed a race condition or similar issue that caused a crash when putting on an Rift - // while already in VR mode. Commenting these out is a workaround. - //releaseOculusSession(); - //_session = nullptr; -} void OculusBaseDisplayPlugin::updatePresentPose() { + ovrTrackingState trackingState; _currentPresentFrameInfo.sensorSampleTime = ovr_GetTimeInSeconds(); _currentPresentFrameInfo.predictedDisplayTime = ovr_GetPredictedDisplayTime(_session, 0); - auto trackingState = ovr_GetTrackingState(_session, _currentRenderFrameInfo.predictedDisplayTime, ovrFalse); - _currentPresentFrameInfo.presentPose = toGlm(trackingState.HeadPose.ThePose); + trackingState = ovr_GetTrackingState(_session, _currentRenderFrameInfo.predictedDisplayTime, ovrFalse); + _currentPresentFrameInfo.presentPose = ovr::toGlm(trackingState.HeadPose.ThePose); _currentPresentFrameInfo.renderPose = _currentPresentFrameInfo.presentPose; } - -OculusBaseDisplayPlugin::~OculusBaseDisplayPlugin() { -} diff --git a/plugins/oculus/src/OculusBaseDisplayPlugin.h b/plugins/oculus/src/OculusBaseDisplayPlugin.h index b3b99c0ad1..d70d14dc28 100644 --- a/plugins/oculus/src/OculusBaseDisplayPlugin.h +++ b/plugins/oculus/src/OculusBaseDisplayPlugin.h @@ -16,37 +16,32 @@ class OculusBaseDisplayPlugin : public HmdDisplayPlugin { using Parent = HmdDisplayPlugin; public: - ~OculusBaseDisplayPlugin(); bool isSupported() const override; + bool hasAsyncReprojection() const override { return true; } + bool getSupportsAutoSwitch() override final { return true; } glm::mat4 getEyeProjection(Eye eye, const glm::mat4& baseProjection) const override; glm::mat4 getCullingProjection(const glm::mat4& baseProjection) const override; - bool hasAsyncReprojection() const override { return true; } - - // Stereo specific methods void resetSensors() override final; bool beginFrameRender(uint32_t frameIndex) override; float getTargetFrameRate() const override { return _hmdDesc.DisplayRefreshRate; } - bool getSupportsAutoSwitch() override final { return true; } - - + protected: void customizeContext() override; void uncustomizeContext() override; bool internalActivate() override; void internalDeactivate() override; - bool activateStandBySession() override; - void deactivateSession() override; void updatePresentPose() override; protected: - ovrSession _session { nullptr }; + ovrSession _session{ nullptr }; ovrGraphicsLuid _luid; - ovrEyeRenderDesc _eyeRenderDescs[2]; - ovrFovPort _eyeFovs[2]; + std::array _eyeRenderDescs; + std::array _eyeFovs; ovrHmdDesc _hmdDesc; ovrLayerEyeFov _sceneLayer; ovrViewScaleDesc _viewScaleDesc; + // ovrLayerEyeFovDepth _depthLayer; }; diff --git a/plugins/oculus/src/OculusControllerManager.cpp b/plugins/oculus/src/OculusControllerManager.cpp index 7a176d36fb..943af301a2 100644 --- a/plugins/oculus/src/OculusControllerManager.cpp +++ b/plugins/oculus/src/OculusControllerManager.cpp @@ -22,34 +22,23 @@ #include #include -#include +#include #include "OculusHelpers.h" -Q_DECLARE_LOGGING_CATEGORY(oculus) - - -static const char* MENU_PARENT = "Avatar"; -static const char* MENU_NAME = "Oculus Touch Controllers"; -static const char* MENU_PATH = "Avatar" ">" "Oculus Touch Controllers"; +using namespace hifi; const char* OculusControllerManager::NAME = "Oculus"; const quint64 LOST_TRACKING_DELAY = 3000000; bool OculusControllerManager::isSupported() const { - return oculusAvailable(); + return hifi::ovr::available(); } bool OculusControllerManager::activate() { InputPlugin::activate(); - if (!_session) { - _session = acquireOculusSession(); - } - Q_ASSERT(_session); - checkForConnectedDevices(); - return true; } @@ -58,33 +47,30 @@ void OculusControllerManager::checkForConnectedDevices() { return; } - unsigned int controllerConnected = ovr_GetConnectedControllerTypes(_session); + ovr::withSession([&] (ovrSession session) { + unsigned int controllerConnected = ovr_GetConnectedControllerTypes(session); - if (!_remote && (controllerConnected & ovrControllerType_Remote) == ovrControllerType_Remote) { - if (OVR_SUCCESS(ovr_GetInputState(_session, ovrControllerType_Remote, &_inputState))) { - auto userInputMapper = DependencyManager::get(); - _remote = std::make_shared(*this); - userInputMapper->registerDevice(_remote); + if (!_remote && (controllerConnected & ovrControllerType_Remote) == ovrControllerType_Remote) { + if (OVR_SUCCESS(ovr_GetInputState(session, ovrControllerType_Remote, &_inputState))) { + auto userInputMapper = DependencyManager::get(); + _remote = std::make_shared(*this); + userInputMapper->registerDevice(_remote); + } } - } - if (!_touch && (controllerConnected & ovrControllerType_Touch) != 0) { - if (OVR_SUCCESS(ovr_GetInputState(_session, ovrControllerType_Touch, &_inputState))) { - auto userInputMapper = DependencyManager::get(); - _touch = std::make_shared(*this); - userInputMapper->registerDevice(_touch); + if (!_touch && (controllerConnected & ovrControllerType_Touch) != 0) { + if (OVR_SUCCESS(ovr_GetInputState(session, ovrControllerType_Touch, &_inputState))) { + auto userInputMapper = DependencyManager::get(); + _touch = std::make_shared(*this); + userInputMapper->registerDevice(_touch); + } } - } + }); } void OculusControllerManager::deactivate() { InputPlugin::deactivate(); - if (_session) { - releaseOculusSession(); - _session = nullptr; - } - // unregister with UserInputMapper auto userInputMapper = DependencyManager::get(); if (_touch) { @@ -100,20 +86,30 @@ void OculusControllerManager::pluginUpdate(float deltaTime, const controller::In checkForConnectedDevices(); - if (_touch) { - if (OVR_SUCCESS(ovr_GetInputState(_session, ovrControllerType_Touch, &_inputState))) { - _touch->update(deltaTime, inputCalibrationData); - } else { - qCWarning(oculus) << "Unable to read Oculus touch input state"; + bool updateRemote = false, updateTouch = false; + + ovr::withSession([&](ovrSession session) { + if (_touch) { + updateTouch = OVR_SUCCESS(ovr_GetInputState(session, ovrControllerType_Touch, &_inputState)); + if (!updateTouch) { + qCWarning(oculusLog) << "Unable to read Oculus touch input state" << ovr::getError(); + } } + if (_remote) { + updateRemote = OVR_SUCCESS(ovr_GetInputState(session, ovrControllerType_Remote, &_inputState)); + if (!updateRemote) { + qCWarning(oculusLog) << "Unable to read Oculus remote input state" << ovr::getError(); + } + } + }); + + + if (_touch && updateTouch) { + _touch->update(deltaTime, inputCalibrationData); } - if (_remote) { - if (OVR_SUCCESS(ovr_GetInputState(_session, ovrControllerType_Remote, &_inputState))) { - _remote->update(deltaTime, inputCalibrationData); - } else { - qCWarning(oculus) << "Unable to read Oculus remote input state"; - } + if (_remote && updateRemote) { + _remote->update(deltaTime, inputCalibrationData); } } @@ -210,15 +206,6 @@ void OculusControllerManager::RemoteDevice::focusOutEvent() { _buttonPressedMap.clear(); } -bool OculusControllerManager::isHeadControllerMounted() const { - ovrSessionStatus status; - bool success = OVR_SUCCESS(ovr_GetSessionStatus(_session, &status)); - if (!success) { - return false; - } - return status.HmdMounted == ovrTrue; -} - void OculusControllerManager::TouchDevice::update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) { _buttonPressedMap.clear(); @@ -226,10 +213,19 @@ void OculusControllerManager::TouchDevice::update(float deltaTime, int numTrackedControllers = 0; quint64 currentTime = usecTimestampNow(); static const auto REQUIRED_HAND_STATUS = ovrStatus_OrientationTracked | ovrStatus_PositionTracked; - auto tracking = ovr_GetTrackingState(_parent._session, 0, false); - ovr_for_each_hand([&](ovrHandType hand) { + bool hasInputFocus = ovr::hasInputFocus(); + auto tracking = ovr::getTrackingState(); // ovr_GetTrackingState(_parent._session, 0, false); + ovr::for_each_hand([&](ovrHandType hand) { ++numTrackedControllers; int controller = (hand == ovrHand_Left ? controller::LEFT_HAND : controller::RIGHT_HAND); + + // Disable hand tracking while in Oculus Dash (Dash renders it's own hands) + if (!hasInputFocus) { + _poseStateMap.erase(controller); + _poseStateMap[controller].valid = false; + return; + } + if (REQUIRED_HAND_STATUS == (tracking.HandStatusFlags[hand] & REQUIRED_HAND_STATUS)) { _poseStateMap.erase(controller); handlePose(deltaTime, inputCalibrationData, hand, tracking.HandPoses[hand]); @@ -253,7 +249,7 @@ void OculusControllerManager::TouchDevice::update(float deltaTime, handleRotationForUntrackedHand(inputCalibrationData, hand, tracking.HandPoses[hand]); }); - if (_parent.isHeadControllerMounted()) { + if (ovr::hmdMounted()) { handleHeadPose(deltaTime, inputCalibrationData, tracking.HeadPose); } else { _poseStateMap[controller::HEAD].valid = false; @@ -311,7 +307,7 @@ void OculusControllerManager::TouchDevice::handlePose(float deltaTime, ovrHandType hand, const ovrPoseStatef& handPose) { auto poseId = hand == ovrHand_Left ? controller::LEFT_HAND : controller::RIGHT_HAND; auto& pose = _poseStateMap[poseId]; - pose = ovrControllerPoseToHandPose(hand, handPose); + pose = ovr::toControllerPose(hand, handPose); // transform into avatar frame glm::mat4 controllerToAvatar = glm::inverse(inputCalibrationData.avatarMat) * inputCalibrationData.sensorToWorldMat; pose = pose.transform(controllerToAvatar); @@ -320,15 +316,15 @@ void OculusControllerManager::TouchDevice::handlePose(float deltaTime, void OculusControllerManager::TouchDevice::handleHeadPose(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, const ovrPoseStatef& headPose) { - glm::mat4 mat = createMatFromQuatAndPos(toGlm(headPose.ThePose.Orientation), - toGlm(headPose.ThePose.Position)); + glm::mat4 mat = createMatFromQuatAndPos(ovr::toGlm(headPose.ThePose.Orientation), + ovr::toGlm(headPose.ThePose.Position)); //perform a 180 flip to make the HMD face the +z instead of -z, beacuse the head faces +z glm::mat4 matYFlip = mat * Matrices::Y_180; controller::Pose pose(extractTranslation(matYFlip), glmExtractRotation(matYFlip), - toGlm(headPose.LinearVelocity), // XXX * matYFlip ? - toGlm(headPose.AngularVelocity)); + ovr::toGlm(headPose.LinearVelocity), // XXX * matYFlip ? + ovr::toGlm(headPose.AngularVelocity)); glm::mat4 sensorToAvatar = glm::inverse(inputCalibrationData.avatarMat) * inputCalibrationData.sensorToWorldMat; glm::mat4 defaultHeadOffset = glm::inverse(inputCalibrationData.defaultCenterEyeMat) * @@ -343,7 +339,7 @@ void OculusControllerManager::TouchDevice::handleRotationForUntrackedHand(const auto poseId = (hand == ovrHand_Left ? controller::LEFT_HAND : controller::RIGHT_HAND); auto& pose = _poseStateMap[poseId]; auto lastHandPose = _lastControllerPose[poseId]; - pose = ovrControllerRotationToHandRotation(hand, handPose, lastHandPose); + pose = ovr::toControllerPose(hand, handPose, lastHandPose); glm::mat4 controllerToAvatar = glm::inverse(inputCalibrationData.avatarMat) * inputCalibrationData.sensorToWorldMat; pose = pose.transform(controllerToAvatar); } @@ -351,36 +347,40 @@ void OculusControllerManager::TouchDevice::handleRotationForUntrackedHand(const bool OculusControllerManager::TouchDevice::triggerHapticPulse(float strength, float duration, controller::Hand hand) { Locker locker(_lock); bool toReturn = true; - if (hand == controller::BOTH || hand == controller::LEFT) { - if (strength == 0.0f) { - _leftHapticStrength = 0.0f; - _leftHapticDuration = 0.0f; - } else { - _leftHapticStrength = (duration > _leftHapticDuration) ? strength : _leftHapticStrength; - if (ovr_SetControllerVibration(_parent._session, ovrControllerType_LTouch, 1.0f, _leftHapticStrength) != ovrSuccess) { - toReturn = false; + ovr::withSession([&](ovrSession session) { + if (hand == controller::BOTH || hand == controller::LEFT) { + if (strength == 0.0f) { + _leftHapticStrength = 0.0f; + _leftHapticDuration = 0.0f; + } else { + _leftHapticStrength = (duration > _leftHapticDuration) ? strength : _leftHapticStrength; + if (ovr_SetControllerVibration(session, ovrControllerType_LTouch, 1.0f, _leftHapticStrength) != ovrSuccess) { + toReturn = false; + } + _leftHapticDuration = std::max(duration, _leftHapticDuration); } - _leftHapticDuration = std::max(duration, _leftHapticDuration); } - } - if (hand == controller::BOTH || hand == controller::RIGHT) { - if (strength == 0.0f) { - _rightHapticStrength = 0.0f; - _rightHapticDuration = 0.0f; - } else { - _rightHapticStrength = (duration > _rightHapticDuration) ? strength : _rightHapticStrength; - if (ovr_SetControllerVibration(_parent._session, ovrControllerType_RTouch, 1.0f, _rightHapticStrength) != ovrSuccess) { - toReturn = false; + if (hand == controller::BOTH || hand == controller::RIGHT) { + if (strength == 0.0f) { + _rightHapticStrength = 0.0f; + _rightHapticDuration = 0.0f; + } else { + _rightHapticStrength = (duration > _rightHapticDuration) ? strength : _rightHapticStrength; + if (ovr_SetControllerVibration(session, ovrControllerType_RTouch, 1.0f, _rightHapticStrength) != ovrSuccess) { + toReturn = false; + } + _rightHapticDuration = std::max(duration, _rightHapticDuration); } - _rightHapticDuration = std::max(duration, _rightHapticDuration); } - } + }); return toReturn; } void OculusControllerManager::TouchDevice::stopHapticPulse(bool leftHand) { auto handType = (leftHand ? ovrControllerType_LTouch : ovrControllerType_RTouch); - ovr_SetControllerVibration(_parent._session, handType, 0.0f, 0.0f); + ovr::withSession([&](ovrSession session) { + ovr_SetControllerVibration(session, handType, 0.0f, 0.0f); + }); } /**jsdoc diff --git a/plugins/oculus/src/OculusControllerManager.h b/plugins/oculus/src/OculusControllerManager.h index 69187f94a6..b08d54babe 100644 --- a/plugins/oculus/src/OculusControllerManager.h +++ b/plugins/oculus/src/OculusControllerManager.h @@ -26,10 +26,8 @@ public: // Plugin functions bool isSupported() const override; const QString getName() const override { return NAME; } - bool isHandController() const override { return _touch != nullptr; } bool isHeadController() const override { return true; } - bool isHeadControllerMounted() const; QStringList getSubdeviceNames() override; bool activate() override; @@ -105,7 +103,6 @@ private: void checkForConnectedDevices(); - ovrSession _session { nullptr }; ovrInputState _inputState {}; RemoteDevice::Pointer _remote; TouchDevice::Pointer _touch; diff --git a/plugins/oculus/src/OculusDisplayPlugin.cpp b/plugins/oculus/src/OculusDisplayPlugin.cpp index 0fd8467e38..308652cacd 100644 --- a/plugins/oculus/src/OculusDisplayPlugin.cpp +++ b/plugins/oculus/src/OculusDisplayPlugin.cpp @@ -19,6 +19,8 @@ #include "OculusHelpers.h" +using namespace hifi; + const char* OculusDisplayPlugin::NAME { "Oculus Rift" }; static ovrPerfHudMode currentDebugMode = ovrPerfHud_Off; @@ -63,7 +65,7 @@ void OculusDisplayPlugin::cycleDebugOutput() { void OculusDisplayPlugin::customizeContext() { Parent::customizeContext(); - _outputFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("OculusOutput", gpu::Element::COLOR_SRGBA_32, _renderTargetSize.x, _renderTargetSize.y)); + _outputFramebuffer.reset(gpu::Framebuffer::create("OculusOutput", gpu::Element::COLOR_SRGBA_32, _renderTargetSize.x, _renderTargetSize.y)); ovrTextureSwapChainDesc desc = { }; desc.Type = ovrTexture_2D; desc.ArraySize = 1; @@ -76,14 +78,14 @@ void OculusDisplayPlugin::customizeContext() { ovrResult result = ovr_CreateTextureSwapChainGL(_session, &desc, &_textureSwapChain); if (!OVR_SUCCESS(result)) { - logCritical("Failed to create swap textures"); + qCritical(oculusLog) << "Failed to create swap textures" << ovr::getError(); return; } int length = 0; result = ovr_GetTextureSwapChainLength(_session, _textureSwapChain, &length); if (!OVR_SUCCESS(result) || !length) { - logCritical("Unable to count swap chain textures"); + qCritical(oculusLog) << "Unable to count swap chain textures" << ovr::getError(); return; } for (int i = 0; i < length; ++i) { @@ -164,8 +166,8 @@ void OculusDisplayPlugin::hmdPresent() { auto result = ovr_CommitTextureSwapChain(_session, _textureSwapChain); Q_ASSERT(OVR_SUCCESS(result)); _sceneLayer.SensorSampleTime = _currentPresentFrameInfo.sensorSampleTime; - _sceneLayer.RenderPose[ovrEyeType::ovrEye_Left] = ovrPoseFromGlm(_currentPresentFrameInfo.renderPose); - _sceneLayer.RenderPose[ovrEyeType::ovrEye_Right] = ovrPoseFromGlm(_currentPresentFrameInfo.renderPose); + _sceneLayer.RenderPose[ovrEyeType::ovrEye_Left] = ovr::poseFromGlm(_currentPresentFrameInfo.renderPose); + _sceneLayer.RenderPose[ovrEyeType::ovrEye_Right] = ovr::poseFromGlm(_currentPresentFrameInfo.renderPose); auto submitStart = usecTimestampNow(); uint64_t nonSubmitInterval = 0; @@ -192,7 +194,7 @@ void OculusDisplayPlugin::hmdPresent() { } if (!OVR_SUCCESS(result)) { - logWarning("Failed to present"); + qWarning(oculusLog) << "Failed to present" << ovr::getError(); } static int compositorDroppedFrames = 0; @@ -234,9 +236,7 @@ QJsonObject OculusDisplayPlugin::getHardwareStats() const { } bool OculusDisplayPlugin::isHmdMounted() const { - ovrSessionStatus status; - return (OVR_SUCCESS(ovr_GetSessionStatus(_session, &status)) && - (ovrFalse != status.HmdMounted)); + return ovr::hmdMounted(); } QString OculusDisplayPlugin::getPreferredAudioInDevice() const { diff --git a/plugins/oculus/src/OculusHelpers.cpp b/plugins/oculus/src/OculusHelpers.cpp index 4632c8ab76..511984c657 100644 --- a/plugins/oculus/src/OculusHelpers.cpp +++ b/plugins/oculus/src/OculusHelpers.cpp @@ -24,41 +24,17 @@ #include Q_LOGGING_CATEGORY(displayplugins, "hifi.plugins.display") -Q_LOGGING_CATEGORY(oculus, "hifi.plugins.display.oculus") - -static std::atomic refCount { 0 }; -static ovrSession session { nullptr }; - -static bool _quitRequested { false }; -static bool _reorientRequested { false }; - -inline ovrErrorInfo getError() { - ovrErrorInfo error; - ovr_GetLastErrorInfo(&error); - return error; -} - -void logWarning(const char* what) { - qWarning(oculus) << what << ":" << getError().ErrorString; -} - -void logCritical(const char* what) { - std::string error("[oculus] "); - error += what; - error += ": "; - error += getError().ErrorString; - qCritical(error.c_str()); -} +Q_LOGGING_CATEGORY(oculusLog, "hifi.plugins.display.oculus") +using namespace hifi; static wchar_t* REQUIRED_OCULUS_DLL = L"LibOVRRT64_1.dll"; static wchar_t FOUND_PATH[MAX_PATH]; -bool oculusAvailable() { +bool ovr::available() { static std::once_flag once; - static bool result { false }; + static bool result{ false }; std::call_once(once, [&] { - static const QString DEBUG_FLAG("HIFI_DEBUG_OPENVR"); static bool enableDebugOpenVR = QProcessEnvironment::systemEnvironment().contains(DEBUG_FLAG); if (enableDebugOpenVR) { @@ -81,113 +57,107 @@ bool oculusAvailable() { return result; } -ovrSession acquireOculusSession() { - if (!session && !oculusAvailable()) { - qCDebug(oculus) << "oculus: no runtime or HMD present"; +class ovrImpl { + using Mutex = std::mutex; + using Lock = std::unique_lock; + std::mutex mutex; + ovrSession session{ nullptr }; + size_t renderCount{ 0 }; + +private: + void setupSession(bool render) { + if (session) { + return; + } + ovrInitParams initParams{ ovrInit_RequestVersion | ovrInit_FocusAware, OVR_MINOR_VERSION, nullptr, 0, 0 }; + if (render) { + initParams.Flags |= ovrInit_MixedRendering; + } else { + initParams.Flags |= ovrInit_Invisible; + } + + if (!OVR_SUCCESS(ovr_Initialize(&initParams))) { + qCWarning(oculusLog) << "Failed to initialze Oculus SDK" << ovr::getError(); + return; + } + + ovrGraphicsLuid luid; + if (!OVR_SUCCESS(ovr_Create(&session, &luid))) { + qCWarning(oculusLog) << "Failed to acquire Oculus session" << ovr::getError(); + return; + } + } + + void releaseSession() { + if (!session) { + return; + } + ovr_Destroy(session); + session = nullptr; + ovr_Shutdown(); + } + +public: + void withSession(const std::function& f) { + Lock lock(mutex); + if (!session) { + setupSession(false); + } + f(session); + } + + ovrSession acquireRenderSession() { + Lock lock(mutex); + if (renderCount++ == 0) { + releaseSession(); + setupSession(true); + } return session; } - if (!session) { - ovrInitParams initParams { - ovrInit_RequestVersion | ovrInit_MixedRendering, OVR_MINOR_VERSION, nullptr, 0, 0 - }; - - if (!OVR_SUCCESS(ovr_Initialize(&initParams))) { - logWarning("Failed to initialize Oculus SDK"); - return session; - } - -#ifdef OCULUS_APP_ID - if (qApp->property(hifi::properties::OCULUS_STORE).toBool()) { - if (ovr_PlatformInitializeWindows(OCULUS_APP_ID) != ovrPlatformInitialize_Success) { - // we were unable to initialize the platform for entitlement check - fail the check - _quitRequested = true; - } else { - qCDebug(oculus) << "Performing Oculus Platform entitlement check"; - ovr_Entitlement_GetIsViewerEntitled(); - } - } -#endif - - Q_ASSERT(0 == refCount); - ovrGraphicsLuid luid; - if (!OVR_SUCCESS(ovr_Create(&session, &luid))) { - logWarning("Failed to acquire Oculus session"); - return session; + void releaseRenderSession(ovrSession session) { + Lock lock(mutex); + if (--renderCount == 0) { + releaseSession(); } } +} _ovr; - ++refCount; - return session; +ovrSession ovr::acquireRenderSession() { + return _ovr.acquireRenderSession(); } -void releaseOculusSession() { - Q_ASSERT(refCount > 0 && session); - // HACK the Oculus runtime doesn't seem to play well with repeated shutdown / restart. - // So for now we'll just hold on to the session -#if 0 - if (!--refCount) { - qCDebug(oculus) << "oculus: zero refcount, shutdown SDK and session"; - ovr_Destroy(session); - ovr_Shutdown(); - session = nullptr; - } -#endif +void ovr::releaseRenderSession(ovrSession session) { + _ovr.releaseRenderSession(session); } -void handleOVREvents() { - if (!session) { - return; - } +void ovr::withSession(const std::function& f) { + _ovr.withSession(f); +} - ovrSessionStatus status; - if (!OVR_SUCCESS(ovr_GetSessionStatus(session, &status))) { - return; - } - - _quitRequested = status.ShouldQuit; - _reorientRequested = status.ShouldRecenter; - - #ifdef OCULUS_APP_ID - - if (qApp->property(hifi::properties::OCULUS_STORE).toBool()) { - // pop messages to see if we got a return for an entitlement check - ovrMessageHandle message = ovr_PopMessage(); - - while (message) { - switch (ovr_Message_GetType(message)) { - case ovrMessage_Entitlement_GetIsViewerEntitled: { - if (!ovr_Message_IsError(message)) { - // this viewer is entitled, no need to flag anything - qCDebug(oculus) << "Oculus Platform entitlement check succeeded, proceeding normally"; - } else { - // we failed the entitlement check, set our flag so the app can stop - qCDebug(oculus) << "Oculus Platform entitlement check failed, app will now quit" << OCULUS_APP_ID; - _quitRequested = true; - } - } - } - - // free the message handle to cleanup and not leak - ovr_FreeMessage(message); - - // pop the next message to check, if there is one - message = ovr_PopMessage(); +ovrSessionStatus ovr::getStatus() { + ovrSessionStatus status{}; + withSession([&](ovrSession session) { + if (!OVR_SUCCESS(ovr_GetSessionStatus(session, &status))) { + qCWarning(oculusLog) << "Failed to get session status" << ovr::getError(); } - } -#endif + }); + return status; } -bool quitRequested() { - return _quitRequested; -} -bool reorientRequested() { - return _reorientRequested; +ovrTrackingState ovr::getTrackingState() { + ovrTrackingState result{}; + withSession([&](ovrSession session) { result = ovr_GetTrackingState(session, 0, ovrFalse); }); + return result; } -controller::Pose ovrControllerPoseToHandPose( - ovrHandType hand, - const ovrPoseStatef& handPose) { +QString ovr::getError() { + static ovrErrorInfo error; + ovr_GetLastErrorInfo(&error); + return QString(error.ErrorString); +} + +controller::Pose hifi::ovr::toControllerPose(ovrHandType hand, const ovrPoseStatef& handPose) { // When the sensor-to-world rotation is identity the coordinate axes look like this: // // user @@ -247,9 +217,8 @@ controller::Pose ovrControllerPoseToHandPose( static const glm::quat rightRotationOffset = glm::inverse(rightQuarterZ) * touchToHand; static const float CONTROLLER_LENGTH_OFFSET = 0.0762f; // three inches - static const glm::vec3 CONTROLLER_OFFSET = glm::vec3(CONTROLLER_LENGTH_OFFSET / 2.0f, - -CONTROLLER_LENGTH_OFFSET / 2.0f, - CONTROLLER_LENGTH_OFFSET * 1.5f); + static const glm::vec3 CONTROLLER_OFFSET = + glm::vec3(CONTROLLER_LENGTH_OFFSET / 2.0f, -CONTROLLER_LENGTH_OFFSET / 2.0f, CONTROLLER_LENGTH_OFFSET * 1.5f); static const glm::vec3 leftTranslationOffset = glm::vec3(-1.0f, 1.0f, 1.0f) * CONTROLLER_OFFSET; static const glm::vec3 rightTranslationOffset = CONTROLLER_OFFSET; @@ -268,12 +237,13 @@ controller::Pose ovrControllerPoseToHandPose( return pose; } -controller::Pose ovrControllerRotationToHandRotation(ovrHandType hand, const ovrPoseStatef& handPose, - const ovrPoseStatef& lastHandPose) { +controller::Pose hifi::ovr::toControllerPose(ovrHandType hand, + const ovrPoseStatef& handPose, + const ovrPoseStatef& lastHandPose) { static const glm::quat yFlip = glm::angleAxis(PI, Vectors::UNIT_Y); static const glm::quat quarterX = glm::angleAxis(PI_OVER_TWO, Vectors::UNIT_X); static const glm::quat touchToHand = yFlip * quarterX; - + static const glm::quat leftQuarterZ = glm::angleAxis(-PI_OVER_TWO, Vectors::UNIT_Z); static const glm::quat rightQuarterZ = glm::angleAxis(PI_OVER_TWO, Vectors::UNIT_Z); @@ -281,9 +251,8 @@ controller::Pose ovrControllerRotationToHandRotation(ovrHandType hand, const ovr static const glm::quat rightRotationOffset = glm::inverse(rightQuarterZ) * touchToHand; static const float CONTROLLER_LENGTH_OFFSET = 0.0762f; // three inches - static const glm::vec3 CONTROLLER_OFFSET = glm::vec3(CONTROLLER_LENGTH_OFFSET / 2.0f, - -CONTROLLER_LENGTH_OFFSET / 2.0f, - CONTROLLER_LENGTH_OFFSET * 1.5f); + static const glm::vec3 CONTROLLER_OFFSET = + glm::vec3(CONTROLLER_LENGTH_OFFSET / 2.0f, -CONTROLLER_LENGTH_OFFSET / 2.0f, CONTROLLER_LENGTH_OFFSET * 1.5f); static const glm::vec3 leftTranslationOffset = glm::vec3(-1.0f, 1.0f, 1.0f) * CONTROLLER_OFFSET; static const glm::vec3 rightTranslationOffset = CONTROLLER_OFFSET; @@ -301,3 +270,52 @@ controller::Pose ovrControllerRotationToHandRotation(ovrHandType hand, const ovr pose.valid = true; return pose; } + +// FIXME These should be moved to an oculusPlatform plugin, they don't interact with the controller or session state +#if 0 +void handleOVREvents() { + updateSessionStatus(true); + +#ifdef OCULUS_APP_ID + + if (qApp->property(hifi::properties::OCULUS_STORE).toBool()) { + // pop messages to see if we got a return for an entitlement check + ovrMessageHandle message = ovr_PopMessage(); + + while (message) { + switch (ovr_Message_GetType(message)) { + case ovrMessage_Entitlement_GetIsViewerEntitled: + { + if (!ovr_Message_IsError(message)) { + // this viewer is entitled, no need to flag anything + qCDebug(oculus) << "Oculus Platform entitlement check succeeded, proceeding normally"; + } else { + // we failed the entitlement check, set our flag so the app can stop + qCDebug(oculus) << "Oculus Platform entitlement check failed, app will now quit" << OCULUS_APP_ID; + _quitRequested = true; + } + } + } + + // free the message handle to cleanup and not leak + ovr_FreeMessage(message); + + // pop the next message to check, if there is one + message = ovr_PopMessage(); + } + } +#endif +} + +#ifdef OCULUS_APP_ID +if (qApp->property(hifi::properties::OCULUS_STORE).toBool()) { + if (ovr_PlatformInitializeWindows(OCULUS_APP_ID) != ovrPlatformInitialize_Success) { + // we were unable to initialize the platform for entitlement check - fail the check + _quitRequested = true; + } else { + qCDebug(oculusLog) << "Performing Oculus Platform entitlement check"; + ovr_Entitlement_GetIsViewerEntitled(); + } +} +#endif +#endif diff --git a/plugins/oculus/src/OculusHelpers.h b/plugins/oculus/src/OculusHelpers.h index 5743f8576b..6588d9754b 100644 --- a/plugins/oculus/src/OculusHelpers.h +++ b/plugins/oculus/src/OculusHelpers.h @@ -7,10 +7,9 @@ // #pragma once - #include -#include +#include #include #include #include @@ -18,106 +17,107 @@ #include Q_DECLARE_LOGGING_CATEGORY(displayplugins) -Q_DECLARE_LOGGING_CATEGORY(oculus) +Q_DECLARE_LOGGING_CATEGORY(oculusLog) -void logWarning(const char* what); -void logCritical(const char* what); -bool oculusAvailable(); -ovrSession acquireOculusSession(); -void releaseOculusSession(); +namespace hifi { + +struct ovr { + static bool available(); + static ovrSession acquireRenderSession(); + static void releaseRenderSession(ovrSession session); + static void withSession(const std::function& f); + static ovrSessionStatus getStatus(); + static ovrTrackingState getTrackingState(); + static QString getError(); -void handleOVREvents(); -bool quitRequested(); -bool reorientRequested(); + static inline bool quitRequested() { return quitRequested(getStatus()); } + static inline bool reorientRequested() { return reorientRequested(getStatus()); } + static inline bool hmdMounted() { return hmdMounted(getStatus()); } + static inline bool hasInputFocus() { return hasInputFocus(getStatus()); } -// Convenience method for looping over each eye with a lambda -template -inline void ovr_for_each_eye(Function function) { - for (ovrEyeType eye = ovrEyeType::ovrEye_Left; - eye < ovrEyeType::ovrEye_Count; - eye = static_cast(eye + 1)) { - function(eye); + static inline bool quitRequested(const ovrSessionStatus& status) { return status.ShouldQuit != ovrFalse; } + static inline bool reorientRequested(const ovrSessionStatus& status) { return status.ShouldRecenter != ovrFalse; } + static inline bool hmdMounted(const ovrSessionStatus& status) { return status.HmdMounted != ovrFalse; } + static inline bool hasInputFocus(const ovrSessionStatus& status) { return status.HasInputFocus != ovrFalse; } + + // Convenience method for looping over each eye with a lambda + static inline void for_each_eye(const std::function& f) { + for (ovrEyeType eye = ovrEye_Left; eye < ovrEye_Count; eye = static_cast(eye + 1)) { + f(eye); + } } -} -template -inline void ovr_for_each_hand(Function function) { - for (ovrHandType hand = ovrHandType::ovrHand_Left; - hand <= ovrHandType::ovrHand_Right; - hand = static_cast(hand + 1)) { - function(hand); + static inline void for_each_hand(const std::function& f) { + for (ovrHandType hand = ovrHand_Left; hand < ovrHand_Count; hand = static_cast(hand + 1)) { + f(hand); + } } -} + static inline glm::mat4 toGlm(const ovrMatrix4f& om) { + return glm::transpose(glm::make_mat4(&om.M[0][0])); + } + static inline glm::mat4 toGlm(const ovrFovPort& fovport, float nearPlane = 0.01f, float farPlane = 10000.0f) { + return toGlm(ovrMatrix4f_Projection(fovport, nearPlane, farPlane, true)); + } + static inline glm::vec3 toGlm(const ovrVector3f& ov) { + return glm::make_vec3(&ov.x); + } + static inline glm::vec2 toGlm(const ovrVector2f& ov) { + return glm::make_vec2(&ov.x); + } -inline glm::mat4 toGlm(const ovrMatrix4f & om) { - return glm::transpose(glm::make_mat4(&om.M[0][0])); -} + static inline glm::uvec2 toGlm(const ovrSizei& ov) { + return glm::uvec2(ov.w, ov.h); + } -inline glm::mat4 toGlm(const ovrFovPort & fovport, float nearPlane = 0.01f, float farPlane = 10000.0f) { - return toGlm(ovrMatrix4f_Projection(fovport, nearPlane, farPlane, true)); -} + static inline glm::quat toGlm(const ovrQuatf& oq) { + return glm::make_quat(&oq.x); + } -inline glm::vec3 toGlm(const ovrVector3f & ov) { - return glm::make_vec3(&ov.x); -} + static inline glm::mat4 toGlm(const ovrPosef& op) { + glm::mat4 orientation = glm::mat4_cast(toGlm(op.Orientation)); + glm::mat4 translation = glm::translate(glm::mat4(), toGlm(op.Position)); + return translation * orientation; + } -inline glm::vec2 toGlm(const ovrVector2f & ov) { - return glm::make_vec2(&ov.x); -} + static inline ovrMatrix4f fromGlm(const glm::mat4& m) { + ovrMatrix4f result; + glm::mat4 transposed(glm::transpose(m)); + memcpy(result.M, &(transposed[0][0]), sizeof(float) * 16); + return result; + } -inline glm::uvec2 toGlm(const ovrSizei & ov) { - return glm::uvec2(ov.w, ov.h); -} + static inline ovrVector3f fromGlm(const glm::vec3& v) { + return { v.x, v.y, v.z }; + } -inline glm::quat toGlm(const ovrQuatf & oq) { - return glm::make_quat(&oq.x); -} + static inline ovrVector2f fromGlm(const glm::vec2& v) { + return { v.x, v.y }; + } -inline glm::mat4 toGlm(const ovrPosef & op) { - glm::mat4 orientation = glm::mat4_cast(toGlm(op.Orientation)); - glm::mat4 translation = glm::translate(glm::mat4(), toGlm(op.Position)); - return translation * orientation; -} + static inline ovrSizei fromGlm(const glm::uvec2& v) { + return { (int)v.x, (int)v.y }; + } -inline ovrMatrix4f ovrFromGlm(const glm::mat4 & m) { - ovrMatrix4f result; - glm::mat4 transposed(glm::transpose(m)); - memcpy(result.M, &(transposed[0][0]), sizeof(float) * 16); - return result; -} + static inline ovrQuatf fromGlm(const glm::quat& q) { + return { q.x, q.y, q.z, q.w }; + } -inline ovrVector3f ovrFromGlm(const glm::vec3 & v) { - return{ v.x, v.y, v.z }; -} + static inline ovrPosef poseFromGlm(const glm::mat4& m) { + glm::vec3 translation = glm::vec3(m[3]) / m[3].w; + glm::quat orientation = glm::quat_cast(m); + ovrPosef result; + result.Orientation = fromGlm(orientation); + result.Position = fromGlm(translation); + return result; + } -inline ovrVector2f ovrFromGlm(const glm::vec2 & v) { - return{ v.x, v.y }; -} + static controller::Pose toControllerPose(ovrHandType hand, const ovrPoseStatef& handPose); + static controller::Pose toControllerPose(ovrHandType hand, const ovrPoseStatef& handPose, const ovrPoseStatef& lastHandPose); -inline ovrSizei ovrFromGlm(const glm::uvec2 & v) { - return{ (int)v.x, (int)v.y }; -} +}; -inline ovrQuatf ovrFromGlm(const glm::quat & q) { - return{ q.x, q.y, q.z, q.w }; -} - -inline ovrPosef ovrPoseFromGlm(const glm::mat4 & m) { - glm::vec3 translation = glm::vec3(m[3]) / m[3].w; - glm::quat orientation = glm::quat_cast(m); - ovrPosef result; - result.Orientation = ovrFromGlm(orientation); - result.Position = ovrFromGlm(translation); - return result; -} - -controller::Pose ovrControllerPoseToHandPose( - ovrHandType hand, - const ovrPoseStatef& handPose); - -controller::Pose ovrControllerRotationToHandRotation(ovrHandType hand, - const ovrPoseStatef& handPose, const ovrPoseStatef& lastHandPose); +} // namespace hifi diff --git a/scripts/defaultScripts.js b/scripts/defaultScripts.js index b0cbf0e246..ddbeaaeea9 100644 --- a/scripts/defaultScripts.js +++ b/scripts/defaultScripts.js @@ -12,6 +12,7 @@ // var DEFAULT_SCRIPTS_COMBINED = [ + "system/request-service.js", "system/progress.js", "system/away.js", "system/audio.js", @@ -23,6 +24,7 @@ var DEFAULT_SCRIPTS_COMBINED = [ "system/makeUserConnection.js", "system/tablet-goto.js", "system/marketplaces/marketplaces.js", + "system/notifications.js", "system/commerce/wallet.js", "system/edit.js", "system/dialTone.js", @@ -31,7 +33,7 @@ var DEFAULT_SCRIPTS_COMBINED = [ "system/emote.js" ]; var DEFAULT_SCRIPTS_SEPARATE = [ - "system/controllers/controllerScripts.js" + "system/controllers/controllerScripts.js", //"system/chat.js" ]; @@ -56,7 +58,6 @@ if (Menu.menuExists(MENU_CATEGORY) && !Menu.menuItemExists(MENU_CATEGORY, MENU_I menuItemName: MENU_ITEM, isCheckable: true, isChecked: previousSetting, - grouping: "Advanced" }); } diff --git a/scripts/developer/facialExpressions.js b/scripts/developer/facialExpressions.js new file mode 100644 index 0000000000..37a4f4f796 --- /dev/null +++ b/scripts/developer/facialExpressions.js @@ -0,0 +1,374 @@ +// +// facialExpressions.js +// A script to set different emotions using blend shapes +// +// Author: Elisa Lupin-Jimenez +// Copyright High Fidelity 2018 +// +// Licensed under the Apache 2.0 License +// See accompanying license file or http://apache.org/ +// +// All assets are under CC Attribution Non-Commerical +// http://creativecommons.org/licenses/ +// + +(function() { + + var TABLET_BUTTON_NAME = "EMOTIONS"; + // TODO: ADD HTML LANDING PAGE + + var TRANSITION_TIME_SECONDS = 0.25; + + var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + var icon = "https://hifi-content.s3.amazonaws.com/elisalj/emoji_scripts/icons/emoji-i.svg"; + var activeIcon = "https://hifi-content.s3.amazonaws.com/elisalj/emoji_scripts/icons/emoji-a.svg"; + var isActive = true; + + var controllerMappingName; + var controllerMapping; + + var tabletButton = tablet.addButton({ + icon: icon, + activeIcon: activeIcon, + text: TABLET_BUTTON_NAME, + isActive: true + }); + + var toggle = function() { + isActive = !isActive; + tabletButton.editProperties({isActive: isActive}); + if (isActive) { + Controller.enableMapping(controllerMappingName); + } else { + setEmotion(DEFAULT); + Controller.disableMapping(controllerMappingName); + } + }; + + tabletButton.clicked.connect(toggle); + + var DEFAULT = { + "EyeOpen_L": 0.00, + "EyeOpen_R": 0.00, + "EyeBlink_L": 0.00, + "EyeBlink_R": 0.00, + "EyeSquint_L": 0.00, + "EyeSquint_R": 0.00, + "BrowsD_L": 0.00, + "BrowsD_R": 0.00, + "BrowsU_L": 0.00, + "BrowsU_C": 0.00, + "JawOpen": 0.00, + "JawFwd": 0.00, + "MouthFrown_L": 0.00, + "MouthFrown_R": 0.00, + "MouthSmile_L": 0.00, + "MouthSmile_R": 0.00, + "MouthDimple_L": 0.00, + "MouthDimple_R": 0.00, + "LipsUpperClose": 0.00, + "LipsLowerClose": 0.00, + "LipsLowerOpen": 0.00, + "ChinUpperRaise": 0.00, + "Sneer": 0.00, + "Puff": 0.00 + }; + + var SMILE = { + "EyeOpen_L": 0.00, + "EyeOpen_R": 0.00, + "EyeBlink_L": 0.30, + "EyeBlink_R": 0.30, + "EyeSquint_L": 0.90, + "EyeSquint_R": 0.90, + "BrowsD_L": 1.00, + "BrowsD_R": 1.00, + "BrowsU_L": 0.00, + "BrowsU_C": 0.00, + "JawOpen": 0.00, + "JawFwd": 0.00, + "MouthFrown_L": 0.00, + "MouthFrown_R": 0.00, + "MouthSmile_L": 1.00, + "MouthSmile_R": 1.00, + "MouthDimple_L": 1.00, + "MouthDimple_R": 1.00, + "LipsUpperClose": 0.40, + "LipsLowerClose": 0.30, + "LipsLowerOpen": 0.25, + "ChinUpperRaise": 0.35, + "Sneer": 0.00, + "Puff": 0.00 + }; + + var LAUGH = { + "EyeOpen_L": 0.00, + "EyeOpen_R": 0.00, + "EyeBlink_L": 0.45, + "EyeBlink_R": 0.45, + "EyeSquint_L": 0.75, + "EyeSquint_R": 0.75, + "BrowsD_L": 0.00, + "BrowsD_R": 0.00, + "BrowsU_L": 0.00, + "BrowsU_C": 0.50, + "JawOpen": 0.50, + "JawFwd": 0.00, + "MouthFrown_L": 0.00, + "MouthFrown_R": 0.00, + "MouthSmile_L": 1.00, + "MouthSmile_R": 1.00, + "MouthDimple_L": 1.00, + "MouthDimple_R": 1.00, + "LipsUpperClose": 0.00, + "LipsLowerClose": 0.00, + "LipsLowerOpen": 0.00, + "ChinUpperRaise": 0.30, + "Sneer": 1.00, + "Puff": 0.30 + }; + + var FLIRT = { + "EyeOpen_L": 0.00, + "EyeOpen_R": 0.00, + "EyeBlink_L": 0.50, + "EyeBlink_R": 0.50, + "EyeSquint_L": 0.25, + "EyeSquint_R": 0.25, + "BrowsD_L": 0.00, + "BrowsD_R": 1.00, + "BrowsU_L": 0.55, + "BrowsU_C": 0.00, + "JawOpen": 0.00, + "JawFwd": 0.00, + "MouthFrown_L": 0.00, + "MouthFrown_R": 0.00, + "MouthSmile_L": 0.50, + "MouthSmile_R": 0.00, + "MouthDimple_L": 1.00, + "MouthDimple_R": 1.00, + "LipsUpperClose": 0.00, + "LipsLowerClose": 0.00, + "LipsLowerOpen": 0.00, + "ChinUpperRaise": 0.00, + "Sneer": 0.00, + "Puff": 0.00 + }; + + var SAD = { + "EyeOpen_L": 0.00, + "EyeOpen_R": 0.00, + "EyeBlink_L": 0.30, + "EyeBlink_R": 0.30, + "EyeSquint_L": 0.30, + "EyeSquint_R": 0.30, + "BrowsD_L": 0.00, + "BrowsD_R": 0.00, + "BrowsU_L": 0.00, + "BrowsU_C": 0.50, + "JawOpen": 0.00, + "JawFwd": 0.80, + "MouthFrown_L": 0.80, + "MouthFrown_R": 0.80, + "MouthSmile_L": 0.00, + "MouthSmile_R": 0.00, + "MouthDimple_L": 0.00, + "MouthDimple_R": 0.00, + "LipsUpperClose": 0.00, + "LipsLowerClose": 0.50, + "LipsLowerOpen": 0.00, + "ChinUpperRaise": 0.00, + "Sneer": 0.00, + "Puff": 0.00 + }; + + var ANGRY = { + "EyeOpen_L": 1.00, + "EyeOpen_R": 1.00, + "EyeBlink_L": 0.00, + "EyeBlink_R": 0.00, + "EyeSquint_L": 1.00, + "EyeSquint_R": 1.00, + "BrowsD_L": 1.00, + "BrowsD_R": 1.00, + "BrowsU_L": 0.00, + "BrowsU_C": 0.00, + "JawOpen": 0.00, + "JawFwd": 0.00, + "MouthFrown_L": 0.50, + "MouthFrown_R": 0.50, + "MouthSmile_L": 0.00, + "MouthSmile_R": 0.00, + "MouthDimple_L": 0.00, + "MouthDimple_R": 0.00, + "LipsUpperClose": 0.50, + "LipsLowerClose": 0.50, + "LipsLowerOpen": 0.00, + "ChinUpperRaise": 0.00, + "Sneer": 0.50, + "Puff": 0.00 + }; + + var FEAR = { + "EyeOpen_L": 1.00, + "EyeOpen_R": 1.00, + "EyeBlink_L": 0.00, + "EyeBlink_R": 0.00, + "EyeSquint_L": 0.00, + "EyeSquint_R": 0.00, + "BrowsD_L": 0.00, + "BrowsD_R": 0.00, + "BrowsU_L": 0.00, + "BrowsU_C": 1.00, + "JawOpen": 0.15, + "JawFwd": 0.00, + "MouthFrown_L": 0.30, + "MouthFrown_R": 0.30, + "MouthSmile_L": 0.00, + "MouthSmile_R": 0.00, + "MouthDimple_L": 0.00, + "MouthDimple_R": 0.00, + "LipsUpperClose": 0.00, + "LipsLowerClose": 0.00, + "LipsLowerOpen": 0.00, + "ChinUpperRaise": 0.00, + "Sneer": 0.00, + "Puff": 0.00 + }; + + var DISGUST = { + "EyeOpen_L": 0.00, + "EyeOpen_R": 0.00, + "EyeBlink_L": 0.25, + "EyeBlink_R": 0.25, + "EyeSquint_L": 1.00, + "EyeSquint_R": 1.00, + "BrowsD_L": 1.00, + "BrowsD_R": 1.00, + "BrowsU_L": 0.00, + "BrowsU_C": 0.00, + "JawOpen": 0.00, + "JawFwd": 0.00, + "MouthFrown_L": 1.00, + "MouthFrown_R": 1.00, + "MouthSmile_L": 0.00, + "MouthSmile_R": 0.00, + "MouthDimple_L": 0.00, + "MouthDimple_R": 0.00, + "LipsUpperClose": 0.00, + "LipsLowerClose": 0.75, + "LipsLowerOpen": 0.00, + "ChinUpperRaise": 0.75, + "Sneer": 1.00, + "Puff": 0.00 + }; + + + function mixValue(valueA, valueB, percentage) { + return valueA + ((valueB - valueA) * percentage); + } + + var lastEmotionUsed = DEFAULT; + var emotion = DEFAULT; + var isChangingEmotion = false; + var changingEmotionPercentage = 0.0; + + Script.update.connect(function(deltaTime) { + if (!isChangingEmotion) { + return; + } + changingEmotionPercentage += deltaTime / TRANSITION_TIME_SECONDS; + if (changingEmotionPercentage >= 1.0) { + changingEmotionPercentage = 1.0; + isChangingEmotion = false; + if (emotion === DEFAULT) { + MyAvatar.hasScriptedBlendshapes = false; + } + } + for (var blendshape in emotion) { + MyAvatar.setBlendshape(blendshape, + mixValue(lastEmotionUsed[blendshape], emotion[blendshape], changingEmotionPercentage)); + } + }); + + function setEmotion(currentEmotion) { + if (emotion !== lastEmotionUsed) { + lastEmotionUsed = emotion; + } + if (currentEmotion !== lastEmotionUsed) { + changingEmotionPercentage = 0.0; + emotion = currentEmotion; + isChangingEmotion = true; + MyAvatar.hasScriptedBlendshapes = true; + } + } + + + controllerMappingName = 'Hifi-FacialExpressions-Mapping'; + controllerMapping = Controller.newMapping(controllerMappingName); + + controllerMapping.from(Controller.Hardware.Keyboard.H).to(function(value) { + if (value !== 0) { + setEmotion(SMILE); + } + }); + + controllerMapping.from(Controller.Hardware.Keyboard.J).to(function(value) { + if (value !== 0) { + setEmotion(LAUGH); + } + }); + + controllerMapping.from(Controller.Hardware.Keyboard.K).to(function(value) { + if (value !== 0) { + setEmotion(FLIRT); + } + }); + + controllerMapping.from(Controller.Hardware.Keyboard.L).to(function(value) { + if (value !== 0) { + setEmotion(SAD); + } + }); + + controllerMapping.from(Controller.Hardware.Keyboard.V).to(function(value) { + if (value !== 0) { + setEmotion(ANGRY); + } + }); + + controllerMapping.from(Controller.Hardware.Keyboard.B).to(function(value) { + if (value !== 0) { + setEmotion(FEAR); + } + }); + + controllerMapping.from(Controller.Hardware.Keyboard.M).to(function(value) { + if (value !== 0) { + setEmotion(DISGUST); + } + }); + + controllerMapping.from(Controller.Hardware.Keyboard.N).to(function(value) { + if (value !== 0) { + setEmotion(DEFAULT); + } + }); + + Controller.enableMapping(controllerMappingName); + + Script.scriptEnding.connect(function() { + tabletButton.clicked.disconnect(toggle); + tablet.removeButton(tabletButton); + Controller.disableMapping(controllerMappingName); + + if (emotion !== DEFAULT || isChangingEmotion) { + isChangingEmotion = false; + for (var blendshape in DEFAULT) { + MyAvatar.setBlendshape(blendshape, DEFAULT[blendshape]); + } + MyAvatar.hasScriptedBlendshapes = false; + } + }); + +}()); \ No newline at end of file diff --git a/scripts/developer/tests/ControlsGallery.qml b/scripts/developer/tests/ControlsGallery.qml new file mode 100644 index 0000000000..ceb8a26dc9 --- /dev/null +++ b/scripts/developer/tests/ControlsGallery.qml @@ -0,0 +1,103 @@ +import QtQuick 2.10 +import QtQuick.Window 2.10 +import QtQuick.Controls 2.2 +import QtQuick.Layouts 1.3 +import "qrc:////qml//styles-uit" as HifiStylesUit +import "qrc:////qml//controls-uit" as HifiControlsUit + +//uncomment to use from qmlscratch tool +//import '../../../interface/resources/qml/controls-uit' as HifiControlsUit +//import '../../../interface/resources/qml/styles-uit' + +//uncomment to use with HIFI_USE_SOURCE_TREE_RESOURCES=1 +//import '../../../resources/qml/controls-uit' as HifiControlsUit +//import '../../../resources/qml/styles-uit' + +Item { + visible: true + width: 640 + height: 480 + + Introspector { + id: introspector + properties: ['realFrom', 'realTo', 'realValue', 'realStepSize', 'decimals'] + visible: true + y: 50 + x: 130 + } + + HifiStylesUit.HifiConstants { + id: hifi + } + + TabBar { + id: bar + width: parent.width + TabButton { + text: "Spinbox" + } + TabButton { + text: "... Other Controls" + } + } + + StackLayout { + id: controlsLayout + currentIndex: bar.currentIndex + anchors.top: bar.bottom + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right + anchors.margins: 20 + + Item { + id: spinboxTab + anchors.fill: parent + + Column { + spacing: 20 + + HifiControlsUit.SpinBox { + realValue: 5.0 + realFrom: 16.0 + realTo: 20.0 + decimals: 2 + realStepSize: 0.01 + + width: 100 + height: 30 + + colorScheme: hifi.colorSchemes.dark + + onFocusChanged: { + if(focus) { + introspector.object = this + } + } + } + + HifiControlsUit.SpinBox { + realValue: 5.0 + realFrom: 1.0 + realTo: 20.0 + decimals: 2 + realStepSize: 0.01 + + width: 100 + height: 30 + + colorScheme: hifi.colorSchemes.light + + onFocusChanged: { + if(focus) { + introspector.object = this + } + } + } + } + } + Item { + id: otherTab + } + } +} diff --git a/scripts/developer/tests/Introspector.qml b/scripts/developer/tests/Introspector.qml new file mode 100644 index 0000000000..d21f5da976 --- /dev/null +++ b/scripts/developer/tests/Introspector.qml @@ -0,0 +1,166 @@ +import QtQuick 2.1; +import QtQuick.Window 2.1; + +MouseArea { + id: base; + opacity: 0.65; + // anchors.fill: parent; + width: 400; + height: 300; + + drag.target: list; + onWheel: { } + + onClicked: { object = null } + property var object: null + onObjectChanged: { + visible = (object != null) + } + + property var properties: [] + onPropertiesChanged: { + console.debug('properties: ', JSON.stringify(properties, 4, 0)) + } + + function getPropertiesList(obj) { + var props = []; + var propertiesObject = obj; + if(properties.length !== 0) { + propertiesObject = {}; + for(var i = 0; i < properties.length; ++i) { + propertiesObject[properties[i]] = properties[i]; + } + } + + for(var prop in propertiesObject) { + + var info = {'name' : prop}; + var value = obj[prop]; + var typeOfValue = typeof(value); + + if(typeof(value) === 'string') { + info['type'] = 'string' + } else if(typeof(value) === 'number') { + if(Number.isInteger(value)) + info['type'] = 'int' + else + info['type'] = 'float' + } else if(typeof(value) === 'boolean') { + info['type'] = 'boolean' + } else if(typeof(value) === 'function') { + continue; + } + + /* + if(prop !== 'parent' && prop !== 'data' && prop !== 'children') + console.debug('typeof(value): ', typeof(value), JSON.stringify(value, null, 4)); + */ + + info['subName'] = '' + props.push(info); + } + + return props; + } + + Rectangle { + color: "lightgray"; + anchors.fill: list; + anchors.margins: -50; + } + ListView { + id: list; + x: 50; + y: 50; + width: 400; + height: 300; + spacing: 5; + model: object !== null ? getPropertiesList(object) : []; + header: Text { + text: object !== null ? object.toString () : ''; + font.bold: true; + font.pixelSize: 20; + } + delegate: Row { + spacing: 20; + + Column { + width: 180; + + Text { + text: (modelData ["subName"] !== "" ? (modelData ["name"] + "." + modelData ["subName"]) : modelData ["name"]); + font.pixelSize: 16; + } + } + Column { + width: 200; + + Text { + text: { + return modelData ["type"] + } + font.pixelSize: 10; + } + TextInput { + id: input; + text: display; + width: parent.width; + font.pixelSize: 16; + font.underline: (text !== display); + Keys.onReturnPressed: { save (); } + Keys.onEnterPressed: { save (); } + Keys.onEscapePressed: { cancel (); } + + property string display : ""; + + function save () { + var tmp; + switch (modelData ["type"]) { + case 'boolean': + tmp = (text === "true" || text === "1"); + break; + case 'float': + tmp = parseFloat (text); + break; + case 'int': + tmp = parseInt (text); + break; + case 'string': + tmp = text; + break; + + default: + break; + } + if (modelData ["subName"] !== "") { + object [modelData ["name"]][modelData ["subName"]] = tmp; + } + else { + object [modelData ["name"]] = tmp; + } + text = display; + } + + function cancel () { + text = display; + } + + Binding on text { value: input.display; } + Binding on display { + value: { + var ret = (modelData ["subName"] !== "" + ? object [modelData ["name"]][modelData ["subName"]] + : object [modelData ["name"]]); + return ret.toString (); + } + } + Rectangle { + z: -1; + color: "white"; + anchors.fill: parent; + } + } + } + } + } +} diff --git a/scripts/developer/tests/controlsGallery.js b/scripts/developer/tests/controlsGallery.js new file mode 100644 index 0000000000..dc3fa7ba3c --- /dev/null +++ b/scripts/developer/tests/controlsGallery.js @@ -0,0 +1,23 @@ +(function() { // BEGIN LOCAL_SCOPE + + console.debug('controlsGallery: creating window') + + var qml = Script.resolvePath('ControlsGallery.qml'); + var qmlWindow = new OverlayWindow({ + title: 'Hifi Controls Gallery', + source: qml, + height: 480, + width: 640, + visible: true + }); + + console.debug('controlsGallery: creating window... done') + + qmlWindow.closed.connect(function() { Script.stop(); }); + + Script.scriptEnding.connect(function() { + console.debug('controlsGallery: end of scripting') + delete qmlWindow; + }); + +}()); // END LOCAL_SCOPE diff --git a/scripts/system/+android/radar.js b/scripts/system/+android/radar.js index 5d93ed4db1..1cbe721ad0 100644 --- a/scripts/system/+android/radar.js +++ b/scripts/system/+android/radar.js @@ -1119,7 +1119,7 @@ function startRadar() { function endRadar() { printd("-- endRadar"); - Camera.mode = "first person"; + Camera.mode = "third person"; radar = false; Controller.setVPadEnabled(true); diff --git a/scripts/system/assets/animations/Cheering.fbx b/scripts/system/assets/animations/Cheer.fbx similarity index 100% rename from scripts/system/assets/animations/Cheering.fbx rename to scripts/system/assets/animations/Cheer.fbx diff --git a/scripts/system/assets/animations/Clapping.fbx b/scripts/system/assets/animations/Clap.fbx similarity index 100% rename from scripts/system/assets/animations/Clapping.fbx rename to scripts/system/assets/animations/Clap.fbx diff --git a/scripts/system/assets/animations/Crying.fbx b/scripts/system/assets/animations/Cry.fbx similarity index 67% rename from scripts/system/assets/animations/Crying.fbx rename to scripts/system/assets/animations/Cry.fbx index 2e60ba2450..7ecd6415a5 100644 Binary files a/scripts/system/assets/animations/Crying.fbx and b/scripts/system/assets/animations/Cry.fbx differ diff --git a/scripts/system/assets/animations/Dancing.fbx b/scripts/system/assets/animations/Dance.fbx similarity index 100% rename from scripts/system/assets/animations/Dancing.fbx rename to scripts/system/assets/animations/Dance.fbx diff --git a/scripts/system/assets/animations/Love.fbx b/scripts/system/assets/animations/Love.fbx new file mode 100644 index 0000000000..159ccafd04 Binary files /dev/null and b/scripts/system/assets/animations/Love.fbx differ diff --git a/scripts/system/assets/animations/Pointing.fbx b/scripts/system/assets/animations/Point.fbx similarity index 100% rename from scripts/system/assets/animations/Pointing.fbx rename to scripts/system/assets/animations/Point.fbx diff --git a/scripts/system/assets/animations/Sit1.fbx b/scripts/system/assets/animations/Sit1.fbx new file mode 100644 index 0000000000..db75219980 Binary files /dev/null and b/scripts/system/assets/animations/Sit1.fbx differ diff --git a/scripts/system/assets/animations/Sit2.fbx b/scripts/system/assets/animations/Sit2.fbx new file mode 100644 index 0000000000..400b599794 Binary files /dev/null and b/scripts/system/assets/animations/Sit2.fbx differ diff --git a/scripts/system/assets/animations/Sit3.fbx b/scripts/system/assets/animations/Sit3.fbx new file mode 100644 index 0000000000..174fd75c4e Binary files /dev/null and b/scripts/system/assets/animations/Sit3.fbx differ diff --git a/scripts/system/assets/animations/Waving.fbx b/scripts/system/assets/animations/Wave.fbx similarity index 100% rename from scripts/system/assets/animations/Waving.fbx rename to scripts/system/assets/animations/Wave.fbx diff --git a/scripts/system/commerce/wallet.js b/scripts/system/commerce/wallet.js index aea752c565..ac25269e41 100644 --- a/scripts/system/commerce/wallet.js +++ b/scripts/system/commerce/wallet.js @@ -511,6 +511,9 @@ case 'wallet_availableUpdatesReceived': // NOP break; + case 'http.request': + // Handled elsewhere, don't log. + break; default: print('Unrecognized message from QML:', JSON.stringify(message)); } diff --git a/scripts/system/controllers/controllerModules/equipEntity.js b/scripts/system/controllers/controllerModules/equipEntity.js index 08b88fe74d..91c8d89daf 100644 --- a/scripts/system/controllers/controllerModules/equipEntity.js +++ b/scripts/system/controllers/controllerModules/equipEntity.js @@ -273,7 +273,6 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa this.shouldSendStart = false; this.equipedWithSecondary = false; this.handHasBeenRightsideUp = false; - this.mouseEquip = false; this.parameters = makeDispatcherModuleParameters( 300, @@ -283,11 +282,10 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa var equipHotspotBuddy = new EquipHotspotBuddy(); - this.setMessageGrabData = function(entityProperties, mouseEquip) { + this.setMessageGrabData = function(entityProperties) { if (entityProperties) { this.messageGrabEntity = true; this.grabEntityProps = entityProperties; - this.mouseEquip = mouseEquip; } }; @@ -584,7 +582,6 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa this.targetEntityID = null; this.messageGrabEntity = false; this.grabEntityProps = null; - this.mouseEquip = false; }; this.updateInputs = function (controllerData) { @@ -630,14 +627,12 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa // if the potentialHotspot is cloneable, clone it and return it // if the potentialHotspot os not cloneable and locked return null - if (potentialEquipHotspot && (((this.triggerSmoothedSqueezed() || this.secondarySmoothedSqueezed()) && !this.waitForTriggerRelease) || this.messageGrabEntity)) { this.grabbedHotspot = potentialEquipHotspot; this.targetEntityID = this.grabbedHotspot.entityID; this.startEquipEntity(controllerData); - this.messageGrabEntity = false; this.equipedWithSecondary = this.secondarySmoothedSqueezed(); return makeRunningValues(true, [potentialEquipHotspot.entityID], []); } else { @@ -661,7 +656,7 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa var timestamp = Date.now(); this.updateInputs(controllerData); - if (!this.mouseEquip && !this.isTargetIDValid(controllerData)) { + if (!this.messageGrabEntity && !this.isTargetIDValid(controllerData)) { this.endEquipEntity(); return makeRunningValues(false, [], []); } @@ -762,9 +757,7 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa var equipModule = (data.hand === "left") ? leftEquipEntity : rightEquipEntity; var entityProperties = Entities.getEntityProperties(data.entityID, DISPATCHER_PROPERTIES); entityProperties.id = data.entityID; - var mouseEquip = false; - equipModule.setMessageGrabData(entityProperties, mouseEquip); - + equipModule.setMessageGrabData(entityProperties); } catch (e) { print("WARNING: equipEntity.js -- error parsing Hifi-Hand-Grab message: " + message); } @@ -812,15 +805,14 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa var distanceToLeftHand = Vec3.distance(entityProperties.position, leftHandPosition); var leftHandAvailable = leftEquipEntity.targetEntityID === null; var rightHandAvailable = rightEquipEntity.targetEntityID === null; - var mouseEquip = true; if (rightHandAvailable && (distanceToRightHand < distanceToLeftHand || !leftHandAvailable)) { // clear any existing grab actions on the entity now (their later removal could affect bootstrapping flags) clearGrabActions(entityID); - rightEquipEntity.setMessageGrabData(entityProperties, mouseEquip); + rightEquipEntity.setMessageGrabData(entityProperties); } else if (leftHandAvailable && (distanceToLeftHand < distanceToRightHand || !rightHandAvailable)) { // clear any existing grab actions on the entity now (their later removal could affect bootstrapping flags) clearGrabActions(entityID); - leftEquipEntity.setMessageGrabData(entityProperties, mouseEquip); + leftEquipEntity.setMessageGrabData(entityProperties); } } } diff --git a/scripts/system/controllers/controllerModules/farActionGrabEntity.js b/scripts/system/controllers/controllerModules/farActionGrabEntity.js index 66cd197abd..e4563fda14 100644 --- a/scripts/system/controllers/controllerModules/farActionGrabEntity.js +++ b/scripts/system/controllers/controllerModules/farActionGrabEntity.js @@ -282,7 +282,7 @@ Script.include("/~/system/libraries/Xform.js"); this.previousRoomControllerPosition = roomControllerPosition; }; - this.endNearGrabAction = function () { + this.endFarGrabAction = function () { ensureDynamic(this.grabbedThingID); this.distanceHolding = false; this.distanceRotating = false; @@ -402,7 +402,7 @@ Script.include("/~/system/libraries/Xform.js"); this.run = function (controllerData) { if (controllerData.triggerValues[this.hand] < TRIGGER_OFF_VALUE || this.notPointingAtEntity(controllerData) || this.targetIsNull()) { - this.endNearGrabAction(); + this.endFarGrabAction(); Selection.removeFromSelectedItemsList(DISPATCHER_HOVERING_LIST, "entity", this.highlightedEntity); this.highlightedEntity = null; @@ -430,11 +430,12 @@ Script.include("/~/system/libraries/Xform.js"); } if (this.actionID) { - // if we are doing a distance grab and the object gets close enough to the controller, + // if we are doing a distance grab and the object or tablet gets close enough to the controller, // stop the far-grab so the near-grab or equip can take over. for (var k = 0; k < nearGrabReadiness.length; k++) { - if (nearGrabReadiness[k].active && nearGrabReadiness[k].targets[0] === this.grabbedThingID) { - this.endNearGrabAction(); + if (nearGrabReadiness[k].active && (nearGrabReadiness[k].targets[0] === this.grabbedThingID + || HMD.tabletID && nearGrabReadiness[k].targets[0] === HMD.tabletID)) { + this.endFarGrabAction(); return makeRunningValues(false, [], []); } } @@ -445,7 +446,7 @@ Script.include("/~/system/libraries/Xform.js"); // where it could near-grab something, stop searching. for (var j = 0; j < nearGrabReadiness.length; j++) { if (nearGrabReadiness[j].active) { - this.endNearGrabAction(); + this.endFarGrabAction(); return makeRunningValues(false, [], []); } } @@ -577,7 +578,7 @@ Script.include("/~/system/libraries/Xform.js"); var disableModule = getEnabledModuleByName(moduleName); if (disableModule) { if (disableModule.disableModules) { - this.endNearGrabAction(); + this.endFarGrabAction(); Selection.removeFromSelectedItemsList(DISPATCHER_HOVERING_LIST, "entity", this.highlightedEntity); this.highlightedEntity = null; diff --git a/scripts/system/controllers/controllerScripts.js b/scripts/system/controllers/controllerScripts.js index 50e627fe5d..975ec3fc22 100644 --- a/scripts/system/controllers/controllerScripts.js +++ b/scripts/system/controllers/controllerScripts.js @@ -14,6 +14,7 @@ var CONTOLLER_SCRIPTS = [ "controllerDisplayManager.js", "grab.js", "toggleAdvancedMovementForHandControllers.js", + "handTouch.js", "controllerDispatcher.js", "controllerModules/nearParentGrabEntity.js", "controllerModules/nearParentGrabOverlay.js", diff --git a/scripts/system/controllers/handTouch.js b/scripts/system/controllers/handTouch.js index 345ba7a1d7..c20b86b775 100644 --- a/scripts/system/controllers/handTouch.js +++ b/scripts/system/controllers/handTouch.js @@ -14,29 +14,29 @@ /* global Script, Overlays, Controller, Vec3, MyAvatar, Entities */ -(function(){ +(function() { + + var MSECONDS_AFTER_LOAD = 2000; var updateFingerWithIndex = 0; + // Keys to access finger data var fingerKeys = ["pinky", "ring", "middle", "index", "thumb"]; // Additionally close the hands to achieve a grabbing effect - var grabPercent = { left: 0, - right: 0 }; - - // var isGrabbing = false; + var grabPercent = { left: 0, right: 0 }; var Palm = function() { - this.position = {x:0, y:0, z:0}; - this.perpendicular = {x:0, y:0, z:0}; + this.position = {x: 0, y: 0, z: 0}; + this.perpendicular = {x: 0, y: 0, z: 0}; this.distance = 0; this.fingers = { - pinky: {x:0, y:0, z:0}, - middle: {x:0, y:0, z:0}, - ring: {x:0, y:0, z:0}, - thumb: {x:0, y:0, z:0}, - index: {x:0, y:0, z:0} + pinky: {x: 0, y: 0, z: 0}, + middle: {x: 0, y: 0, z: 0}, + ring: {x: 0, y: 0, z: 0}, + thumb: {x: 0, y: 0, z: 0}, + index: {x: 0, y: 0, z: 0} }; this.set = false; }; @@ -72,52 +72,132 @@ right: 0 }; - // joint data for opened pose - + // joint data for open pose var dataOpen = { left: { - pinky:[{x: -0.0066, y:-0.0224, z:-0.2174, w:0.9758},{x: 0.0112, y:0.0001, z:0.0093, w:0.9999},{x: -0.0346, y:0.0003, z:-0.0073, w:0.9994}], - ring:[{x: -0.0029, y:-0.0094, z:-0.1413, w:0.9899},{x: 0.0112, y:0.0001, z:0.0059, w:0.9999},{x: -0.0346, y:0.0002, z:-0.006, w:0.9994}], - middle:[{x: -0.0016, y:0, z:-0.0286, w:0.9996},{x: 0.0112, y:-0.0001, z:-0.0063, w:0.9999},{x: -0.0346, y:-0.0003, z:0.0073, w:0.9994}], - index:[{x: -0.0016, y:0.0001, z:0.0199, w:0.9998},{x: 0.0112, y:0, z:0.0081, w:0.9999},{x: -0.0346, y:0.0008, z:-0.023, w:0.9991}], - thumb:[{x: 0.0354, y:0.0363, z:0.3275, w:0.9435},{x: -0.0945, y:0.0938, z:0.0995, w:0.9861},{x: -0.0952, y:0.0718, z:0.1382, w:0.9832}] + pinky: [ + {x: -0.0066, y: -0.0224, z: -0.2174, w: 0.9758}, + {x: 0.0112, y: 0.0001, z: 0.0093, w: 0.9999}, + {x: -0.0346, y: 0.0003, z: -0.0073, w: 0.9994} + ], + ring: [ + {x: -0.0029, y: -0.0094, z: -0.1413, w: 0.9899}, + {x: 0.0112, y: 0.0001, z: 0.0059, w: 0.9999}, + {x: -0.0346, y: 0.0002, z: -0.006, w: 0.9994} + ], + middle: [ + {x: -0.0016, y: 0, z: -0.0286, w: 0.9996}, + {x: 0.0112, y: -0.0001, z: -0.0063, w: 0.9999}, + {x: -0.0346, y: -0.0003, z: 0.0073, w: 0.9994} + ], + index: [ + {x: -0.0016, y: 0.0001, z: 0.0199, w: 0.9998}, + {x: 0.0112, y: 0, z: 0.0081, w: 0.9999}, + {x: -0.0346, y: 0.0008, z: -0.023, w: 0.9991} + ], + thumb: [ + {x: 0.0354, y: 0.0363, z: 0.3275, w: 0.9435}, + {x: -0.0945, y: 0.0938, z: 0.0995, w: 0.9861}, + {x: -0.0952, y: 0.0718, z: 0.1382, w: 0.9832} + ] }, right: { - pinky:[{x: -0.0034, y:0.023, z:0.1051, w:0.9942},{x: 0.0106, y:-0.0001, z:-0.0091, w:0.9999},{x: -0.0346, y:-0.0003, z:0.0075, w:0.9994}], - ring:[{x: -0.0013, y:0.0097, z:0.0311, w:0.9995},{x: 0.0106, y:-0.0001, z:-0.0056, w:0.9999},{x: -0.0346, y:-0.0002, z:0.0061, w:0.9994}], - middle:[{x: -0.001, y:0, z:0.0285, w:0.9996},{x: 0.0106, y:0.0001, z:0.0062, w:0.9999},{x: -0.0346, y:0.0003, z:-0.0074, w:0.9994}], - index:[{x: -0.001, y:0, z:-0.0199, w:0.9998},{x: 0.0106, y:-0.0001, z:-0.0079, w:0.9999},{x: -0.0346, y:-0.0008, z:0.0229, w:0.9991}], - thumb:[{x: 0.0355, y:-0.0363, z:-0.3263, w:0.9439},{x: -0.0946, y:-0.0938, z:-0.0996, w:0.9861},{x: -0.0952, y:-0.0719, z:-0.1376, w:0.9833}] + pinky: [ + {x: -0.0034, y: 0.023, z: 0.1051, w: 0.9942}, + {x: 0.0106, y: -0.0001, z: -0.0091, w: 0.9999}, + {x: -0.0346, y: -0.0003, z: 0.0075, w: 0.9994} + ], + ring: [ + {x: -0.0013, y: 0.0097, z: 0.0311, w: 0.9995}, + {x: 0.0106, y: -0.0001, z: -0.0056, w: 0.9999}, + {x: -0.0346, y: -0.0002, z: 0.0061, w: 0.9994} + ], + middle: [ + {x: -0.001, y: 0, z: 0.0285, w: 0.9996}, + {x: 0.0106, y: 0.0001, z: 0.0062, w: 0.9999}, + {x: -0.0346, y: 0.0003, z: -0.0074, w: 0.9994} + ], + index: [ + {x: -0.001, y: 0, z: -0.0199, w: 0.9998}, + {x: 0.0106, y: -0.0001, z: -0.0079, w: 0.9999}, + {x: -0.0346, y: -0.0008, z: 0.0229, w: 0.9991} + ], + thumb: [ + {x: 0.0355, y: -0.0363, z: -0.3263, w: 0.9439}, + {x: -0.0946, y: -0.0938, z: -0.0996, w: 0.9861}, + {x: -0.0952, y: -0.0719, z: -0.1376, w: 0.9833} + ] } }; + + // joint data for close pose var dataClose = { left: { - pinky:[{x: 0.5878, y:-0.1735, z:-0.1123, w:0.7821},{x: 0.5704, y:0.0053, z:0.0076, w:0.8213},{x: 0.6069, y:-0.0044, z:-0.0058, w:0.7947}], - ring:[{x: 0.5761, y:-0.0989, z:-0.1025, w:0.8048},{x: 0.5332, y:0.0032, z:0.005, w:0.846},{x: 0.5773, y:-0.0035, z:-0.0049, w:0.8165}], - middle:[{x: 0.543, y:-0.0469, z:-0.0333, w:0.8378},{x: 0.5419, y:-0.0034, z:-0.0053, w:0.8404},{x: 0.5015, y:0.0037, z:0.0063, w:0.8651}], - index:[{x: 0.3051, y:-0.0156, z:-0.014, w:0.9521},{x: 0.6414, y:0.0051, z:0.0063, w:0.7671},{x: 0.5646, y:-0.013, z:-0.019, w:0.8251}], - thumb:[{x: 0.313, y:-0.0348, z:0.3192, w:0.8938},{x: 0, y:0, z:-0.37, w:0.929},{x: 0, y:0, z:-0.2604, w:0.9655}] + pinky: [ + {x: 0.5878, y: -0.1735, z: -0.1123, w: 0.7821}, + {x: 0.5704, y: 0.0053, z: 0.0076, w: 0.8213}, + {x: 0.6069, y: -0.0044, z: -0.0058, w: 0.7947} + ], + ring: [ + {x: 0.5761, y: -0.0989, z: -0.1025, w: 0.8048}, + {x: 0.5332, y: 0.0032, z: 0.005, w: 0.846}, + {x: 0.5773, y: -0.0035, z: -0.0049, w: 0.8165} + ], + middle: [ + {x: 0.543, y: -0.0469, z: -0.0333, w: 0.8378}, + {x: 0.5419, y: -0.0034, z: -0.0053, w: 0.8404}, + {x: 0.5015, y: 0.0037, z: 0.0063, w: 0.8651} + ], + index: [ + {x: 0.3051, y: -0.0156, z: -0.014, w: 0.9521}, + {x: 0.6414, y: 0.0051, z: 0.0063, w: 0.7671}, + {x: 0.5646, y: -0.013, z: -0.019, w: 0.8251} + ], + thumb: [ + {x: 0.313, y: -0.0348, z: 0.3192, w: 0.8938}, + {x: 0, y: 0, z: -0.37, w: 0.929}, + {x: 0, y: 0, z: -0.2604, w: 0.9655} + ] }, right: { - pinky:[{x: 0.5881, y:0.1728, z:0.1114, w:0.7823},{x: 0.5704, y:-0.0052, z:-0.0075, w:0.8213},{x: 0.6069, y:0.0046, z:0.006, w:0.7947}], - ring:[{x: 0.5729, y:0.1181, z:0.0898, w:0.8061},{x: 0.5332, y:-0.003, z:-0.0048, w:0.846},{x: 0.5773, y:0.0035, z:0.005, w:0.8165}], - middle:[{x: 0.543, y:0.0468, z:0.0332, w:0.8378},{x: 0.5419, y:0.0034, z:0.0052, w:0.8404},{x: 0.5047, y:-0.0037, z:-0.0064, w:0.8632}], - index:[{x: 0.306, y:-0.0076, z:-0.0584, w:0.9502},{x: 0.6409, y:-0.005, z:-0.006, w:0.7675},{x: 0.5646, y:0.0129, z:0.0189, w:0.8251}], - thumb:[{x: 0.313, y:0.0352, z:-0.3181, w:0.8942},{x: 0, y:0, z:0.3698, w:0.9291},{x: 0, y:0, z:0.2609, w:0.9654}] + pinky: [ + {x: 0.5881, y: 0.1728, z: 0.1114, w: 0.7823}, + {x: 0.5704, y: -0.0052, z: -0.0075, w: 0.8213}, + {x: 0.6069, y: 0.0046, z: 0.006, w: 0.7947} + ], + ring: [ + {x: 0.5729, y: 0.1181, z: 0.0898, w: 0.8061}, + {x: 0.5332, y: -0.003, z: -0.0048, w: 0.846}, + {x: 0.5773, y: 0.0035, z: 0.005, w: 0.8165} + ], + middle: [ + {x: 0.543, y: 0.0468, z: 0.0332, w: 0.8378}, + {x: 0.5419, y: 0.0034, z: 0.0052, w: 0.8404}, + {x: 0.5047, y: -0.0037, z: -0.0064, w: 0.8632} + ], + index: [ + {x: 0.306, y: -0.0076, z: -0.0584, w: 0.9502}, + {x: 0.6409, y: -0.005, z: -0.006, w: 0.7675}, + {x: 0.5646, y: 0.0129, z: 0.0189, w: 0.8251} + ], + thumb: [ + {x: 0.313, y: 0.0352, z: -0.3181, w: 0.8942}, + {x: 0, y: 0, z: 0.3698, w: 0.9291}, + {x: 0, y: 0, z: 0.2609, w: 0.9654} + ] } }; // snapshot for the default pose - var dataDefault = { - left:{ - pinky:[{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], + left: { + pinky: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], middle: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], ring: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], thumb: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], index: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], set: false }, - right:{ - pinky:[{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], + right: { + pinky: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], middle: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], ring: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], thumb: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], @@ -127,17 +207,16 @@ }; // joint data for the current frame - var dataCurrent = { - left:{ - pinky:[{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], + left: { + pinky: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], middle: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], ring: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], thumb: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], index: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}] }, - right:{ - pinky:[{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], + right: { + pinky: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], middle: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], ring: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], thumb: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], @@ -146,17 +225,16 @@ }; // interpolated values on joint data to smooth movement - var dataDelta = { - left:{ - pinky:[{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], + left: { + pinky: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], middle: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], ring: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], thumb: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], index: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}] }, - right:{ - pinky:[{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], + right: { + pinky: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], middle: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], ring: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], thumb: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], @@ -165,35 +243,30 @@ }; // Acquire an updated value per hand every 5 frames when finger is touching (faster in) - var touchAnimationSteps = 5; - // Acquire an updated value per hand every 10 frames when finger is returning to default position (slower out) - + // Acquire an updated value per hand every 20 frames when finger is returning to default position (slower out) var defaultAnimationSteps = 10; // Debugging info - var showSphere = false; var showLines = false; // This get setup on creation - var linesCreated = false; var sphereCreated = false; // Register object with API Debugger - var varsToDebug = { scriptLoaded: false, - toggleDebugSphere: function(){ + toggleDebugSphere: function() { showSphere = !showSphere; if (showSphere && !sphereCreated) { createDebugSphere(); sphereCreated = true; } }, - toggleDebugLines: function(){ + toggleDebugLines: function() { showLines = !showLines; if (showLines && !linesCreated) { createDebugLines(); @@ -228,16 +301,17 @@ left: new Palm(), right: new Palm() }, - offset: {x:0, y:0, z:0}, + offset: {x: 0, y: 0, z: 0}, avatarLoaded: false }; // Add/Subtract the joint data - per finger joint - function addVals(val1, val2, sign) { var val = []; - if (val1.length != val2.length) return; + if (val1.length !== val2.length) { + return; + } for (var i = 0; i < val1.length; i++) { val.push({x: 0, y: 0, z: 0, w: 0}); val[i].x = val1[i].x + sign*val2[i].x; @@ -249,7 +323,6 @@ } // Multiply/Divide the joint data - per finger joint - function multiplyValsBy(val1, num) { var val = []; for (var i = 0; i < val1.length; i++) { @@ -263,7 +336,6 @@ } // Calculate the finger lengths by adding its joint lengths - function getJointDistances(jointNamesArray) { var result = {distances: [], totalDistance: 0}; for (var i = 1; i < jointNamesArray.length; i++) { @@ -282,12 +354,13 @@ var handJoint = handJointNames[side]; var jointIndex = MyAvatar.getJointIndex(handJoint); - var worldPosHand = MyAvatar.jointToWorldPoint({x:0, y:0, z:0}, jointIndex); + var worldPosHand = MyAvatar.jointToWorldPoint({x: 0, y: 0, z: 0}, jointIndex); dataOut.position = MyAvatar.jointToWorldPoint(dataIn.position, jointIndex); - // dataOut.perpendicular = Vec3.subtract(MyAvatar.jointToWorldPoint(dataIn.perpendicular, jointIndex), worldPosHand); - var localPerpendicular = side == "right" ? {x:0.2, y:0, z:1} : {x:-0.2, y:0, z:1}; - dataOut.perpendicular = Vec3.normalize(Vec3.subtract(MyAvatar.jointToWorldPoint(localPerpendicular, jointIndex), worldPosHand)); + var localPerpendicular = side === "right" ? {x: 0.2, y: 0, z: 1} : {x: -0.2, y: 0, z: 1}; + dataOut.perpendicular = Vec3.normalize( + Vec3.subtract(MyAvatar.jointToWorldPoint(localPerpendicular, jointIndex), worldPosHand) + ); dataOut.distance = dataIn.distance; for (var i = 0; i < fingerKeys.length; i++) { var finger = fingerKeys[i]; @@ -299,7 +372,7 @@ var handJoint = handJointNames[side]; var jointIndex = MyAvatar.getJointIndex(handJoint); - var worldPosHand = MyAvatar.jointToWorldPoint({x:0, y:0, z:0}, jointIndex); + var worldPosHand = MyAvatar.jointToWorldPoint({x: 0, y: 0, z: 0}, jointIndex); dataOut.position = MyAvatar.worldToJointPoint(dataIn.position, jointIndex); dataOut.perpendicular = MyAvatar.worldToJointPoint(Vec3.sum(worldPosHand, dataIn.perpendicular), jointIndex); @@ -310,7 +383,8 @@ } } - // Calculate the sphere that look up for entities, the center of the palm, perpendicular vector from the palm plane and origin of the the finger rays + // Calculate touch field; Sphere at the center of the palm, + // perpendicular vector from the palm plane and origin of the the finger rays function estimatePalmData(side) { // Return data object @@ -323,7 +397,7 @@ // Store position of the hand joint var worldPosHand = MyAvatar.jointToWorldPoint(jointOffset, jointIndexHand); - var minusWorldPosHand = {x:-worldPosHand.x, y:-worldPosHand.y, z:-worldPosHand.z}; + var minusWorldPosHand = {x: -worldPosHand.x, y: -worldPosHand.y, z: -worldPosHand.z}; // Data for finger rays var directions = {pinky: undefined, middle: undefined, ring: undefined, thumb: undefined, index: undefined}; @@ -337,7 +411,7 @@ var handJointWeight = 1; var fingerJointWeight = 2; - var palmCenter = {x:0, y:0, z:0}; + var palmCenter = {x: 0, y: 0, z: 0}; palmCenter = Vec3.sum(worldPosHand, palmCenter); weightCount += handJointWeight; @@ -352,7 +426,7 @@ positions[finger] = MyAvatar.jointToWorldPoint(jointOffset, jointIndex); directions[finger] = Vec3.normalize(Vec3.sum(positions[finger], minusWorldPosHand)); data.fingers[finger] = Vec3.sum(positions[finger], Vec3.multiply(fingerLength, directions[finger])); - if (finger != "thumb") { + if (finger !== "thumb") { // finger joints have double the weight than the hand joint // This would better position the palm estimation @@ -364,23 +438,25 @@ } // perpendicular change direction depending on the side - - data.perpendicular = (side == "right") ? - Vec3.normalize(Vec3.cross(directions.index, directions.pinky)): - Vec3.normalize(Vec3.cross(directions.pinky, directions.index)); + data.perpendicular = (side === "right") ? + Vec3.normalize(Vec3.cross(directions.index, directions.pinky)): + Vec3.normalize(Vec3.cross(directions.pinky, directions.index)); data.position = Vec3.multiply(1.0/weightCount, palmCenter); - if (side == "right") varsToDebug.offset = MyAvatar.worldToJointPoint(worldPosHand, jointIndexHand); + if (side === "right") { + varsToDebug.offset = MyAvatar.worldToJointPoint(worldPosHand, jointIndexHand); + } var palmDistanceMultiplier = 1.55; // 1.55 based on test/error for the sphere radius that best fits the hand data.distance = palmDistanceMultiplier*Vec3.distance(data.position, positions.index); // move back thumb ray origin var thumbBackMultiplier = 0.2; - data.fingers.thumb = Vec3.sum(data.fingers.thumb, Vec3.multiply( -thumbBackMultiplier * thumbLength, data.perpendicular)); + data.fingers.thumb = Vec3.sum( + data.fingers.thumb, Vec3.multiply( -thumbBackMultiplier * thumbLength, data.perpendicular)); - //return getDataRelativeToHandJoint(side, data); + // return getDataRelativeToHandJoint(side, data); dataRelativeToHandJoint(side, data, palmData[side]); palmData[side].set = true; // return palmData[side]; @@ -389,19 +465,16 @@ // Register GlobalDebugger for API Debugger Script.registerValue("GlobalDebugger", varsToDebug); - - // store the rays for the fingers - only for debug purposes - var fingerRays = { - left:{ + left: { pinky: undefined, middle: undefined, ring: undefined, thumb: undefined, index: undefined }, - right:{ + right: { pinky: undefined, middle: undefined, ring: undefined, @@ -419,14 +492,14 @@ for (var i = 0; i < fingerKeys.length; i++) { fingerRays.left[fingerKeys[i]] = Overlays.addOverlay("line3d", { color: { red: 0, green: 0, blue: 255 }, - start: { x:0, y:0, z:0 }, - end: { x:0, y:1, z:0 }, + start: { x: 0, y: 0, z: 0 }, + end: { x: 0, y: 1, z: 0 }, visible: showLines }); fingerRays.right[fingerKeys[i]] = Overlays.addOverlay("line3d", { color: { red: 0, green: 0, blue: 255 }, - start: { x:0, y:0, z:0 }, - end: { x:0, y:1, z:0 }, + start: { x: 0, y: 0, z: 0 }, + end: { x: 0, y: 1, z: 0 }, visible: showLines }); } @@ -434,14 +507,14 @@ palmRay = { left: Overlays.addOverlay("line3d", { color: { red: 255, green: 0, blue: 0 }, - start: { x:0, y:0, z:0 }, - end: { x:0, y:1, z:0 }, + start: { x: 0, y: 0, z: 0 }, + end: { x: 0, y: 1, z: 0 }, visible: showLines }), right: Overlays.addOverlay("line3d", { color: { red: 255, green: 0, blue: 0 }, - start: { x:0, y:0, z:0 }, - end: { x:0, y:1, z:0 }, + start: { x: 0, y: 0, z: 0 }, + end: { x: 0, y: 1, z: 0 }, visible: showLines }) }; @@ -481,6 +554,98 @@ dataDefault[side].set = true; } + var rayPicks = { + left: { + pinky: undefined, + middle: undefined, + ring: undefined, + thumb: undefined, + index: undefined + }, + right: { + pinky: undefined, + middle: undefined, + ring: undefined, + thumb: undefined, + index: undefined + } + }; + + var dataFailed = { + left: { + pinky: 0, + middle: 0, + ring: 0, + thumb: 0, + index: 0 + }, + right: { + pinky: 0, + middle: 0, + ring: 0, + thumb: 0, + index: 0 + } + }; + + function clearRayPicks(side) { + for (var i = 0; i < fingerKeys.length; i++) { + var finger = fingerKeys[i]; + if (rayPicks[side][finger] !== undefined) { + RayPick.removeRayPick(rayPicks[side][finger]); + rayPicks[side][finger] = undefined; + } + } + } + + function createRayPicks(side) { + var data = palmData[side]; + clearRayPicks(side); + for (var i = 0; i < fingerKeys.length; i++) { + var finger = fingerKeys[i]; + var LOOKUP_DISTANCE_MULTIPLIER = 1.5; + var dist = LOOKUP_DISTANCE_MULTIPLIER*data.distance; + console.log("distance: " + dist); + var checkOffset = { + x: data.perpendicular.x * dist, + y: data.perpendicular.y * dist, + z: data.perpendicular.z * dist + }; + + var checkPoint = Vec3.sum(data.position, Vec3.multiply(2, checkOffset)); + var sensorToWorldScale = MyAvatar.getSensorToWorldScale(); + + var origin = data.fingers[finger]; + + var direction = Vec3.normalize(Vec3.subtract(checkPoint, origin)); + + origin = Vec3.multiply(1/sensorToWorldScale, origin); + + rayPicks[side][finger] = RayPick.createRayPick( + { + "enabled": false, + "joint": handJointNames[side], + "posOffset": origin, + "dirOffset": direction, + "filter": RayPick.PICK_ENTITIES + } + ); + + RayPick.setPrecisionPicking(rayPicks[side][finger], true); + } + } + function activateNextRay(side, index) { + var nextIndex = (index < fingerKeys.length-1) ? index + 1 : 0; + for (var i = 0; i < fingerKeys.length; i++) { + var finger = fingerKeys[i]; + if (i === nextIndex) { + RayPick.enableRayPick(rayPicks[side][finger]); + } else { + RayPick.disableRayPick(rayPicks[side][finger]); + } + } + } + function updateSphereHand(side) { var data = new Palm(); @@ -493,11 +658,12 @@ // Situate the debugging overlays - var checkOffset = { x: data.perpendicular.x * dist, - y: data.perpendicular.y * dist, - z: data.perpendicular.z * dist }; - - + var checkOffset = { + x: data.perpendicular.x * dist, + y: data.perpendicular.y * dist, + z: data.perpendicular.z * dist + }; + var spherePos = Vec3.sum(palmPoint, checkOffset); var checkPoint = Vec3.sum(palmPoint, Vec3.multiply(2, checkOffset)); @@ -529,21 +695,32 @@ } // Update the intersection of only one finger at a time - - var finger = fingerKeys[updateFingerWithIndex]; + + var finger = fingerKeys[updateFingerWithIndex]; + var grabbables = Entities.findEntities(spherePos, dist); - var newFingerData = dataDefault[side][finger]; - var animationSteps = defaultAnimationSteps; + var intersection; + if (rayPicks[side][finger] !== undefined) { + intersection = RayPick.getPrevRayPickResult(rayPicks[side][finger]); + } + + var animationSteps = defaultAnimationSteps; + var newFingerData = dataDefault[side][finger]; + var isAbleToGrab = false; if (grabbables.length > 0) { - var origin = data.fingers[finger]; - var direction = Vec3.normalize(Vec3.subtract(checkPoint, origin)); - var intersection = Entities.findRayIntersection({origin: origin, direction: direction}, true, grabbables, [], true, false); + + RayPick.setIncludeItems(rayPicks[side][finger], grabbables); + + if (intersection === undefined) { + return; + } + var percent = 0; // Initialize - var isAbleToGrab = intersection.intersects && intersection.distance < LOOKUP_DISTANCE_MULTIPLIER*dist; + isAbleToGrab = intersection.intersects && intersection.distance < LOOKUP_DISTANCE_MULTIPLIER*dist; if (isAbleToGrab && !getTouching(side)) { - acquireDefaultPose(side); // take a snapshot of the default pose before touch starts + acquireDefaultPose(side); // take a snapshot of the default pose before touch starts newFingerData = dataDefault[side][finger]; // assign default pose to finger data } // Store if this finger is touching something @@ -558,20 +735,30 @@ var THUMB_FACTOR = 0.2; var FINGER_FACTOR = 0.05; - var grabMultiplier = finger === "thumb" ? THUMB_FACTOR : FINGER_FACTOR; // Amount of grab coefficient added to the fingers - thumb is higher + // Amount of grab coefficient added to the fingers - thumb is higher + var grabMultiplier = finger === "thumb" ? THUMB_FACTOR : FINGER_FACTOR; percent += grabMultiplier * grabPercent[side]; // Calculate new interpolation data var totalDistance = addVals(dataClose[side][finger], dataOpen[side][finger], -1); - newFingerData = addVals(dataOpen[side][finger], multiplyValsBy(totalDistance, percent), 1); // assign close/open ratio to finger to simulate touch + // Assign close/open ratio to finger to simulate touch + newFingerData = addVals(dataOpen[side][finger], multiplyValsBy(totalDistance, percent), 1); animationSteps = touchAnimationSteps; } varsToDebug.fingerPercent[side][finger] = percent; - } - - // Calculate animation increments - dataDelta[side][finger] = multiplyValsBy(addVals(newFingerData, dataCurrent[side][finger], -1), 1.0/animationSteps); - + + } + if (!isAbleToGrab) { + dataFailed[side][finger] = dataFailed[side][finger] === 0 ? 1 : 2; + } else { + dataFailed[side][finger] = 0; + } + // If it only fails once it will not update increments + if (dataFailed[side][finger] !== 1) { + // Calculate animation increments + dataDelta[side][finger] = + multiplyValsBy(addVals(newFingerData, dataCurrent[side][finger], -1), 1.0/animationSteps); + } } // Recreate the finger joint names @@ -643,31 +830,39 @@ } function reEstimatePalmData() { - ["right", "left"].forEach(function(side){ + ["right", "left"].forEach(function(side) { estimatePalmData(side); }); } + function recreateRayPicks() { + ["right", "left"].forEach(function(side) { + createRayPicks(side); + }); + } + MyAvatar.onLoadComplete.connect(function () { // Sometimes the rig is not ready when this signal is trigger console.log("avatar loaded"); - Script.setInterval(function(){ + Script.setTimeout(function() { reEstimatePalmData(); - }, 2000); + recreateRayPicks(); + }, MSECONDS_AFTER_LOAD); }); - MyAvatar.sensorToWorldScaleChanged.connect(function(){ + MyAvatar.sensorToWorldScaleChanged.connect(function() { reEstimatePalmData(); }); Script.scriptEnding.connect(function () { - ["right", "left"].forEach(function(side){ + ["right", "left"].forEach(function(side) { if (linesCreated) { Overlays.deleteOverlay(palmRay[side]); } if (sphereCreated) { Overlays.deleteOverlay(sphereHand[side]); } + clearRayPicks(side); for (var i = 0; i < fingerKeys.length; i++) { var finger = fingerKeys[i]; @@ -684,27 +879,24 @@ } } }); - - - }); - Script.update.connect(function(){ + Script.update.connect(function() { // index of the finger that needs to be updated this frame - - updateFingerWithIndex = (updateFingerWithIndex < fingerKeys.length-1) ? updateFingerWithIndex + 1 : 0; - - ["right", "left"].forEach(function(side){ - + ["right", "left"].forEach(function(side) { + if (!palmData[side].set) { reEstimatePalmData(); + recreateRayPicks(); } + // recalculate the base data updateSphereHand(side); + activateNextRay(side, updateFingerWithIndex); // this vars manage the transition to default pose var isHandTouching = getTouching(side); @@ -725,7 +917,8 @@ for (var j = 0; j < names.length; j++) { var index = MyAvatar.getJointIndex(names[j]); // if no finger is touching restate the default poses - if (isHandTouching || (dataDefault[side].set && countToDefault[side] < 5*touchAnimationSteps)) { + if (isHandTouching || (dataDefault[side].set && + countToDefault[side] < fingerKeys.length*touchAnimationSteps)) { var quatRot = dataCurrent[side][finger][j]; MyAvatar.setJointRotation(index, quatRot); } else { @@ -735,5 +928,4 @@ } }); }); - }()); diff --git a/scripts/system/controllers/toggleAdvancedMovementForHandControllers.js b/scripts/system/controllers/toggleAdvancedMovementForHandControllers.js index 2f3a1d9628..92f72f8724 100644 --- a/scripts/system/controllers/toggleAdvancedMovementForHandControllers.js +++ b/scripts/system/controllers/toggleAdvancedMovementForHandControllers.js @@ -28,7 +28,7 @@ var isDisabled = false; var previousFlyingState = MyAvatar.getFlyingEnabled(); - var previousDrivingState = MyAvatar.useAdvancedMovementControls; + var previousDrivingState = false; function rotate180() { var newOrientation = Quat.multiply(MyAvatar.orientation, Quat.angleAxis(180, { @@ -100,7 +100,7 @@ Controller.enableMapping(DRIVING_MAPPING_NAME); } - if (MyAvatar.getFyingEnabled()) { + if (MyAvatar.getFlyingEnabled()) { Controller.disableMapping(FLYING_MAPPING_NAME); } else { Controller.enableMapping(FLYING_MAPPING_NAME); @@ -171,25 +171,4 @@ Messages.subscribe(HIFI_ADVANCED_MOVEMENT_DISABLER_CHANNEL); Messages.messageReceived.connect(handleMessage); - function initializeControls() { - if(HMD.active) { - if (Controller.Hardware.Vive !== undefined || Controller.Hardware.OculusTouch !== undefined) { - if (MyAvatar.useAdvancedMovementControls) { - Controller.disableMapping(DRIVING_MAPPING_NAME); - } else { - Controller.enableMapping(DRIVING_MAPPING_NAME); - } - - if (MyAvatar.getFlyingEnabled()) { - Controller.disableMapping(FLYING_MAPPING_NAME); - } else { - Controller.enableMapping(FLYING_MAPPING_NAME); - } - }); - - } - } - - initializeControls(); - }()); // END LOCAL_SCOPE diff --git a/scripts/system/edit.js b/scripts/system/edit.js index f549c7dd85..05f5e3cb19 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -2362,26 +2362,21 @@ var showMenuItem = propertyMenu.addMenuItem("Show in Marketplace"); var propertiesTool = new PropertiesTool(); var particleExplorerTool = new ParticleExplorerTool(); -var selectedParticleEntity = 0; var selectedParticleEntityID = null; function selectParticleEntity(entityID) { - var properties = Entities.getEntityProperties(entityID); selectedParticleEntityID = entityID; + + var properties = Entities.getEntityProperties(entityID); if (properties.emitOrientation) { properties.emitOrientation = Quat.safeEulerAngles(properties.emitOrientation); } - var particleData = { - messageType: "particle_settings", - currentProperties: properties - }; + particleExplorerTool.destroyWebView(); particleExplorerTool.createWebView(); - selectedParticleEntity = entityID; particleExplorerTool.setActiveParticleEntity(entityID); - - particleExplorerTool.webView.emitScriptEvent(JSON.stringify(particleData)); + particleExplorerTool.setActiveParticleProperties(properties); // Switch to particle explorer var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); @@ -2404,13 +2399,13 @@ entityListTool.webView.webEventReceived.connect(function (data) { var ids = data.entityIds; if (ids.length === 1) { if (Entities.getEntityProperties(ids[0], "type").type === "ParticleEffect") { - if (JSON.stringify(selectedParticleEntity) === JSON.stringify(ids[0])) { + if (JSON.stringify(selectedParticleEntityID) === JSON.stringify(ids[0])) { // This particle entity is already selected, so return return; } // Destroy the old particles web view first } else { - selectedParticleEntity = 0; + selectedParticleEntityID = 0; particleExplorerTool.destroyWebView(); } } diff --git a/scripts/system/emote.js b/scripts/system/emote.js index 139870fd63..87fc86d569 100644 --- a/scripts/system/emote.js +++ b/scripts/system/emote.js @@ -16,9 +16,11 @@ (function() { // BEGIN LOCAL_SCOPE -var EMOTE_ANIMATIONS = ['Crying', 'Surprised', 'Dancing', 'Cheering', 'Waving', 'Fall', 'Pointing', 'Clapping']; +var EMOTE_ANIMATIONS = ['Cry', 'Surprised', 'Dance', 'Cheer', 'Wave', 'Fall', 'Point', 'Clap', 'Sit1', 'Sit2', 'Sit3', 'Love']; var ANIMATIONS = Array(); +var eventMappingName = "io.highfidelity.away"; // restoreAnimation on hand controller button events, too +var eventMapping = Controller.newMapping(eventMappingName); EMOTE_ANIMATIONS.forEach(function (name) { var animationURL = Script.resolvePath("assets/animations/" + name + ".fbx"); @@ -31,16 +33,15 @@ EMOTE_ANIMATIONS.forEach(function (name) { var EMOTE_APP_BASE = "html/EmoteApp.html"; var EMOTE_APP_URL = Script.resolvePath(EMOTE_APP_BASE); var EMOTE_LABEL = "EMOTE"; -var EMOTE_APP_SORT_ORDER = 11; +var EMOTE_APP_SORT_ORDER = 12; var FPS = 60; var MSEC_PER_SEC = 1000; -var FINISHED = 3; // see ScriptableResource::State var onEmoteScreen = false; var button; var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); -var activeTimer = false; // used to cancel active timer if a user plays an amimation while another animation is playing -var activeEmote = false; // to keep track of the currently playing emote +var activeTimer = false; // Used to cancel active timer if a user plays an animation while another animation is playing +var activeEmote = false; // To keep track of the currently playing emote button = tablet.addButton({ icon: "icons/tablet-icons/EmoteAppIcon.svg", @@ -58,7 +59,7 @@ function onClicked() { } function onScreenChanged(type, url) { - onEmoteScreen = type === "Web" && (url.indexOf(EMOTE_APP_BASE) == url.length - EMOTE_APP_BASE.length); + onEmoteScreen = type === "Web" && (url.indexOf(EMOTE_APP_BASE) === url.length - EMOTE_APP_BASE.length); button.editProperties({ isActive: onEmoteScreen }); } @@ -71,34 +72,79 @@ function onWebEventReceived(event) { } if (event.type === "click") { + var emoteName = event.data; - - if (ANIMATIONS[emoteName].resource.state == FINISHED) { - if (activeTimer !== false) { - Script.clearTimeout(activeTimer); + + if (activeTimer !== false) { + Script.clearTimeout(activeTimer); + } + + // If the activeEmote is different from the chosen emote, then play the new emote. Other wise, + // This is a second click on the same emote as the activeEmote, and we will just stop it. + if (activeEmote !== emoteName) { + activeEmote = emoteName; + + // Allow for a random sitting animation when a user selects sit + var randSit = Math.floor(Math.random() * 3) + 1; + if (emoteName === "Sit"){ + emoteName = event.data + randSit; // "Sit1, Sit2, Sit3" } - - // if the activeEmote is different from the chosen emote, then play the new emote. Other wise, - // this is a second click on the same emote as the activeEmote, and we will just stop it. - if (activeEmote !== emoteName) { - activeEmote = emoteName; - var frameCount = ANIMATIONS[emoteName].animation.frames.length; + + var frameCount = ANIMATIONS[emoteName].animation.frames.length; + + // Three types of emotes (non-looping end, non-looping return, looping) + if (emoteName.match(/^Sit.*$/) || emoteName === "Fall") { // non-looping end + + MyAvatar.overrideAnimation(ANIMATIONS[emoteName].url, FPS, false, 0, frameCount); + + // Non-looping return + } else if (emoteName === "Love" || emoteName === "Surprised" || emoteName === "Cry" || emoteName === "Point"){ + MyAvatar.overrideAnimation(ANIMATIONS[emoteName].url, FPS, false, 0, frameCount); - var timeOut = MSEC_PER_SEC * frameCount / FPS; activeTimer = Script.setTimeout(function () { MyAvatar.restoreAnimation(); activeTimer = false; activeEmote = false; }, timeOut); - } else { - activeEmote = false; - MyAvatar.restoreAnimation(); + + } else { // Looping + + MyAvatar.overrideAnimation(ANIMATIONS[emoteName].url, FPS, true, 0, frameCount); + } + + } else { + activeEmote = false; + MyAvatar.restoreAnimation(); } + + } } +// If a user provides input, end the emote animation and restore the navigation animation states (idle, walk, run) +function restoreAnimation() { + MyAvatar.restoreAnimation(); +} + +Controller.keyPressEvent.connect(restoreAnimation); + +// Note peek() so as to not interfere with other mappings. +eventMapping.from(Controller.Standard.LeftPrimaryThumb).peek().to(restoreAnimation); +eventMapping.from(Controller.Standard.RightPrimaryThumb).peek().to(restoreAnimation); +eventMapping.from(Controller.Standard.LeftSecondaryThumb).peek().to(restoreAnimation); +eventMapping.from(Controller.Standard.RightSecondaryThumb).peek().to(restoreAnimation); +eventMapping.from(Controller.Standard.LB).peek().to(restoreAnimation); +eventMapping.from(Controller.Standard.LS).peek().to(restoreAnimation); +eventMapping.from(Controller.Standard.LeftGrip).peek().to(restoreAnimation); +eventMapping.from(Controller.Standard.RB).peek().to(restoreAnimation); +eventMapping.from(Controller.Standard.RS).peek().to(restoreAnimation); +eventMapping.from(Controller.Standard.RightGrip).peek().to(restoreAnimation); +eventMapping.from(Controller.Standard.Back).peek().to(restoreAnimation); +eventMapping.from(Controller.Standard.Start).peek().to(restoreAnimation); +Controller.enableMapping(eventMappingName); + button.clicked.connect(onClicked); tablet.screenChanged.connect(onScreenChanged); tablet.webEventReceived.connect(onWebEventReceived); diff --git a/scripts/system/html/EmoteApp.html b/scripts/system/html/EmoteApp.html index 0a423b9b6c..16dee490a9 100644 --- a/scripts/system/html/EmoteApp.html +++ b/scripts/system/html/EmoteApp.html @@ -38,7 +38,7 @@ .content { margin-top: 90px; - padding: 30px; + padding: 10px 30px; } input[type=button] { @@ -47,9 +47,9 @@ font-size: 20px; text-transform: uppercase; vertical-align: top; - height: 105px; + height: 90px; min-width: 190px; - padding: 0px 18px; + padding: 0px 10px; margin-right: 6px; border-radius: 5px; border: none; @@ -97,15 +97,17 @@

    Emote App

    -

    Click an emotion to Emote:

    -

    +

    Choose an emote:

    +

    -

    -

    -

    +

    +

    +

    -

    -

    +

    +

    +

    +

    diff --git a/scripts/system/html/js/marketplacesInject.js b/scripts/system/html/js/marketplacesInject.js index 864c7d92b4..9b91d06d41 100644 --- a/scripts/system/html/js/marketplacesInject.js +++ b/scripts/system/html/js/marketplacesInject.js @@ -250,7 +250,7 @@ }); } - function buyButtonClicked(id, name, author, price, href, referrer, edition) { + function buyButtonClicked(id, name, author, price, href, referrer, edition, type) { EventBridge.emitWebEvent(JSON.stringify({ type: "CHECKOUT", itemId: id, @@ -259,7 +259,8 @@ itemHref: href, referrer: referrer, itemAuthor: author, - itemEdition: edition + itemEdition: edition, + itemType: type.trim() })); } @@ -305,13 +306,21 @@ // change pricing to GET/BUY on button hover $('body').on('mouseenter', '#price-or-edit .price', function () { var $this = $(this); + var buyString = "BUY"; + var getString = "GET"; + // Protection against the button getting stuck in the "BUY"/"GET" state. + // That happens when the browser gets two MOUSEENTER events before getting a + // MOUSELEAVE event. + if ($this.text() === buyString || $this.text() === getString) { + return; + } $this.data('initialHtml', $this.html()); var cost = $(this).parent().siblings().text(); if (parseInt(cost) > 0) { - $this.text('BUY'); + $this.text(buyString); } else { - $this.text('GET'); + $this.text(getString); } }); @@ -328,7 +337,8 @@ $(this).closest('.grid-item').find('.item-cost').text(), $(this).attr('data-href'), "mainPage", - -1); + -1, + $(this).closest('.grid-item').find('.item-type').text()); }); } @@ -419,6 +429,7 @@ } var cost = $('.item-cost').text(); + var type = $('.item-type').text(); var isUpdating = window.location.href.indexOf('edition=') > -1; var urlParams = new URLSearchParams(window.location.search); if (isUpdating) { @@ -438,7 +449,8 @@ cost, href, "itemPage", - urlParams.get('edition')); + urlParams.get('edition'), + type); } }); maybeAddPurchasesButton(); diff --git a/scripts/system/libraries/WebTablet.js b/scripts/system/libraries/WebTablet.js index 783b91f5f0..532f58493f 100644 --- a/scripts/system/libraries/WebTablet.js +++ b/scripts/system/libraries/WebTablet.js @@ -15,6 +15,7 @@ Script.include(Script.resolvePath("../libraries/controllers.js")); Script.include(Script.resolvePath("../libraries/Xform.js")); var Y_AXIS = {x: 0, y: 1, z: 0}; +var X_AXIS = {x: 1, y: 0, z: 0}; var DEFAULT_DPI = 31; var DEFAULT_WIDTH = 0.4375; var DEFAULT_VERTICAL_FIELD_OF_VIEW = 45; // degrees @@ -33,10 +34,16 @@ var DELAY_FOR_30HZ = 33; // milliseconds // will need to be recaclulated if dimensions of fbx model change. var TABLET_NATURAL_DIMENSIONS = {x: 32.083, y: 48.553, z: 2.269}; -var HOME_BUTTON_TEXTURE = "http://hifi-content.s3.amazonaws.com/alan/dev/tablet-with-home-button.fbx/tablet-with-home-button.fbm/button-close.png"; +var HOME_BUTTON_TEXTURE = Script.resourcesPath() + "images/button-close.png"; // var HOME_BUTTON_TEXTURE = Script.resourcesPath() + "meshes/tablet-with-home-button.fbx/tablet-with-home-button.fbm/button-close.png"; // var TABLET_MODEL_PATH = "http://hifi-content.s3.amazonaws.com/alan/dev/tablet-with-home-button.fbx"; +var LOCAL_BEZEL_HIGHLIGHT = Script.resourcesPath() + "images/buttonBezel_highlight.png"; +var LOCAL_NORMAL_BEZEL = Script.resourcesPath() + "images/buttonBezel.png"; + var LOCAL_TABLET_MODEL_PATH = Script.resourcesPath() + "meshes/tablet-with-home-button-small-bezel.fbx"; +var HIGH_PRIORITY = 1; +var LOW_PRIORITY = 0; +var SUBMESH = 2; // returns object with two fields: // * position - position in front of the user @@ -115,7 +122,8 @@ WebTablet = function (url, width, dpi, hand, clientOnly, location, visible) { }), dimensions: { x: tabletWidth, y: tabletHeight, z: tabletDepth }, parentID: MyAvatar.SELF_ID, - visible: visible + visible: visible, + isGroupCulled: true }; // compute position, rotation & parentJointIndex of the tablet @@ -133,11 +141,10 @@ WebTablet = function (url, width, dpi, hand, clientOnly, location, visible) { Overlays.deleteOverlay(this.webOverlayID); } - var RAYPICK_OFFSET = 0.0007; // Sufficient for raypick to reliably intersect tablet screen before tablet model. - var WEB_ENTITY_Z_OFFSET = (tabletDepth / 2.0) / sensorScaleFactor + RAYPICK_OFFSET; - var WEB_ENTITY_Y_OFFSET = 1 * tabletScaleFactor; - var screenWidth = 0.9275 * tabletWidth; - var screenHeight = 0.8983 * tabletHeight; + var WEB_ENTITY_Z_OFFSET = (tabletDepth / 2.5) * sensorScaleFactor; + var WEB_ENTITY_Y_OFFSET = 1.25 * tabletScaleFactor; + var screenWidth = 0.9367 * tabletWidth; + var screenHeight = 0.9000 * tabletHeight; this.webOverlayID = Overlays.addOverlay("web3d", { name: "WebTablet Web", url: url, @@ -153,12 +160,14 @@ WebTablet = function (url, width, dpi, hand, clientOnly, location, visible) { visible: visible }); - var HOME_BUTTON_Y_OFFSET = (tabletHeight / 2) - (tabletHeight / 20) + 0.003 * sensorScaleFactor; // FIXME: Circle3D overlays currently at the wrong dimensions, so we need to account for that here var homeButtonDim = 4.0 * tabletScaleFactor / 3.0; + var HOME_BUTTON_X_OFFSET = 0.00079 * sensorScaleFactor; + var HOME_BUTTON_Y_OFFSET = -1 * ((tabletHeight / 2) - (4.0 * tabletScaleFactor / 2)); + var HOME_BUTTON_Z_OFFSET = (tabletDepth / 1.9) * sensorScaleFactor; this.homeButtonID = Overlays.addOverlay("circle3d", { name: "homeButton", - localPosition: { x: 0.0, y: -HOME_BUTTON_Y_OFFSET, z: -WEB_ENTITY_Z_OFFSET }, + localPosition: { x: HOME_BUTTON_X_OFFSET, y: HOME_BUTTON_Y_OFFSET, z: -HOME_BUTTON_Z_OFFSET }, localRotation: { x: 0, y: 1, z: 0, w: 0}, dimensions: { x: homeButtonDim, y: homeButtonDim, z: homeButtonDim }, solid: true, @@ -169,24 +178,46 @@ WebTablet = function (url, width, dpi, hand, clientOnly, location, visible) { parentJointIndex: -1 }); - this.homeButtonHighlightID = Overlays.addOverlay("circle3d", { - name: "homeButtonHighlight", - localPosition: { x: 0, y: -HOME_BUTTON_Y_OFFSET, z: -WEB_ENTITY_Z_OFFSET }, - localRotation: { x: 0, y: 1, z: 0, w: 0 }, - dimensions: { x: homeButtonDim, y: homeButtonDim, z: homeButtonDim }, - color: { red: 255, green: 255, blue: 255 }, - solid: true, - innerRadius: 0.9, - ignoreIntersection: true, - alpha: 1.0, - visible: visible, - drawInFront: false, - parentID: this.tabletEntityID, - parentJointIndex: -1 - }); + this.homeButtonUnhighlightMaterial = Entities.addEntity({ + type: "Material", + materialURL: "materialData", + localPosition: { x: 0.0, y: 0.0, z: 0.0 }, + priority: HIGH_PRIORITY, + materialData: JSON.stringify({ + materials: { + albedoMap: LOCAL_NORMAL_BEZEL + } + + }), + userData: JSON.stringify({ + "grabbableKey": {"grabbable": false} + }), + visible: false, + parentMaterialName: SUBMESH, + parentID: this.tabletEntityID + }, true); + + this.homeButtonHighlightMaterial = Entities.addEntity({ + type: "Material", + materialURL: "materialData", + localPosition: { x: 0.0, y: 0.0, z: 0.0 }, + priority: LOW_PRIORITY, + visible: false, + materialData: JSON.stringify({ + materials: { + emissiveMap: LOCAL_BEZEL_HIGHLIGHT + } + + }), + userData: JSON.stringify({ + "grabbableKey": {"grabbable": false} + }), + parentMaterialName: SUBMESH, + parentID: this.tabletEntityID + }, true); this.receive = function (channel, senderID, senderUUID, localOnly) { - if (_this.homeButtonID == senderID) { + if (_this.homeButtonID === senderID) { var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); var onHomeScreen = tablet.onHomeScreen(); var isMessageOpen; @@ -338,7 +369,8 @@ WebTablet.prototype.destroy = function () { Overlays.deleteOverlay(this.webOverlayID); Overlays.deleteOverlay(this.tabletEntityID); Overlays.deleteOverlay(this.homeButtonID); - Overlays.deleteOverlay(this.homeButtonHighlightID); + Entities.deleteEntity(this.homeButtonUnhighlightMaterial); + Entities.deleteEntity(this.homeButtonHighlightMaterial); HMD.displayModeChanged.disconnect(this.myOnHmdChanged); Controller.mousePressEvent.disconnect(this.myMousePressEvent); @@ -432,21 +464,24 @@ WebTablet.prototype.calculateWorldAttitudeRelativeToCamera = function (windowPos WebTablet.prototype.onHoverEnterOverlay = function (overlayID, pointerEvent) { if (overlayID === this.homeButtonID) { - Overlays.editOverlay(this.homeButtonHighlightID, { alpha: 1.0 }); + Entities.editEntity(this.homeButtonUnhighlightMaterial, {priority: LOW_PRIORITY}); + Entities.editEntity(this.homeButtonHighlightMaterial, {priority: HIGH_PRIORITY}); } -} +}; WebTablet.prototype.onHoverOverOverlay = function (overlayID, pointerEvent) { if (overlayID !== this.homeButtonID) { - Overlays.editOverlay(this.homeButtonHighlightID, { alpha: 0.0 }); + Entities.editEntity(this.homeButtonUnhighlightMaterial, {priority: HIGH_PRIORITY}); + Entities.editEntity(this.homeButtonHighlightMaterial, {priority: LOW_PRIORITY}); } -} +}; WebTablet.prototype.onHoverLeaveOverlay = function (overlayID, pointerEvent) { if (overlayID === this.homeButtonID) { - Overlays.editOverlay(this.homeButtonHighlightID, { alpha: 0.0 }); + Entities.editEntity(this.homeButtonUnhighlightMaterial, {priority: HIGH_PRIORITY}); + Entities.editEntity(this.homeButtonHighlightMaterial, {priority: LOW_PRIORITY}); } -} +}; // compute position, rotation & parentJointIndex of the tablet WebTablet.prototype.calculateTabletAttachmentProperties = function (hand, useMouse, tabletProperties) { @@ -574,22 +609,6 @@ WebTablet.prototype.scheduleMouseMoveProcessor = function() { } }; -WebTablet.prototype.handleHomeButtonHover = function(x, y) { - var pickRay = Camera.computePickRay(x, y); - var entityPickResults; - var homebuttonHovered = false; - entityPickResults = Overlays.findRayIntersection(pickRay, true, [this.tabletEntityID]); - if (entityPickResults.intersects && (entityPickResults.entityID === this.tabletEntityID || - entityPickResults.overlayID === this.tabletEntityID)) { - var overlayPickResults = Overlays.findRayIntersection(pickRay, true, [this.homeButtonID], []); - if (overlayPickResults.intersects && overlayPickResults.overlayID === this.homeButtonID) { - homebuttonHovered = true; - } - } - - Overlays.editOverlay(this.homeButtonHighlightID, { alpha: homebuttonHovered ? 1.0 : 0.0 }); -} - WebTablet.prototype.mouseMoveEvent = function (event) { if (this.dragging) { this.currentMouse = { @@ -597,8 +616,6 @@ WebTablet.prototype.mouseMoveEvent = function (event) { y: event.y }; this.scheduleMouseMoveProcessor(); - } else { - this.handleHomeButtonHover(event.x, event.y); } }; @@ -625,8 +642,6 @@ WebTablet.prototype.mouseMoveProcessor = function () { }); } this.scheduleMouseMoveProcessor(); - } else { - this.handleHomeButtonHover(this.currentMouse.x, this.currentMouse.y); } }; diff --git a/scripts/system/libraries/controllerDispatcherUtils.js b/scripts/system/libraries/controllerDispatcherUtils.js index 1506ce17b2..5dfb0d5b69 100644 --- a/scripts/system/libraries/controllerDispatcherUtils.js +++ b/scripts/system/libraries/controllerDispatcherUtils.js @@ -129,7 +129,8 @@ DISPATCHER_PROPERTIES = [ "userData", "type", "href", - "cloneable" + "cloneable", + "cloneDynamic" ]; // priority -- a lower priority means the module will be asked sooner than one with a higher priority in a given update step diff --git a/scripts/system/libraries/entityCameraTool.js b/scripts/system/libraries/entityCameraTool.js index 0e52353dfb..fb808cc7ea 100644 --- a/scripts/system/libraries/entityCameraTool.js +++ b/scripts/system/libraries/entityCameraTool.js @@ -91,7 +91,7 @@ CameraManager = function() { } var CAPTURED_KEYS = []; - for (key in keyToActionMapping) { + for (var key in keyToActionMapping) { CAPTURED_KEYS.push(key); } @@ -99,9 +99,9 @@ CameraManager = function() { var action = keyToActionMapping[event.text]; if (action !== undefined) { if (event.isShifted) { - if (action == "orbitForward") { + if (action === "orbitForward") { action = "orbitUp"; - } else if (action == "orbitBackward") { + } else if (action === "orbitBackward") { action = "orbitDown"; } } @@ -133,7 +133,7 @@ CameraManager = function() { }; that.enable = function() { - if (Camera.mode == "independent" || that.enabled || HMD.active) { + if (Camera.mode === "independent" || that.enabled || HMD.active) { return; } @@ -235,7 +235,7 @@ CameraManager = function() { } that.setFocalPoint = function(pos) { - that.targetFocalPoint = pos + that.targetFocalPoint = pos; that.updateCamera(); } @@ -276,7 +276,7 @@ CameraManager = function() { } that.mouseMoveEvent = function(event) { - if (that.enabled && that.mode != MODE_INACTIVE) { + if (that.enabled && that.mode !== MODE_INACTIVE) { var x = Reticle.getPosition().x; var y = Reticle.getPosition().y; if (!hasDragged) { @@ -284,11 +284,11 @@ CameraManager = function() { that.lastMousePosition.y = y; hasDragged = true; } - if (that.mode == MODE_ORBIT) { + if (that.mode === MODE_ORBIT) { var diffX = x - that.lastMousePosition.x; var diffY = y - that.lastMousePosition.y; - that.targetYaw -= MOUSE_SENSITIVITY * (diffX / 5.0) - that.targetPitch += MOUSE_SENSITIVITY * (diffY / 10.0) + that.targetYaw -= MOUSE_SENSITIVITY * (diffX / 5.0); + that.targetPitch += MOUSE_SENSITIVITY * (diffY / 10.0); while (that.targetYaw > 180.0) that.targetYaw -= 360; while (that.targetYaw < -180.0) that.targetYaw += 360; @@ -297,7 +297,7 @@ CameraManager = function() { if (that.targetPitch < -90) that.targetPitch = -90; that.updateCamera(); - } else if (that.mode == MODE_PAN) { + } else if (that.mode === MODE_PAN) { var diffX = x - that.lastMousePosition.x; var diffY = y - that.lastMousePosition.y; @@ -316,19 +316,19 @@ CameraManager = function() { var newY = y; var updatePosition = false; - if (x <= Window.x) { - newX = Window.x + Window.innerWidth; + if (x <= 0) { + newX = Window.innerWidth; updatePosition = true; - } else if (x >= (Window.x + Window.innerWidth)) { - newX = Window.x; + } else if (x >= Window.innerWidth) { + newX = 0; updatePosition = true; } - if (y <= Window.y) { - newY = Window.y + Window.innerHeight; + if (y <= 0) { + newY = Window.innerHeight; updatePosition = true; - } else if (y >= (Window.y + Window.innerHeight)) { - newY = Window.y; + } else if (y >= Window.innerHeight) { + newY = 0; updatePosition = true; } @@ -410,7 +410,7 @@ CameraManager = function() { } that.updateCamera = function() { - if (!that.enabled || Camera.mode != "independent") { + if (!that.enabled || Camera.mode !== "independent") { cameraTool.update(); return; } @@ -464,7 +464,7 @@ CameraManager = function() { // Ease the position and orbit of the camera that.update = function(dt) { - if (Camera.mode != "independent") { + if (Camera.mode !== "independent") { that.updateCamera(); return; } diff --git a/scripts/system/libraries/entitySelectionTool.js b/scripts/system/libraries/entitySelectionTool.js index cb5d510598..b42ed5cf64 100644 --- a/scripts/system/libraries/entitySelectionTool.js +++ b/scripts/system/libraries/entitySelectionTool.js @@ -1034,6 +1034,29 @@ SelectionDisplay = (function() { } }; + // FUNCTION: TOGGLE SPACE MODE + that.toggleSpaceMode = function() { + var wantDebug = false; + if (wantDebug) { + print("========> ToggleSpaceMode called. ========="); + } + if ((spaceMode === SPACE_WORLD) && (SelectionManager.selections.length > 1)) { + if (wantDebug) { + print("Local space editing is not available with multiple selections"); + } + return; + } + if (wantDebug) { + print("PreToggle: " + spaceMode); + } + spaceMode = (spaceMode === SPACE_LOCAL) ? SPACE_WORLD : SPACE_LOCAL; + that.updateHandles(); + if (wantDebug) { + print("PostToggle: " + spaceMode); + print("======== ToggleSpaceMode called. <========="); + } + }; + function addHandleTool(overlay, tool) { handleTools[overlay] = tool; return tool; diff --git a/scripts/system/libraries/utils.js b/scripts/system/libraries/utils.js index 7e9e1d7e6a..220ecd1959 100644 --- a/scripts/system/libraries/utils.js +++ b/scripts/system/libraries/utils.js @@ -32,7 +32,7 @@ if (!Function.prototype.bind) { if (this.prototype) { // Function.prototype doesn't have a prototype property - fNOP.prototype = this.prototype; + fNOP.prototype = this.prototype; } fBound.prototype = new fNOP(); @@ -370,7 +370,7 @@ getTabletWidthFromSettings = function () { resizeTablet = function (width, newParentJointIndex, sensorToWorldScaleOverride) { - if (!HMD.tabletID || !HMD.tabletScreenID || !HMD.homeButtonID || !HMD.homeButtonHighlightID) { + if (!HMD.tabletID || !HMD.tabletScreenID || !HMD.homeButtonID) { return; } var sensorScaleFactor = sensorToWorldScaleOverride || MyAvatar.sensorToWorldScale; @@ -381,6 +381,7 @@ resizeTablet = function (width, newParentJointIndex, sensorToWorldScaleOverride) sensorScaleOffsetOverride = 1 / sensorScaleFactor; } + // will need to be recaclulated if dimensions of fbx model change. var TABLET_NATURAL_DIMENSIONS = {x: 32.083, y: 48.553, z: 2.269}; var DEFAULT_DPI = 31; @@ -399,32 +400,26 @@ resizeTablet = function (width, newParentJointIndex, sensorToWorldScaleOverride) }); // update webOverlay - var RAYPICK_OFFSET = 0.0007; // Sufficient for raypick to reliably intersect tablet screen before tablet model. - var WEB_ENTITY_Z_OFFSET = (tabletDepth / 2.0) * sensorScaleOffsetOverride + RAYPICK_OFFSET; - var WEB_ENTITY_Y_OFFSET = 1 * tabletScaleFactor; - print(WEB_ENTITY_Y_OFFSET); - var screenWidth = 0.9275 * tabletWidth; - var screenHeight = 0.8983 * tabletHeight; + var WEB_ENTITY_Z_OFFSET = (tabletDepth / 2.5) * sensorScaleOffsetOverride; + var WEB_ENTITY_Y_OFFSET = 1.25 * tabletScaleFactor * sensorScaleOffsetOverride; + var screenWidth = 0.9367 * tabletWidth; + var screenHeight = 0.9000 * tabletHeight; var landscape = Tablet.getTablet("com.highfidelity.interface.tablet.system").landscape; Overlays.editOverlay(HMD.tabletScreenID, { - localPosition: { x: 0, y: WEB_ENTITY_Y_OFFSET, z: -WEB_ENTITY_Z_OFFSET }, + localPosition: { x: 0, y: WEB_ENTITY_Y_OFFSET, z: -WEB_ENTITY_Z_OFFSET}, dimensions: {x: landscape ? screenHeight : screenWidth, y: landscape ? screenWidth : screenHeight, z: 0.1}, dpi: tabletDpi }); // update homeButton - var HOME_BUTTON_Y_OFFSET = ((tabletHeight / 2) - (tabletHeight / 20) + 0.003 * sensorScaleFactor) * sensorScaleOffsetOverride; // FIXME: Circle3D overlays currently at the wrong dimensions, so we need to account for that here var homeButtonDim = 4.0 * tabletScaleFactor / 3.0; + var HOME_BUTTON_X_OFFSET = 0.00079 * sensorScaleOffsetOverride * sensorScaleFactor; + var HOME_BUTTON_Y_OFFSET = -1 * ((tabletHeight / 2) - (4.0 * tabletScaleFactor / 2)) * sensorScaleOffsetOverride; + var HOME_BUTTON_Z_OFFSET = (tabletDepth / 1.9) * sensorScaleOffsetOverride; Overlays.editOverlay(HMD.homeButtonID, { - localPosition: { x: 0, y: -HOME_BUTTON_Y_OFFSET, z: -WEB_ENTITY_Z_OFFSET }, - localRotation: Quat.angleAxis(180, Vec3.UNIT_Y), - dimensions: { x: homeButtonDim, y: homeButtonDim, z: homeButtonDim } - }); - - Overlays.editOverlay(HMD.homeButtonHighlightID, { - localPosition: { x: 0, y: -HOME_BUTTON_Y_OFFSET, z: -WEB_ENTITY_Z_OFFSET }, - localRotation: Quat.angleAxis(180, Vec3.UNIT_Y), + localPosition: { x: HOME_BUTTON_X_OFFSET, y: HOME_BUTTON_Y_OFFSET, z: -HOME_BUTTON_Z_OFFSET }, + localRotation: { x: 0, y: 1, z: 0, w: 0 }, dimensions: { x: homeButtonDim, y: homeButtonDim, z: homeButtonDim } }); }; diff --git a/scripts/system/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js index dc4d5aa844..fd7b9c703a 100644 --- a/scripts/system/marketplaces/marketplaces.js +++ b/scripts/system/marketplaces/marketplaces.js @@ -114,7 +114,6 @@ var selectionDisplay = null; // for gridTool.js to ignore Overlays.editOverlay(HMD.tabletID, { isVisibleInSecondaryCamera : visibleInSecondaryCam }); Overlays.editOverlay(HMD.homeButtonID, { isVisibleInSecondaryCamera : visibleInSecondaryCam }); - Overlays.editOverlay(HMD.homeButtonHighlightIDtabletID, { isVisibleInSecondaryCamera : visibleInSecondaryCam }); Overlays.editOverlay(HMD.tabletScreenID, { isVisibleInSecondaryCamera : visibleInSecondaryCam }); } @@ -988,6 +987,11 @@ var selectionDisplay = null; // for gridTool.js to ignore sendAssetParticleEffectUpdateTimer = Script.setInterval(updateSendAssetParticleEffect, SEND_ASSET_PARTICLE_TIMER_UPDATE); } break; + case 'http.request': + // Handled elsewhere, don't log. + break; + case 'goToPurchases_fromWalletHome': // HRS FIXME What's this about? + break; default: print('Unrecognized message from Checkout.qml or Purchases.qml: ' + JSON.stringify(message)); } diff --git a/scripts/system/notifications.js b/scripts/system/notifications.js index a28f343ad3..0778e2a44b 100644 --- a/scripts/system/notifications.js +++ b/scripts/system/notifications.js @@ -79,11 +79,7 @@ var frame = 0; var ctrlIsPressed = false; var ready = true; - var MENU_NAME = 'Tools > Notifications'; - var PLAY_NOTIFICATION_SOUNDS_MENU_ITEM = "Play Notification Sounds"; var NOTIFICATION_MENU_ITEM_POST = " Notifications"; - var PLAY_NOTIFICATION_SOUNDS_SETTING = "play_notification_sounds"; - var PLAY_NOTIFICATION_SOUNDS_TYPE_SETTING_PRE = "play_notification_sounds_type_"; var NOTIFICATIONS_MESSAGE_CHANNEL = "Hifi-Notifications"; var NotificationType = { @@ -401,11 +397,6 @@ alpha: backgroundAlpha }; - if (Menu.isOptionChecked(PLAY_NOTIFICATION_SOUNDS_MENU_ITEM) && - Menu.isOptionChecked(NotificationType.getMenuString(notificationType))) { - randomSounds.playRandom(); - } - return notify(noticeProperties, buttonProperties, height, imageProperties); } @@ -618,30 +609,6 @@ } } - function setup() { - var type; - Menu.addMenu(MENU_NAME); - var checked = Settings.getValue(PLAY_NOTIFICATION_SOUNDS_SETTING); - checked = checked === '' ? true : checked; - Menu.addMenuItem({ - menuName: MENU_NAME, - menuItemName: PLAY_NOTIFICATION_SOUNDS_MENU_ITEM, - isCheckable: true, - isChecked: Settings.getValue(PLAY_NOTIFICATION_SOUNDS_SETTING) - }); - Menu.addSeparator(MENU_NAME, "Play sounds for:"); - for (type in NotificationType.properties) { - checked = Settings.getValue(PLAY_NOTIFICATION_SOUNDS_TYPE_SETTING_PRE + (parseInt(type, 10) + 1)); - checked = checked === '' ? true : checked; - Menu.addMenuItem({ - menuName: MENU_NAME, - menuItemName: NotificationType.properties[type].text + NOTIFICATION_MENU_ITEM_POST, - isCheckable: true, - isChecked: checked - }); - } - } - // When our script shuts down, we should clean up all of our overlays function scriptEnding() { var notificationIndex; @@ -649,27 +616,14 @@ Overlays.deleteOverlay(notifications[notificationIndex]); Overlays.deleteOverlay(buttons[notificationIndex]); } - Menu.removeMenu(MENU_NAME); Messages.unsubscribe(NOTIFICATIONS_MESSAGE_CHANNEL); } - function menuItemEvent(menuItem) { - if (menuItem === PLAY_NOTIFICATION_SOUNDS_MENU_ITEM) { - Settings.setValue(PLAY_NOTIFICATION_SOUNDS_SETTING, Menu.isOptionChecked(PLAY_NOTIFICATION_SOUNDS_MENU_ITEM)); - return; - } - var notificationType = NotificationType.getTypeFromMenuItem(menuItem); - if (notificationType !== notificationType.UNKNOWN) { - Settings.setValue(PLAY_NOTIFICATION_SOUNDS_TYPE_SETTING_PRE + notificationType, Menu.isOptionChecked(menuItem)); - } - } - Controller.keyPressEvent.connect(keyPressEvent); Controller.mousePressEvent.connect(mousePressEvent); Controller.keyReleaseEvent.connect(keyReleaseEvent); Script.update.connect(update); Script.scriptEnding.connect(scriptEnding); - Menu.menuItemEvent.connect(menuItemEvent); Window.domainConnectionRefused.connect(onDomainConnectionRefused); Window.stillSnapshotTaken.connect(onSnapshotTaken); Window.snapshot360Taken.connect(onSnapshotTaken); @@ -684,7 +638,4 @@ Messages.subscribe(NOTIFICATIONS_MESSAGE_CHANNEL); Messages.messageReceived.connect(onMessageReceived); - - setup(); - }()); // END LOCAL_SCOPE diff --git a/scripts/system/pal.js b/scripts/system/pal.js index c70c2729f5..7175685b4f 100644 --- a/scripts/system/pal.js +++ b/scripts/system/pal.js @@ -40,7 +40,6 @@ var HOVER_TEXTURES = { var UNSELECTED_COLOR = { red: 0x1F, green: 0xC6, blue: 0xA6}; var SELECTED_COLOR = {red: 0xF3, green: 0x91, blue: 0x29}; var HOVER_COLOR = {red: 0xD0, green: 0xD0, blue: 0xD0}; // almost white for now -var conserveResources = true; Script.include("/~/system/libraries/controllers.js"); @@ -317,6 +316,8 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See } ); break; + case 'http.request': + break; // Handled by request-service. default: print('Unrecognized message from Pal.qml:', JSON.stringify(message)); } @@ -429,7 +430,7 @@ function addAvatarNode(id) { alpha: 0.8, color: color(selected, false, 0.0), ignoreRayIntersection: false - }, selected, !conserveResources); + }, selected, true); } // Each open/refresh will capture a stable set of avatarsOfInterest, within the specified filter. var avatarsOfInterest = {}; @@ -494,7 +495,6 @@ function populateNearbyUserList(selectData, oldAudioData) { print('PAL data:', JSON.stringify(avatarPalDatum)); }); getConnectionData(false, location.domainID); // Even admins don't get relationship data in requestUsernameFromID (which is still needed for admin status, which comes from domain). - conserveResources = Object.keys(avatarsOfInterest).length > 20; sendToQml({ method: 'nearbyUsers', params: data }); if (selectData) { selectData[2] = true; @@ -717,7 +717,7 @@ function onTabletScreenChanged(type, url) { ContextOverlay.enabled = false; Users.requestsDomainListData = true; - audioTimer = createAudioInterval(conserveResources ? AUDIO_LEVEL_CONSERVED_UPDATE_INTERVAL_MS : AUDIO_LEVEL_UPDATE_INTERVAL_MS); + audioTimer = createAudioInterval(AUDIO_LEVEL_UPDATE_INTERVAL_MS); tablet.tabletShownChanged.connect(tabletVisibilityChanged); Script.update.connect(updateOverlays); @@ -872,7 +872,6 @@ startup(); var isWired = false; var audioTimer; var AUDIO_LEVEL_UPDATE_INTERVAL_MS = 100; // 10hz for now (change this and change the AVERAGING_RATIO too) -var AUDIO_LEVEL_CONSERVED_UPDATE_INTERVAL_MS = 300; function off() { if (isWired) { Script.update.disconnect(updateOverlays); diff --git a/scripts/system/particle_explorer/particleExplorerTool.js b/scripts/system/particle_explorer/particleExplorerTool.js index d85fc169b1..de2cb0bd8b 100644 --- a/scripts/system/particle_explorer/particleExplorerTool.js +++ b/scripts/system/particle_explorer/particleExplorerTool.js @@ -16,37 +16,62 @@ var PARTICLE_EXPLORER_HTML_URL = Script.resolvePath('particleExplorer.html'); ParticleExplorerTool = function() { var that = {}; + that.activeParticleEntity = 0; + that.activeParticleProperties = {}; + that.createWebView = function() { that.webView = Tablet.getTablet("com.highfidelity.interface.tablet.system"); that.webView.setVisible = function(value) {}; that.webView.webEventReceived.connect(that.webEventReceived); - } + }; that.destroyWebView = function() { if (!that.webView) { return; } that.activeParticleEntity = 0; + that.activeParticleProperties = {}; var messageData = { messageType: "particle_close" }; that.webView.emitScriptEvent(JSON.stringify(messageData)); + }; + + function sendActiveParticleProperties() { + that.webView.emitScriptEvent(JSON.stringify({ + messageType: "particle_settings", + currentProperties: that.activeParticleProperties + })); } - that.webEventReceived = function(data) { - var data = JSON.parse(data); + that.webEventReceived = function(message) { + var data = JSON.parse(message); if (data.messageType === "settings_update") { if (data.updatedSettings.emitOrientation) { data.updatedSettings.emitOrientation = Quat.fromVec3Degrees(data.updatedSettings.emitOrientation); } Entities.editEntity(that.activeParticleEntity, data.updatedSettings); + + for (var key in data.updatedSettings) { + if (that.activeParticleProperties.hasOwnProperty(key)) { + that.activeParticleProperties[key] = data.updatedSettings[key]; + } + } + + } else if (data.messageType === "page_loaded") { + sendActiveParticleProperties(); } - } + }; that.setActiveParticleEntity = function(id) { that.activeParticleEntity = id; - } + }; + + that.setActiveParticleProperties = function(properties) { + that.activeParticleProperties = properties; + sendActiveParticleProperties(); + }; return that; }; diff --git a/scripts/system/request-service.js b/scripts/system/request-service.js new file mode 100644 index 0000000000..b57f2d4cd7 --- /dev/null +++ b/scripts/system/request-service.js @@ -0,0 +1,48 @@ +"use strict"; +// +// request-service.js +// +// Created by Howard Stearns on May 22, 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 + + // QML has its own XMLHttpRequest, but: + // - npm request is easier to use. + // - It is not easy to hack QML's XMLHttpRequest to use our MetaverseServer, and to supply the user's auth when contacting it. + // a. Our custom XMLHttpRequestClass object only works with QScriptEngine, not QML's javascript. + // b. We have hacked profiles that intercept requests to our MetavserseServer (providing the correct auth), but those + // only work in QML WebEngineView. Setting up communication between ordinary QML and a hiddent WebEngineView is + // tantamount to the following anyway, and would still have to duplicate the code from request.js. + + // So, this script does two things: + // 1. Allows any root .qml to signal sendToScript({id: aString, method: 'http.request', params: byNameOptions}) + // We will then asynchonously call fromScript({id: theSameString, method: 'http.response', error: errorOrFalsey, response: body}) + // on that root object. + // RootHttpRequest.qml does this. + // 2. If the uri used (computed from byNameOptions, see request.js) is to our metaverse, we will use the appropriate auth. + + var request = Script.require('request').request; + var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + function fromQml(message) { // messages are {id, method, params}, like json-rpc. See also sendToQml. + switch (message.method) { + case 'http.request': + request(message.params, function (error, response) { + tablet.sendToQml({ + id: message.id, + method: 'http.response', + error: error, // Alas, this isn't always a JSON-RPC conforming error object. + response: response, + jsonrpc: '2.0' + }); + }); + break; + } + } + tablet.fromQml.connect(fromQml); + Script.scriptEnding.connect(function () { tablet.fromQml.disconnect(fromQml); }); +}()); // END LOCAL_SCOPE diff --git a/scripts/system/snapshot.js b/scripts/system/snapshot.js index 29089e6597..9b540aefc8 100644 --- a/scripts/system/snapshot.js +++ b/scripts/system/snapshot.js @@ -37,8 +37,8 @@ var shareAfterLogin = false; var snapshotToShareAfterLogin = []; var METAVERSE_BASE = Account.metaverseServerURL; var isLoggedIn; -var numGifSnapshotUploadsPending = 0; -var numStillSnapshotUploadsPending = 0; +var mostRecentGifSnapshotFilename = ""; +var mostRecentStillSnapshotFilename = ""; // It's totally unnecessary to return to C++ to perform many of these requests, such as DELETEing an old story, // POSTING a new one, PUTTING a new audience, or GETTING story data. It's far more efficient to do all of that within JS @@ -64,6 +64,10 @@ function fileExtensionMatches(filePath, extension) { return filePath.split('.').pop().toLowerCase() === extension; } +function getFilenameFromPath(str) { + return str.split('\\').pop().split('/').pop(); +} + function onMessage(message) { // Receives message from the html dialog via the qwebchannel EventBridge. This is complicated by the following: // 1. Although we can send POJOs, we cannot receive a toplevel object. (Arrays of POJOs are fine, though.) @@ -147,9 +151,9 @@ function onMessage(message) { print('Sharing snapshot with audience "for_url":', message.data); Window.shareSnapshot(message.data, Settings.getValue("previousSnapshotHref")); if (isGif) { - numGifSnapshotUploadsPending++; + mostRecentGifSnapshotFilename = getFilenameFromPath(message.data); } else { - numStillSnapshotUploadsPending++; + mostRecentStillSnapshotFilename = getFilenameFromPath(message.data); } } else { shareAfterLogin = true; @@ -295,6 +299,7 @@ function printToPolaroid(image_url) { "description": "Printed from Snaps", "modelURL": POLAROID_MODEL_URL, + "dimensions": { "x": 0.5667, "y": 0.0212, "z": 0.4176 }, "position": model_pos, "rotation": model_rot, @@ -384,13 +389,11 @@ function snapshotUploaded(isError, reply) { ignoreStillSnapshotData = false; storyIDsToMaybeDelete.push(storyID); if (isGif) { - numGifSnapshotUploadsPending--; - if (numGifSnapshotUploadsPending !== 0) { + if (mostRecentGifSnapshotFilename !== replyJson.user_story.details.original_image_file_name) { ignoreGifSnapshotData = true; } } else { - numStillSnapshotUploadsPending--; - if (numStillSnapshotUploadsPending !== 0) { + if (mostRecentStillSnapshotFilename !== replyJson.user_story.details.original_image_file_name) { ignoreStillSnapshotData = true; } } @@ -685,9 +688,9 @@ function onUsernameChanged() { Window.shareSnapshot(element.path, element.href); var isGif = fileExtensionMatches(element.path, "gif"); if (isGif) { - numGifSnapshotUploadsPending++; + mostRecentGifSnapshotFilename = getFilenameFromPath(element.path); } else { - numStillSnapshotUploadsPending++; + mostRecentStillSnapshotFilename = getFilenameFromPath(element.path); } }); } diff --git a/scripts/system/tablet-goto.js b/scripts/system/tablet-goto.js index 9cd8420a88..46ddeb2bab 100644 --- a/scripts/system/tablet-goto.js +++ b/scripts/system/tablet-goto.js @@ -41,24 +41,6 @@ sortOrder: 8 }); - function fromQml(message) { - console.debug('tablet-goto::fromQml: message = ', JSON.stringify(message)); - - var response = {id: message.id, jsonrpc: "2.0"}; - switch (message.method) { - case 'request': - request(message.params, function (error, data) { - debug('rpc', request, 'error:', error, 'data:', data); - response.error = error; - response.result = data; - tablet.sendToQml(response); - }); - return; - default: - response.error = {message: 'Unrecognized message', data: message}; - } - tablet.sendToQml(response); - } function messagesWaiting(isWaiting) { button.editProperties({ icon: isWaiting ? WAITING_ICON : NORMAL_ICON @@ -66,21 +48,6 @@ }); } - var hasEventBridge = false; - function wireEventBridge(on) { - if (on) { - if (!hasEventBridge) { - tablet.fromQml.connect(fromQml); - hasEventBridge = true; - } - } else { - if (hasEventBridge) { - tablet.fromQml.disconnect(fromQml); - hasEventBridge = false; - } - } - } - function onClicked() { if (onGotoScreen) { // for toolbar-mode: go back to home screen, this will close the window. @@ -98,15 +65,11 @@ onGotoScreen = true; shouldActivateButton = true; button.editProperties({isActive: shouldActivateButton}); - wireEventBridge(true); messagesWaiting(false); - tablet.sendToQml({ method: 'refreshFeeds', protocolSignature: Window.protocolSignature() }) - } else { shouldActivateButton = false; onGotoScreen = false; button.editProperties({isActive: shouldActivateButton}); - wireEventBridge(false); } } button.clicked.connect(onClicked); diff --git a/scripts/system/tablet-ui/tabletUI.js b/scripts/system/tablet-ui/tabletUI.js index ee3dab7308..29dc457197 100644 --- a/scripts/system/tablet-ui/tabletUI.js +++ b/scripts/system/tablet-ui/tabletUI.js @@ -24,7 +24,7 @@ var validCheckTime = Date.now(); var debugTablet = false; var tabletScalePercentage = 70.0; - UIWebTablet = null; + var UIWebTablet = null; var MSECS_PER_SEC = 1000.0; var MUTE_MICROPHONE_MENU_ITEM = "Mute Microphone"; var gTablet = null; @@ -103,8 +103,9 @@ UIWebTablet.register(); HMD.tabletID = UIWebTablet.tabletEntityID; HMD.homeButtonID = UIWebTablet.homeButtonID; - HMD.homeButtonHighlightID = UIWebTablet.homeButtonHighlightID; HMD.tabletScreenID = UIWebTablet.webOverlayID; + HMD.homeButtonHighlightMaterialID = UIWebTablet.homeButtonHighlightMaterial; + HMD.homeButtonUnhighlightMaterialID = UIWebTablet.homeButtonUnhighlightMaterial; HMD.displayModeChanged.connect(onHmdChanged); MyAvatar.sensorToWorldScaleChanged.connect(onSensorToWorldScaleChanged); @@ -130,7 +131,6 @@ tabletProperties.visible = true; Overlays.editOverlay(HMD.tabletID, tabletProperties); Overlays.editOverlay(HMD.homeButtonID, { visible: true }); - Overlays.editOverlay(HMD.homeButtonHighlightID, { visible: true }); Overlays.editOverlay(HMD.tabletScreenID, { visible: true }); Overlays.editOverlay(HMD.tabletScreenID, { maxFPS: 90 }); updateTabletWidthFromSettings(true); @@ -151,7 +151,6 @@ Overlays.editOverlay(HMD.tabletID, { visible: false }); Overlays.editOverlay(HMD.homeButtonID, { visible: false }); - Overlays.editOverlay(HMD.homeButtonHighlightID, { visible: false }); Overlays.editOverlay(HMD.tabletScreenID, { visible: false }); Overlays.editOverlay(HMD.tabletScreenID, { maxFPS: 1 }); } @@ -172,7 +171,6 @@ UIWebTablet = null; HMD.tabletID = null; HMD.homeButtonID = null; - HMD.homeButtonHighlightID = null; HMD.tabletScreenID = null; } else if (debugTablet) { print("TABLET closeTabletUI, UIWebTablet is null"); @@ -325,7 +323,8 @@ Overlays.deleteOverlay(tabletID); HMD.tabletID = null; HMD.homeButtonID = null; - HMD.homeButtonHighlightID = null; HMD.tabletScreenID = null; + HMD.homeButtonHighlightMaterialID = null; + HMD.homeButtonUnhighlightMaterialID = null; }); }()); // END LOCAL_SCOPE diff --git a/server-console/package.json b/server-console/package.json index 6dd39ea6f8..565658702b 100644 --- a/server-console/package.json +++ b/server-console/package.json @@ -27,7 +27,7 @@ "cheerio": "^0.19.0", "electron-log": "1.1.1", "extend": "^3.0.0", - "fs-extra": "^1.0.0", + "fs-extra": "^6.0.0", "node-notifier": "^5.2.1", "os-homedir": "^1.0.1", "request": "^2.85.0", diff --git a/server-console/src/main.js b/server-console/src/main.js index b08db6222f..dbbe699325 100644 --- a/server-console/src/main.js +++ b/server-console/src/main.js @@ -60,7 +60,14 @@ function getBuildInfo() { } } - const DEFAULT_BUILD_INFO = { releaseType: "", buildIdentifier: "dev" }; + const DEFAULT_BUILD_INFO = { + releaseType: "", + buildIdentifier: "dev", + buildNumber: "0", + stableBuild: "0", + organization: "High Fidelity - dev", + appUserModelId: "com.highfidelity.sandbox-dev" + }; var buildInfo = DEFAULT_BUILD_INFO; if (buildInfoPath) { @@ -75,13 +82,14 @@ function getBuildInfo() { } const buildInfo = getBuildInfo(); -function getRootHifiDataDirectory() { - var organization = "High Fidelity"; - if (buildInfo.releaseType != "PRODUCTION") { - organization += ' - ' + buildInfo.buildIdentifier; - } +function getRootHifiDataDirectory(local) { + var organization = buildInfo.organization; if (osType == 'Windows_NT') { - return path.resolve(osHomeDir(), 'AppData/Roaming', organization); + if (local) { + return path.resolve(osHomeDir(), 'AppData/Local', organization); + } else { + return path.resolve(osHomeDir(), 'AppData/Roaming', organization); + } } else if (osType == 'Darwin') { return path.resolve(osHomeDir(), 'Library/Application Support', organization); } else { @@ -97,8 +105,8 @@ function getAssignmentClientResourcesDirectory() { return path.join(getRootHifiDataDirectory(), '/assignment-client'); } -function getApplicationDataDirectory() { - return path.join(getRootHifiDataDirectory(), '/Server Console'); +function getApplicationDataDirectory(local) { + return path.join(getRootHifiDataDirectory(local), '/Server Console'); } // Update lock filepath @@ -107,17 +115,43 @@ const UPDATER_LOCK_FULL_PATH = getRootHifiDataDirectory() + "/" + UPDATER_LOCK_F // Configure log global.log = require('electron-log'); -const logFile = getApplicationDataDirectory() + '/log.txt'; +const oldLogFile = path.join(getApplicationDataDirectory(), '/log.txt'); +const logFile = path.join(getApplicationDataDirectory(true), '/log.txt'); +if (oldLogFile != logFile && fs.existsSync(oldLogFile)) { + if (!fs.existsSync(oldLogFile)) { + fs.moveSync(oldLogFile, logFile); + } else { + fs.remove(oldLogFile); + } +} fs.ensureFileSync(logFile); // Ensure file exists log.transports.file.maxSize = 5 * 1024 * 1024; log.transports.file.file = logFile; log.debug("build info", buildInfo); log.debug("Root hifi directory is: ", getRootHifiDataDirectory()); +log.debug("App Data directory:", getApplicationDataDirectory()); +fs.ensureDirSync(getApplicationDataDirectory()); + +var oldLogPath = path.join(getApplicationDataDirectory(), '/logs'); +var logPath = path.join(getApplicationDataDirectory(true), '/logs'); +if (oldLogPath != logPath && fs.existsSync(oldLogPath)) { + if (!fs.existsSync(oldLogPath)) { + fs.moveSync(oldLogPath, logPath); + } else { + fs.remove(oldLogPath); + } +} +fs.ensureDirSync(logPath); +log.debug("Log directory:", logPath); + +const configPath = path.join(getApplicationDataDirectory(), 'config.json'); +var userConfig = new Config(); +userConfig.load(configPath); + const ipcMain = electron.ipcMain; - var isShuttingDown = false; function shutdown() { log.debug("Normal shutdown (isShuttingDown: " + isShuttingDown + ")"); @@ -224,14 +258,7 @@ function deleteOldFiles(directoryPath, maxAgeInSeconds, filenameRegex) { } } -var logPath = path.join(getApplicationDataDirectory(), '/logs'); - -log.debug("Log directory:", logPath); -log.debug("Data directory:", getRootHifiDataDirectory()); - -const configPath = path.join(getApplicationDataDirectory(), 'config.json'); -var userConfig = new Config(); -userConfig.load(configPath); +app.setAppUserModelId(buildInfo.appUserModelId); // print out uncaught exceptions in the console process.on('uncaughtException', function(err) { @@ -436,13 +463,6 @@ var labels = { logWindow.open(); } }, - restoreBackup: { - label: 'Restore Backup Instructions', - click: function() { - var folder = getRootHifiDataDirectory() + "/Server Backup"; - openBackupInstructions(folder); - } - }, share: { label: 'Share', click: function() { @@ -478,7 +498,6 @@ function buildMenuArray(serverState) { menuArray.push(labels.stopServer); menuArray.push(labels.settings); menuArray.push(labels.viewLogs); - menuArray.push(labels.restoreBackup); menuArray.push(separator); menuArray.push(labels.share); menuArray.push(separator); @@ -545,103 +564,6 @@ function backupResourceDirectories(folder) { } } -function openBackupInstructions(folder) { - // Explain user how to restore server - var window = new BrowserWindow({ - icon: appIcon, - width: 800, - height: 520, - }); - window.loadURL('file://' + __dirname + '/content-update.html'); - if (!debug) { - window.setMenu(null); - } - window.show(); - - electron.ipcMain.on('setSize', function(event, obj) { - window.setSize(obj.width, obj.height); - }); - electron.ipcMain.on('ready', function() { - log.debug("got ready"); - window.webContents.send('update', folder); - }); -} -function backupResourceDirectoriesAndRestart() { - homeServer.stop(); - - var folder = getRootHifiDataDirectory() + "/Server Backup - " + Date.now(); - if (backupResourceDirectories(folder)) { - maybeInstallDefaultContentSet(onContentLoaded); - openBackupInstructions(folder); - } else { - dialog.showMessageBox({ - type: 'warning', - buttons: ['Ok'], - title: 'Update Error', - message: 'There was an error updating the content, aborting.' - }, function() {}); - } -} - -function checkNewContent() { - if (argv.noUpdater) { - return; - } - - // Start downloading content set - var req = request.head({ - url: HOME_CONTENT_URL - }, function (error, response, body) { - if (error === null) { - var localContent = Date.parse(userConfig.get('homeContentLastModified')); - var remoteContent = Date.parse(response.headers['last-modified']); - - var shouldUpdate = isNaN(localContent) || (!isNaN(remoteContent) && (remoteContent > localContent)); - - var wantDebug = false; - if (wantDebug) { - log.debug('Last Modified: ' + response.headers['last-modified']); - log.debug(localContent + " " + remoteContent + " " + shouldUpdate + " " + new Date()); - log.debug("Remote content is " + (shouldUpdate ? "newer" : "older") + " that local content."); - } - - if (shouldUpdate) { - dialog.showMessageBox({ - type: 'question', - buttons: ['Yes', 'No'], - defaultId: 1, - cancelId: 1, - title: 'High Fidelity Sandbox', - message: 'A newer version of the home content set is available.\nDo you wish to update?', - noLink: true, - }, function(idx) { - if (idx === 0) { - dialog.showMessageBox({ - type: 'warning', - buttons: ['Yes', 'No'], - defaultId: 1, - cancelId: 1, - title: 'Are you sure?', - message: 'Updating with the new content will remove all your current content and settings and place them in a backup folder.\nAre you sure?', - noLink: true, - }, function(idx) { - if (idx === 0) { - backupResourceDirectoriesAndRestart(); - } - }); - } else { - // They don't want to update, mark content set as current - userConfig.set('homeContentLastModified', new Date()); - userConfig.save(configPath); - } - }); - } else if (fs.existsSync(UPDATER_LOCK_FULL_PATH)) { - backupResourceDirectoriesAndRestart(); - } - } - }); -} - function removeIncompleteUpdate(acResourceDirectory, dsResourceDirectory) { if (fs.existsSync(UPDATER_LOCK_FULL_PATH)) { log.debug('Removing incomplete content update files before copying new update'); @@ -684,7 +606,6 @@ function maybeInstallDefaultContentSet(onComplete) { log.debug("User has existing data, suppressing downloader"); onComplete(); - checkNewContent(); return; } @@ -861,33 +782,27 @@ function onContentLoaded() { // maybeShowSplash(); if (buildInfo.releaseType == 'PRODUCTION' && !argv.noUpdater) { - var currentVersion = null; - try { - currentVersion = parseInt(buildInfo.buildIdentifier); - } catch (e) { - } - if (currentVersion !== null) { - const CHECK_FOR_UPDATES_INTERVAL_SECONDS = 60 * 30; - var hasShownUpdateNotification = false; - const updateChecker = new updater.UpdateChecker(currentVersion, CHECK_FOR_UPDATES_INTERVAL_SECONDS); - updateChecker.on('update-available', function(latestVersion, url) { - if (!hasShownUpdateNotification) { - notifier.notify({ - icon: notificationIcon, - title: 'An update is available!', - message: 'High Fidelity version ' + latestVersion + ' is available', - wait: true, - url: url - }); - hasShownUpdateNotification = true; - } - }); - notifier.on('click', function(notifierObject, options) { - log.debug("Got click", options.url); - shell.openExternal(options.url); - }); - } + const CHECK_FOR_UPDATES_INTERVAL_SECONDS = 60 * 30; + var hasShownUpdateNotification = false; + const updateChecker = new updater.UpdateChecker(buildInfo, CHECK_FOR_UPDATES_INTERVAL_SECONDS); + updateChecker.on('update-available', function(latestVersion, url) { + if (!hasShownUpdateNotification) { + notifier.notify({ + icon: notificationIcon, + title: 'An update is available!', + message: 'High Fidelity version ' + latestVersion + ' is available', + wait: true, + appID: buildInfo.appUserModelId, + url: url + }); + hasShownUpdateNotification = true; + } + }); + notifier.on('click', function(notifierObject, options) { + log.debug("Got click", options.url); + shell.openExternal(options.url); + }); } deleteOldFiles(logPath, DELETE_LOG_FILES_OLDER_THAN_X_SECONDS, LOG_FILE_REGEX); diff --git a/server-console/src/modules/hf-updater.js b/server-console/src/modules/hf-updater.js index 489364f655..8362174c5d 100644 --- a/server-console/src/modules/hf-updater.js +++ b/server-console/src/modules/hf-updater.js @@ -8,10 +8,48 @@ const os = require('os'); const platform = os.type() == 'Windows_NT' ? 'windows' : 'mac'; const BUILDS_URL = 'https://highfidelity.com/builds.xml'; +const DEV_BUILDS_URL = 'https://highfidelity.com/dev-builds.xml'; -function UpdateChecker(currentVersion, checkForUpdatesEveryXSeconds) { - this.currentVersion = currentVersion; - log.debug('cur', currentVersion); +// returns 1 if A is greater, 0 if equal, -1 if A is lesser +function semanticVersionCompare(versionA, versionB) { + var versionAParts = versionA.split('.'); + var versionBParts = versionB.split('.'); + + // make sure each version has 3 parts + var partsLength = versionAParts.length; + while (partsLength < 3) { + partsLength = versionAParts.push(0); + } + + partsLength = versionBParts.length; + while (partsLength < 3) { + partsLength = versionBParts.push(0); + } + + // map all of the parts to numbers + versionAParts = versionAParts.map(Number); + versionBParts = versionBParts.map(Number); + + for (var i = 0; i < 3; ++i) { + if (versionAParts[i] == versionBParts[i]) { + continue; + } else if (versionAParts[i] > versionBParts[i]) { + return 1; + } else { + return -1; + } + } + + return 0; +} + +function UpdateChecker(buildInfo, checkForUpdatesEveryXSeconds) { + this.stableBuild = (buildInfo.stableBuild == "1"); + + this.buildsURL = this.stableBuild ? BUILDS_URL : DEV_BUILDS_URL; + this.currentVersion = this.stableBuild ? buildInfo.buildIdentifier : parseInt(buildInfo.buildNumber); + + log.debug('Current version is', this.currentVersion); setInterval(this.checkForUpdates.bind(this), checkForUpdatesEveryXSeconds * 1000); this.checkForUpdates(); @@ -20,7 +58,7 @@ util.inherits(UpdateChecker, events.EventEmitter); UpdateChecker.prototype = extend(UpdateChecker.prototype, { checkForUpdates: function() { log.debug("Checking for updates"); - request(BUILDS_URL, (error, response, body) => { + request(this.buildsURL, (error, response, body) => { if (error) { log.debug("Error", error); return; @@ -29,12 +67,32 @@ UpdateChecker.prototype = extend(UpdateChecker.prototype, { try { var $ = cheerio.load(body, { xmlMode: true }); const latestBuild = $('project[name="interface"] platform[name="' + platform + '"]').children().first(); - const latestVersion = parseInt(latestBuild.find('version').text()); - log.debug("Latest version is:", latestVersion, this.currentVersion); - if (latestVersion > this.currentVersion) { + + var latestVersion = 0; + + if (this.stableBuild) { + latestVersion = latestBuild.find('stable_version').text(); + } else { + latestVersion = parseInt(latestBuild.find('version').text()); + } + + log.debug("Latest available update version is:", latestVersion); + + updateAvailable = false; + + if (this.stableBuild) { + // compare the semantic versions to see if the update is newer + updateAvailable = (semanticVersionCompare(latestVersion, this.currentVersion) == 1); + } else { + // for master builds we just compare the versions as integers + updateAvailable = latestVersion > this.currentVersion; + } + + if (updateAvailable) { const url = latestBuild.find('url').text(); this.emit('update-available', latestVersion, url); } + } catch (e) { log.warn("Error when checking for updates", e); } diff --git a/tests/animation/src/RotationConstraintTests.cpp b/tests/animation/src/RotationConstraintTests.cpp index f8f94e8bee..c1b6bc5dba 100644 --- a/tests/animation/src/RotationConstraintTests.cpp +++ b/tests/animation/src/RotationConstraintTests.cpp @@ -47,7 +47,6 @@ void RotationConstraintTests::testElbowConstraint() { glm::quat inputRotation = referenceRotation; glm::quat outputRotation = inputRotation; bool updated = elbow.apply(outputRotation); - QVERIFY(updated == false); glm::quat expectedRotation = referenceRotation; QCOMPARE_WITH_ABS_ERROR(expectedRotation, outputRotation, EPSILON); } @@ -62,7 +61,6 @@ void RotationConstraintTests::testElbowConstraint() { glm::quat inputRotation = glm::angleAxis(angle, hingeAxis) * referenceRotation; glm::quat outputRotation = inputRotation; bool updated = elbow.apply(outputRotation); - QVERIFY(updated == false); QCOMPARE_WITH_ABS_ERROR(inputRotation, outputRotation, EPSILON); } } @@ -72,7 +70,6 @@ void RotationConstraintTests::testElbowConstraint() { glm::quat inputRotation = glm::angleAxis(angle, hingeAxis) * referenceRotation; glm::quat outputRotation = inputRotation; bool updated = elbow.apply(outputRotation); - QVERIFY(updated == true); glm::quat expectedRotation = glm::angleAxis(minAngle, hingeAxis) * referenceRotation; QCOMPARE_WITH_ABS_ERROR(expectedRotation, outputRotation, EPSILON); } @@ -82,7 +79,6 @@ void RotationConstraintTests::testElbowConstraint() { glm::quat inputRotation = glm::angleAxis(angle, hingeAxis) * referenceRotation; glm::quat outputRotation = inputRotation; bool updated = elbow.apply(outputRotation); - QVERIFY(updated == true); glm::quat expectedRotation = glm::angleAxis(maxAngle, hingeAxis) * referenceRotation; QCOMPARE_WITH_ABS_ERROR(expectedRotation, outputRotation, EPSILON); } @@ -94,7 +90,6 @@ void RotationConstraintTests::testElbowConstraint() { glm::quat inputRotation = glm::angleAxis(someAngle, twistVector) * referenceRotation; glm::quat outputRotation = inputRotation; bool updated = elbow.apply(outputRotation); - QVERIFY(updated == true); glm::quat expectedRotation = referenceRotation; QCOMPARE_WITH_ABS_ERROR(expectedRotation, outputRotation, EPSILON); } diff --git a/tools/010-templates/README.md b/tools/010-templates/README.md new file mode 100644 index 0000000000..df3ce6d0e5 --- /dev/null +++ b/tools/010-templates/README.md @@ -0,0 +1 @@ +This directory contains [010 editor](https://www.sweetscape.com/010editor/) templates for parsing and inspecting different file types. diff --git a/tools/010-templates/fbx.bt b/tools/010-templates/fbx.bt new file mode 100644 index 0000000000..dcb620066e --- /dev/null +++ b/tools/010-templates/fbx.bt @@ -0,0 +1,102 @@ +// +// fbx.bt +// tools/010-templates +// +// Created by Ryan Huffman +// Copyright 2018 High Fidelity, Inc. +// +// FBX file template +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +local char use64BitAddresses = 1; + +struct Header { + char prefix[23]; + int32 version; +}; + +struct Property { + char type; + if (type == 'Y') { + int16 value; + } else if (type == 'C') { + char value; + } else if (type == 'I') { + int32 value; + } else if (type == 'F') { + float value; + } else if (type == 'D') { + double value; + } else if (type == 'L') { + int64 value; + } else if (type == 'S' || type == 'R') { + uint32 size; + char value[size]; + } else { + uint32 length; + uint32 encoding; + uint32 compressedLength; + if (encoding == 1) { + char compressedData[compressedLength]; + } else if (type == 'f') { + float values[this.length]; + } else if (type == 'd') { + double values[this.length]; + } else if (type == 'l') { + int64 values[this.length]; + } else if (type == 'i') { + int32 values[this.length]; + } else if (type == 'b') { + char values[this.length]; + } else { + Printf("%c", type); + Assert(false, "Error, unknown property type"); + } + } +}; + +struct Node; + +string nodeName(Node& node) { + if (!exists(node.name)) { + return "Node ----- "; + } + local string s; + SPrintf(s, "Node (%s) ", node.name); + return s; +} + +struct Node { + if (use64BitAddresses) { + int64 endOffset; + uint64 propertyCount; + uint64 propertyListLength; + } else { + int32 endOffset; + uint32 propertyCount; + uint32 propertyListLength; + } + uchar nameLength; + char name[this.nameLength]; + Property properties[this.propertyCount]; + while (FTell() < endOffset) { + Node children; + } +}; + +struct File { + Header header; + use64BitAddresses = header.version >= 7500; + local int i = 0; + Node node; + local string name = node.name; + while (name != "") { + Node node; + i++; + name = exists(node[i].name) ? node[i].name : ""; + } + +} file; diff --git a/tools/010-templates/ktx.bt b/tools/010-templates/ktx.bt new file mode 100644 index 0000000000..9690dbb391 --- /dev/null +++ b/tools/010-templates/ktx.bt @@ -0,0 +1,52 @@ +// +// ktx.bt +// tools/010-templates +// +// Created by Ryan Huffman +// Copyright 2018 High Fidelity, Inc. +// +// KTX file template +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +struct Header { + char identifier[12]; + uint32 endianness; + uint32 glType; + uint32 glTypeSize; + uint32 glFormat; + uint32 glInternalFormat; + uint32 glBaseInternalFormat; + uint32 pixelWidth; + uint32 pixelHeight; + uint32 pixelDepth; + uint32 numberOfArrayElements; + uint32 numberOfFaces; + uint32 numberOfMipmapLevels; + uint32 bytesOfKeyValueData; +}; + +struct KV { + uint32 byteSize; + local uint32 keyLength = ReadStringLength(FTell()); + char key[keyLength]; + char value[byteSize - keyLength] ; + char padding[3 - ((byteSize + 3) % 4)]; +}; + +string kvName(KV& kv) { + local string s; + SPrintf(s, "KeyValue (%s) ", kv.key); + return s; +} + +struct File { + Header header; + local uint32 endOfKV = FTell() + header.bytesOfKeyValueData; + while (FTell() < endOfKV) { + KV keyValue ; + } + char imageData[FileSize() - FTell()]; +} file; diff --git a/tools/auto-tester/src/ImageComparer.cpp b/tools/auto-tester/src/ImageComparer.cpp index eb892484a2..3afdeaec0a 100644 --- a/tools/auto-tester/src/ImageComparer.cpp +++ b/tools/auto-tester/src/ImageComparer.cpp @@ -15,6 +15,12 @@ // Computes SSIM - see https://en.wikipedia.org/wiki/Structural_similarity // The value is computed for the luminance component and the average value is returned double ImageComparer::compareImages(QImage resultImage, QImage expectedImage) const { + // Make sure the image is 8 bits per colour + QImage::Format format = expectedImage.format(); + if (format != QImage::Format::Format_ARGB32) { + throw -1; + } + const int L = 255; // (2^number of bits per pixel) - 1 const double K1 { 0.01 }; const double K2 { 0.03 }; @@ -54,8 +60,8 @@ double ImageComparer::compareImages(QImage resultImage, QImage expectedImage) co // Collect pixels into linear arrays int i{ 0 }; - for (int xx = 0; xx < WIN_SIZE; ++xx) { - for (int yy = 0; yy < WIN_SIZE; ++yy) { + for (int xx = 0; xx < WIN_SIZE && x + xx < expectedImage.width(); ++xx) { + for (int yy = 0; yy < WIN_SIZE && y + yy < expectedImage.height(); ++yy) { // Get pixels QRgb pixelP = expectedImage.pixel(QPoint(x + xx, y + yy)); QRgb pixelQ = resultImage.pixel(QPoint(x + xx, y + yy)); diff --git a/tools/auto-tester/src/Test.cpp b/tools/auto-tester/src/Test.cpp index 23d097b855..1a25cc5c0e 100644 --- a/tools/auto-tester/src/Test.cpp +++ b/tools/auto-tester/src/Test.cpp @@ -63,7 +63,7 @@ bool Test::compareImageLists(bool isInteractiveMode, QProgressBar* progressBar) // Loop over both lists and compare each pair of images // Quit loop if user has aborted due to a failed test. - const double THRESHOLD { 0.99 }; + const double THRESHOLD { 0.9995 }; bool success{ true }; bool keepOn{ true }; for (int i = 0; keepOn && i < expectedImagesFullFilenames.length(); ++i) { @@ -131,7 +131,9 @@ void Test::appendTestResultsToFile(const QString& testResultsFolderPath, TestFai exit(-1); } - QString failureFolderPath { testResultsFolderPath + "/" + "Failure_" + QString::number(index) + "--" + testFailure._actualImageFilename.left(testFailure._actualImageFilename.length() - 4) }; + QString err = QString::number(testFailure._error).left(6); + + QString failureFolderPath { testResultsFolderPath + "/" + err + "-Failure_" + QString::number(index) + "--" + testFailure._actualImageFilename.left(testFailure._actualImageFilename.length() - 4) }; if (!QDir().mkdir(failureFolderPath)) { QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Failed to create folder " + failureFolderPath); exit(-1); @@ -395,9 +397,9 @@ void Test::createRecursiveScript(const QString& topLevelDirectory, bool interact const QString DATE_TIME_FORMAT("MMM d yyyy, h:mm"); textStream << "// This is an automatically generated file, created by auto-tester on " << QDateTime::currentDateTime().toString(DATE_TIME_FORMAT) << endl << endl; - textStream << "user = \"" + GIT_HUB_USER + "/\"" << endl; - textStream << "repository = \"" + GIT_HUB_REPOSITORY + "/\"" << endl; - textStream << "branch = \"" + GIT_HUB_BRANCH + "/\"" << endl << endl; + textStream << "user = \"" + GIT_HUB_USER + "/\";" << endl; + textStream << "repository = \"" + GIT_HUB_REPOSITORY + "/\";" << endl; + textStream << "branch = \"" + GIT_HUB_BRANCH + "/\";" << endl << endl; textStream << "var autoTester = Script.require(\"https://github.com/" + GIT_HUB_USER + "/hifi_tests/blob/" + GIT_HUB_BRANCH + "/tests/utils/autoTester.js?raw=true\");" << endl << endl; diff --git a/tools/auto-tester/src/ui/AutoTester.cpp b/tools/auto-tester/src/ui/AutoTester.cpp index ea2633edf4..14329e36c2 100644 --- a/tools/auto-tester/src/ui/AutoTester.cpp +++ b/tools/auto-tester/src/ui/AutoTester.cpp @@ -10,6 +10,11 @@ // #include "AutoTester.h" +#ifdef Q_OS_WIN +#include +#include +#endif + AutoTester::AutoTester(QWidget *parent) : QMainWindow(parent) { ui.setupUi(this); ui.checkBoxInteractiveMode->setChecked(true); @@ -20,6 +25,11 @@ AutoTester::AutoTester(QWidget *parent) : QMainWindow(parent) { connect(ui.actionClose, &QAction::triggered, this, &AutoTester::on_closeButton_clicked); connect(ui.actionAbout, &QAction::triggered, this, &AutoTester::about); +#ifndef Q_OS_WIN + ui.hideTaskbarButton->setVisible(false); + ui.showTaskbarButton->setVisible(false); +#endif + test = new Test(); } @@ -56,6 +66,30 @@ void AutoTester::on_createTestsOutlineButton_clicked() { test->createTestsOutline(); } +// To toggle between show and hide +// if (uState & ABS_AUTOHIDE) on_showTaskbarButton_clicked(); +// else on_hideTaskbarButton_clicked(); +// +void AutoTester::on_hideTaskbarButton_clicked() { +#ifdef Q_OS_WIN + APPBARDATA abd = { sizeof abd }; + UINT uState = (UINT)SHAppBarMessage(ABM_GETSTATE, &abd); + LPARAM param = uState & ABS_ALWAYSONTOP; + abd.lParam = ABS_AUTOHIDE | param; + SHAppBarMessage(ABM_SETSTATE, &abd); +#endif +} + +void AutoTester::on_showTaskbarButton_clicked() { +#ifdef Q_OS_WIN + APPBARDATA abd = { sizeof abd }; + UINT uState = (UINT)SHAppBarMessage(ABM_GETSTATE, &abd); + LPARAM param = uState & ABS_ALWAYSONTOP; + abd.lParam = param; + SHAppBarMessage(ABM_SETSTATE, &abd); +#endif +} + void AutoTester::on_closeButton_clicked() { exit(0); } @@ -95,7 +129,7 @@ void AutoTester::saveImage(int index) { pixmap.loadFromData(downloaders[index]->downloadedData()); QImage image = pixmap.toImage(); - image = image.convertToFormat(QImage::Format_RGB32); + image = image.convertToFormat(QImage::Format_ARGB32); QString fullPathname = _directoryName + "/" + _filenames[index]; if (!image.save(fullPathname, 0, 100)) { diff --git a/tools/auto-tester/src/ui/AutoTester.h b/tools/auto-tester/src/ui/AutoTester.h index 5991afed1b..7b419a9af8 100644 --- a/tools/auto-tester/src/ui/AutoTester.h +++ b/tools/auto-tester/src/ui/AutoTester.h @@ -36,6 +36,10 @@ private slots: void on_createMDFileButton_clicked(); void on_createAllMDFilesButton_clicked(); void on_createTestsOutlineButton_clicked(); + + void on_hideTaskbarButton_clicked(); + void on_showTaskbarButton_clicked(); + void on_closeButton_clicked(); void saveImage(int index); diff --git a/tools/auto-tester/src/ui/AutoTester.ui b/tools/auto-tester/src/ui/AutoTester.ui index c9ea229012..236138acf4 100644 --- a/tools/auto-tester/src/ui/AutoTester.ui +++ b/tools/auto-tester/src/ui/AutoTester.ui @@ -147,6 +147,32 @@ Create Tests Outline + + + + 490 + 280 + 91 + 40 + + + + Show Taskbar + + + + + + 490 + 230 + 91 + 40 + + + + Hide Taskbar + + diff --git a/tools/dissectors/hf-domain.lua b/tools/dissectors/hf-domain.lua new file mode 100644 index 0000000000..093026bc92 --- /dev/null +++ b/tools/dissectors/hf-domain.lua @@ -0,0 +1,23 @@ +-- create the domain protocol +p_hf_domain = Proto("hf-domain", "HF Domain Protocol") + +-- domain packet fields +local f_domain_id = ProtoField.guid("hf_domain.domain_id", "Domain ID") +local f_domain_local_id = ProtoField.uint16("hf_domain.domain_local_id", "Domain Local ID") + +p_hf_domain.fields = { + f_domain_id, f_domain_local_id +} + +function p_hf_domain.dissector(buf, pinfo, tree) + pinfo.cols.protocol = p_hf_domain.name + + domain_subtree = tree:add(p_hf_domain, buf()) + + local i = 0 + + domain_subtree:add(f_domain_id, buf(i, 16)) + i = i + 16 + + domain_subtree:add_le(f_domain_local_id, buf(i, 2)) +end diff --git a/tools/dissectors/hf-entity.lua b/tools/dissectors/hf-entity.lua index f4de5a995d..51daa3497d 100644 --- a/tools/dissectors/hf-entity.lua +++ b/tools/dissectors/hf-entity.lua @@ -4,11 +4,21 @@ p_hf_entity = Proto("hf-entity", "HF Entity Protocol") -- entity packet fields local f_entity_sequence_number = ProtoField.uint16("hf_entity.sequence_number", "Sequence Number") local f_entity_timestamp = ProtoField.uint64("hf_entity.timestamp", "Timestamp") -local f_octal_code_bytes = ProtoField.uint8("hf_entity.octal_code_bytes", "Octal Code Bytes") +local f_octal_code_three_bit_sections = ProtoField.uint8("hf_entity.octal_code_three_bit_sections", "Octal Code Three Bit Sections") +local f_octal_code = ProtoField.bytes("hf_entity.octal_code", "Octal Code") local f_entity_id = ProtoField.guid("hf_entity.entity_id", "Entity ID") +local f_last_edited = ProtoField.uint64("hf_entity.last_edited", "Last Edited") +local f_coded_property_type = ProtoField.bytes("hf_entity.coded_property_type", "Coded Property Type") +local f_property_type = ProtoField.uint32("hf_entity.property_type", "Property Type") +local f_coded_update_delta = ProtoField.bytes("hf_entity.f_coded_update_delta", "Coded Update Delta") +local f_update_delta = ProtoField.uint32("hf_entity.update_delta", "Update Delta") p_hf_entity.fields = { - f_entity_sequence_number, f_entity_timestamp, f_octal_code_bytes, f_entity_id + f_entity_sequence_number, f_entity_timestamp, + f_octal_code_three_bit_sections, f_octal_code, + f_last_edited, f_entity_id, + f_coded_property_type, f_property_type, + f_coded_update_delta, f_update_delta } function p_hf_entity.dissector(buf, pinfo, tree) @@ -16,21 +26,72 @@ function p_hf_entity.dissector(buf, pinfo, tree) entity_subtree = tree:add(p_hf_entity, buf()) - i = 0 + local i = 0 entity_subtree:add_le(f_entity_sequence_number, buf(i, 2)) i = i + 2 - entity_subtree:add_le(f_entity_timestamp, buf(i, 4)) - i = i + 4 + entity_subtree:add_le(f_entity_timestamp, buf(i, 8)) + i = i + 8 - -- figure out the number of bytes the octal code takes - local octal_code_bytes = buf(i, 1):le_uint() - entity_subtree:add_le(f_octal_code_bytes, buf(i, 1)) + -- figure out the number of three bit sections in the octal code + local octal_code_three_bit_sections = buf(i, 1):le_uint() + entity_subtree:add_le(f_octal_code_three_bit_sections, buf(i, 1)) + i = i + 1 - -- skip over the octal code - i = i + 1 + octal_code_bytes + -- read the bytes for the octal code + local octal_code_bytes = math.ceil((octal_code_three_bit_sections * 3) / 8) + entity_subtree:add_le(f_octal_code, buf(i, octal_code_bytes)) + i = i + octal_code_bytes + + -- read the last edited timestamp + entity_subtree:add_le(f_last_edited, buf(i, 8)) + i = i + 8 -- read the entity ID entity_subtree:add(f_entity_id, buf(i, 16)) + i = i + 16 + + -- figure out the property type and the size of the coded value + local property_type, coded_property_bytes = number_of_coded_bytes(buf(i)) + entity_subtree:add(f_coded_property_type, buf(i, coded_property_bytes)) + entity_subtree:add(f_property_type, property_type) + i = i + coded_property_bytes + + -- figure out the update delta and the size of the coded value + local update_delta, coded_update_delta_bytes = number_of_coded_bytes(buf(i)) + entity_subtree:add(f_coded_update_delta, buf(i, coded_update_delta_bytes)) + entity_subtree:add(f_update_delta, update_delta) + i = i + coded_update_delta_bytes +end + +function number_of_coded_bytes(buf) + local coded_buffer = buf(0, 4):le_uint() -- max 64 bit value means max 10 header bits + + -- first figure out the total number of bytes for the coded value based + -- on the bits in the header + local total_coded_bytes = 1 + + for bit = 0, 10, 1 do + local header_bit = bit32.extract(coded_buffer, bit) + + if header_bit == 1 then + total_coded_bytes = total_coded_bytes + 1 + else + break + end + end + + -- pull out the bits and write them to our decoded value + local decoded_value = 0 + local decoded_position = 0 + local total_bits = total_coded_bytes * 8 + + for bit = total_coded_bytes, total_bits - 1, 1 do + local value_bit = bit32.extract(coded_buffer, total_bits - bit - 1) + decoded_value = bit32.replace(decoded_value, value_bit, decoded_position) + decoded_position = decoded_position + 1 + end + + return decoded_value, total_coded_bytes end diff --git a/tools/dissectors/hfudt.lua b/tools/dissectors/hfudt.lua index 9d2df801b2..c8b1d9feee 100644 --- a/tools/dissectors/hfudt.lua +++ b/tools/dissectors/hfudt.lua @@ -118,6 +118,10 @@ local packet_types = { [54] = "AssetGetInfoReply" } +local unsourced_packet_types = { + ["DomainList"] = true +} + function p_hfudt.dissector(buf, pinfo, tree) -- make sure this isn't a STUN packet - those don't follow HFUDT format @@ -230,54 +234,63 @@ function p_hfudt.dissector(buf, pinfo, tree) -- if the message bit is set, handle the second word if message_bit == 1 then - payload_offset = 12 + payload_offset = 12 - local second_word = buf(4, 4):le_uint() + local second_word = buf(4, 4):le_uint() - -- read message position from upper 2 bits - local message_position = bit32.rshift(second_word, 30) - local position = subtree:add(f_message_position, message_position) + -- read message position from upper 2 bits + local message_position = bit32.rshift(second_word, 30) + local position = subtree:add(f_message_position, message_position) - if message_positions[message_position] ~= nil then - -- if we know this position then add the name - position:append_text(" (".. message_positions[message_position] .. ")") - end + if message_positions[message_position] ~= nil then + -- if we know this position then add the name + position:append_text(" (".. message_positions[message_position] .. ")") + end - -- read message number from lower 30 bits - subtree:add(f_message_number, bit32.band(second_word, 0x3FFFFFFF)) + -- read message number from lower 30 bits + subtree:add(f_message_number, bit32.band(second_word, 0x3FFFFFFF)) - -- read the message part number - subtree:add(f_message_part_number, buf(8, 4):le_uint()) + -- read the message part number + subtree:add(f_message_part_number, buf(8, 4):le_uint()) end -- read the type local packet_type = buf(payload_offset, 1):le_uint() local ptype = subtree:add_le(f_type, buf(payload_offset, 1)) - if packet_types[packet_type] ~= nil then - subtree:add(f_type_text, packet_types[packet_type]) + local packet_type_text = packet_types[packet_type] + if packet_type_text ~= nil then + subtree:add(f_type_text, packet_type_text) -- if we know this packet type then add the name - ptype:append_text(" (".. packet_types[packet_type] .. ")") + ptype:append_text(" (".. packet_type_text .. ")") end - + -- read the version subtree:add_le(f_version, buf(payload_offset + 1, 1)) - -- read node local ID - local sender_id = buf(payload_offset + 2, 2) - subtree:add_le(f_sender_id, sender_id) + local i = payload_offset + 2 - local i = payload_offset + 4 + if unsourced_packet_types[packet_type_text] == nil then + -- read node local ID + local sender_id = buf(payload_offset + 2, 2) + subtree:add_le(f_sender_id, sender_id) + i = i + 2 - -- read HMAC MD5 hash - subtree:add(f_hmac_hash, buf(i, 16)) - i = i + 16 + -- read HMAC MD5 hash + subtree:add(f_hmac_hash, buf(i, 16)) + i = i + 16 + end + + -- Domain packets + if packet_type_text == "DomainList" then + Dissector.get("hf-domain"):call(buf(i):tvb(), pinfo, tree) + end -- AvatarData or BulkAvatarDataPacket - if packet_types[packet_type] == "AvatarData" or packet_types[packet_type] == "BulkAvatarDataPacket" then + if packet_type_text == "AvatarData" or packet_type_text == "BulkAvatarData" then Dissector.get("hf-avatar"):call(buf(i):tvb(), pinfo, tree) end - if packet_types[packet_type] == "EntityEdit" then + if packet_type_text == "EntityEdit" then Dissector.get("hf-entity"):call(buf(i):tvb(), pinfo, tree) end end diff --git a/tools/jsdoc/.gitignore b/tools/jsdoc/.gitignore index c585e19389..148363ca03 100644 --- a/tools/jsdoc/.gitignore +++ b/tools/jsdoc/.gitignore @@ -1 +1,2 @@ -out \ No newline at end of file +out + diff --git a/tools/jsdoc/CMakeLists.txt b/tools/jsdoc/CMakeLists.txt index 4a6c18f243..1c4333983f 100644 --- a/tools/jsdoc/CMakeLists.txt +++ b/tools/jsdoc/CMakeLists.txt @@ -8,7 +8,7 @@ set(JSDOC_WORKING_DIR ${CMAKE_SOURCE_DIR}/tools/jsdoc) file(TO_NATIVE_PATH ${JSDOC_WORKING_DIR}/node_modules/.bin/jsdoc JSDOC_PATH) file(TO_NATIVE_PATH ${JSDOC_WORKING_DIR}/config.json JSDOC_CONFIG_PATH) file(TO_NATIVE_PATH ${JSDOC_WORKING_DIR}/out OUTPUT_DIR) -file(TO_NATIVE_PATH ${JSDOC_WORKING_DIR} NATIVE_JSDOC_WORKING_DIR) +file(TO_NATIVE_PATH ${JSDOC_WORKING_DIR}/root.js NATIVE_JSDOC_WORKING_DIR) add_custom_command(TARGET ${TARGET_NAME} COMMAND ${NPM_EXECUTABLE} --no-progress install && ${JSDOC_PATH} ${NATIVE_JSDOC_WORKING_DIR} -c ${JSDOC_CONFIG_PATH} -d ${OUTPUT_DIR} diff --git a/tools/jsdoc/README.md b/tools/jsdoc/README.md index 5cce6bb2a6..5cdb1ea44e 100644 --- a/tools/jsdoc/README.md +++ b/tools/jsdoc/README.md @@ -2,12 +2,27 @@ ##Prerequisites -* Install node.js +* Install node.js. * Install jsdoc via npm. `npm install jsdoc -g` +If you would like the extra functionality for gravPrep: +* Run npm install + To generate html documentation for the High Fidelity JavaScript API: * `cd tools/jsdoc` * `jsdoc . -c config.json` The out folder should contain index.html. + +To generate the grav automation files, run node gravPrep.js after you have made a JSdoc output folder. + +This will create files that are needed for hifi-grav and hifi-grav-content repos + +The md files for hifi-grav-content are located in out/grav/06.api-reference. + +The template twig html files for hifi-grav are located out/grav/templates. + +if you would like to copy these to a local version of the docs on your system you can run with the follows args: + +* node grav true "path/to/grav/" "path/to/grav/content" \ No newline at end of file diff --git a/tools/jsdoc/gravPrep.js b/tools/jsdoc/gravPrep.js new file mode 100644 index 0000000000..849837bae0 --- /dev/null +++ b/tools/jsdoc/gravPrep.js @@ -0,0 +1,671 @@ +// Dependencies +const htmlclean = require('htmlclean'); +const fs = require('fs'); +const path = require('path'); +const pretty = require('pretty'); +const cheerio = require('cheerio'); +const rimraf = require('rimraf'); +const dedent = require('dedent-js'); + +// Arg Vars +const copyLocal = process.argv[2]; +console.log("copyLocal:", copyLocal); +let targetTemplateDirectory = ''; +let targetMDDirectory = ''; +if (copyLocal) { + targetTemplateDirectory = process.argv[3]; + targetMDDirectory = process.argv[4];; +} + +// Required directories +let dir_out = path.join(__dirname, 'out'); + +let dir_grav = path.join(dir_out, 'grav'); +let dir_css = path.join(dir_grav, 'css'); +let dir_js = path.join(dir_grav, 'js'); +let dir_template = path.join(dir_grav, 'templates'); + +let dir_md = path.join(dir_grav, '06.api-reference'); +let dir_md_objects = path.join(dir_md, '02.Objects'); +let dir_md_namespaces = path.join(dir_md, '01.Namespaces'); +let dir_md_globals = path.join(dir_md, '03.Globals'); + +// Array to itterate over and create if doesn't exist +let dirArray = [dir_grav, dir_css, dir_js, dir_template, dir_md, dir_md_objects, dir_md_namespaces, dir_md_globals]; + +// Base Grouping Directories for MD files +let baseMDDirectories = ["API-Reference", "Globals", "Namespaces", "Objects"]; + +// Maps for directory names +let map_dir_md = { + "API-Reference": dir_md, + "Globals": dir_md_globals, + "Objects": dir_md_objects, + "Namespaces": dir_md_namespaces, + "Class": dir_md_objects, + "Namespace": dir_md_namespaces, + "Global": dir_md_globals +} + +// Map for Links +let map_links = { + "Global": "globals", + "Namespace": "namespaces", + "Class": "objects" +} + +// Mapping for GroupNames and Members +let groupNameMemberMap = { + "Objects": [], + "Namespaces": [], + "Globals": [] +} + +// Html variables to be handle regex replacements +const html_reg_static = /\(static\)<\/span>/g +const html_reg_title = /\.+?\<\/h1\>/g; +const html_reg_htmlExt = /\.html/g; +const html_reg_objectHeader = /
    [\s\S]+?<\/header>/; +const html_reg_objectSpanNew = /
    <\/h5>/; +const html_reg_propertiesHeaderEdit = '

    Properties:

    '; +const html_reg_propertiesHeaderEdit_Replace = '

    Properties

    '; +const html_reg_typeEdit = /(
    Returns[\s\S]*?Type)(<\/dt[\s\S]*?type">)(.*?)(<\/span><\/dd>[\s\S]*?<\/dl>)/g; +const html_reg_typeEdit_replace = '$1: $3' +const html_reg_methodSize = /()/g; +const html_reg_methodSize_replace = ''; +const html_reg_findByName = '
    ` +const html_reg_signalTitle = `

    Signals

    `; +const html_reg_typeDefinitonsTitle = /

    Type Definitions<\/h3>/; +const html_reg_typeDefinitonsTitle_replace = `

    Type Definitions

    ` +const html_reg_classDefinitonsTitle = /

    Classes<\/h3>/; +const html_reg_classDefinitonsTitle_replace = `

    Classes

    ` +const html_reg_firstDivClose = ``; +const html_reg_allNonHTTPLinks = /()/g; +const html_reg_allHTTPLinks = /()/g; +const html_reg_pretty = /(
    )([\s\S]*?)(<\/pre>)/g;
    +const html_reg_pretty_replace = "
    $2<\/pre>";
    +const html_reg_availableIn = /(
    Report the children of an entity.
    [\s\S]+?Available in:[\s\S]+?<\/table>)/g; +const html_reg_findControllerCuratedList = /
    Functions<\/h5>[\s\S]*?

    Input Recordings[\s\S]*?<\/ul>/g +const html_reg_findEntityMethods = /

    Entity Methods:[\s\S]+?<\/ul>/g; +const html_reg_EntityMethodsHeader = '
    Entity Methods:
    '; +const html_reg_EntityMethodsHeader_replace = '
    Entity Methods
    '; +const html_reg_dlClassDetails = /
    <\/dl>/g +const html_reg_typeDefType = /(
    )(Type:)(<\/h5>[\s\S]*?)([\s\S]*?<\/ul>)/g; +const html_reg_typeDefType_replace = `
    $2 $4
    `; +const html_reg_returnSize = /
    Returns:<\/h5>/g; +const html_reg_returnSize_replace = '
    Returns:<\/h6>'; +const html_reg_depreciated = /(
    [\s\S]+?)(
    )([\s\S]+?)([\s\S]+?)(<\/ul>[\s\S]+?)(<\/dd>)/g; +const html_reg_depreciated_replace = '$1
    $4
    ' + +// Procedural functions + +//remove .html from non http links +function removeHTML(match, p1, p2, p3) { + p2 = p2.replace(".html", ""); + return [p1, p2, p3].join(""); +} + +// Turn links to lower case that aren't part of IDs +function allLinksToLowerCase(match, p1, p2, p3) { + // split on id # and make sure only the preceding is lower case + if (p2.indexOf("#") > -1) { + p2 = p2.split("#"); + p2 = [p2[0].toLowerCase(), "#", p2[1]].join(""); + } else { + p2 = p2.toLowerCase(); + } + return [p1, p2, p3].join(""); +} + +// Helper for fixing formatting of page links +function fixLinkGrouping(match, p1, p2, p3) { + // Handle if referencing ID + let count = (p2.match(/\./g) || []).length; + if (p2.indexOf("#") > -1) { + let split = p2.split("#"); + if (count >= 2) { + // console.log("MULTI DOTS!"); + split = p2.split("."); + // This is a case where we are in an object page and there are multiple levels referenced (only doing 2 levels at the moment) + // console.log("split", split) + return [p1, "/api-reference/", returnRightGroup(split[1].slice(0, -1)), "/", split[1], ".", split[2], p3].join(""); + } + if (split[0] === "global") { + return [p1, "/api-reference/", "globals", "#", split[1], p3].join(""); + } + return [p1, "/api-reference/", returnRightGroup(split[0]), "/", p2, p3].join(""); + } else { + // Handle if there are member references + // console.log("count", count) + let split; + if (count === 1) { + split = p2.split("."); + return [p1, "/api-reference/", returnRightGroup(split[1]), "/", split[1], p3].join(""); + } + return [p1, "/api-reference/", returnRightGroup(p2), "/", p2, p3].join(""); + } +} + +// Return the right group for where the method or type came from +function returnRightGroup(methodToCheck) { + for (var key in groupNameMemberMap) { + for (i = 0; i < groupNameMemberMap[key].length; i++) { + if (methodToCheck.toLowerCase() === groupNameMemberMap[key][i].toLowerCase()) { + return key.toLowerCase(); + } else { + // console.log("Couldn't find group: ", methodToCheck); + } + } + } +} + +// Create the actual MD file +function createMD(title, directory, needsDir, isGlobal) { + let mdSource = makeMdSource(title); + + if (needsDir) { + if (!fs.existsSync(directory)) { + fs.mkdirSync(directory); + } + } + + let destinationMDFile = path.join(directory, `API_${title}.md`); + fs.writeFileSync(destinationMDFile, mdSource); +} + +// Create the actual Template file +function createTemplate(title, content) { + let twigBasePartial = makeTwigFile(content); + let destinationFile = path.join(dir_template, `API_${title}.html.twig`); + fs.writeFileSync(destinationFile, twigBasePartial); +} + +// Copy file from source to target - used for recurssive call +function copyFileSync(source, target) { + let targetFile = target; + + // If target is a directory a new file with the same name will be created + if (fs.existsSync(target)) { + if (fs.lstatSync(target).isDirectory()) { + targetFile = path.join(target, path.basename(source)); + } + } + + fs.writeFileSync(targetFile, fs.readFileSync(source)); +} + +// Copy file from source to target +function copyFolderRecursiveSync(source, target) { + var files = []; + + // Check if folder needs to be created or integrated + var targetFolder = path.join(target, path.basename(source)); + if (!fs.existsSync(targetFolder)) { + fs.mkdirSync(targetFolder); + } + + // Copy + if (fs.lstatSync(source).isDirectory()) { + files = fs.readdirSync(source); + files.forEach(function(file) { + var curSource = path.join(source, file); + if (fs.lstatSync(curSource).isDirectory()) { + copyFolderRecursiveSync(curSource, targetFolder); + } else { + copyFileSync(curSource, targetFolder); + } + }); + } +} + +// Clean up the Html +function prepareHtml(source) { + let htmlBefore = fs.readFileSync(source, { encoding: 'utf8' }); + let htmlAfter = htmlclean(htmlBefore); + let htmlAfterPretty = pretty(htmlAfter); + return cheerio.load(htmlAfterPretty); +} + +// Base file for MD's +function makeMdSource(title) { + return dedent( + ` + --- + title: ${title} + taxonomy: + category: + - docs + visible: true + highlight: + enabled: false + --- + ` + ) +} + +// Base file for Templates +function makeTwigFile(contentHtml) { + return dedent( + ` + {% extends 'partials/base_noGit.html.twig' %} + {% set tags = page.taxonomy.tag %} + {% if tags %} + {% set progress = page.collection({'items':{'@taxonomy':{'category': 'docs', 'tag': tags}},'order': {'by': 'default', 'dir': 'asc'}}) %} + {% else %} + {% set progress = page.collection({'items':{'@taxonomy':{'category': 'docs'}},'order': {'by': 'default', 'dir': 'asc'}}) %} + {% endif %} + + {% block navigation %} +
    + {% endblock %} + + {% block content %} +
    +
    +

    {{ page.title }}

    + ${contentHtml} +
    +
    + {% endblock %} + ` + ) +} + +// Handle NameSpace Group +function handleNamespace(title, content) { + let destinationDirectory = path.join(map_dir_md["Namespace"], title); + createMD(title, destinationDirectory, true); + createTemplate(title, content); +} + +// Handle Class Group +function handleClass(title, content) { + let destinationDirectory = path.join(map_dir_md["Class"], title); + createMD(title, destinationDirectory, true) + + let formatedHtml = content + .replace(html_reg_objectSpanNew, "") + createTemplate(title, formatedHtml); +} + +// Handle Global Group +function handleGlobal(title, content) { + createMD("Globals", map_dir_md["Global"], false, true); + createTemplate("Globals", content); +} + +// Handle Group TOCs +function makeGroupTOC(group) { + let mappedGroup; + if (!Array.isArray(group)) { + mappedGroup = groupNameMemberMap[group]; + } else { + mappedGroup = group; + } + let htmlGroup = mappedGroup.map(item => { + return dedent( + ` +
    + ${item} +
    + ` + ) + }) + return htmlGroup.join("\n"); + } + +// Handle Class TOCS +function makeClassTOC(group){ + let linkArray = [] + group.forEach( item => { + linkArray.push(`
    ${item.type}
    `) + item.array.forEach( link => { + if ( link.indexOf('.') > -1 ){ + linkArray.push(``); + } else { + linkArray.push(``); + + } + }) + linkArray.push("
    "); + }) + return linkArray.join("\n"); +} + +// Extract IDS for TOC +function extractIDs(groupToExtract){ + let firstLine = ""; + let id = ""; + let extractedIDs = []; + groupToExtract.forEach((item)=>{ + firstLine = item.split("\n")[0]; + try { + id = firstLine.split('id="')[1].split(`"`)[0]; + } catch (e){ + id = ""; + } + if (id){ + extractedIDs.push(id) + } + }) + return extractedIDs; +} + +// Helper for splitting up html +// Takes: Content to split, SearchTerm to Split by, and term to End Splitting By +// Returns: [newContent after Split, Array of extracted ] +function splitBy(content, searchTerm, endSplitTerm, title){ + let foundArray = []; + let curIndex = -1; + let afterCurSearchIndex = -1 + let nextIndex = 0; + let findbyNameLength = searchTerm.length; + let curEndSplitTermIndex = -1; + let classHeader; + do { + // Find the index of where to stop searching + curEndSplitTermIndex = content.indexOf(endSplitTerm); + // console.log("curEndSplitTermIndex", curEndSplitTermIndex) + // Find the index of the the next Search term + curIndex = content.indexOf(searchTerm); + // console.log("curIndex", curIndex) + + // The index of where the next search will start + afterCurSearchIndex = curIndex+findbyNameLength; + // Find the content of the next Index + nextIndex = content.indexOf(searchTerm,afterCurSearchIndex); + // If the next index isn't found, then next index === index of the end term + if (nextIndex === -1){ + nextIndex = curEndSplitTermIndex; + } + if (curIndex > curEndSplitTermIndex){ + break; + } + // Push from the cur index to the next found || the end term + let contentSlice = content.slice(curIndex, nextIndex); + if (contentSlice.indexOf(`id="${title}"`) === -1){ + foundArray.push(contentSlice); + } else { + classHeader = contentSlice; + } + + // Remove that content + content = content.replace(contentSlice, ""); + + curEndSplitTermIndex = content.indexOf(endSplitTerm); + nextIndex = content.indexOf(searchTerm,afterCurSearchIndex); + // Handle if nextIndex goes beyond endSplitTerm + if (nextIndex > curEndSplitTermIndex) { + curIndex = content.indexOf(searchTerm); + contentSlice = content.slice(curIndex, curEndSplitTermIndex); + if (contentSlice.indexOf(`id="${title}"`) === -1){ + foundArray.push(contentSlice); + } + content = content.replace(contentSlice, ""); + break; + } + } while (curIndex > -1) + if (classHeader){ + content = append(content, html_reg_findByArticleClose, classHeader, true); + } + return [content, foundArray]; +} + +// Split the signals and methods [Might make this more generic] +function splitMethodsSignals(allItemToSplit){ + let methodArray = []; + let signalArray = []; + + allItemToSplit.forEach( (content, index) => { + firstLine = content.split("\n")[0]; + if (firstLine.indexOf("{Signal}") > -1){ + signalArray.push(content); + } else if (firstLine.indexOf("span") > -1) { + methodArray.push(content); + } else { + } + }) + return [methodArray, signalArray]; +} + +// Helper to append +// Takes content, the search term to appendTo, the content to append, +// and bool if the append is before the found area +function append(content, searchTermToAppendto, contentToAppend, appendBefore){ + let contentArray = content.split("\n"); + let foundIndex = findArrayTrim(contentArray, searchTermToAppendto) + foundIndex = appendBefore ? foundIndex : foundIndex +1 + + contentArray.splice(foundIndex,0,contentToAppend) + return contentArray.join("\n") +} + +// Helper function for append +function findArrayTrim(array, searchTerm){ + var index = -1; + for (var i = 0; i < array.length; i++){ + index = array[i].trim().indexOf(searchTerm.trim()); + if (index > -1){ + return i + } + } + return index; +} + +// Remove grav directory if exists to make sure old files aren't kept +if (fs.existsSync(dir_grav)){ + console.log("dir_grav exists"); + rimraf.sync(dir_grav); +} + +// Create Grav directories in JSDOC output +dirArray.forEach(function(dir){ + if (!fs.existsSync(dir)) { + fs.mkdirSync(dir); + } +}) + +// Create baseMD files +baseMDDirectories.forEach( md => { + createMD(md, map_dir_md[md]); +}) + +// Read jsdoc output folder and process html files +let files = fs.readdirSync(dir_out); + +// Create initial Group name member map to handle individual link +files.forEach(function (file){ + let curSource = path.join(dir_out, file); + if (path.extname(curSource) == ".html" && path.basename(curSource, '.html') !== "index") { + let loadedHtml = prepareHtml(curSource); + let splitTitle = loadedHtml("title").text().split(": "); + let groupName = splitTitle[1]; + let htmlTitle = splitTitle.pop(); + switch(groupName){ + case "Namespace": + groupNameMemberMap["Namespaces"].push(htmlTitle); + break; + case "Class": + groupNameMemberMap["Objects"].push(htmlTitle); + break; + default: + console.log(`Case not handled for ${groupName}`); + } + } +}) +files.forEach(function (file, index){ + // For testing individual files + // if (index !== 59) return; + let curSource = path.join(dir_out, file); + if (path.extname(curSource) == ".html" && path.basename(curSource, '.html') !== "index") { + // Clean up the html source + let loadedHtml = prepareHtml(curSource); + + // Extract the title, group name, and the main div + let splitTitle = loadedHtml("title").text().split(": "); + let groupName = splitTitle[1]; + let htmlTitle = splitTitle.pop(); + console.log("html title", htmlTitle) + let mainDiv = loadedHtml("#main") + + let methodIDs = []; + let signalIDs = []; + let typeDefIDs = []; + // Basic Regex HTML edits + let currentContent = mainDiv.html() + .replace(html_reg_findByMethod, "") //Remove Method title to be remade later + .replace(html_reg_static,"") // Remove static from the file names + .replace(html_reg_title,"") // Remove title + .replace(html_reg_objectHeader,"") // Remove extra Object Header + .replace(html_reg_dlClassDetails, "") // Remove unneccsary dlClassDetails Tag + .replace(html_reg_allNonHTTPLinks, removeHTML) // Remove the .html extension from all links + .replace(html_reg_allNonHTTPLinks, allLinksToLowerCase) // Turn all links into lowercase before ID tags + .replace(html_reg_allNonHTTPLinks, fixLinkGrouping) // Make sure links refer to correct grouping + .replace(html_reg_propertiesHeaderEdit, html_reg_propertiesHeaderEdit_Replace) // Remove : from Properties + .replace(html_reg_typeEdit, html_reg_typeEdit_replace) // Put type on the same line + .replace(html_reg_returnSize, html_reg_returnSize_replace) // Make return size h6 instead of h5 + .replace(html_reg_methodSize, html_reg_methodSize_replace) // Make method size into h5 + .replace(html_reg_pretty, html_reg_pretty_replace) // remove the references to pretty + .replace(html_reg_classDefinitonsTitle, html_reg_classDefinitonsTitle_replace) // Change the class def titles + .replace(html_reg_depreciated, html_reg_depreciated_replace); // format depreciated better + + // Further HTML Manipulation + // Make end term either Type Definitions or by the article + let endTerm; + let foundTypeDefinitions; + let foundSignalsAndMethods; + if (currentContent.indexOf("Type Definitions") > -1){ + // console.log("Found Type Definitions"); + endTerm = `

    Type Definitions

    `; + // Split HTML by Each named entry + let contentSplitArray = splitBy(currentContent, html_reg_findByName, endTerm, htmlTitle); + foundSignalsAndMethods = contentSplitArray[1]; + // console.log("foundSignalsAndMethods", foundSignalsAndMethods) + // Create a reference to the current content after split and the split functions + currentContent = contentSplitArray[0] + .replace(html_reg_typeDefType, html_reg_typeDefType_replace) // Edit how the typedef type looks + .replace(html_reg_typeDefinitonsTitle, ""); // Remove Type Definitions Title to be remade later; + endTerm = html_reg_findByArticleClose; + // Grab split Type Definitions + let contentSplitArrayForTypeDefs = splitBy(currentContent, html_reg_findByName, endTerm, htmlTitle); + currentContent = contentSplitArrayForTypeDefs[0]; + foundTypeDefinitions = contentSplitArrayForTypeDefs[1]; + // console.log("foundTypeDefinitions", foundTypeDefinitions) + + } else { + endTerm = html_reg_findByArticleClose; + let contentSplitArray = splitBy(currentContent, html_reg_findByName, endTerm, htmlTitle); + foundSignalsAndMethods = contentSplitArray[1]; + currentContent = contentSplitArray[0]; + } + + // Create references to the split methods and signals + let processedMethodsSignalsAndTypeDefs = splitMethodsSignals(foundSignalsAndMethods); + let splitMethods = processedMethodsSignalsAndTypeDefs[0]; + let splitSignals = processedMethodsSignalsAndTypeDefs[1]; + let splitTypeDefinitionIDS; + let splitMethodIDS = extractIDs(splitMethods); + let splitSignalIDS = extractIDs(splitSignals); + if (foundTypeDefinitions){ + splitTypeDefinitionIDS = extractIDs(foundTypeDefinitions); + } + let arrayToPassToClassToc = []; + + if (splitMethods.length > 0) { + arrayToPassToClassToc.push({type: "Methods", array: splitMethodIDS}); + // Add the Methods header to the Methods HTML + splitMethods.unshift(html_reg_findByMethod_replace) + currentContent = append(currentContent, html_reg_findByArticleClose, splitMethods.join('\n'), true); + } + if (splitSignals.length > 0) { + arrayToPassToClassToc.push({type: "Signals", array: splitSignalIDS}); + // Add the Signals header to the Signals HTML + splitSignals.unshift(html_reg_signalTitle) + currentContent = append(currentContent, html_reg_findByArticleClose, splitSignals.join('\n'),true); + } + if (foundTypeDefinitions && foundTypeDefinitions.length > 0) { + arrayToPassToClassToc.push({type: "Type Definitions", array: splitTypeDefinitionIDS}); + // Add the Type Defs header to the Type Defs HTML + foundTypeDefinitions.unshift(html_reg_typeDefinitonsTitle_replace) + currentContent = append(currentContent, html_reg_findByArticleClose, foundTypeDefinitions.join('\n'), true); + } + + let classTOC = makeClassTOC(arrayToPassToClassToc); + if (groupName === "Global"){ + currentContent = append(currentContent, html_reg_findByTitle, classTOC); + } else if (htmlTitle === "Controller") { + let curatedList = currentContent.match(html_reg_findControllerCuratedList); + currentContent = currentContent.replace(html_reg_findControllerCuratedList, ""); + let entityMethods = currentContent.match(html_reg_findEntityMethods); + currentContent = currentContent.replace(html_reg_findEntityMethods, ""); + currentContent = append(currentContent, html_reg_firstDivClose, [classTOC, curatedList, entityMethods].join("\n")); + currentContent = currentContent.replace(html_reg_EntityMethodsHeader, html_reg_EntityMethodsHeader_replace); + } else { + currentContent = append(currentContent, html_reg_firstDivClose, classTOC); + } + + // Final Pretty Content + currentContent = htmlclean(currentContent); + currentContent = pretty(currentContent); + + // Handle Unique Categories + switch(groupName){ + case "Namespace": + handleNamespace(htmlTitle, currentContent); + break; + case "Class": + handleClass(htmlTitle, currentContent); + break; + case "Global": + handleGlobal(htmlTitle, currentContent); + break; + default: + console.log(`Case not handled for ${groupName}`); + } + } +}) + +// Create the base Templates after processing individual files +createTemplate("API-Reference", makeGroupTOC(["Namespaces", "Objects", "Globals"])); +createTemplate("Namespaces", makeGroupTOC("Namespaces")); +createTemplate("Objects", makeGroupTOC("Objects")); + +// Copy the files to the target Directories if Local +if (copyLocal){ + // Copy files to the Twig Directory + let templateFiles = fs.readdirSync(path.resolve(targetTemplateDirectory)); + // Remove Existing API files + templateFiles.forEach(function(file){ + let curSource = path.join(targetTemplateDirectory, file); + + if(path.basename(file, '.html').indexOf("API") > -1){ + fs.unlink(curSource); + } + + }) + copyFolderRecursiveSync(dir_template, targetTemplateDirectory); + + // Copy files to the Md Directory + let baseMdRefDir = path.join(targetMDDirectory,"06.api-reference"); + // Remove existing MD directory + if (fs.existsSync(baseMdRefDir)){ + rimraf.sync(baseMdRefDir); + } + copyFolderRecursiveSync(dir_md, targetMDDirectory); +} \ No newline at end of file diff --git a/tools/jsdoc/package.json b/tools/jsdoc/package.json index 215ceec177..4bbb2ad4f2 100644 --- a/tools/jsdoc/package.json +++ b/tools/jsdoc/package.json @@ -1,7 +1,14 @@ { "name": "hifiJSDoc", "dependencies": { - "jsdoc": "^3.5.5" + "axios": "^0.18.0", + "cheerio": "^1.0.0-rc.2", + "dedent-js": "^1.0.1", + "htmlclean": "^3.0.8", + "jsdoc": "^3.5.5", + "pretty": "^2.0.0", + "request": "^2.85.0", + "rimraf": "^2.6.2" }, "private": true } diff --git a/tools/jsdoc/plugins/hifi.js b/tools/jsdoc/plugins/hifi.js index 049d32cceb..76f33e2c73 100644 --- a/tools/jsdoc/plugins/hifi.js +++ b/tools/jsdoc/plugins/hifi.js @@ -153,4 +153,4 @@ exports.defineTags = function (dictionary) { doclet.hifiServerEntity = true; } }); -}; +}; \ No newline at end of file diff --git a/tools/oven/CMakeLists.txt b/tools/oven/CMakeLists.txt index 71bb997303..1b77a2585f 100644 --- a/tools/oven/CMakeLists.txt +++ b/tools/oven/CMakeLists.txt @@ -11,7 +11,7 @@ if (WIN32) elseif (UNIX AND NOT APPLE) find_package(Threads REQUIRED) if(THREADS_HAVE_PTHREAD_ARG) - target_compile_options(PUBLIC oven "-pthread") + target_compile_options(oven PUBLIC "-pthread") endif() elseif (APPLE) # Fix up the rpath so macdeployqt works diff --git a/tools/oven/src/BakerCLI.cpp b/tools/oven/src/BakerCLI.cpp index a7b8401269..0db70f6fe4 100644 --- a/tools/oven/src/BakerCLI.cpp +++ b/tools/oven/src/BakerCLI.cpp @@ -16,6 +16,8 @@ #include #include +#include + #include "OvenCLIApplication.h" #include "ModelBakingLoggingCategory.h" #include "FBXBaker.h" @@ -38,17 +40,15 @@ void BakerCLI::bakeFile(QUrl inputUrl, const QString& outputPath, const QString& static const QString MODEL_EXTENSION { "fbx" }; static const QString SCRIPT_EXTENSION { "js" }; - QString extension = type; - - if (extension.isNull()) { - auto url = inputUrl.toDisplayString(); - extension = url.mid(url.lastIndexOf('.')); - } - // check what kind of baker we should be creating - bool isFBX = extension == MODEL_EXTENSION; - bool isScript = extension == SCRIPT_EXTENSION; + bool isFBX = type == MODEL_EXTENSION; + bool isScript = type == SCRIPT_EXTENSION; + // If the type doesn't match the above, we assume we have a texture, and the type specified is the + // texture usage type (albedo, cubemap, normals, etc.) + auto url = inputUrl.toDisplayString(); + auto idx = url.lastIndexOf('.'); + auto extension = idx >= 0 ? url.mid(idx + 1).toLower() : ""; bool isSupportedImage = QImageReader::supportedImageFormats().contains(extension.toLatin1()); _outputPath = outputPath; @@ -65,7 +65,29 @@ void BakerCLI::bakeFile(QUrl inputUrl, const QString& outputPath, const QString& _baker = std::unique_ptr { new JSBaker(inputUrl, outputPath) }; _baker->moveToThread(Oven::instance().getNextWorkerThread()); } else if (isSupportedImage) { - _baker = std::unique_ptr { new TextureBaker(inputUrl, image::TextureUsage::CUBE_TEXTURE, outputPath) }; + static const std::unordered_map STRING_TO_TEXTURE_USAGE_TYPE_MAP { + { "default", image::TextureUsage::DEFAULT_TEXTURE }, + { "strict", image::TextureUsage::STRICT_TEXTURE }, + { "albedo", image::TextureUsage::ALBEDO_TEXTURE }, + { "normal", image::TextureUsage::NORMAL_TEXTURE }, + { "bump", image::TextureUsage::BUMP_TEXTURE }, + { "specular", image::TextureUsage::SPECULAR_TEXTURE }, + { "metallic", image::TextureUsage::METALLIC_TEXTURE }, + { "roughness", image::TextureUsage::ROUGHNESS_TEXTURE }, + { "gloss", image::TextureUsage::GLOSS_TEXTURE }, + { "emissive", image::TextureUsage::EMISSIVE_TEXTURE }, + { "cube", image::TextureUsage::CUBE_TEXTURE }, + { "occlusion", image::TextureUsage::OCCLUSION_TEXTURE }, + { "scattering", image::TextureUsage::SCATTERING_TEXTURE }, + { "lightmap", image::TextureUsage::LIGHTMAP_TEXTURE }, + }; + + auto it = STRING_TO_TEXTURE_USAGE_TYPE_MAP.find(type); + if (it == STRING_TO_TEXTURE_USAGE_TYPE_MAP.end()) { + qCDebug(model_baking) << "Unknown texture usage type:" << type; + QCoreApplication::exit(OVEN_STATUS_CODE_FAIL); + } + _baker = std::unique_ptr { new TextureBaker(inputUrl, it->second, outputPath) }; _baker->moveToThread(Oven::instance().getNextWorkerThread()); } else { qCDebug(model_baking) << "Failed to determine baker type for file" << inputUrl; diff --git a/tools/oven/src/Oven.cpp b/tools/oven/src/Oven.cpp index c3fec2d15e..52b6db1aa5 100644 --- a/tools/oven/src/Oven.cpp +++ b/tools/oven/src/Oven.cpp @@ -25,12 +25,6 @@ Oven* Oven::_staticInstance { nullptr }; Oven::Oven() { _staticInstance = this; - // enable compression in image library - image::setColorTexturesCompressionEnabled(true); - image::setGrayscaleTexturesCompressionEnabled(true); - image::setNormalTexturesCompressionEnabled(true); - image::setCubeTexturesCompressionEnabled(true); - // setup our worker threads setupWorkerThreads(QThread::idealThreadCount()); diff --git a/tools/oven/src/OvenCLIApplication.cpp b/tools/oven/src/OvenCLIApplication.cpp index ab3178db01..c405c5f4a0 100644 --- a/tools/oven/src/OvenCLIApplication.cpp +++ b/tools/oven/src/OvenCLIApplication.cpp @@ -14,11 +14,15 @@ #include #include +#include +#include + #include "BakerCLI.h" static const QString CLI_INPUT_PARAMETER = "i"; static const QString CLI_OUTPUT_PARAMETER = "o"; static const QString CLI_TYPE_PARAMETER = "t"; +static const QString CLI_DISABLE_TEXTURE_COMPRESSION_PARAMETER = "disable-texture-compression"; OvenCLIApplication::OvenCLIApplication(int argc, char* argv[]) : QCoreApplication(argc, argv) @@ -29,7 +33,8 @@ OvenCLIApplication::OvenCLIApplication(int argc, char* argv[]) : parser.addOptions({ { CLI_INPUT_PARAMETER, "Path to file that you would like to bake.", "input" }, { CLI_OUTPUT_PARAMETER, "Path to folder that will be used as output.", "output" }, - { CLI_TYPE_PARAMETER, "Type of asset.", "type" } + { CLI_TYPE_PARAMETER, "Type of asset.", "type" }, + { CLI_DISABLE_TEXTURE_COMPRESSION_PARAMETER, "Disable texture compression." } }); parser.addHelpOption(); @@ -40,6 +45,12 @@ OvenCLIApplication::OvenCLIApplication(int argc, char* argv[]) : QUrl inputUrl(QDir::fromNativeSeparators(parser.value(CLI_INPUT_PARAMETER))); QUrl outputUrl(QDir::fromNativeSeparators(parser.value(CLI_OUTPUT_PARAMETER))); QString type = parser.isSet(CLI_TYPE_PARAMETER) ? parser.value(CLI_TYPE_PARAMETER) : QString::null; + + if (parser.isSet(CLI_DISABLE_TEXTURE_COMPRESSION_PARAMETER)) { + qDebug() << "Disabling texture compression"; + TextureBaker::setCompressionEnabled(false); + } + QMetaObject::invokeMethod(cli, "bakeFile", Qt::QueuedConnection, Q_ARG(QUrl, inputUrl), Q_ARG(QString, outputUrl.toString()), Q_ARG(QString, type)); } else { diff --git a/unpublishedScripts/interaction/Interaction.js b/unpublishedScripts/interaction/Interaction.js deleted file mode 100644 index bb763c01e7..0000000000 --- a/unpublishedScripts/interaction/Interaction.js +++ /dev/null @@ -1,179 +0,0 @@ -// -// Interaction.js -// scripts/interaction -// -// Created by Trevor Berninger on 3/20/17. -// Copyright 2017 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -(function(){ - print("loading interaction script"); - - var Avatar = false; - var NPC = false; - var previousNPC = false; - var hasCenteredOnNPC = false; - var distance = 10; - var r = 8; - var player = false; - - var baselineX = 0; - var baselineY = 0; - var nodRange = 20; - var shakeRange = 20; - - var ticker = false; - var heartbeatTimer = false; - - function callOnNPC(message) { - if(NPC) - Messages.sendMessage("interactionComs", NPC + ":" + message); - else - Messages.sendMessage("interactionComs", previousNPC + ":" + message); - } - - LimitlessSpeechRecognition.onFinishedSpeaking.connect(function(speech) { - print("Got: " + speech); - callOnNPC("voiceData:" + speech); - }); - - LimitlessSpeechRecognition.onReceivedTranscription.connect(function(speech) { - callOnNPC("speaking"); - }); - - function setBaselineRotations(rot) { - baselineX = rot.x; - baselineY = rot.y; - } - - function findLookedAtNPC() { - var intersection = AvatarList.findRayIntersection({origin: MyAvatar.position, direction: Quat.getFront(Camera.getOrientation())}, true); - if (intersection.intersects && intersection.distance <= distance){ - var npcAvatar = AvatarList.getAvatar(intersection.avatarID); - if (npcAvatar.displayName.search("NPC") != -1) { - setBaselineRotations(Quat.safeEulerAngles(Camera.getOrientation())); - return intersection.avatarID; - } - } - return false; - } - - function isStillFocusedNPC() { - var avatar = AvatarList.getAvatar(NPC); - if (avatar) { - var avatarPosition = avatar.position; - return Vec3.distance(MyAvatar.position, avatarPosition) <= distance && Math.abs(Quat.dot(Camera.getOrientation(), Quat.lookAtSimple(MyAvatar.position, avatarPosition))) > 0.6; - } - return false; // NPC reference died. Maybe it crashed or we teleported to a new world? - } - - function onWeLostFocus() { - print("lost NPC: " + NPC); - callOnNPC("onLostFocused"); - var baselineX = 0; - var baselineY = 0; - } - - function onWeGainedFocus() { - print("found NPC: " + NPC); - callOnNPC("onFocused"); - var rotation = Quat.safeEulerAngles(Camera.getOrientation()); - baselineX = rotation.x; - baselineY = rotation.y; - LimitlessSpeechRecognition.setListeningToVoice(true); - } - - function checkFocus() { - var newNPC = findLookedAtNPC(); - - if (NPC && newNPC != NPC && !isStillFocusedNPC()) { - onWeLostFocus(); - previousNPC = NPC; - NPC = false; - } - if (!NPC && newNPC != false) { - NPC = newNPC; - onWeGainedFocus(); - } - } - - function checkGesture() { - var rotation = Quat.safeEulerAngles(Camera.getOrientation()); - - var deltaX = Math.abs(rotation.x - baselineX); - if (deltaX > 180) { - deltaX -= 180; - } - var deltaY = Math.abs(rotation.y - baselineY); - if (deltaY > 180) { - deltaY -= 180; - } - - if (deltaX >= nodRange && deltaY <= shakeRange) { - callOnNPC("onNodReceived"); - } else if (deltaY >= shakeRange && deltaX <= nodRange) { - callOnNPC("onShakeReceived"); - } - } - - function tick() { - checkFocus(); - if (NPC) { - checkGesture(); - } - } - - function heartbeat() { - callOnNPC("beat"); - } - - Messages.subscribe("interactionComs"); - - Messages.messageReceived.connect(function (channel, message, sender) { - if(channel === "interactionComs" && player) { - var codeIndex = message.search('clientexec'); - if(codeIndex != -1) { - var code = message.substr(codeIndex+11); - Script.evaluate(code, ''); - } - } - }); - - this.enterEntity = function(id) { - player = true; - print("Something entered me: " + id); - LimitlessSpeechRecognition.setAuthKey("testKey"); - if (!ticker) { - ticker = Script.setInterval(tick, 333); - } - if(!heartbeatTimer) { - heartbeatTimer = Script.setInterval(heartbeat, 1000); - } - }; - this.leaveEntity = function(id) { - LimitlessSpeechRecognition.setListeningToVoice(false); - player = false; - print("Something left me: " + id); - if (previousNPC) - Messages.sendMessage("interactionComs", previousNPC + ":leftArea"); - if (ticker) { - ticker.stop(); - ticker = false; - } - if (heartbeatTimer) { - heartbeatTimer.stop(); - heartbeatTimer = false; - } - }; - this.unload = function() { - print("Okay. I'm Unloading!"); - if (ticker) { - ticker.stop(); - ticker = false; - } - }; - print("finished loading interaction script"); -}); diff --git a/unpublishedScripts/interaction/NPCHelpers.js b/unpublishedScripts/interaction/NPCHelpers.js deleted file mode 100644 index 188178b281..0000000000 --- a/unpublishedScripts/interaction/NPCHelpers.js +++ /dev/null @@ -1,179 +0,0 @@ -// -// NPCHelpers.js -// scripts/interaction -// -// Created by Trevor Berninger on 3/20/17. -// Copyright 2017 High Fidelity Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -var audioInjector = false; -var blocked = false; -var playingResponseAnim = false; -var storyURL = ""; -var _qid = "start"; - -print("TESTTEST"); - -function strContains(str, sub) { - return str.search(sub) != -1; -} - -function callbackOnCondition(conditionFunc, ms, callback, count) { - var thisCount = 0; - if (typeof count !== 'undefined') { - thisCount = count; - } - if (conditionFunc()) { - callback(); - } else if (thisCount < 10) { - Script.setTimeout(function() { - callbackOnCondition(conditionFunc, ms, callback, thisCount + 1); - }, ms); - } else { - print("callbackOnCondition timeout"); - } -} - -function playAnim(animURL, looping, onFinished) { - print("got anim: " + animURL); - print("looping: " + looping); - // Start caching the animation if not already cached. - AnimationCache.getAnimation(animURL); - - // Tell the avatar to animate so that we can tell if the animation is ready without crashing - Avatar.startAnimation(animURL, 30, 1, false, false, 0, 1); - - // Continually check if the animation is ready - callbackOnCondition(function(){ - var details = Avatar.getAnimationDetails(); - // if we are running the request animation and are past the first frame, the anim is loaded properly - print("running: " + details.running); - print("url and animURL: " + details.url.trim().replace(/ /g, "%20") + " | " + animURL.trim().replace(/ /g, "%20")); - print("currentFrame: " + details.currentFrame); - return details.running && details.url.trim().replace(/ /g, "%20") == animURL.trim().replace(/ /g, "%20") && details.currentFrame > 0; - }, 250, function(){ - var timeOfAnim = ((AnimationCache.getAnimation(animURL).frames.length / 30) * 1000) + 100; // frames to miliseconds plus a small buffer - print("animation loaded. length: " + timeOfAnim); - // Start the animation again but this time with frame information - Avatar.startAnimation(animURL, 30, 1, looping, true, 0, AnimationCache.getAnimation(animURL).frames.length); - if (typeof onFinished !== 'undefined') { - print("onFinished defined. setting the timeout with timeOfAnim"); - timers.push(Script.setTimeout(onFinished, timeOfAnim)); - } - }); -} - -function playSound(soundURL, onFinished) { - callbackOnCondition(function() { - return SoundCache.getSound(soundURL).downloaded; - }, 250, function() { - if (audioInjector) { - audioInjector.stop(); - } - audioInjector = Audio.playSound(SoundCache.getSound(soundURL), {position: Avatar.position, volume: 1.0}); - if (typeof onFinished !== 'undefined') { - audioInjector.finished.connect(onFinished); - } - }); -} - -function npcRespond(soundURL, animURL, onFinished) { - if (typeof soundURL !== 'undefined' && soundURL != '') { - print("npcRespond got soundURL!"); - playSound(soundURL, function(){ - print("sound finished"); - var animDetails = Avatar.getAnimationDetails(); - print("animDetails.lastFrame: " + animDetails.lastFrame); - print("animDetails.currentFrame: " + animDetails.currentFrame); - if (animDetails.lastFrame < animDetails.currentFrame + 1 || !playingResponseAnim) { - onFinished(); - } - audioInjector = false; - }); - } - if (typeof animURL !== 'undefined' && animURL != '') { - print("npcRespond got animURL!"); - playingResponseAnim = true; - playAnim(animURL, false, function() { - print("anim finished"); - playingResponseAnim = false; - print("injector: " + audioInjector); - if (!audioInjector || !audioInjector.isPlaying()) { - print("resetting Timer"); - print("about to call onFinished"); - onFinished(); - } - }); - } -} - -function npcRespondBlocking(soundURL, animURL, onFinished) { - print("blocking response requested"); - if (!blocked) { - print("not already blocked"); - blocked = true; - npcRespond(soundURL, animURL, function(){ - if (onFinished){ - onFinished(); - }blocked = false; - }); - } -} - -function npcContinueStory(soundURL, animURL, nextID, onFinished) { - if (!nextID) { - nextID = _qid; - } - npcRespondBlocking(soundURL, animURL, function(){ - if (onFinished){ - onFinished(); - }setQid(nextID); - }); -} - -function setQid(newQid) { - print("setting quid"); - print("_qid: " + _qid); - _qid = newQid; - print("_qid: " + _qid); - doActionFromServer("init"); -} - -function runOnClient(code) { - Messages.sendMessage("interactionComs", "clientexec:" + code); -} - -function doActionFromServer(action, data, useServerCache) { - if (action == "start") { - ignoreCount = 0; - _qid = "start"; - } - var xhr = new XMLHttpRequest(); - xhr.open("POST", "http://gserv_devel.studiolimitless.com/story", true); - xhr.onreadystatechange = function(){ - if (xhr.readyState == 4){ - if (xhr.status == 200) { - print("200!"); - print("evaluating: " + xhr.responseText); - Script.evaluate(xhr.responseText, ""); - } else if (xhr.status == 444) { - print("Limitless Serv 444: API error: " + xhr.responseText); - } else { - print("HTTP Code: " + xhr.status + ": " + xhr.responseText); - } - } - }; - xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); - var postData = "url=" + storyURL + "&action=" + action + "&qid=" + _qid; - if (typeof data !== 'undefined' && data != '') { - postData += "&data=" + data; - } - if (typeof useServerCache !== 'undefined' && !useServerCache) { - postData += "&nocache=true"; - } - print("Sending: " + postData); - xhr.send(postData); -} diff --git a/unpublishedScripts/interaction/NPC_AC.js b/unpublishedScripts/interaction/NPC_AC.js deleted file mode 100644 index eb2d9f4caf..0000000000 --- a/unpublishedScripts/interaction/NPC_AC.js +++ /dev/null @@ -1,102 +0,0 @@ -// -// NPC_AC.js -// scripts/interaction -// -// Created by Trevor Berninger on 3/20/17. -// Copyright 2017 High Fidelity Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -var currentlyUsedIndices = []; -var timers = []; -var currentlyEngaged = false; -var questionNumber = 0; -var heartbeatTimeout = false; -function getRandomRiddle() { - var randIndex = null; - do { - randIndex = Math.floor(Math.random() * 15) + 1; - } while (randIndex in currentlyUsedIndices); - - currentlyUsedIndices.push(randIndex); - return randIndex.toString(); -} - -Script.include("https://raw.githubusercontent.com/Delamare2112/hifi/Interaction/unpublishedScripts/interaction/NPCHelpers.js", function(){ - print("NPCHelpers included.");main(); -}); - -var idleAnim = "https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/idle.fbx"; -var FST = "https://s3.amazonaws.com/hifi-public/tony/fixed-sphinx/sphinx.fst"; - -Agent.isAvatar = true; -Avatar.skeletonModelURL = FST; -Avatar.displayName = "NPC"; -Avatar.position = {x: 0.3, y: -23.4, z: 8.0}; -Avatar.orientation = {x: 0, y: 1, z: 0, w: 0}; -// Avatar.position = {x: 1340.3555, y: 4.078, z: -420.1562}; -// Avatar.orientation = {x: 0, y: -0.707, z: 0, w: 0.707}; -Avatar.scale = 2; - -Messages.subscribe("interactionComs"); - -function endInteraction() { - print("ending interaction"); - blocked = false; - currentlyEngaged = false; - if(audioInjector) - audioInjector.stop(); - for (var t in timers) { - Script.clearTimeout(timers[t]); - } - if(_qid != "Restarting") { - npcRespondBlocking( - 'https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/ScratchDialogue/EarlyExit_0' + (Math.floor(Math.random() * 2) + 1).toString() + '.wav', - 'https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/Animation/reversedSphinx.fbx', - function(){ - Avatar.startAnimation('https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/Animation/Hifi_Sphinx_Anim_Entrance_Kneel_Combined_with_Intro.fbx', 0); - } - ); - } -} - -function main() { - storyURL = "https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/Sphinx.json"; - Messages.messageReceived.connect(function (channel, message, sender) { - if(!strContains(message, 'beat')) - print(sender + " -> NPC @" + Agent.sessionUUID + ": " + message); - if (channel === "interactionComs" && strContains(message, Agent.sessionUUID)) { - if (strContains(message, 'beat')) { - if(heartbeatTimeout) { - Script.clearTimeout(heartbeatTimeout); - heartbeatTimeout = false; - } - heartbeatTimeout = Script.setTimeout(endInteraction, 1500); - } - else if (strContains(message, "onFocused") && !currentlyEngaged) { - blocked = false; - currentlyEngaged = true; - currentlyUsedIndices = []; - doActionFromServer("start"); - } else if (strContains(message, "leftArea")) { - - } else if (strContains(message, "speaking")) { - - } else { - var voiceDataIndex = message.search("voiceData"); - if (voiceDataIndex != -1) { - var words = message.substr(voiceDataIndex+10); - if (!isNaN(_qid) && (strContains(words, "repeat") || (strContains(words, "say") && strContains(words, "again")))) { - doActionFromServer("init"); - } else { - doActionFromServer("words", words); - } - } - } - } - }); - // Script.update.connect(updateGem); - Avatar.startAnimation("https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/Animation/Hifi_Sphinx_Anim_Entrance_Kneel_Combined_with_Intro.fbx", 0); -} diff --git a/unpublishedScripts/interaction/Sphinx.json b/unpublishedScripts/interaction/Sphinx.json deleted file mode 100644 index 2a76417fd7..0000000000 --- a/unpublishedScripts/interaction/Sphinx.json +++ /dev/null @@ -1,159 +0,0 @@ -{ - "Name": "10 Questions", - "Defaults": - { - "Actions": - { - "positive": "var x=function(){if(questionNumber>=2){setQid('Finished');return;}var suffix=['A', 'B'][questionNumber++] + '_0' + (Math.floor(Math.random() * 2) + 2).toString() + '.wav';npcContinueStory('https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/ScratchDialogue/RightAnswer'+suffix, 'https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/Animation/RightAnswerB_02.fbx', getRandomRiddle());};x();", - "unknown": "var suffix=(Math.floor(Math.random() * 3) + 1).toString();npcContinueStory('https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/ScratchDialogue/WrongAnswer_0' + suffix + '.wav','https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/Animation/WrongAnswer_0' + suffix + '.fbx', getRandomRiddle());", - "hint": "var suffix=(Math.floor(Math.random() * 2) + 1).toString();npcContinueStory('https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/ScratchDialogue/Hint_0' + suffix + '.wav','https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/Animation/Hint_0' + suffix + '.fbx')" - }, - "Responses": - { - "positive": ["yes","yup","yeah","yahoo","sure","affirmative","okay","aye","right","exactly","course","naturally","unquestionably","positively","yep","definitely","certainly","fine","absolutely","positive","love","fantastic"], - "thinking": ["oh", "think about", "i know", "what was", "well", "not sure", "one before", "hold", "one moment", "one second", "1 second", "1 sec", "one sec"], - "hint": ["hint", "heads"] - } - }, - "Story": - [ - { - "QID": "start", - "init": "questionNumber=0;npcContinueStory('https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/ScratchDialogue/HiFi_Sphinx_Anim_Combined_Entrance_Audio.wav', 'https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/Animation/Hifi_Sphinx_Anim_Entrance_Kneel_Combined_with_Intro.fbx', getRandomRiddle());" - }, - { - "QID": "1", - "init": "npcRespondBlocking('https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/ScratchDialogue/Riddle_Blackboard.wav', 'https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/Animation/Riddle_Blackboard.fbx');", - "responses": - { - "positive": ["blackboard", "chalkboard", "chalk board", "slate"] - } - }, - { - "QID": "2", - "init": "npcRespondBlocking('https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/ScratchDialogue/Riddle_Breath.wav', 'https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/Animation/Riddle_Breath.fbx');", - "responses": - { - "positive": ["breath", "death"] - } - }, - { - "QID": "3", - "init": "npcRespondBlocking('https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/ScratchDialogue/Riddle_Clock.wav', 'https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/Animation/Riddle_Clock.fbx');", - "responses": - { - "positive": ["clock", "cock"] - } - }, - { - "QID": "4", - "init": "npcRespondBlocking('https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/ScratchDialogue/Riddle_Coffin.wav', 'https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/Animation/Riddle_Coffin.fbx');", - "responses": - { - "positive": ["coffin", "casket", "possum"] - } - }, - { - "QID": "5", - "init": "npcRespondBlocking('https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/ScratchDialogue/Riddle_Coin.wav', 'https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/Animation/Riddle_Coin.fbx');", - "responses": - { - "positive": ["coin", "boing", "coinage", "coin piece", "change", "join"] - } - }, - { - "QID": "6", - "init": "npcRespondBlocking('https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/ScratchDialogue/Riddle_Corn.wav', 'https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/Animation/Riddle_Corn.fbx');", - "responses": - { - "positive": ["corn", "born", "maize", "maze", "means", "torn", "horn", "worn", "porn"] - } - }, - { - "QID": "7", - "init": "npcRespondBlocking('https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/ScratchDialogue/Riddle_Darkness.wav', 'https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/Animation/Riddle_Darkness.fbx');", - "responses": - { - "positive": ["darkness", "dark", "blackness"] - } - }, - { - "QID": "8", - "init": "npcRespondBlocking('https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/ScratchDialogue/Riddle_Gloves.wav', 'https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/Animation/Riddle_Gloves.fbx');", - "responses": - { - "positive": ["gloves", "love"] - } - }, - { - "QID": "9", - "init": "npcRespondBlocking('https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/ScratchDialogue/Riddle_Gold.wav', 'https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/Animation/Riddle_Gold.fbx');", - "responses": - { - "positive": ["gold", "old", "bold", "cold", "told"] - } - }, - { - "QID": "10", - "init": "npcRespondBlocking('https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/ScratchDialogue/Riddle_River.wav', 'https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/Animation/Riddle_River.fbx');", - "responses": - { - "positive": ["river", "bigger", "stream", "creek", "brook"] - } - }, - { - "QID": "11", - "init": "npcRespondBlocking('https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/ScratchDialogue/Riddle_Secret.wav', 'https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/Animation/Riddle_Secret.fbx');", - "responses": - { - "positive": ["secret"] - } - }, - { - "QID": "12", - "init": "npcRespondBlocking('https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/ScratchDialogue/Riddle_Shadow.wav', 'https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/Animation/Riddle_Shadow.fbx');", - "responses": - { - "positive": ["shadow"] - } - }, - { - "QID": "13", - "init": "npcRespondBlocking('https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/ScratchDialogue/Riddle_Silence.wav', 'https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/Animation/Riddle_Silence.fbx');", - "responses": - { - "positive": ["silence", "lance", "quiet"] - } - }, - { - "QID": "14", - "init": "npcRespondBlocking('https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/ScratchDialogue/Riddle_Stairs.wav', 'https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/Animation/Riddle_Stairs.fbx');", - "responses": - { - "positive": ["stairs", "steps", "stair", "stairwell", "there's", "stairway"] - } - }, - { - "QID": "15", - "init": "npcRespondBlocking('https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/ScratchDialogue/Riddle_Umbrella.wav', 'https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/Animation/Riddle_Umbrella.fbx');", - "responses": - { - "positive": ["umbrella"] - } - }, - { - "QID": "Finished", - "init": "Script.clearTimeout(heartbeatTimeout);heartbeatTimeout = false;npcRespondBlocking('https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/ScratchDialogue/ConclusionRight_02.wav', 'https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/Animation/ConclusionRight_02.fbx', function(){runOnClient('MyAvatar.goToLocation({x: 5, y: -29, z: -63}, true, true);');setQid('Restarting');});", - "positive": "", - "negative": "", - "unknown": "" - }, - { - "QID": "Restarting", - "init": "npcRespondBlocking('', 'https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/Animation/reversedSphinx.fbx', function(){Avatar.startAnimation('https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/Animation/Hifi_Sphinx_Anim_Entrance_Kneel_Combined_with_Intro.fbx', 0);_qid='';});", - "positive": "", - "negative": "", - "unknown": "" - } - ] -} diff --git a/unpublishedScripts/marketplace/spectator-camera/SpectatorCamera.qml b/unpublishedScripts/marketplace/spectator-camera/SpectatorCamera.qml index 1b3698acd8..e0c836fb1c 100644 --- a/unpublishedScripts/marketplace/spectator-camera/SpectatorCamera.qml +++ b/unpublishedScripts/marketplace/spectator-camera/SpectatorCamera.qml @@ -25,6 +25,8 @@ Rectangle { HifiStylesUit.HifiConstants { id: hifi; } id: root; + property bool uiReady: false; + property bool processingStillSnapshot: false; property bool processing360Snapshot: false; // Style color: "#404040"; @@ -58,7 +60,7 @@ Rectangle { // "Spectator" text HifiStylesUit.RalewaySemiBold { id: titleBarText; - text: "Spectator Camera"; + text: "Spectator Camera 2.2"; // Anchors anchors.left: parent.left; anchors.leftMargin: 30; @@ -91,13 +93,16 @@ Rectangle { } onClicked: { + if (!checked) { + flashCheckBox.checked = false; + } sendToScript({method: (checked ? 'spectatorCameraOn' : 'spectatorCameraOff')}); sendToScript({method: 'updateCameravFoV', vFoV: fieldOfViewSlider.value}); } background: Rectangle { color: parent.checked ? "#1FC6A6" : hifi.colors.white; - implicitWidth: masterSwitch.switchWidth; + implicitWidth: masterSwitch.width; implicitHeight: masterSwitch.height; radius: height/2; } @@ -127,7 +132,7 @@ Rectangle { z: 999; id: processingSnapshot; anchors.fill: parent; - visible: root.processing360Snapshot; + visible: root.processing360Snapshot || !root.uiReady; color: Qt.rgba(0.0, 0.0, 0.0, 0.85); // This object is always used in a popup. @@ -149,7 +154,7 @@ Rectangle { } HifiStylesUit.RalewaySemiBold { - text: "Processing..."; + text: root.uiReady ? "Processing..." : ""; // Anchors anchors.top: processingImage.bottom; anchors.topMargin: 4; @@ -202,10 +207,20 @@ Rectangle { verticalAlignment: Text.AlignVCenter; } + HifiStylesUit.FiraSansRegular { + text: ":)"; + size: 28; + color: hifi.colors.white; + visible: root.processing360Snapshot || root.processingStillSnapshot; + anchors.fill: parent; + horizontalAlignment: Text.AlignHCenter; + verticalAlignment: Text.AlignVCenter; + } + // Spectator Camera Preview Hifi.ResourceImageItem { id: spectatorCameraPreview; - visible: masterSwitch.checked; + visible: masterSwitch.checked && !root.processing360Snapshot && !root.processingStillSnapshot; url: showCameraView.checked || !HMD.active ? "resource://spectatorCameraFrame" : "resource://hmdPreviewFrame"; ready: masterSwitch.checked; mirrorVertically: true; @@ -311,7 +326,30 @@ Rectangle { } } } - + + HifiStylesUit.HiFiGlyphs { + id: flashGlyph; + visible: flashCheckBox.visible; + text: hifi.glyphs.lightning; + size: 26; + color: hifi.colors.white; + anchors.verticalCenter: flashCheckBox.verticalCenter; + anchors.right: flashCheckBox.left; + anchors.rightMargin: -2; + } + HifiControlsUit.CheckBox { + id: flashCheckBox; + visible: masterSwitch.checked; + color: hifi.colors.white; + colorScheme: hifi.colorSchemes.dark; + anchors.right: takeSnapshotButton.left; + anchors.rightMargin: -8; + anchors.verticalCenter: takeSnapshotButton.verticalCenter; + boxSize: 22; + onClicked: { + sendToScript({method: 'setFlashStatus', enabled: checked}); + } + } HifiControlsUit.Button { id: takeSnapshotButton; enabled: masterSwitch.checked; @@ -325,6 +363,7 @@ Rectangle { width: 135; height: 35; onClicked: { + root.processingStillSnapshot = true; sendToScript({method: 'takeSecondaryCameraSnapshot'}); } } @@ -582,8 +621,12 @@ Rectangle { // function fromScript(message) { switch (message.method) { - case 'updateSpectatorCameraCheckbox': - masterSwitch.checked = message.params; + case 'initializeUI': + masterSwitch.checked = message.masterSwitchOn; + flashCheckBox.checked = message.flashCheckboxChecked; + showCameraView.checked = message.monitorShowsCamView; + showHmdPreview.checked = !message.monitorShowsCamView; + root.uiReady = true; break; case 'updateMonitorShowsSwitch': showCameraView.checked = message.params; @@ -611,6 +654,12 @@ Rectangle { case 'finishedProcessing360Snapshot': root.processing360Snapshot = false; break; + case 'startedProcessingStillSnapshot': + root.processingStillSnapshot = true; + break; + case 'finishedProcessingStillSnapshot': + root.processingStillSnapshot = false; + break; default: console.log('Unrecognized message from spectatorCamera.js:', JSON.stringify(message)); } diff --git a/unpublishedScripts/marketplace/spectator-camera/flashOff.wav b/unpublishedScripts/marketplace/spectator-camera/flashOff.wav new file mode 100644 index 0000000000..fef7668de8 Binary files /dev/null and b/unpublishedScripts/marketplace/spectator-camera/flashOff.wav differ diff --git a/unpublishedScripts/marketplace/spectator-camera/flashOn.wav b/unpublishedScripts/marketplace/spectator-camera/flashOn.wav new file mode 100644 index 0000000000..f7e95c9607 Binary files /dev/null and b/unpublishedScripts/marketplace/spectator-camera/flashOn.wav differ diff --git a/unpublishedScripts/marketplace/spectator-camera/spectatorCamera.app.json b/unpublishedScripts/marketplace/spectator-camera/spectatorCamera.app.json index e71c657581..6370454f97 100644 --- a/unpublishedScripts/marketplace/spectator-camera/spectatorCamera.app.json +++ b/unpublishedScripts/marketplace/spectator-camera/spectatorCamera.app.json @@ -1,4 +1,4 @@ { - "scriptURL": "http://mpassets-staging.highfidelity.com/26156ea5-cdff-43c2-9581-d6b0fa5e00ef-v1/spectatorCamera.js", - "homeURL": "http://mpassets-staging.highfidelity.com/26156ea5-cdff-43c2-9581-d6b0fa5e00ef-v1/SpectatorCamera.qml" + "scriptURL": "http://mpassets.highfidelity.com/80d02930-f409-4f1a-824f-ae0109da32d6-v1/spectatorCamera.js", + "homeURL": "http://mpassets.highfidelity.com/80d02930-f409-4f1a-824f-ae0109da32d6-v1/SpectatorCamera.qml" } \ No newline at end of file diff --git a/unpublishedScripts/marketplace/spectator-camera/spectatorCamera.js b/unpublishedScripts/marketplace/spectator-camera/spectatorCamera.js index e95c05aef9..3e749e38a2 100644 --- a/unpublishedScripts/marketplace/spectator-camera/spectatorCamera.js +++ b/unpublishedScripts/marketplace/spectator-camera/spectatorCamera.js @@ -83,7 +83,8 @@ "position": cameraPosition, "shapeType": "simple-compound", "type": "Model", - "userData": "{\"grabbableKey\":{\"grabbable\":true}}" + "userData": "{\"grabbableKey\":{\"grabbable\":true}}", + "isVisibleInSecondaryCamera": false }, true); spectatorCameraConfig.attachedEntityId = camera; updateOverlay(); @@ -96,7 +97,7 @@ if (button) { button.editProperties({ isActive: onSpectatorCameraScreen || camera }); } - Audio.playSound(CAMERA_ON_SOUND, { + Audio.playSound(SOUND_CAMERA_ON, { volume: 0.15, position: cameraPosition, localOnly: true @@ -112,8 +113,14 @@ var WAIT_AFTER_DOMAIN_SWITCH_BEFORE_CAMERA_DELETE_MS = 1 * 1000; function spectatorCameraOff(isChangingDomains) { function deleteCamera() { - Entities.deleteEntity(camera); - camera = false; + if (flash) { + Entities.deleteEntity(flash); + flash = false; + } + if (camera) { + Entities.deleteEntity(camera); + camera = false; + } if (button) { // Change button to active when window is first openend OR if the camera is on, false otherwise. button.editProperties({ isActive: onSpectatorCameraScreen || camera }); @@ -390,21 +397,81 @@ } var takeSnapshotControllerMapping; var takeSnapshotControllerMappingName = 'Hifi-SpectatorCamera-Mapping-TakeSnapshot'; + + var flash = false; + function setFlashStatus(enabled) { + var cameraPosition = Entities.getEntityProperties(camera, ["positon"]).position; + if (enabled) { + if (camera) { + Audio.playSound(SOUND_FLASH_ON, { + position: cameraPosition, + localOnly: true, + volume: 0.8 + }); + flash = Entities.addEntity({ + "collidesWith": "", + "collisionMask": 0, + "color": { + "blue": 173, + "green": 252, + "red": 255 + }, + "cutoff": 90, + "dimensions": { + "x": 4, + "y": 4, + "z": 4 + }, + "dynamic": false, + "falloffRadius": 0.20000000298023224, + "intensity": 37, + "isSpotlight": true, + "localRotation": { w: 1, x: 0, y: 0, z: 0 }, + "localPosition": { x: 0, y: -0.005, z: -0.08 }, + "name": "Camera Flash", + "type": "Light", + "parentID": camera, + }, true); + } + } else { + if (flash) { + Audio.playSound(SOUND_FLASH_OFF, { + position: cameraPosition, + localOnly: true, + volume: 0.8 + }); + Entities.deleteEntity(flash); + flash = false; + } + } + } + function onStillSnapshotTaken() { Render.getConfig("SecondaryCameraJob.ToneMapping").curve = 1; + sendToQml({ + method: 'finishedProcessingStillSnapshot' + }); } function maybeTakeSnapshot() { if (camera) { + sendToQml({ + method: 'startedProcessingStillSnapshot' + }); + Render.getConfig("SecondaryCameraJob.ToneMapping").curve = 0; // Wait a moment before taking the snapshot for the tonemapping curve to update Script.setTimeout(function () { - Audio.playSound(SNAPSHOT_SOUND, { + Audio.playSound(SOUND_SNAPSHOT, { position: { x: MyAvatar.position.x, y: MyAvatar.position.y, z: MyAvatar.position.z }, localOnly: true, volume: 1.0 }); Window.takeSecondaryCameraSnapshot(); }, 250); + } else { + sendToQml({ + method: 'finishedProcessingStillSnapshot' + }); } } function on360SnapshotTaken() { @@ -417,7 +484,7 @@ } function maybeTake360Snapshot() { if (camera) { - Audio.playSound(SNAPSHOT_SOUND, { + Audio.playSound(SOUND_SNAPSHOT, { position: { x: MyAvatar.position.x, y: MyAvatar.position.y, z: MyAvatar.position.z }, localOnly: true, volume: 1.0 @@ -507,18 +574,8 @@ } function updateSpectatorCameraQML() { - sendToQml({ method: 'updateSpectatorCameraCheckbox', params: !!camera }); - sendToQml({ method: 'updateMonitorShowsSwitch', params: monitorShowsCameraView }); - if (!switchViewControllerMapping || !takeSnapshotControllerMapping) { - registerButtonMappings(); - } else { - sendToQml({ - method: 'updateControllerMappingCheckbox', - switchViewSetting: switchViewFromController, - takeSnapshotSetting: takeSnapshotFromController, - controller: controllerType - }); - } + sendToQml({ method: 'initializeUI', masterSwitchOn: !!camera, flashCheckboxChecked: !!flash, monitorShowsCamView: monitorShowsCameraView }); + registerButtonMappings(); Menu.setIsOptionChecked("Disable Preview", false); Menu.setIsOptionChecked("Mono Preview", true); } @@ -536,9 +593,13 @@ button.editProperties({ isActive: onSpectatorCameraScreen || camera }); } - if (onSpectatorCameraScreen) { - updateSpectatorCameraQML(); - } + // In the case of a remote QML app, it takes a bit of time + // for the event bridge to actually connect, so we have to wait... + Script.setTimeout(function () { + if (onSpectatorCameraScreen) { + updateSpectatorCameraQML(); + } + }, 700); } // Function Name: sendToQml() @@ -575,6 +636,9 @@ case 'updateCameravFoV': spectatorCameraConfig.vFoV = message.vFoV; break; + case 'setFlashStatus': + setFlashStatus(message.enabled); + break; case 'takeSecondaryCameraSnapshot': maybeTakeSnapshot(); break; @@ -599,9 +663,7 @@ // Description: // -Called from C++ when HMD mode is changed. The argument "isHMDMode" is true if HMD is on; false otherwise. function onHMDChanged(isHMDMode) { - if (!switchViewControllerMapping || !takeSnapshotControllerMapping) { - registerButtonMappings(); - } + registerButtonMappings(); if (!isHMDMode) { setMonitorShowsCameraView(false); } else { @@ -645,8 +707,10 @@ } // These functions will be called when the script is loaded. - var CAMERA_ON_SOUND = SoundCache.getSound(Script.resolvePath("cameraOn.wav")); - var SNAPSHOT_SOUND = SoundCache.getSound(Script.resourcesPath() + "sounds/snapshot/snap.wav"); + var SOUND_CAMERA_ON = SoundCache.getSound(Script.resolvePath("cameraOn.wav")); + var SOUND_SNAPSHOT = SoundCache.getSound(Script.resolvePath("snap.wav")); + var SOUND_FLASH_ON = SoundCache.getSound(Script.resolvePath("flashOn.wav")); + var SOUND_FLASH_OFF = SoundCache.getSound(Script.resolvePath("flashOff.wav")); startup(); Script.scriptEnding.connect(shutdown);