Merge branch 'master' of github.com:highfidelity/hifi into stt_parenting2

This commit is contained in:
sabrina-shanman 2018-10-01 17:17:32 -07:00
commit 67dc526ee2
205 changed files with 4551 additions and 1029 deletions

View file

@ -133,6 +133,10 @@ dependencies {
implementation 'com.android.support.constraint:constraint-layout:1.0.2'
implementation 'com.android.support:design:26.1.0'
compile 'com.android.support:support-v4:26.1.0'
compile 'com.android.support:appcompat-v7:26.1.0'
compile 'com.android.support:support-vector-drawable:26.1.0'
implementation 'com.android.support:appcompat-v7:26.1.0'
compile 'com.android.support:recyclerview-v7:26.1.0'
compile 'com.android.support:cardview-v7:26.1.0'

View file

@ -156,7 +156,7 @@ JNIEXPORT void Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeOnCrea
JavaVM* jvm;
env->GetJavaVM(&jvm);
QObject::connect(&AndroidHelper::instance(), &AndroidHelper::androidActivityRequested, [jvm](const QString& a, const bool backToScene, QList<QString> args) {
QObject::connect(&AndroidHelper::instance(), &AndroidHelper::androidActivityRequested, [jvm](const QString& a, const bool backToScene, QMap<QString, QString> args) {
JNIEnv* myNewEnv;
JavaVMAttachArgs jvmArgs;
jvmArgs.version = JNI_VERSION_1_6; // choose your JNI version
@ -182,9 +182,11 @@ JNIEXPORT void Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeOnCrea
jmethodID mapClassConstructor = myNewEnv->GetMethodID(hashMapClass, "<init>", "()V");
jobject hashmap = myNewEnv->NewObject(hashMapClass, mapClassConstructor);
jmethodID mapClassPut = myNewEnv->GetMethodID(hashMapClass, "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
for (const QString& arg: args) {
QAndroidJniObject jArg = QAndroidJniObject::fromString(arg);
myNewEnv->CallObjectMethod(hashmap, mapClassPut, QAndroidJniObject::fromString("url").object<jstring>(), jArg.object<jstring>());
QMap<QString, QString>::iterator i;
for (i = args.begin(); i != args.end(); ++i) {
QAndroidJniObject jKey = QAndroidJniObject::fromString(i.key());
QAndroidJniObject jValue = QAndroidJniObject::fromString(i.value());
myNewEnv->CallObjectMethod(hashmap, mapClassPut, jKey.object<jstring>(), jValue.object<jstring>());
}
__interfaceActivity.callMethod<void>("openAndroidActivity", "(Ljava/lang/String;ZLjava/util/HashMap;)V", string.object<jstring>(), jBackToScene, hashmap);
if (attachedHere) {
@ -255,6 +257,16 @@ JNIEXPORT jstring JNICALL Java_io_highfidelity_hifiinterface_fragment_HomeFragme
return env->NewStringUTF(lastLocation.toString().toLatin1().data());
}
JNIEXPORT void JNICALL
Java_io_highfidelity_hifiinterface_fragment_LoginFragment_nativeCancelLogin(JNIEnv *env, jobject instance) {
auto accountManager = DependencyManager::get<AccountManager>();
QObject::disconnect(accountManager.data(), &AccountManager::loginComplete, nullptr, nullptr);
QObject::disconnect(accountManager.data(), &AccountManager::loginFailed, nullptr, nullptr);
}
JNIEXPORT void JNICALL
Java_io_highfidelity_hifiinterface_fragment_LoginFragment_nativeLogin(JNIEnv *env, jobject instance,
jstring username_, jstring password_,
@ -273,17 +285,23 @@ Java_io_highfidelity_hifiinterface_fragment_LoginFragment_nativeLogin(JNIEnv *en
QObject::connect(accountManager.data(), &AccountManager::loginComplete, [](const QUrl& authURL) {
jboolean jSuccess = (jboolean) true;
__loginCompletedListener.callMethod<void>("handleLoginCompleted", "(Z)V", jSuccess);
if (__loginCompletedListener.isValid()) {
__loginCompletedListener.callMethod<void>("handleLoginCompleted", "(Z)V", jSuccess);
}
});
QObject::connect(accountManager.data(), &AccountManager::loginFailed, []() {
jboolean jSuccess = (jboolean) false;
__loginCompletedListener.callMethod<void>("handleLoginCompleted", "(Z)V", jSuccess);
if (__loginCompletedListener.isValid()) {
__loginCompletedListener.callMethod<void>("handleLoginCompleted", "(Z)V", jSuccess);
}
});
QObject::connect(accountManager.data(), &AccountManager::usernameChanged, [](const QString& username) {
QAndroidJniObject string = QAndroidJniObject::fromString(username);
__usernameChangedListener.callMethod<void>("handleUsernameChanged", "(Ljava/lang/String;)V", string.object<jstring>());
if (__usernameChangedListener.isValid()) {
__usernameChangedListener.callMethod<void>("handleUsernameChanged", "(Ljava/lang/String;)V", string.object<jstring>());
}
});
QMetaObject::invokeMethod(accountManager.data(), "requestAccessToken",

View file

@ -39,6 +39,7 @@ import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import io.highfidelity.hifiinterface.fragment.WebViewFragment;
import io.highfidelity.hifiinterface.receiver.HeadsetStateReceiver;
@ -303,14 +304,22 @@ public class InterfaceActivity extends QtActivity implements WebViewFragment.OnW
switch (activityName) {
case "Home":
case "Privacy Policy":
case "Login": {
nativeBeforeEnterBackground();
Intent intent = new Intent(this, MainActivity.class);
intent.putExtra(MainActivity.EXTRA_FRAGMENT, activityName);
intent.putExtra(MainActivity.EXTRA_BACK_TO_SCENE, backToScene);
startActivity(intent);
break;
}
case "Login":
nativeBeforeEnterBackground();
Intent loginIntent = new Intent(this, MainActivity.class);
loginIntent.putExtra(MainActivity.EXTRA_FRAGMENT, activityName);
loginIntent.putExtra(MainActivity.EXTRA_BACK_TO_SCENE, backToScene);
if (args != null && args.containsKey(DOMAIN_URL)) {
loginIntent.putExtra(DOMAIN_URL, (String) args.get(DOMAIN_URL));
}
startActivity(loginIntent);
break;
case "WebView":
runOnUiThread(() -> {
webSlidingDrawer.setVisibility(View.VISIBLE);

View file

@ -29,6 +29,9 @@ import android.widget.TextView;
import com.squareup.picasso.Callback;
import com.squareup.picasso.Picasso;
import java.util.HashMap;
import java.util.Map;
import io.highfidelity.hifiinterface.fragment.FriendsFragment;
import io.highfidelity.hifiinterface.fragment.HomeFragment;
import io.highfidelity.hifiinterface.fragment.LoginFragment;
@ -45,6 +48,7 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On
public static final String DEFAULT_FRAGMENT = "Home";
public static final String EXTRA_FRAGMENT = "fragment";
public static final String EXTRA_BACK_TO_SCENE = "backToScene";
public static final String EXTRA_BACK_TO_URL = "url";
private String TAG = "HighFidelity";
@ -62,6 +66,7 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On
private MenuItem mPeopleMenuItem;
private boolean backToScene;
private String backToUrl;
@Override
protected void onCreate(Bundle savedInstanceState) {
@ -105,9 +110,8 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On
loadFragment(DEFAULT_FRAGMENT);
}
if (getIntent().hasExtra(EXTRA_BACK_TO_SCENE)) {
backToScene = getIntent().getBooleanExtra(EXTRA_BACK_TO_SCENE, false);
}
backToScene = getIntent().getBooleanExtra(EXTRA_BACK_TO_SCENE, false);
backToUrl = getIntent().getStringExtra(EXTRA_BACK_TO_URL);
}
}
@ -301,7 +305,7 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On
}
private void goToLastLocation() {
goToDomain("");
goToDomain(backToUrl != null? backToUrl : "");
}
private void goToDomain(String domainUrl) {

View file

@ -4,12 +4,10 @@ import android.app.Activity;
import android.app.Fragment;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@ -19,8 +17,13 @@ import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import org.qtproject.qt5.android.QtNative;
import io.highfidelity.hifiinterface.R;
import static org.qtproject.qt5.android.QtActivityDelegate.ApplicationActive;
import static org.qtproject.qt5.android.QtActivityDelegate.ApplicationInactive;
public class LoginFragment extends Fragment {
private EditText mUsername;
@ -32,6 +35,7 @@ public class LoginFragment extends Fragment {
private ProgressDialog mDialog;
public native void nativeLogin(String username, String password, Activity usernameChangedListener);
public native void nativeCancelLogin();
private LoginFragment.OnLoginInteractionListener mListener;
@ -55,44 +59,6 @@ public class LoginFragment extends Fragment {
mLoginButton = rootView.findViewById(R.id.loginButton);
mForgotPassword = rootView.findViewById(R.id.forgotPassword);
mUsername.addTextChangedListener(new TextWatcher() {
boolean ignoreNextChange = false;
boolean hadBlankSpace = false;
@Override
public void beforeTextChanged(CharSequence charSequence, int start, int count, int after) {
hadBlankSpace = charSequence.length() > 0 && charSequence.charAt(charSequence.length()-1) == ' ';
}
@Override
public void onTextChanged(CharSequence charSequence, int start, int count, int after) {
}
@Override
public void afterTextChanged(Editable editable) {
if (!ignoreNextChange) {
ignoreNextChange = true;
boolean spaceFound = false;
for (int i = 0; i < editable.length(); i++) {
if (editable.charAt(i) == ' ') {
spaceFound=true;
editable.delete(i, i + 1);
i--;
}
}
if (hadBlankSpace && !spaceFound && editable.length() > 0) {
editable.delete(editable.length()-1, editable.length());
}
editable.append(' ');
ignoreNextChange = false;
}
}
});
mLoginButton.setOnClickListener(view -> login());
mForgotPassword.setOnClickListener(view -> forgotPassword());
@ -125,10 +91,19 @@ public class LoginFragment extends Fragment {
mListener = null;
}
@Override
public void onResume() {
super.onResume();
// This hack intends to keep Qt threads running even after the app comes from background
QtNative.setApplicationState(ApplicationActive);
}
@Override
public void onStop() {
super.onStop();
cancelActivityIndicator();
// Leave the Qt app paused
QtNative.setApplicationState(ApplicationInactive);
hideKeyboard();
}
@ -164,7 +139,15 @@ public class LoginFragment extends Fragment {
mDialog = new ProgressDialog(getContext());
}
mDialog.setMessage(getString(R.string.logging_in));
mDialog.setCancelable(false);
mDialog.setCancelable(true);
mDialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface dialogInterface) {
nativeCancelLogin();
cancelActivityIndicator();
mLoginButton.setEnabled(true);
}
});
mDialog.show();
}
@ -184,7 +167,6 @@ public class LoginFragment extends Fragment {
}
public void handleLoginCompleted(boolean success) {
Log.d("[LOGIN]", "handleLoginCompleted " + success);
getActivity().runOnUiThread(() -> {
mLoginButton.setEnabled(true);
cancelActivityIndicator();

View file

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="36dp"
android:height="22dp"
android:viewportWidth="36"
android:viewportHeight="22">
<path
android:fillColor="#3D3D3D"
android:fillType="evenOdd"
android:pathData="M3.59534,11.0156 C6.16042,13.4128,9.65987,15.5898,13.6042,16.1774 C17.686,16.7856,22.4164,15.7196,27.3057,11.0659 C22.0721,6.07309,17.0642,5.14115,12.9153,5.90073 C8.99427,6.61859,5.69298,8.87688,3.59534,11.0156 Z M12.455,3.27591 C17.7727,2.30235,23.9836,3.74895,30.1053,10.1333 L31,11.0664 L30.1053,11.9994 C24.3636,17.9875,18.4774,19.5983,13.2276,18.8161 C8.06048,18.0463,3.70384,14.9892,0.837069,11.9994 L0,11.1265 L0.778477,10.1986 C3.05338,7.48717,7.2318,4.23217,12.455,3.27591 Z" />
<path
android:fillColor="#3D3D3D"
android:pathData="M15.6539,7.11119 C17.6719,7.11119,19.3078,8.81726,19.3078,10.9218 C19.3078,13.0263,17.6719,14.7324,15.6539,14.7324 C13.6359,14.7324,12,13.0263,12,10.9218 C12,8.81726,13.6359,7.11119,15.6539,7.11119 Z" />
<!--path
android:fillColor="#000000"
android:strokeColor="#ffffff"
android:strokeWidth="2.7"
android:strokeLineCap="round"
android:pathData="M27,2.90919 L8.90919,21" /-->
<path
android:fillColor="#000000"
android:strokeColor="#3D3D3D"
android:strokeWidth="3"
android:strokeLineCap="round"
android:pathData="M25,2.12132 L7.12132,20" />
</vector>

View file

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="36dp"
android:height="16dp"
android:viewportWidth="36"
android:viewportHeight="16">
<path
android:fillColor="#3D3D3D"
android:fillType="evenOdd"
android:pathData="M3.59534,8.01564 C6.16042,10.4128,9.65987,12.5898,13.6042,13.1774 C17.686,13.7856,22.4164,12.7196,27.3057,8.06585 C22.0721,3.07309,17.0642,2.14115,12.9153,2.90073 C8.99427,3.61859,5.69298,5.87688,3.59534,8.01564 Z M12.455,0.275915 C17.7727,-0.697651,23.9836,0.748949,30.1053,7.13329 L31,8.06636 L30.1053,8.99944 C24.3636,14.9875,18.4774,16.5983,13.2276,15.8161 C8.06048,15.0463,3.70384,11.9892,0.837069,8.99944 L0,8.12646 L0.778477,7.1986 C3.05338,4.48717,7.2318,1.23217,12.455,0.275915 Z" />
<path
android:fillColor="#3D3D3D"
android:pathData="M15.6441,4.11118 C17.6621,4.11118,19.298,5.81725,19.298,7.92179 C19.298,10.0263,17.6621,11.7324,15.6441,11.7324 C13.6261,11.7324,11.9902,10.0263,11.9902,7.92179 C11.9902,5.81725,13.6261,4.11118,15.6441,4.11118 Z" />
</vector>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_checked="true" android:drawable="@drawable/ic_eye_noshow"/>
<item android:drawable="@drawable/ic_eye_show" />
</selector>

View file

@ -41,38 +41,51 @@
android:paddingTop="14dp"
android:ems="10"
android:fontFamily="@font/raleway"
android:textSize="14sp"
android:textSize="17sp"
android:inputType="textEmailAddress"
android:textStyle="italic"
android:textColor="@color/editTextColor"
android:textColorHint="@color/editTextColor"
android:gravity="right|center_vertical"
android:gravity="left|center_vertical"
app:layout_constraintTop_toBottomOf="@id/header"
android:layout_marginTop="70dp"
android:hint="@string/username_or_email" />
<EditText
<android.support.design.widget.TextInputLayout
android:id="@+id/passwordLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="46dp"
android:layout_marginRight="46dp"
app:passwordToggleTint="@color/showPasswordColor"
app:passwordToggleEnabled="true"
app:hintAnimationEnabled="false"
app:passwordToggleDrawable="@drawable/selector_show_password"
app:hintEnabled="false"
app:layout_constraintTop_toBottomOf="@id/username"
android:layout_marginTop="13dp"
>
<android.support.design.widget.TextInputEditText
android:id="@+id/password"
android:layout_width="match_parent"
android:layout_height="35dp"
android:layout_marginLeft="46dp"
android:layout_marginRight="46dp"
android:background="@drawable/rounded_edit"
android:padding="7dp"
android:paddingRight="12dp"
android:drawablePadding="55dp"
android:paddingTop="14dp"
android:drawableEnd="@drawable/ic_eye_noshow"
android:ems="10"
android:fontFamily="@font/raleway"
android:textSize="14sp"
android:inputType="textPassword"
android:textSize="17sp"
android:textStyle="italic"
android:textColor="@color/editTextColor"
android:textColorHint="@color/editTextColor"
android:gravity="right|center_vertical"
app:layout_constraintTop_toBottomOf="@id/username"
android:gravity="left|center_vertical"
android:imeOptions="actionDone"
android:hint="@string/password"
android:layout_marginTop="13dp"
android:imeOptions="actionDone"/>
android:inputType="textPassword" />
</android.support.design.widget.TextInputLayout>
<Button
android:id="@+id/loginButton"
@ -90,7 +103,7 @@
android:textAllCaps="false"
android:textSize="15sp"
app:layout_constraintRight_toRightOf="@id/username"
app:layout_constraintTop_toBottomOf="@id/password"
app:layout_constraintTop_toBottomOf="@id/passwordLayout"
app:layout_goneMarginTop="4dp"/>
<TextView
@ -102,7 +115,7 @@
android:text="@string/forgot_password"
android:textStyle="italic"
android:paddingRight="10dp"
app:layout_constraintLeft_toLeftOf="@id/password"
app:layout_constraintLeft_toLeftOf="@id/passwordLayout"
app:layout_constraintTop_toTopOf="@id/loginButton"
app:layout_constraintRight_toLeftOf="@id/loginButton"
android:textColor="@color/colorButton1"/>

View file

@ -6,6 +6,7 @@
<color name="colorAccent">#54D7FD</color>
<color name="backgroundEditText">#E3E3E3</color>
<color name="editTextColor">#575757</color>
<color name="showPasswordColor">#3D3D3D</color>
<color name="tabs">#1EB5EC</color>
<color name="colorButton1">#00B4EF</color>
<color name="backgroundDark">#333333</color>

View file

@ -10,8 +10,8 @@
<string name="popular">POPULAR</string>
<string name="bookmarks">BOOKMARKS</string>
<string name="goto_url_hint">Type a domain url</string>
<string name="username_or_email">Username or email\u00A0</string>
<string name="password">Password\u00A0</string>
<string name="username_or_email">Username or email</string>
<string name="password">Password</string>
<string name="login">Login</string>
<string name="logout">Logout</string>
<string name="forgot_password">Forgot password?\u00A0</string>

View file

@ -1110,7 +1110,36 @@ function moveTableRow(row, move_up) {
}
// we need to fire a change event on one of the remaining inputs so that the sidebar badge is updated
badgeForDifferences($(table))
badgeForDifferences($(table));
// figure out which group this row is in
var panelParentID = row.closest('.panel').attr('id');
// get the short name for the setting from the table
var tableShortName = row.closest('table').data('short-name');
var changed = tableHasChanged(panelParentID, tableShortName);
$(table).find('.' + Settings.DATA_ROW_CLASS).each(function(){
var hiddenInput = $(this).find('td.' + Settings.DATA_COL_CLASS + ' input');
if (changed) {
hiddenInput.attr('data-changed', true);
} else {
hiddenInput.removeAttr('data-changed');
}
});
}
function tableHasChanged(panelParentID, tableShortName) {
// get a JSON representation of that section
var panelSettingJSON = form2js(panelParentID, ".", false, cleanupFormValues, true)[panelParentID][tableShortName]
if (Settings.initialValues[panelParentID]) {
var initialPanelSettingJSON = Settings.initialValues[panelParentID][tableShortName]
} else {
var initialPanelSettingJSON = {};
}
return !_.isEqual(panelSettingJSON, initialPanelSettingJSON);
}
function updateDataChangedForSiblingRows(row, forceTrue) {
@ -1123,16 +1152,8 @@ function updateDataChangedForSiblingRows(row, forceTrue) {
// get the short name for the setting from the table
var tableShortName = row.closest('table').data('short-name')
// get a JSON representation of that section
var panelSettingJSON = form2js(panelParentID, ".", false, cleanupFormValues, true)[panelParentID][tableShortName]
if (Settings.initialValues[panelParentID]) {
var initialPanelSettingJSON = Settings.initialValues[panelParentID][tableShortName]
} else {
var initialPanelSettingJSON = {};
}
// if they are equal, we don't need data-changed
isTrue = !_.isEqual(panelSettingJSON, initialPanelSettingJSON)
isTrue = tableHasChanged(panelParentID, tableShortName);
} else {
isTrue = true
}
@ -1140,9 +1161,9 @@ function updateDataChangedForSiblingRows(row, forceTrue) {
row.siblings('.' + Settings.DATA_ROW_CLASS).each(function(){
var hiddenInput = $(this).find('td.' + Settings.DATA_COL_CLASS + ' input')
if (isTrue) {
hiddenInput.attr('data-changed', isTrue)
hiddenInput.attr('data-changed', isTrue);
} else {
hiddenInput.removeAttr('data-changed')
hiddenInput.removeAttr('data-changed');
}
})
}

View file

@ -2906,7 +2906,7 @@ void DomainServer::updateReplicationNodes(ReplicationServerDirection direction)
// collect them in a vector to separately remove them with handleKillNode (since eachNode has a read lock and
// we cannot recursively take the write lock required by handleKillNode)
std::vector<SharedNodePointer> nodesToKill;
nodeList->eachNode([this, direction, replicationNodesInSettings, replicationDirection, &nodesToKill](const SharedNodePointer& otherNode) {
nodeList->eachNode([direction, replicationNodesInSettings, replicationDirection, &nodesToKill](const SharedNodePointer& otherNode) {
if ((direction == Upstream && NodeType::isUpstream(otherNode->getType()))
|| (direction == Downstream && NodeType::isDownstream(otherNode->getType()))) {
bool nodeInSettings = find(replicationNodesInSettings.cbegin(), replicationNodesInSettings.cend(),

View file

@ -0,0 +1,57 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Generator: Adobe Illustrator 19.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
x="0px"
y="0px"
viewBox="0 0 50 50"
style="enable-background:new 0 0 50 50;"
xml:space="preserve"
id="svg2"
inkscape:version="0.91 r13725"
sodipodi:docname="goto-a.svg"><metadata
id="metadata14"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
id="defs12" /><sodipodi:namedview
pagecolor="#ff0000"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="852"
inkscape:window-height="480"
id="namedview10"
showgrid="false"
inkscape:zoom="4.72"
inkscape:cx="25"
inkscape:cy="25"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="0"
inkscape:current-layer="svg2" /><style
type="text/css"
id="style4">
.st0{fill:#FFFFFF;}
.st1{fill:#EF3B4E;}
</style>
<circle class="st1" cx="44.1" cy="6" r="5.6"/>
<g
id="Layer_2" /><g
id="Layer_1"
style="fill:#000000;fill-opacity:1"><path
class="st0"
d="M47.2,41.3l-9.1-9.1c-0.8-0.8-1.9-1.1-3-1l-2.4-2.4c1.8-2.6,2.8-5.7,2.8-9c0-8.9-7.2-16.1-16.1-16.1 S3.3,11,3.3,19.8c0,8.9,7.2,16.1,16.1,16.1c4.1,0,7.8-1.5,10.6-4l2.2,2.2c-0.2,1.1,0.1,2.2,1,3l9.1,9.1c1.4,1.4,3.6,1.4,4.9,0 C48.5,44.9,48.5,42.7,47.2,41.3z M19.4,32.2c-6.8,0-12.3-5.5-12.3-12.3c0-6.8,5.5-12.3,12.3-12.3s12.3,5.5,12.3,12.3 C31.8,26.6,26.2,32.2,19.4,32.2z"
id="path8"
style="fill:#000000;fill-opacity:1" /></g></svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View file

Before

Width:  |  Height:  |  Size: 911 B

After

Width:  |  Height:  |  Size: 911 B

View file

@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" x="0px" y="0px" viewBox="0 0 96 96" style="enable-background:new 0 0 96 96;" xml:space="preserve">
<style type="text/css">
.st1{fill:#EF3B4E;}
</style>
<circle class="st1" cx="84.6" cy="11.5" r="10.75"/>
<g><path d="M2.4,70.5c0,6.1,4.9,11,11,11H76c6.1,0,11-4.9,11-11V59.6c3.7-0.7,6.6-3.9,6.6-7.9v-7.5c0-3.9-2.8-7.2-6.6-7.9V25.5 c0-6.1-4.9-11-11-11H13.4c-6.1,0-11,4.9-11,11V70.5z M87.6,51.8c0,1.1-0.9,2-2,2H72.2c-2.8,0-5-2.2-5-5v-1.5c0-2.8,2.2-5,5-5h13.3 c1.1,0,2,0.9,2,2V51.8z M8.4,25.5c0-2.8,2.2-5,5-5H76c2.8,0,5,2.2,5,5v10.7h-8.7c-6.1,0-11,4.9-11,11v1.5c0,6.1,4.9,11,11,11H81 v10.7c0,2.8-2.2,5-5,5H13.4c-2.8,0-5-2.2-5-5V25.5z"></path></g></svg>

After

Width:  |  Height:  |  Size: 755 B

View file

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 21.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 96 96" style="enable-background:new 0 0 96 96;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FFFFFF;}
.st1{fill:#EF3B4E;}
</style>
<circle class="st1" cx="84.6" cy="11.5" r="10.75"/>
<g>
<path class="st0" d="M2.4,70.5c0,6.1,4.9,11,11,11H76c6.1,0,11-4.9,11-11V59.6c3.7-0.7,6.6-3.9,6.6-7.9v-7.5c0-3.9-2.8-7.2-6.6-7.9
V25.5c0-6.1-4.9-11-11-11H13.4c-6.1,0-11,4.9-11,11C2.4,25.5,2.4,70.5,2.4,70.5z M87.6,51.8c0,1.1-0.9,2-2,2H72.2c-2.8,0-5-2.2-5-5
v-1.5c0-2.8,2.2-5,5-5h13.3c1.1,0,2,0.9,2,2L87.6,51.8L87.6,51.8z M8.4,25.5c0-2.8,2.2-5,5-5H76c2.8,0,5,2.2,5,5v10.7h-8.7
c-6.1,0-11,4.9-11,11v1.5c0,6.1,4.9,11,11,11H81v10.7c0,2.8-2.2,5-5,5H13.4c-2.8,0-5-2.2-5-5V25.5z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 984 B

View file

@ -23,6 +23,7 @@ Item {
property bool failAfterSignUp: false
function login() {
flavorText.visible = false
mainTextContainer.visible = false
toggleLoading(true)
loginDialog.login(usernameField.text, passwordField.text)
@ -43,7 +44,7 @@ Item {
function resize() {
var targetWidth = Math.max(titleWidth, form.contentWidth);
var targetHeight = hifi.dimensions.contentSpacing.y + mainTextContainer.height +
var targetHeight = hifi.dimensions.contentSpacing.y + flavorText.height + mainTextContainer.height +
4 * hifi.dimensions.contentSpacing.y + form.height;
if (additionalInformation.visible) {
@ -106,14 +107,15 @@ Item {
ShortcutText {
id: mainTextContainer
anchors {
top: parent.top
top: flavorText.bottom
left: parent.left
margins: 0
topMargin: hifi.dimensions.contentSpacing.y
topMargin: 1.5 * hifi.dimensions.contentSpacing.y
}
visible: false
text: qsTr("Username or password incorrect.")
height: flavorText.height - 20
wrapMode: Text.WordWrap
color: hifi.colors.redAccent
lineHeight: 1
@ -128,7 +130,7 @@ Item {
anchors {
top: mainTextContainer.bottom
topMargin: 2 * hifi.dimensions.contentSpacing.y
topMargin: 1.5 * hifi.dimensions.contentSpacing.y
}
spacing: 2 * hifi.dimensions.contentSpacing.y
@ -139,6 +141,7 @@ Item {
focus: true
placeholderText: "Username or Email"
activeFocusOnPress: true
onHeightChanged: d.resize(); onWidthChanged: d.resize();
ShortcutText {
z: 10
@ -172,7 +175,7 @@ Item {
width: parent.width
placeholderText: "Password"
activeFocusOnPress: true
echoMode: TextInput.Password
echoMode: passwordFieldMouseArea.showPassword ? TextInput.Normal : TextInput.Password
onHeightChanged: d.resize(); onWidthChanged: d.resize();
ShortcutText {
@ -212,29 +215,28 @@ Item {
Image {
id: showPasswordImage
y: (passwordField.height - (passwordField.height * 16 / 23)) / 2
width: passwordField.width - (passwordField.width - (((passwordField.height) * 31/23)))
width: passwordField.height * 16 / 23
height: passwordField.height * 16 / 23
anchors {
right: parent.right
rightMargin: 3
rightMargin: 8
top: parent.top
topMargin: passwordFieldMouseArea.showPassword ? 6 : 8
bottom: parent.bottom
bottomMargin: passwordFieldMouseArea.showPassword ? 5 : 8
}
source: passwordFieldMouseArea.showPassword ? "../../images/eyeClosed.svg" : "../../images/eyeOpen.svg"
MouseArea {
id: passwordFieldMouseArea
anchors.fill: parent
acceptedButtons: Qt.LeftButton
property bool showPassword: false
onClicked: {
showPassword = !showPassword;
}
}
source: "../../images/eyeOpen.svg"
}
MouseArea {
id: passwordFieldMouseArea
anchors.fill: parent
acceptedButtons: Qt.LeftButton
property bool showPassword: false
onClicked: {
showPassword = !showPassword;
passwordField.echoMode = showPassword ? TextInput.Normal : TextInput.Password;
showPasswordImage.source = showPassword ? "../../images/eyeClosed.svg" : "../../images/eyeOpen.svg";
showPasswordImage.height = showPassword ? passwordField.height : passwordField.height * 16 / 23;
showPasswordImage.y = showPassword ? 0 : (passwordField.height - showPasswordImage.height) / 2;
}
}
}
Keys.onReturnPressed: linkAccountBody.login()
@ -284,7 +286,7 @@ Item {
anchors.verticalCenter: parent.verticalCenter
width: 200
text: qsTr(loginDialog.isSteamRunning() ? "Link Account" : "Login")
text: qsTr(loginDialog.isSteamRunning() ? "Link Account" : "Log in")
color: hifi.buttons.blue
onClicked: linkAccountBody.login()
@ -336,6 +338,7 @@ Item {
if (failAfterSignUp) {
mainTextContainer.text = "Account created successfully."
flavorText.visible = true
mainTextContainer.visible = true
}
@ -374,6 +377,7 @@ Item {
UserActivityLogger.logAction("encourageLoginDialog", data);
Settings.setValue("loginDialogPoppedUp", false);
}
flavorText.visible = true
mainTextContainer.visible = true
toggleLoading(false)
}

View file

@ -314,7 +314,7 @@ Rectangle {
}
};
wearableUpdated(getCurrentWearable().id, jointIndex, properties);
wearableUpdated(getCurrentWearable().id, wearablesCombobox.currentIndex, properties);
}
onCurrentIndexChanged: {

View file

@ -24,6 +24,7 @@ Item {
fragmentShader: {
"
#version 150 core
varying highp vec2 qt_TexCoord0;
uniform lowp sampler2D source;
uniform lowp sampler2D mask;

View file

@ -552,6 +552,10 @@ Rectangle {
// Alignment
horizontalAlignment: Text.AlignLeft;
verticalAlignment: Text.AlignVCenter;
onLinkActivated: {
// Only case is to go to the bank.
sendToScript({method: 'gotoBank'});
}
}
}
@ -1107,25 +1111,32 @@ Rectangle {
}
function handleBuyAgainLogic() {
// If you can buy this item again...
if (canBuyAgain()) {
// If you can't afford another copy of the item...
if (root.balanceAfterPurchase < 0) {
// If you already own the item...
if (root.alreadyOwned) {
buyText.text = "<b>Your Wallet does not have sufficient funds to purchase this item again.</b>";
// Else if you don't already own the item...
} else {
buyText.text = "<b>Your Wallet does not have sufficient funds to purchase this item.</b>";
}
buyTextContainer.color = "#FFC3CD";
buyTextContainer.border.color = "#F3808F";
buyGlyph.text = hifi.glyphs.alert;
buyGlyph.size = 54;
// If you CAN afford another copy of the item...
// General rules, implemented in various scattered places in this file:
// 1. If you already own the item, a viewInMyPurchasesButton is visible,
// and the buyButton is visible (and says "Buy it again") ONLY if it is a type you canBuyAgain.
// 2. Separately,
// a. If you don't have enough money to buy, the buyText becomes visible and tells you, and the buyButton is disabled.
// b. Otherwise, if the item is a content set and you don't have rez permission, the buyText becomes visible and tells you so.
// If you can't afford another copy of the item...
if (root.balanceAfterPurchase < 0) {
// If you already own the item...
if (!root.alreadyOwned) {
buyText.text = "<b>Your Wallet does not have sufficient funds to purchase this item.</b>";
// Else if you don't already own the item...
} else if (canBuyAgain()) {
buyText.text = "<b>Your Wallet does not have sufficient funds to purchase this item again.</b>";
} else {
handleContentSets();
buyText.text = "<b>While you do not have sufficient funds to buy this, you already have this item.</b>"
}
buyText.text += " Visit <a href='#'>Bank of High Fidelity</a> to get more HFC."
buyTextContainer.color = "#FFC3CD";
buyTextContainer.border.color = "#F3808F";
buyGlyph.text = hifi.glyphs.alert;
buyGlyph.size = 54;
// If you CAN afford another copy of the item...
} else {
handleContentSets();
}
}

View file

@ -0,0 +1,290 @@
//
// marketplaceItemTester
// qml/hifi/commerce/marketplaceItemTester
//
// Load items not in the marketplace for testing purposes
//
// Created by Zach Fox on 2018-09-05
// 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
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import QtQuick.Dialogs 1.0
import QtQuick.Layouts 1.1
import Hifi 1.0 as Hifi
import "../../../styles-uit" as HifiStylesUit
import "../../../controls-uit" as HifiControlsUit
Rectangle {
id: root
property string installedApps
property var nextResourceObjectId: 0
signal sendToScript(var message)
HifiStylesUit.HifiConstants { id: hifi }
ListModel { id: resourceListModel }
color: hifi.colors.white
AnimatedImage {
id: spinner;
source: "spinner.gif"
width: 74;
height: width;
anchors.verticalCenter: parent.verticalCenter;
anchors.horizontalCenter: parent.horizontalCenter;
}
function fromScript(message) {
switch (message.method) {
case "newResourceObjectInTest":
var resourceObject = message.resourceObject;
resourceListModel.append(resourceObject);
spinner.visible = false;
break;
case "nextObjectIdInTest":
nextResourceObjectId = message.id;
spinner.visible = false;
break;
}
}
function buildResourceObj(resource) {
resource = resource.trim();
var assetType = (resource.match(/\.app\.json$/) ? "application" :
resource.match(/\.fst$/) ? "avatar" :
resource.match(/\.json\.gz$/) ? "content set" :
resource.match(/\.json$/) ? "entity or wearable" :
"unknown");
return { "id": nextResourceObjectId++,
"resource": resource,
"assetType": assetType };
}
function installResourceObj(resourceObj) {
if ("application" === resourceObj.assetType) {
Commerce.installApp(resourceObj.resource);
}
}
function addAllInstalledAppsToList() {
var i, apps = Commerce.getInstalledApps().split(","), len = apps.length;
for(i = 0; i < len - 1; ++i) {
if (i in apps) {
resourceListModel.append(buildResourceObj(apps[i]));
}
}
}
function toUrl(resource) {
var httpPattern = /^http/i;
return httpPattern.test(resource) ? resource : "file:///" + resource;
}
function rezEntity(resource, entityType) {
sendToScript({
method: 'tester_rezClicked',
itemHref: toUrl(resource),
itemType: entityType});
}
ListView {
anchors.fill: parent
anchors.leftMargin: 12
anchors.bottomMargin: 40
anchors.rightMargin: 12
model: resourceListModel
spacing: 5
interactive: false
delegate: RowLayout {
anchors.left: parent.left
width: parent.width
spacing: 5
property var actions: {
"forward": function(resource, assetType){
switch(assetType) {
case "application":
Commerce.openApp(resource);
break;
case "avatar":
MyAvatar.useFullAvatarURL(resource);
break;
case "content set":
urlHandler.handleUrl("hifi://localhost/0,0,0");
Commerce.replaceContentSet(toUrl(resource), "");
break;
case "entity":
case "wearable":
rezEntity(resource, assetType);
break;
default:
print("Marketplace item tester unsupported assetType " + assetType);
}
},
"trash": function(resource, assetType){
if ("application" === assetType) {
Commerce.uninstallApp(resource);
}
sendToScript({
method: "tester_deleteResourceObject",
objectId: resourceListModel.get(index).id});
resourceListModel.remove(index);
}
}
Column {
Layout.preferredWidth: root.width * .6
spacing: 5
Text {
text: {
var match = resource.match(/\/([^/]*)$/);
return match ? match[1] : resource;
}
font.pointSize: 12
horizontalAlignment: Text.AlignBottom
}
Text {
text: resource
font.pointSize: 8
width: root.width * .6
horizontalAlignment: Text.AlignBottom
wrapMode: Text.WrapAnywhere
}
}
ComboBox {
id: comboBox
Layout.preferredWidth: root.width * .2
model: [
"application",
"avatar",
"content set",
"entity",
"wearable",
"unknown"
]
currentIndex: (("entity or wearable" === assetType) ?
model.indexOf("unknown") : model.indexOf(assetType))
Component.onCompleted: {
onCurrentIndexChanged.connect(function() {
assetType = model[currentIndex];
sendToScript({
method: "tester_updateResourceObjectAssetType",
objectId: resourceListModel.get(index)["id"],
assetType: assetType });
});
}
}
Repeater {
model: [ "forward", "trash" ]
HifiStylesUit.HiFiGlyphs {
property var glyphs: {
"application": hifi.glyphs.install,
"avatar": hifi.glyphs.avatar,
"content set": hifi.glyphs.globe,
"entity": hifi.glyphs.wand,
"trash": hifi.glyphs.trash,
"unknown": hifi.glyphs.circleSlash,
"wearable": hifi.glyphs.hat,
}
text: (("trash" === modelData) ?
glyphs.trash :
glyphs[comboBox.model[comboBox.currentIndex]])
size: ("trash" === modelData) ? 22 : 30
color: hifi.colors.black
horizontalAlignment: Text.AlignHCenter
MouseArea {
anchors.fill: parent
onClicked: {
actions[modelData](resource, comboBox.currentText);
}
}
}
}
}
headerPositioning: ListView.OverlayHeader
header: HifiStylesUit.RalewayRegular {
id: rootHeader
text: "Marketplace Item Tester"
height: 80
width: paintedWidth
size: 22
color: hifi.colors.black
anchors.left: parent.left
anchors.leftMargin: 12
}
footerPositioning: ListView.OverlayFooter
footer: Row {
id: rootActions
spacing: 20
anchors.horizontalCenter: parent.horizontalCenter
property string currentAction
property var actions: {
"Load File": function(){
rootActions.currentAction = "load file";
Window.browseChanged.connect(onResourceSelected);
Window.browseAsync("Please select a file (*.app.json *.json *.fst *.json.gz)", "", "Assets (*.app.json *.json *.fst *.json.gz)");
},
"Load URL": function(){
rootActions.currentAction = "load url";
Window.promptTextChanged.connect(onResourceSelected);
Window.promptAsync("Please enter a URL", "");
}
}
function onResourceSelected(resource) {
// It is possible that we received the present signal
// from something other than our browserAsync window.
// Alas, there is nothing we can do about that so charge
// ahead as though we are sure the present signal is one
// we expect.
switch(currentAction) {
case "load file":
Window.browseChanged.disconnect(onResourceSelected);
break
case "load url":
Window.promptTextChanged.disconnect(onResourceSelected);
break;
}
if (resource) {
var resourceObj = buildResourceObj(resource);
installResourceObj(resourceObj);
sendToScript({
method: 'tester_newResourceObject',
resourceObject: resourceObj });
}
}
Repeater {
model: [ "Load File", "Load URL" ]
HifiControlsUit.Button {
color: hifi.buttons.blue
fontSize: 20
text: modelData
width: root.width / 3
height: 40
onClicked: actions[text]()
}
}
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

View file

@ -59,7 +59,7 @@ Item {
Connections {
target: Commerce;
onContentSetChanged: {
if (contentSetHref === root.itemHref) {
showConfirmation = true;
@ -135,7 +135,7 @@ Item {
anchors.topMargin: 8;
width: 30;
height: width;
HiFiGlyphs {
id: closeContextMenuGlyph;
text: hifi.glyphs.close;
@ -376,7 +376,7 @@ Item {
}
}
}
transform: Rotation {
id: rotation;
origin.x: flipable.width/2;
@ -509,7 +509,7 @@ Item {
}
verticalAlignment: Text.AlignTop;
}
HiFiGlyphs {
id: statusIcon;
text: {
@ -588,7 +588,7 @@ Item {
border.width: 1;
border.color: "#E2334D";
}
HiFiGlyphs {
id: contextMenuGlyph;
text: hifi.glyphs.verticalEllipsis;
@ -615,7 +615,7 @@ Item {
}
}
}
Rectangle {
id: rezzedNotifContainer;
z: 998;
@ -663,13 +663,13 @@ Item {
Tablet.playSound(TabletEnums.ButtonHover);
}
}
onFocusChanged: {
if (focus) {
Tablet.playSound(TabletEnums.ButtonHover);
}
}
onClicked: {
Tablet.playSound(TabletEnums.ButtonClick);
if (root.itemType === "contentSet") {
@ -775,7 +775,7 @@ Item {
// Style
color: hifi.colors.redAccent;
horizontalAlignment: Text.AlignRight;
MouseArea {
anchors.fill: parent;
hoverEnabled: true;

View file

@ -93,7 +93,7 @@ Rectangle {
console.log("Failed to get Available Updates", result.data.message);
} else {
sendToScript({method: 'purchases_availableUpdatesReceived', numUpdates: result.data.updates.length });
root.numUpdatesAvailable = result.data.updates.length;
root.numUpdatesAvailable = result.total_entries;
}
}

View file

@ -829,6 +829,7 @@ Rectangle {
Commerce.getWalletAuthenticatedStatus(); // before writing security image, ensures that salt/account password is set.
Commerce.chooseSecurityImage(securityImagePath);
Commerce.generateKeyPair();
followReferrer({ referrer: walletSetup.referrer });
}
function addLeadingZero(n) {
@ -836,7 +837,7 @@ Rectangle {
}
function followReferrer(msg) {
if (msg.referrer === '' || msg.referrer === 'marketplace cta') {
if (msg.referrer === '') {
root.activeView = "initialize";
Commerce.getWalletStatus();
} else if (msg.referrer === 'purchases') {

View file

@ -45,14 +45,6 @@ Item {
onHistoryResult : {
transactionHistoryModel.handlePage(null, result);
}
onAvailableUpdatesResult: {
if (result.status !== 'success') {
console.log("Failed to get Available Updates", result.data.message);
} else {
sendToScript({method: 'wallet_availableUpdatesReceived', numUpdates: result.data.updates.length });
}
}
}
Connections {

View file

@ -28,7 +28,7 @@ Item {
property string activeView: "step_1";
property string lastPage;
property bool hasShownSecurityImageTip: false;
property string referrer;
property string referrer: '';
property string keyFilePath;
property date startingTimestamp;
property string setupAttemptID;

View file

@ -25,7 +25,7 @@ AndroidHelper::AndroidHelper() {
AndroidHelper::~AndroidHelper() {
}
void AndroidHelper::requestActivity(const QString &activityName, const bool backToScene, QList<QString> args) {
void AndroidHelper::requestActivity(const QString &activityName, const bool backToScene, QMap<QString, QString> args) {
emit androidActivityRequested(activityName, backToScene, args);
}
@ -49,8 +49,10 @@ void AndroidHelper::performHapticFeedback(int duration) {
emit hapticFeedbackRequested(duration);
}
void AndroidHelper::showLoginDialog() {
emit androidActivityRequested("Login", true);
void AndroidHelper::showLoginDialog(QUrl url) {
QMap<QString, QString> args;
args["url"] = url.toString();
emit androidActivityRequested("Login", true, args);
}
void AndroidHelper::processURL(const QString &url) {

View file

@ -13,6 +13,8 @@
#define hifi_Android_Helper_h
#include <QObject>
#include <QMap>
#include <QUrl>
class AndroidHelper : public QObject {
Q_OBJECT
@ -21,7 +23,7 @@ public:
static AndroidHelper instance;
return instance;
}
void requestActivity(const QString &activityName, const bool backToScene, QList<QString> args = QList<QString>());
void requestActivity(const QString &activityName, const bool backToScene, QMap<QString, QString> args = QMap<QString, QString>());
void notifyLoadComplete();
void notifyEnterForeground();
void notifyBeforeEnterBackground();
@ -35,10 +37,10 @@ public:
void operator=(AndroidHelper const&) = delete;
public slots:
void showLoginDialog();
void showLoginDialog(QUrl url);
signals:
void androidActivityRequested(const QString &activityName, const bool backToScene, QList<QString> args = QList<QString>());
void androidActivityRequested(const QString &activityName, const bool backToScene, QMap<QString, QString> args = QMap<QString, QString>());
void qtAppLoadComplete();
void enterForeground();
void beforeEnterBackground();

View file

@ -270,7 +270,7 @@ class RenderEventHandler : public QObject {
public:
RenderEventHandler() {
// Transfer to a new thread
moveToNewNamedThread(this, "RenderThread", [this](QThread* renderThread) {
moveToNewNamedThread(this, "RenderThread", [](QThread* renderThread) {
hifi::qt::addBlockingForbiddenThread("Render", renderThread);
qApp->_lastTimeRendered.start();
}, std::bind(&RenderEventHandler::initialize, this), QThread::HighestPriority);
@ -964,7 +964,7 @@ Q_GUI_EXPORT void qt_gl_set_global_share_context(QOpenGLContext *context);
Setting::Handle<int> sessionRunTime{ "sessionRunTime", 0 };
const float DEFAULT_HMD_TABLET_SCALE_PERCENT = 70.0f;
const float DEFAULT_HMD_TABLET_SCALE_PERCENT = 60.0f;
const float DEFAULT_DESKTOP_TABLET_SCALE_PERCENT = 75.0f;
const bool DEFAULT_DESKTOP_TABLET_BECOMES_TOOLBAR = true;
const bool DEFAULT_HMD_TABLET_BECOMES_TOOLBAR = false;
@ -997,7 +997,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
_enableProcessOctreeThread(true),
_lastNackTime(usecTimestampNow()),
_lastSendDownstreamAudioStats(usecTimestampNow()),
_aboutToQuit(false),
_notifiedPacketVersionMismatchThisDomain(false),
_maxOctreePPS(maxOctreePacketsPerSecond.get()),
_lastFaceTrackerUpdate(0),
@ -1237,7 +1236,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
auto dialogsManager = DependencyManager::get<DialogsManager>();
#if defined(Q_OS_ANDROID)
connect(accountManager.data(), &AccountManager::authRequired, this, []() {
AndroidHelper::instance().showLoginDialog();
auto addressManager = DependencyManager::get<AddressManager>();
AndroidHelper::instance().showLoginDialog(addressManager->currentAddress());
});
#else
connect(accountManager.data(), &AccountManager::authRequired, dialogsManager.data(), &DialogsManager::showLoginDialog);
@ -1692,21 +1692,21 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
return DependencyManager::get<OffscreenUi>()->navigationFocused() ? 1 : 0;
});
_applicationStateDevice->setInputVariant(STATE_PLATFORM_WINDOWS, []() -> float {
#if defined(Q_OS_WIN)
#if defined(Q_OS_WIN)
return 1;
#else
return 0;
#endif
});
_applicationStateDevice->setInputVariant(STATE_PLATFORM_MAC, []() -> float {
#if defined(Q_OS_MAC)
#if defined(Q_OS_MAC)
return 1;
#else
return 0;
#endif
});
_applicationStateDevice->setInputVariant(STATE_PLATFORM_ANDROID, []() -> float {
#if defined(Q_OS_ANDROID)
#if defined(Q_OS_ANDROID)
return 1;
#else
return 0;
@ -1756,7 +1756,13 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
// we can unlock the desktop repositioning code, since all the positions will be
// relative to the desktop size for this plugin
auto offscreenUi = DependencyManager::get<OffscreenUi>();
offscreenUi->getDesktop()->setProperty("repositionLocked", false);
connect(offscreenUi.data(), &OffscreenUi::desktopReady, []() {
auto offscreenUi = DependencyManager::get<OffscreenUi>();
auto desktop = offscreenUi->getDesktop();
if (desktop) {
desktop->setProperty("repositionLocked", false);
}
});
// Make sure we don't time out during slow operations at startup
updateHeartbeat();
@ -2315,7 +2321,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
QTimer* checkLoginTimer = new QTimer(this);
checkLoginTimer->setInterval(CHECK_LOGIN_TIMER);
checkLoginTimer->setSingleShot(true);
connect(checkLoginTimer, &QTimer::timeout, this, [this]() {
connect(checkLoginTimer, &QTimer::timeout, this, []() {
auto accountManager = DependencyManager::get<AccountManager>();
auto dialogsManager = DependencyManager::get<DialogsManager>();
if (!accountManager->isLoggedIn()) {
@ -2889,9 +2895,10 @@ void Application::initializeUi() {
QUrl{ "hifi/commerce/common/CommerceLightbox.qml" },
QUrl{ "hifi/commerce/common/EmulatedMarketplaceHeader.qml" },
QUrl{ "hifi/commerce/common/FirstUseTutorial.qml" },
QUrl{ "hifi/commerce/common/SortableListModel.qml" },
QUrl{ "hifi/commerce/common/sendAsset/SendAsset.qml" },
QUrl{ "hifi/commerce/common/SortableListModel.qml" },
QUrl{ "hifi/commerce/inspectionCertificate/InspectionCertificate.qml" },
QUrl{ "hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml"},
QUrl{ "hifi/commerce/purchases/PurchasedItem.qml" },
QUrl{ "hifi/commerce/purchases/Purchases.qml" },
QUrl{ "hifi/commerce/wallet/Help.qml" },
@ -3435,7 +3442,12 @@ void Application::handleSandboxStatus(QNetworkReply* reply) {
int urlIndex = arguments().indexOf(HIFI_URL_COMMAND_LINE_KEY);
QString addressLookupString;
if (urlIndex != -1) {
addressLookupString = arguments().value(urlIndex + 1);
QUrl url(arguments().value(urlIndex + 1));
if (url.scheme() == URL_SCHEME_HIFIAPP) {
Setting::Handle<QVariant>("startUpApp").set(url.path());
} else {
addressLookupString = url.toString();
}
}
static const QString SENT_TO_PREVIOUS_LOCATION = "previous_location";
@ -4673,8 +4685,14 @@ void Application::idle() {
checkChangeCursor();
Stats::getInstance()->updateStats();
AnimStats::getInstance()->updateStats();
auto stats = Stats::getInstance();
if (stats) {
stats->updateStats();
}
auto animStats = AnimStats::getInstance();
if (animStats) {
animStats->updateStats();
}
// Normally we check PipelineWarnings, but since idle will often take more than 10ms we only show these idle timing
// details if we're in ExtraDebugging mode. However, the ::update() and its subcomponents will show their timing
@ -7683,6 +7701,9 @@ void Application::openUrl(const QUrl& url) const {
if (!url.isEmpty()) {
if (url.scheme() == URL_SCHEME_HIFI) {
DependencyManager::get<AddressManager>()->handleLookupString(url.toString());
} else if (url.scheme() == URL_SCHEME_HIFIAPP) {
QmlCommerce commerce;
commerce.openSystemApp(url.path());
} else {
// address manager did not handle - ask QDesktopServices to handle
QDesktopServices::openUrl(url);

View file

@ -560,6 +560,8 @@ private:
MainWindow* _window;
QElapsedTimer& _sessionRunTimer;
bool _aboutToQuit { false };
bool _previousSessionCrashed;
DisplayPluginPointer _displayPlugin;
@ -651,8 +653,6 @@ private:
quint64 _lastNackTime;
quint64 _lastSendDownstreamAudioStats;
bool _aboutToQuit;
bool _notifiedPacketVersionMismatchThisDomain;
ConditionalGuard _settingsGuard;

View file

@ -157,7 +157,10 @@ void Application::paintGL() {
renderArgs._context->enableStereo(false);
{
Stats::getInstance()->setRenderDetails(renderArgs._details);
auto stats = Stats::getInstance();
if (stats) {
stats->setRenderDetails(renderArgs._details);
}
}
uint64_t lastPaintDuration = usecTimestampNow() - lastPaintBegin;

View file

@ -48,6 +48,10 @@ AvatarActionHold::~AvatarActionHold() {
myAvatar->removeHoldAction(this);
}
}
auto ownerEntity = _ownerEntity.lock();
if (ownerEntity) {
ownerEntity->setTransitingWithAvatar(false);
}
#if WANT_DEBUG
qDebug() << "AvatarActionHold::~AvatarActionHold" << (void*)this;
@ -131,6 +135,15 @@ bool AvatarActionHold::getTarget(float deltaTimeStep, glm::quat& rotation, glm::
glm::vec3 palmPosition;
glm::quat palmRotation;
bool isTransitingWithAvatar = holdingAvatar->getTransit()->isTransiting();
if (isTransitingWithAvatar != _isTransitingWithAvatar) {
_isTransitingWithAvatar = isTransitingWithAvatar;
auto ownerEntity = _ownerEntity.lock();
if (ownerEntity) {
ownerEntity->setTransitingWithAvatar(_isTransitingWithAvatar);
}
}
if (holdingAvatar->isMyAvatar()) {
std::shared_ptr<MyAvatar> myAvatar = avatarManager->getMyAvatar();
@ -404,11 +417,14 @@ bool AvatarActionHold::updateArguments(QVariantMap arguments) {
_kinematicSetVelocity = kinematicSetVelocity;
_ignoreIK = ignoreIK;
_active = true;
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
auto ownerEntity = _ownerEntity.lock();
if (ownerEntity) {
ownerEntity->setDynamicDataDirty(true);
ownerEntity->setDynamicDataNeedsTransmit(true);
ownerEntity->setDynamicDataNeedsTransmit(true);
ownerEntity->setTransitingWithAvatar(myAvatar->getTransit()->isTransiting());
}
});
}

View file

@ -59,6 +59,8 @@ private:
bool _kinematicSetVelocity { false };
bool _previousSet { false };
bool _ignoreIK { false };
bool _isTransitingWithAvatar { false };
glm::vec3 _previousPositionalTarget;
glm::quat _previousRotationalTarget;

View file

@ -78,6 +78,15 @@ AvatarManager::AvatarManager(QObject* parent) :
removeAvatar(nodeID, KillAvatarReason::AvatarIgnored);
}
});
const float AVATAR_TRANSIT_TRIGGER_DISTANCE = 1.0f;
const int AVATAR_TRANSIT_FRAME_COUNT = 11; // Based on testing
const int AVATAR_TRANSIT_FRAMES_PER_METER = 1; // Based on testing
_transitConfig._totalFrames = AVATAR_TRANSIT_FRAME_COUNT;
_transitConfig._triggerDistance = AVATAR_TRANSIT_TRIGGER_DISTANCE;
_transitConfig._framesPerMeter = AVATAR_TRANSIT_FRAMES_PER_METER;
_transitConfig._isDistanceBased = true;
}
AvatarSharedPointer AvatarManager::addAvatar(const QUuid& sessionUUID, const QWeakPointer<Node>& mixerWeakPointer) {
@ -129,6 +138,10 @@ void AvatarManager::updateMyAvatar(float deltaTime) {
bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);
PerformanceWarning warn(showWarnings, "AvatarManager::updateMyAvatar()");
AvatarTransit::Status status = _myAvatar->updateTransit(deltaTime, _myAvatar->getNextPosition(), _transitConfig);
bool sendFirstTransitPackage = (status == AvatarTransit::Status::START_TRANSIT);
bool blockTransitData = (status == AvatarTransit::Status::TRANSITING);
_myAvatar->update(deltaTime);
render::Transaction transaction;
_myAvatar->updateRenderItem(transaction);
@ -137,9 +150,13 @@ void AvatarManager::updateMyAvatar(float deltaTime) {
quint64 now = usecTimestampNow();
quint64 dt = now - _lastSendAvatarDataTime;
if (dt > MIN_TIME_BETWEEN_MY_AVATAR_DATA_SENDS && !_myAvatarDataPacketsPaused) {
if (sendFirstTransitPackage || (dt > MIN_TIME_BETWEEN_MY_AVATAR_DATA_SENDS && !_myAvatarDataPacketsPaused && !blockTransitData)) {
// send head/hand data to the avatar mixer and voxel server
PerformanceTimer perfTimer("send");
if (sendFirstTransitPackage) {
_myAvatar->overrideNextPackagePositionData(_myAvatar->getTransit()->getEndPosition());
}
_myAvatar->sendAvatarDataPacket();
_lastSendAvatarDataTime = now;
_myAvatarSendRate.increment();
@ -234,11 +251,13 @@ void AvatarManager::updateOtherAvatars(float deltaTime) {
const SortableAvatar& sortData = *it;
const auto avatar = std::static_pointer_cast<OtherAvatar>(sortData.getAvatar());
// TODO: to help us scale to more avatars it would be nice to not have to poll orb state here
// if the geometry is loaded then turn off the orb
// TODO: to help us scale to more avatars it would be nice to not have to poll this stuff every update
if (avatar->getSkeletonModel()->isLoaded()) {
// remove the orb if it is there
avatar->removeOrb();
if (avatar->needsPhysicsUpdate()) {
_avatarsToChangeInPhysics.insert(avatar);
}
} else {
avatar->updateOrbPosition();
}
@ -256,6 +275,7 @@ void AvatarManager::updateOtherAvatars(float deltaTime) {
if (inView && avatar->hasNewJointData()) {
numAvatarsUpdated++;
}
avatar->_transit.update(deltaTime, avatar->_globalPosition, _transitConfig);
avatar->simulate(deltaTime, inView);
avatar->updateRenderItem(renderTransaction);
avatar->updateSpaceProxy(workloadTransaction);
@ -809,7 +829,7 @@ void AvatarManager::setAvatarSortCoefficient(const QString& name, const QScriptV
}
}
QVariantMap AvatarManager::getPalData(const QList<QString> specificAvatarIdentifiers) {
QVariantMap AvatarManager::getPalData(const QList<QString> specificAvatarIdentifiers) {
QJsonArray palData;
auto avatarMap = getHashCopy();

View file

@ -31,6 +31,7 @@
#include "MyAvatar.h"
#include "OtherAvatar.h"
using SortedAvatar = std::pair<float, std::shared_ptr<Avatar>>;
/**jsdoc
@ -232,6 +233,8 @@ private:
mutable std::mutex _spaceLock;
workload::SpacePointer _space;
std::vector<int32_t> _spaceProxiesToDelete;
AvatarTransit::TransitConfig _transitConfig;
};
#endif // hifi_AvatarManager_h

View file

@ -116,6 +116,7 @@ MyAvatar::MyAvatar(QThread* thread) :
_bodySensorMatrix(),
_goToPending(false),
_goToSafe(true),
_goToFeetAjustment(false),
_goToPosition(),
_goToOrientation(),
_prevShouldDrawHead(true),
@ -498,7 +499,7 @@ void MyAvatar::update(float deltaTime) {
setCurrentStandingHeight(computeStandingHeightMode(getControllerPoseInAvatarFrame(controller::Action::HEAD)));
setAverageHeadRotation(computeAverageHeadRotation(getControllerPoseInAvatarFrame(controller::Action::HEAD)));
if (_drawAverageFacingEnabled) {
if (_drawAverageFacingEnabled) {
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));
@ -526,6 +527,12 @@ void MyAvatar::update(float deltaTime) {
_physicsSafetyPending = getCollisionsEnabled();
_characterController.recomputeFlying(); // In case we've gone to into the sky.
}
if (_goToFeetAjustment && _skeletonModelLoaded) {
auto feetAjustment = getWorldPosition() - getWorldFeetPosition();
_goToPosition = getWorldPosition() + feetAjustment;
setWorldPosition(_goToPosition);
_goToFeetAjustment = false;
}
if (_physicsSafetyPending && qApp->isPhysicsEnabled() && _characterController.isEnabledAndReady()) {
// When needed and ready, arrange to check and fix.
_physicsSafetyPending = false;
@ -632,9 +639,8 @@ void MyAvatar::updateChildCauterization(SpatiallyNestablePointer object, bool ca
void MyAvatar::simulate(float deltaTime) {
PerformanceTimer perfTimer("simulate");
animateScaleChanges(deltaTime);
setFlyingEnabled(getFlyingEnabled());
if (_cauterizationNeedsUpdate) {
@ -922,6 +928,7 @@ void MyAvatar::updateSensorToWorldMatrix() {
updateJointFromController(controller::Action::RIGHT_HAND, _controllerRightHandMatrixCache);
if (hasSensorToWorldScaleChanged) {
setTransitScale(sensorToWorldScale);
emit sensorToWorldScaleChanged(sensorToWorldScale);
}
@ -1728,6 +1735,7 @@ void MyAvatar::setSkeletonModelURL(const QUrl& skeletonModelURL) {
_headBoneSet.clear();
_cauterizationNeedsUpdate = true;
_skeletonModelLoaded = false;
std::shared_ptr<QMetaObject::Connection> skeletonConnection = std::make_shared<QMetaObject::Connection>();
*skeletonConnection = QObject::connect(_skeletonModel.get(), &SkeletonModel::skeletonLoaded, [this, skeletonModelChangeCount, skeletonConnection]() {
@ -1745,6 +1753,7 @@ void MyAvatar::setSkeletonModelURL(const QUrl& skeletonModelURL) {
_skeletonModel->setCauterizeBoneSet(_headBoneSet);
_fstAnimGraphOverrideUrl = _skeletonModel->getGeometry()->getAnimGraphOverrideUrl();
initAnimGraph();
_skeletonModelLoaded = true;
}
QObject::disconnect(*skeletonConnection);
});
@ -2945,46 +2954,10 @@ void MyAvatar::goToLocation(const QVariant& propertiesVar) {
}
void MyAvatar::goToFeetLocation(const glm::vec3& newPosition,
bool hasOrientation, const glm::quat& newOrientation,
bool shouldFaceLocation) {
qCDebug(interfaceapp).nospace() << "MyAvatar goToFeetLocation - moving to " << newPosition.x << ", "
<< newPosition.y << ", " << newPosition.z;
ShapeInfo shapeInfo;
computeShapeInfo(shapeInfo);
glm::vec3 halfExtents = shapeInfo.getHalfExtents();
glm::vec3 localFeetPos = shapeInfo.getOffset() - glm::vec3(0.0f, halfExtents.y + halfExtents.x, 0.0f);
glm::mat4 localFeet = createMatFromQuatAndPos(Quaternions::IDENTITY, localFeetPos);
glm::mat4 worldFeet = createMatFromQuatAndPos(Quaternions::IDENTITY, newPosition);
glm::mat4 avatarMat = worldFeet * glm::inverse(localFeet);
glm::vec3 adjustedPosition = extractTranslation(avatarMat);
_goToPending = true;
_goToPosition = adjustedPosition;
_goToOrientation = getWorldOrientation();
if (hasOrientation) {
qCDebug(interfaceapp).nospace() << "MyAvatar goToFeetLocation - new orientation is "
<< newOrientation.x << ", " << newOrientation.y << ", " << newOrientation.z << ", " << newOrientation.w;
// orient the user to face the target
glm::quat quatOrientation = cancelOutRollAndPitch(newOrientation);
if (shouldFaceLocation) {
quatOrientation = newOrientation * glm::angleAxis(PI, Vectors::UP);
// move the user a couple units away
const float DISTANCE_TO_USER = 2.0f;
_goToPosition = adjustedPosition - quatOrientation * IDENTITY_FORWARD * DISTANCE_TO_USER;
}
_goToOrientation = quatOrientation;
}
emit transformChanged();
bool hasOrientation, const glm::quat& newOrientation,
bool shouldFaceLocation) {
_goToFeetAjustment = true;
goToLocation(newPosition, hasOrientation, newOrientation, shouldFaceLocation);
}
void MyAvatar::goToLocation(const glm::vec3& newPosition,

View file

@ -1115,6 +1115,8 @@ public:
virtual QVariantList getAttachmentsVariant() const override;
virtual void setAttachmentsVariant(const QVariantList& variant) override;
glm::vec3 getNextPosition() { return _goToPending ? _goToPosition : getWorldPosition(); };
public slots:
/**jsdoc
@ -1732,6 +1734,7 @@ private:
bool _goToPending { false };
bool _physicsSafetyPending { false };
bool _goToSafe { true };
bool _goToFeetAjustment { false };
glm::vec3 _goToPosition;
glm::quat _goToOrientation;
@ -1807,6 +1810,7 @@ private:
bool _haveReceivedHeightLimitsFromDomain { false };
int _disableHandTouchCount { 0 };
bool _skeletonModelLoaded { false };
Setting::Handle<QString> _dominantHandSetting;
Setting::Handle<float> _headPitchSetting;

View file

@ -119,6 +119,11 @@ bool OtherAvatar::shouldBeInPhysicsSimulation() const {
return (_workloadRegion < workload::Region::R3 && !isDead());
}
bool OtherAvatar::needsPhysicsUpdate() const {
constexpr uint32_t FLAGS_OF_INTEREST = Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS | Simulation::DIRTY_POSITION;
return (_motionState && (bool)(_motionState->getIncomingDirtyFlags() & FLAGS_OF_INTEREST));
}
void OtherAvatar::rebuildCollisionShape() {
if (_motionState) {
_motionState->addDirtyFlags(Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS);

View file

@ -43,6 +43,7 @@ public:
void setWorkloadRegion(uint8_t region);
bool shouldBeInPhysicsSimulation() const;
bool needsPhysicsUpdate() const;
friend AvatarManager;

View file

@ -47,6 +47,54 @@ QmlCommerce::QmlCommerce() {
_appsPath = PathUtils::getAppDataPath() + "Apps/";
}
void QmlCommerce::openSystemApp(const QString& appName) {
static QMap<QString, QString> systemApps {
{"GOTO", "hifi/tablet/TabletAddressDialog.qml"},
{"PEOPLE", "hifi/Pal.qml"},
{"WALLET", "hifi/commerce/wallet/Wallet.qml"},
{"MARKET", "/marketplace.html"}
};
static QMap<QString, QString> systemInject{
{"MARKET", "/scripts/system/html/js/marketplacesInject.js"}
};
auto tablet = dynamic_cast<TabletProxy*>(
DependencyManager::get<TabletScriptingInterface>()->getTablet("com.highfidelity.interface.tablet.system"));
QMap<QString, QString>::const_iterator appPathIter = systemApps.find(appName);
if (appPathIter != systemApps.end()) {
if (appPathIter->contains(".qml", Qt::CaseInsensitive)) {
tablet->loadQMLSource(*appPathIter);
}
else if (appPathIter->contains(".html", Qt::CaseInsensitive)) {
QMap<QString, QString>::const_iterator injectIter = systemInject.find(appName);
if (appPathIter == systemInject.end()) {
tablet->gotoWebScreen(NetworkingConstants::METAVERSE_SERVER_URL().toString() + *appPathIter);
}
else {
QString inject = "file:///" + qApp->applicationDirPath() + *injectIter;
tablet->gotoWebScreen(NetworkingConstants::METAVERSE_SERVER_URL().toString() + *appPathIter, inject);
}
}
else {
qCDebug(commerce) << "Attempted to open unknown type of URL!";
return;
}
}
else {
qCDebug(commerce) << "Attempted to open unknown APP!";
return;
}
DependencyManager::get<HMDScriptingInterface>()->openTablet();
}
void QmlCommerce::getWalletStatus() {
auto wallet = DependencyManager::get<Wallet>();
wallet->getWalletStatus();
@ -199,12 +247,18 @@ void QmlCommerce::transferAssetToUsername(const QString& username,
}
void QmlCommerce::replaceContentSet(const QString& itemHref, const QString& certificateID) {
auto ledger = DependencyManager::get<Ledger>();
ledger->updateLocation(certificateID, DependencyManager::get<AddressManager>()->getPlaceName(), true);
if (!certificateID.isEmpty()) {
auto ledger = DependencyManager::get<Ledger>();
ledger->updateLocation(
certificateID,
DependencyManager::get<AddressManager>()->getPlaceName(),
true);
}
qApp->replaceDomainContent(itemHref);
QJsonObject messageProperties = { { "status", "SuccessfulRequestToReplaceContent" }, { "content_set_url", itemHref } };
QJsonObject messageProperties = {
{ "status", "SuccessfulRequestToReplaceContent" },
{ "content_set_url", itemHref } };
UserActivityLogger::getInstance().logAction("replace_domain_content", messageProperties);
emit contentSetChanged(itemHref);
}
@ -228,6 +282,7 @@ QString QmlCommerce::getInstalledApps(const QString& justInstalledAppID) {
// Thus, we protect against deleting the .app.json from the user's disk (below)
// by skipping that check for the app we just installed.
if ((justInstalledAppID != "") && ((justInstalledAppID + ".app.json") == appFileName)) {
installedAppsFromMarketplace += appFileName + ",";
continue;
}
@ -353,7 +408,7 @@ bool QmlCommerce::openApp(const QString& itemHref) {
// Read from the file to know what .html or .qml document to open
QFile appFile(_appsPath + "/" + appHref.fileName());
if (!appFile.open(QIODevice::ReadOnly)) {
qCDebug(commerce) << "Couldn't open local .app.json file.";
qCDebug(commerce) << "Couldn't open local .app.json file:" << appFile;
return false;
}
QJsonDocument appFileJsonDocument = QJsonDocument::fromJson(appFile.readAll());

View file

@ -24,6 +24,7 @@ class QmlCommerce : public QObject {
public:
QmlCommerce();
void openSystemApp(const QString& appPath);
signals:
void walletStatusResult(uint walletStatus);

View file

@ -176,7 +176,7 @@ int main(int argc, const char* argv[]) {
if (socket.waitForConnected(LOCAL_SERVER_TIMEOUT_MS)) {
if (parser.isSet(urlOption)) {
QUrl url = QUrl(parser.value(urlOption));
if (url.isValid() && url.scheme() == URL_SCHEME_HIFI) {
if (url.isValid() && (url.scheme() == URL_SCHEME_HIFI || url.scheme() == URL_SCHEME_HIFIAPP)) {
qDebug() << "Writing URL to local socket";
socket.write(url.toString().toUtf8());
if (!socket.waitForBytesWritten(5000)) {

View file

@ -36,6 +36,14 @@ void LaserPointer::editRenderStatePath(const std::string& state, const QVariant&
}
}
PickResultPointer LaserPointer::getPickResultCopy(const PickResultPointer& pickResult) const {
auto rayPickResult = std::dynamic_pointer_cast<RayPickResult>(pickResult);
if (!rayPickResult) {
return std::make_shared<RayPickResult>();
}
return std::make_shared<RayPickResult>(*rayPickResult.get());
}
QVariantMap LaserPointer::toVariantMap() const {
QVariantMap qVariantMap;

View file

@ -47,6 +47,8 @@ public:
static std::shared_ptr<StartEndRenderState> buildRenderState(const QVariantMap& propMap);
protected:
PickResultPointer getPickResultCopy(const PickResultPointer& pickResult) const override;
void editRenderStatePath(const std::string& state, const QVariant& pathProps) override;
glm::vec3 getPickOrigin(const PickResultPointer& pickResult) const override;

View file

@ -30,6 +30,14 @@ ParabolaPointer::ParabolaPointer(const QVariant& rayProps, const RenderStateMap&
{
}
PickResultPointer ParabolaPointer::getPickResultCopy(const PickResultPointer& pickResult) const {
auto parabolaPickResult = std::dynamic_pointer_cast<ParabolaPickResult>(pickResult);
if (!parabolaPickResult) {
return std::make_shared<ParabolaPickResult>();
}
return std::make_shared<ParabolaPickResult>(*parabolaPickResult.get());
}
void ParabolaPointer::editRenderStatePath(const std::string& state, const QVariant& pathProps) {
auto renderState = std::static_pointer_cast<RenderState>(_renderStates[state]);
if (renderState) {

View file

@ -102,6 +102,8 @@ public:
static std::shared_ptr<StartEndRenderState> buildRenderState(const QVariantMap& propMap);
protected:
virtual PickResultPointer getPickResultCopy(const PickResultPointer& pickResult) const override;
void editRenderStatePath(const std::string& state, const QVariant& pathProps) override;
glm::vec3 getPickOrigin(const PickResultPointer& pickResult) const override;

View file

@ -27,6 +27,7 @@ class StartEndRenderState {
public:
StartEndRenderState() {}
StartEndRenderState(const OverlayID& startID, const OverlayID& endID);
virtual ~StartEndRenderState() {}
const OverlayID& getStartID() const { return _startID; }
const OverlayID& getEndID() const { return _endID; }

View file

@ -147,6 +147,14 @@ bool StylusPointer::shouldTrigger(const PickResultPointer& pickResult) {
return false;
}
PickResultPointer StylusPointer::getPickResultCopy(const PickResultPointer& pickResult) const {
auto stylusPickResult = std::dynamic_pointer_cast<StylusPickResult>(pickResult);
if (!stylusPickResult) {
return std::make_shared<StylusPickResult>();
}
return std::make_shared<StylusPickResult>(*stylusPickResult.get());
}
Pointer::PickedObject StylusPointer::getHoveredObject(const PickResultPointer& pickResult) {
auto stylusPickResult = std::static_pointer_cast<const StylusPickResult>(pickResult);
if (!stylusPickResult) {

View file

@ -42,6 +42,7 @@ protected:
Buttons getPressedButtons(const PickResultPointer& pickResult) override;
bool shouldHover(const PickResultPointer& pickResult) override;
bool shouldTrigger(const PickResultPointer& pickResult) override;
virtual PickResultPointer getPickResultCopy(const PickResultPointer& pickResult) const override;
PointerEvent buildPointerEvent(const PickedObject& target, const PickResultPointer& pickResult, const std::string& button = "", bool hover = true) override;

View file

@ -57,18 +57,24 @@ class QScriptEngine;
* @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} miniTabletID - The UUID of the mini tablet's body model overlay. <code>null</code> if not in HMD mode.
* @property {Uuid} miniTabletScreenID - The UUID of the mini tablet's screen overlay. <code>null</code> if not in HMD mode.
* @property {number} miniTabletHand - The hand that the mini tablet is displayed on: <code>0</code> for left hand,
* <code>1</code> for right hand, <code>-1</code> if not in HMD mode.
*/
class HMDScriptingInterface : public AbstractHMDScriptingInterface, public Dependency {
Q_OBJECT
Q_PROPERTY(glm::vec3 position READ getPosition)
Q_PROPERTY(glm::quat orientation READ getOrientation)
Q_PROPERTY(bool mounted READ isMounted NOTIFY mountedChanged)
Q_PROPERTY(bool showTablet READ getShouldShowTablet)
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 tabletScreenID READ getCurrentTabletScreenID WRITE setCurrentTabletScreenID)
Q_PROPERTY(QUuid homeButtonHighlightID READ getCurrentHomeButtonHighlightID WRITE setCurrentHomeButtonHighlightID)
Q_PROPERTY(QUuid miniTabletID READ getCurrentMiniTabletID WRITE setCurrentMiniTabletID)
Q_PROPERTY(QUuid miniTabletScreenID READ getCurrentMiniTabletScreenID WRITE setCurrentMiniTabletScreenID)
Q_PROPERTY(int miniTabletHand READ getCurrentMiniTabletHand WRITE setCurrentMiniTabletHand)
public:
@ -350,7 +356,7 @@ public:
static QScriptValue getHUDLookAtPosition2D(QScriptContext* context, QScriptEngine* engine);
static QScriptValue getHUDLookAtPosition3D(QScriptContext* context, QScriptEngine* engine);
bool isMounted() const;
bool isMounted() const override;
void toggleShouldShowTablet();
void setShouldShowTablet(bool value);
@ -369,6 +375,15 @@ public:
void setCurrentTabletScreenID(QUuid tabletID) { _tabletScreenID = tabletID; }
QUuid getCurrentTabletScreenID() const { return _tabletScreenID; }
void setCurrentMiniTabletID(QUuid miniTabletID) { _miniTabletID = miniTabletID; }
QUuid getCurrentMiniTabletID() const { return _miniTabletID; }
void setCurrentMiniTabletScreenID(QUuid miniTabletScreenID) { _miniTabletScreenID = miniTabletScreenID; }
QUuid getCurrentMiniTabletScreenID() const { return _miniTabletScreenID; }
void setCurrentMiniTabletHand(int miniTabletHand) { _miniTabletHand = miniTabletHand; }
int getCurrentMiniTabletHand() const { return _miniTabletHand; }
private:
bool _showTablet { false };
bool _tabletContextualMode { false };
@ -377,6 +392,9 @@ private:
QUuid _homeButtonID;
QUuid _tabletEntityID;
QUuid _homeButtonHighlightID;
QUuid _miniTabletID;
QUuid _miniTabletScreenID;
int _miniTabletHand { -1 };
// Get the position of the HMD
glm::vec3 getPosition() const;

View file

@ -134,7 +134,8 @@ void WindowScriptingInterface::openUrl(const QUrl& url) {
DependencyManager::get<AddressManager>()->handleLookupString(url.toString());
} else {
#if defined(Q_OS_ANDROID)
QList<QString> args = { url.toString() };
QMap<QString, QString> args;
args["url"] = url.toString();
AndroidHelper::instance().requestActivity("WebView", true, args);
#else
// address manager did not handle - ask QDesktopServices to handle

View file

@ -75,7 +75,14 @@ void OverlayConductor::centerUI() {
void OverlayConductor::update(float dt) {
auto offscreenUi = DependencyManager::get<OffscreenUi>();
bool currentVisible = !offscreenUi->getDesktop()->property("pinned").toBool();
if (!offscreenUi) {
return;
}
auto desktop = offscreenUi->getDesktop();
if (!desktop) {
return;
}
bool currentVisible = !desktop->property("pinned").toBool();
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
// centerUI when hmd mode is first enabled and mounted

View file

@ -74,7 +74,7 @@ void Circle3DOverlay::render(RenderArgs* args) {
const float FULL_CIRCLE = 360.0f;
const float SLICES = 180.0f; // The amount of segment to create the circle
const float SLICE_ANGLE = FULL_CIRCLE / SLICES;
const float SLICE_ANGLE_RADIANS = glm::radians(FULL_CIRCLE / SLICES);
const float MAX_COLOR = 255.0f;
auto geometryCache = DependencyManager::get<GeometryCache>();
@ -111,28 +111,38 @@ void Circle3DOverlay::render(RenderArgs* args) {
vec4 innerEndColor = vec4(toGlm(_innerEndColor), _innerEndAlpha) * pulseModifier;
vec4 outerEndColor = vec4(toGlm(_outerEndColor), _outerEndAlpha) * pulseModifier;
const auto startAtRadians = glm::radians(_startAt);
const auto endAtRadians = glm::radians(_endAt);
const auto totalRange = _endAt - _startAt;
if (_innerRadius <= 0) {
_solidPrimitive = gpu::TRIANGLE_FAN;
points << vec2();
colors << innerStartColor;
for (float angle = _startAt; angle <= _endAt; angle += SLICE_ANGLE) {
float range = (angle - _startAt) / (_endAt - _startAt);
float angleRadians = glm::radians(angle);
for (float angleRadians = startAtRadians; angleRadians < endAtRadians; angleRadians += SLICE_ANGLE_RADIANS) {
float range = (angleRadians - startAtRadians) / totalRange;
points << glm::vec2(cosf(angleRadians) * _outerRadius, sinf(angleRadians) * _outerRadius);
colors << glm::mix(outerStartColor, outerEndColor, range);
}
points << glm::vec2(cosf(endAtRadians) * _outerRadius, sinf(endAtRadians) * _outerRadius);
colors << outerEndColor;
} else {
_solidPrimitive = gpu::TRIANGLE_STRIP;
for (float angle = _startAt; angle <= _endAt; angle += SLICE_ANGLE) {
float range = (angle - _startAt) / (_endAt - _startAt);
for (float angleRadians = startAtRadians; angleRadians < endAtRadians; angleRadians += SLICE_ANGLE_RADIANS) {
float range = (angleRadians - startAtRadians) / totalRange;
float angleRadians = glm::radians(angle);
points << glm::vec2(cosf(angleRadians) * _innerRadius, sinf(angleRadians) * _innerRadius);
colors << glm::mix(innerStartColor, innerEndColor, range);
points << glm::vec2(cosf(angleRadians) * _outerRadius, sinf(angleRadians) * _outerRadius);
colors << glm::mix(outerStartColor, outerEndColor, range);
}
points << glm::vec2(cosf(endAtRadians) * _innerRadius, sinf(endAtRadians) * _innerRadius);
colors << innerEndColor;
points << glm::vec2(cosf(endAtRadians) * _outerRadius, sinf(endAtRadians) * _outerRadius);
colors << outerEndColor;
}
geometryCache->updateVertices(_quadVerticesID, points, colors);
}
@ -147,29 +157,28 @@ void Circle3DOverlay::render(RenderArgs* args) {
if (geometryChanged) {
QVector<glm::vec2> points;
float angle = _startAt;
float angleInRadians = glm::radians(angle);
glm::vec2 firstPoint(cosf(angleInRadians) * _outerRadius, sinf(angleInRadians) * _outerRadius);
const auto startAtRadians = glm::radians(_startAt);
const auto endAtRadians = glm::radians(_endAt);
float angleRadians = startAtRadians;
glm::vec2 firstPoint(cosf(angleRadians) * _outerRadius, sinf(angleRadians) * _outerRadius);
points << firstPoint;
while (angle < _endAt) {
angle += SLICE_ANGLE;
angleInRadians = glm::radians(angle);
glm::vec2 thisPoint(cosf(angleInRadians) * _outerRadius, sinf(angleInRadians) * _outerRadius);
while (angleRadians < endAtRadians) {
angleRadians += SLICE_ANGLE_RADIANS;
glm::vec2 thisPoint(cosf(angleRadians) * _outerRadius, sinf(angleRadians) * _outerRadius);
points << thisPoint;
if (getIsDashedLine()) {
angle += SLICE_ANGLE / 2.0f; // short gap
angleInRadians = glm::radians(angle);
glm::vec2 dashStartPoint(cosf(angleInRadians) * _outerRadius, sinf(angleInRadians) * _outerRadius);
angleRadians += SLICE_ANGLE_RADIANS / 2.0f; // short gap
glm::vec2 dashStartPoint(cosf(angleRadians) * _outerRadius, sinf(angleRadians) * _outerRadius);
points << dashStartPoint;
}
}
// get the last slice portion....
angle = _endAt;
angleInRadians = glm::radians(angle);
glm::vec2 lastPoint(cosf(angleInRadians) * _outerRadius, sinf(angleInRadians) * _outerRadius);
angleRadians = endAtRadians;
glm::vec2 lastPoint(cosf(angleRadians) * _outerRadius, sinf(angleRadians) * _outerRadius);
points << lastPoint;
geometryCache->updateVertices(_lineVerticesID, points, vec4(toGlm(getColor()), getAlpha()));
}

View file

@ -114,7 +114,6 @@ void Text3DOverlay::render(RenderArgs* args) {
float scaleFactor = (maxHeight / FIXED_FONT_SCALING_RATIO) * _lineHeight;
glm::vec2 clipMinimum(0.0f, 0.0f);
glm::vec2 clipDimensions((dimensions.x - (_leftMargin + _rightMargin)) / scaleFactor,
(dimensions.y - (_topMargin + _bottomMargin)) / scaleFactor);
@ -296,4 +295,4 @@ QSizeF Text3DOverlay::textSize(const QString& text) const {
float pointToWorldScale = (maxHeight / FIXED_FONT_SCALING_RATIO) * _lineHeight;
return QSizeF(extents.x, extents.y) * pointToWorldScale;
}
}

View file

@ -13,6 +13,7 @@
#define hifi_AudioHRTF_h
#include <stdint.h>
#include <string.h>
static const int HRTF_AZIMUTHS = 72; // 360 / 5-degree steps
static const int HRTF_TAPS = 64; // minimum-phase FIR coefficients
@ -56,6 +57,27 @@ public:
void setGainAdjustment(float gain) { _gainAdjust = HRTF_GAIN * gain; };
float getGainAdjustment() { return _gainAdjust; }
// clear internal state, but retain settings
void reset() {
// FIR history
memset(_firState, 0, sizeof(_firState));
// integer delay history
memset(_delayState, 0, sizeof(_delayState));
// biquad history
memset(_bqState, 0, sizeof(_bqState));
// parameter history
_azimuthState = 0.0f;
_distanceState = 0.0f;
_gainState = 0.0f;
// _gainAdjust is retained
_silentState = true;
}
private:
AudioHRTF(const AudioHRTF&) = delete;
AudioHRTF& operator=(const AudioHRTF&) = delete;
@ -88,7 +110,7 @@ private:
// global and local gain adjustment
float _gainAdjust = HRTF_GAIN;
bool _silentState = false;
bool _silentState = true;
};
#endif // AudioHRTF_h

View file

@ -113,6 +113,80 @@ void Avatar::setShowNamesAboveHeads(bool show) {
showNamesAboveHeads = show;
}
AvatarTransit::Status AvatarTransit::update(float deltaTime, const glm::vec3& avatarPosition, const AvatarTransit::TransitConfig& config) {
glm::vec3 currentPosition = _isTransiting ? _currentPosition : avatarPosition;
float oneFrameDistance = glm::length(currentPosition - _lastPosition);
const float MAX_TRANSIT_DISTANCE = 30.0f;
float scaledMaxTransitDistance = MAX_TRANSIT_DISTANCE * _scale;
if (oneFrameDistance > config._triggerDistance && oneFrameDistance < scaledMaxTransitDistance && !_isTransiting) {
start(deltaTime, _lastPosition, currentPosition, config);
}
_lastPosition = currentPosition;
_status = updatePosition(deltaTime);
return _status;
}
void AvatarTransit::start(float deltaTime, const glm::vec3& startPosition, const glm::vec3& endPosition, const AvatarTransit::TransitConfig& config) {
_startPosition = startPosition;
_endPosition = endPosition;
_transitLine = endPosition - startPosition;
_totalDistance = glm::length(_transitLine);
_easeType = config._easeType;
const float REFERENCE_FRAMES_PER_SECOND = 30.0f;
int transitFrames = (!config._isDistanceBased) ? config._totalFrames : config._framesPerMeter * _totalDistance;
_totalTime = (float)transitFrames / REFERENCE_FRAMES_PER_SECOND;
_currentTime = 0.0f;
_isTransiting = true;
}
float AvatarTransit::getEaseValue(AvatarTransit::EaseType type, float value) {
switch (type) {
case EaseType::NONE:
return value;
break;
case EaseType::EASE_IN:
return value * value;
break;
case EaseType::EASE_OUT:
return value * (2.0f - value);
break;
case EaseType::EASE_IN_OUT:
return (value < 0.5f) ? 2.0f * value * value : -1.0f + (4.0f - 2.0f * value) * value;
break;
}
return value;
}
AvatarTransit::Status AvatarTransit::updatePosition(float deltaTime) {
Status status = Status::IDLE;
if (_isTransiting) {
float nextTime = _currentTime + deltaTime;
glm::vec3 newPosition;
if (nextTime >= _totalTime) {
_currentPosition = _endPosition;
_isTransiting = false;
status = Status::END_TRANSIT;
} else {
if (_currentTime == 0) {
status = Status::START_TRANSIT;
} else {
status = Status::TRANSITING;
}
float percentageIntoTransit = nextTime / _totalTime;
_currentPosition = _startPosition + getEaseValue(_easeType, percentageIntoTransit) * _transitLine;
}
_currentTime = nextTime;
}
return status;
}
bool AvatarTransit::getNextPosition(glm::vec3& nextPosition) {
nextPosition = _currentPosition;
return _isTransiting;
}
Avatar::Avatar(QThread* thread) :
_voiceSphereID(GeometryCache::UNKNOWN_ID)
{
@ -449,7 +523,18 @@ void Avatar::relayJointDataToChildren() {
void Avatar::simulate(float deltaTime, bool inView) {
PROFILE_RANGE(simulation, "simulate");
if (_transit.isTransiting()) {
glm::vec3 nextPosition;
if (_transit.getNextPosition(nextPosition)) {
_globalPosition = nextPosition;
_globalPositionChanged = usecTimestampNow();
if (!hasParent()) {
setLocalPosition(nextPosition);
}
}
}
_simulationRate.increment();
if (inView) {
_simulationInViewRate.increment();
@ -460,7 +545,7 @@ void Avatar::simulate(float deltaTime, bool inView) {
PROFILE_RANGE(simulation, "updateJoints");
if (inView) {
Head* head = getHead();
if (_hasNewJointData) {
if (_hasNewJointData || _transit.isTransiting()) {
_skeletonModel->getRig().copyJointsFromJointData(_jointData);
glm::mat4 rootTransform = glm::scale(_skeletonModel->getScale()) * glm::translate(_skeletonModel->getOffset());
_skeletonModel->getRig().computeExternalPoses(rootTransform);
@ -1881,6 +1966,22 @@ float Avatar::getUnscaledEyeHeightFromSkeleton() const {
}
}
AvatarTransit::Status Avatar::updateTransit(float deltaTime, const glm::vec3& avatarPosition, const AvatarTransit::TransitConfig& config) {
std::lock_guard<std::mutex> lock(_transitLock);
return _transit.update(deltaTime, avatarPosition, config);
}
void Avatar::setTransitScale(float scale) {
std::lock_guard<std::mutex> lock(_transitLock);
return _transit.setScale(scale);
}
void Avatar::overrideNextPackagePositionData(const glm::vec3& position) {
std::lock_guard<std::mutex> lock(_transitLock);
_overrideGlobalPosition = true;
_globalPositionOverride = position;
}
void Avatar::addMaterial(graphics::MaterialLayer material, const std::string& parentMaterialName) {
std::lock_guard<std::mutex> lock(_materialsLock);
_materials[parentMaterialName].push(material);

View file

@ -50,6 +50,62 @@ enum ScreenTintLayer {
class Texture;
class AvatarTransit {
public:
enum Status {
IDLE = 0,
START_TRANSIT,
TRANSITING,
END_TRANSIT
};
enum EaseType {
NONE = 0,
EASE_IN,
EASE_OUT,
EASE_IN_OUT
};
struct TransitConfig {
TransitConfig() {};
int _totalFrames { 0 };
int _framesPerMeter { 0 };
bool _isDistanceBased { false };
float _triggerDistance { 0 };
EaseType _easeType { EaseType::EASE_OUT };
};
AvatarTransit() {};
Status update(float deltaTime, const glm::vec3& avatarPosition, const TransitConfig& config);
Status getStatus() { return _status; }
bool isTransiting() { return _isTransiting; }
glm::vec3 getCurrentPosition() { return _currentPosition; }
bool getNextPosition(glm::vec3& nextPosition);
glm::vec3 getEndPosition() { return _endPosition; }
float getTransitTime() { return _totalTime; }
void setScale(float scale) { _scale = scale; }
private:
Status updatePosition(float deltaTime);
void start(float deltaTime, const glm::vec3& startPosition, const glm::vec3& endPosition, const TransitConfig& config);
float getEaseValue(AvatarTransit::EaseType type, float value);
bool _isTransiting { false };
glm::vec3 _startPosition;
glm::vec3 _endPosition;
glm::vec3 _currentPosition;
glm::vec3 _lastPosition;
glm::vec3 _transitLine;
float _totalDistance { 0.0f };
float _totalTime { 0.0f };
float _currentTime { 0.0f };
EaseType _easeType { EaseType::EASE_OUT };
Status _status { Status::IDLE };
float _scale { 1.0f };
};
class Avatar : public AvatarData, public scriptable::ModelProvider {
Q_OBJECT
@ -370,6 +426,13 @@ public:
virtual scriptable::ScriptableModelBase getScriptableModel() override;
std::shared_ptr<AvatarTransit> getTransit() { return std::make_shared<AvatarTransit>(_transit); };
AvatarTransit::Status updateTransit(float deltaTime, const glm::vec3& avatarPosition, const AvatarTransit::TransitConfig& config);
void setTransitScale(float scale);
void overrideNextPackagePositionData(const glm::vec3& position);
signals:
void targetScaleChanged(float targetScale);
@ -505,6 +568,7 @@ protected:
RateCounter<> _skeletonModelSimulationRate;
RateCounter<> _jointDataSimulationRate;
protected:
class AvatarEntityDataHash {
public:
@ -528,6 +592,9 @@ protected:
bool _reconstructSoftEntitiesJointMap { false };
float _modelScale { 1.0f };
AvatarTransit _transit;
std::mutex _transitLock;
static int _jointConesID;
int _voiceSphereID;

View file

@ -369,7 +369,12 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent
if (hasAvatarGlobalPosition) {
auto startSection = destinationBuffer;
AVATAR_MEMCPY(_globalPosition);
if (_overrideGlobalPosition) {
AVATAR_MEMCPY(_globalPositionOverride);
} else {
AVATAR_MEMCPY(_globalPosition);
}
int numBytes = destinationBuffer - startSection;
@ -2088,6 +2093,10 @@ void AvatarData::sendAvatarDataPacket(bool sendAll) {
}
}
if (_overrideGlobalPosition) {
_overrideGlobalPosition = false;
}
doneEncoding(cullSmallData);
static AvatarDataSequenceNumber sequenceNumber = 0;

View file

@ -1372,7 +1372,8 @@ protected:
// where Entities are located. This is currently only used by the mixer to decide how often to send
// updates about one avatar to another.
glm::vec3 _globalPosition { 0, 0, 0 };
glm::vec3 _globalPositionOverride { 0, 0, 0 };
bool _overrideGlobalPosition { false };
quint64 _globalPositionChanged { 0 };
quint64 _avatarBoundingBoxChanged { 0 };

View file

@ -266,6 +266,7 @@ AvatarSharedPointer AvatarHashMap::parseAvatarData(QSharedPointer<ReceivedMessag
}
}
// have the matching (or new) avatar parse the data from the packet
int bytesRead = avatar->parseDataFromBuffer(byteArray);
message->seek(positionBeforeRead + bytesRead);
@ -316,7 +317,6 @@ void AvatarHashMap::processAvatarIdentityPacket(QSharedPointer<ReceivedMessage>
// In this case, the "sendingNode" is the Avatar Mixer.
avatar->processAvatarIdentity(message->getMessage(), identityChanged, displayNameChanged);
_replicas.processAvatarIdentity(identityUUID, message->getMessage(), identityChanged, displayNameChanged);
}
}
@ -329,6 +329,7 @@ void AvatarHashMap::processBulkAvatarTraits(QSharedPointer<ReceivedMessage> mess
// grab the avatar so we can ask it to process trait data
bool isNewAvatar;
auto avatar = newOrExistingAvatar(avatarID, sendingNode, isNewAvatar);
// read the first trait type for this avatar
AvatarTraits::TraitType traitType;
message->readPrimitive(&traitType);

View file

@ -98,6 +98,7 @@ enum Hand {
class InputDevice {
public:
InputDevice(const QString& name) : _name(name) {}
virtual ~InputDevice() {}
using Pointer = std::shared_ptr<InputDevice>;

View file

@ -24,6 +24,8 @@ public:
AndConditional(Conditional::Pointer& first, Conditional::Pointer& second)
: _children({ first, second }) {}
virtual ~AndConditional() {}
virtual bool satisfied() override;
private:

View file

@ -18,6 +18,7 @@ namespace controller {
class EndpointConditional : public Conditional {
public:
EndpointConditional(Endpoint::Pointer endpoint) : _endpoint(endpoint) {}
virtual ~EndpointConditional() {}
virtual bool satisfied() override { return _endpoint && _endpoint->peek() != 0.0f; }
private:
Endpoint::Pointer _endpoint;

View file

@ -19,6 +19,7 @@ namespace controller {
using Pointer = std::shared_ptr<NotConditional>;
NotConditional(Conditional::Pointer operand) : _operand(operand) { }
virtual ~NotConditional() {}
virtual bool satisfied() override;

View file

@ -18,6 +18,7 @@ class ClampFilter : public Filter {
REGISTER_FILTER_CLASS(ClampFilter);
public:
ClampFilter(float min = 0.0, float max = 1.0) : _min(min), _max(max) {};
virtual ~ClampFilter() {}
virtual float apply(float value) const override {
return glm::clamp(value, _min, _max);
}

View file

@ -18,6 +18,7 @@ class ConstrainToIntegerFilter : public Filter {
REGISTER_FILTER_CLASS(ConstrainToIntegerFilter);
public:
ConstrainToIntegerFilter() {};
virtual ~ConstrainToIntegerFilter() {}
virtual float apply(float value) const override {
return glm::sign(value);

View file

@ -18,6 +18,7 @@ class ConstrainToPositiveIntegerFilter : public Filter {
REGISTER_FILTER_CLASS(ConstrainToPositiveIntegerFilter);
public:
ConstrainToPositiveIntegerFilter() {};
virtual ~ConstrainToPositiveIntegerFilter() {};
virtual float apply(float value) const override {
return (value <= 0.0f) ? 0.0f : 1.0f;

View file

@ -18,6 +18,7 @@ class DeadZoneFilter : public Filter {
REGISTER_FILTER_CLASS(DeadZoneFilter);
public:
DeadZoneFilter(float min = 0.0) : _min(min) {};
virtual ~DeadZoneFilter() {}
virtual float apply(float value) const override;

View file

@ -20,6 +20,7 @@ namespace controller {
ExponentialSmoothingFilter() {}
ExponentialSmoothingFilter(float rotationConstant, float translationConstant) :
_translationConstant(translationConstant), _rotationConstant(rotationConstant) {}
virtual ~ExponentialSmoothingFilter() {}
float apply(float value) const override { return value; }
Pose apply(Pose value) const override;

View file

@ -18,6 +18,7 @@ class HysteresisFilter : public Filter {
REGISTER_FILTER_CLASS(HysteresisFilter);
public:
HysteresisFilter(float min = 0.25, float max = 0.75);
virtual ~HysteresisFilter() {}
virtual float apply(float value) const override;
virtual Pose apply(Pose value) const override { return value; }

View file

@ -19,6 +19,7 @@ class InvertFilter : public ScaleFilter {
public:
using ScaleFilter::parseParameters;
InvertFilter() : ScaleFilter(-1.0f) {}
virtual ~InvertFilter() {}
virtual bool parseParameters(const QJsonArray& parameters) { return true; }

View file

@ -20,6 +20,7 @@ namespace controller {
LowVelocityFilter() {}
LowVelocityFilter(float rotationConstant, float translationConstant) :
_translationConstant(translationConstant), _rotationConstant(rotationConstant) {}
virtual ~LowVelocityFilter() {}
float apply(float value) const override { return value; }
Pose apply(Pose newPose) const override;

View file

@ -10,6 +10,7 @@ class NotFilter : public Filter {
REGISTER_FILTER_CLASS(NotFilter);
public:
NotFilter();
virtual ~NotFilter() {}
virtual float apply(float value) const override;
virtual Pose apply(Pose value) const override { return value; }

View file

@ -21,6 +21,7 @@ class PostTransformFilter : public Filter {
public:
PostTransformFilter() { }
PostTransformFilter(glm::mat4 transform) : _transform(transform) {}
virtual ~PostTransformFilter() {}
virtual float apply(float value) const override { return value; }
virtual Pose apply(Pose value) const override { return value.postTransform(_transform); }
virtual bool parseParameters(const QJsonValue& parameters) override { return parseMat4Parameter(parameters, _transform); }

View file

@ -20,6 +20,7 @@ class PulseFilter : public Filter {
public:
PulseFilter() {}
PulseFilter(float interval) : _interval(interval) {}
virtual ~PulseFilter() {}
virtual float apply(float value) const override;

View file

@ -21,6 +21,7 @@ class RotateFilter : public Filter {
public:
RotateFilter() { }
RotateFilter(glm::quat rotation) : _rotation(rotation) {}
virtual ~RotateFilter() {}
virtual float apply(float value) const override { return value; }

View file

@ -21,6 +21,7 @@ class ScaleFilter : public Filter {
public:
ScaleFilter() {}
ScaleFilter(float scale) : _scale(scale) {}
virtual ~ScaleFilter() {}
virtual float apply(float value) const override {
return value * _scale;

View file

@ -21,6 +21,7 @@ class TransformFilter : public Filter {
public:
TransformFilter() { }
TransformFilter(glm::mat4 transform) : _transform(transform) {}
virtual ~TransformFilter() {}
virtual float apply(float value) const override { return value; }
virtual Pose apply(Pose value) const override { return value.transform(_transform); }

View file

@ -21,6 +21,7 @@ class TranslateFilter : public Filter {
public:
TranslateFilter() { }
TranslateFilter(glm::vec3 translate) : _translate(translate) {}
virtual ~TranslateFilter() {}
virtual float apply(float value) const override { return value; }
virtual Pose apply(Pose value) const override { return value.transform(glm::translate(_translate)); }

View file

@ -20,6 +20,7 @@ class AbstractHMDScriptingInterface : public QObject {
Q_PROPERTY(float eyeHeight READ getEyeHeight)
Q_PROPERTY(float playerHeight READ getPlayerHeight)
Q_PROPERTY(float ipdScale READ getIPDScale WRITE setIPDScale NOTIFY IPDScaleChanged)
Q_PROPERTY(bool mounted READ isMounted NOTIFY mountedChanged)
public:
AbstractHMDScriptingInterface();
@ -29,6 +30,7 @@ public:
float getIPDScale() const;
void setIPDScale(float ipdScale);
bool isHMDMode() const;
virtual bool isMounted() const = 0;
signals:
/**jsdoc

View file

@ -187,6 +187,13 @@ void EntityTreeRenderer::resetEntitiesScriptEngine() {
connect(entityScriptingInterface.data(), &EntityScriptingInterface::hoverLeaveEntity, _entitiesScriptEngine.data(), [&](const EntityItemID& entityID, const PointerEvent& event) {
_entitiesScriptEngine->callEntityScriptMethod(entityID, "hoverLeaveEntity", event);
});
connect(_entitiesScriptEngine.data(), &ScriptEngine::entityScriptPreloadFinished, [&](const EntityItemID& entityID) {
EntityItemPointer entity = getTree()->findEntityByID(entityID);
if (entity) {
entity->setScriptHasFinishedPreload(true);
}
});
}
void EntityTreeRenderer::clear() {
@ -512,7 +519,11 @@ bool EntityTreeRenderer::findBestZoneAndMaybeContainingEntities(QVector<EntityIt
// be ignored because they can have events fired on them.
// FIXME - this could be optimized further by determining if the script is loaded
// and if it has either an enterEntity or leaveEntity method
if (isZone || hasScript) {
//
// also, don't flag a scripted entity as containing the avatar until the script is loaded,
// so that the script is awake in time to receive the "entityEntity" call (even if the entity is a zone).
if ((!hasScript && isZone) ||
(hasScript && entity->isScriptPreloadFinished())) {
// now check to see if the point contains our entity, this can be expensive if
// the entity has a collision hull
if (entity->contains(_avatarPosition)) {
@ -972,6 +983,7 @@ void EntityTreeRenderer::checkAndCallPreload(const EntityItemID& entityID, bool
entity->scriptHasUnloaded();
}
if (shouldLoad) {
entity->setScriptHasFinishedPreload(false);
_entitiesScriptEngine->loadEntityScript(entityID, resolveScriptURL(scriptUrl), reload);
entity->scriptHasPreloaded();
}

View file

@ -305,7 +305,7 @@ void EntityRenderer::updateInScene(const ScenePointer& scene, Transaction& trans
}
doRenderUpdateSynchronous(scene, transaction, _entity);
transaction.updateItem<EntityRenderer>(_renderItemID, [this](EntityRenderer& self) {
transaction.updateItem<PayloadProxyInterface>(_renderItemID, [this](PayloadProxyInterface& self) {
if (!isValidRenderItem()) {
return;
}

View file

@ -250,8 +250,8 @@ void RenderableModelEntityItem::updateModelBounds() {
}
EntityItemProperties RenderableModelEntityItem::getProperties(EntityPropertyFlags desiredProperties) const {
EntityItemProperties properties = ModelEntityItem::getProperties(desiredProperties); // get the properties from our base class
EntityItemProperties RenderableModelEntityItem::getProperties(const EntityPropertyFlags& desiredProperties, bool allowEmptyDesiredProperties) const {
EntityItemProperties properties = ModelEntityItem::getProperties(desiredProperties, allowEmptyDesiredProperties); // get the properties from our base class
if (_originalTexturesRead) {
properties.setTextureNames(_originalTextures);
}

View file

@ -62,7 +62,7 @@ public:
virtual void setUnscaledDimensions(const glm::vec3& value) override;
virtual EntityItemProperties getProperties(EntityPropertyFlags desiredProperties = EntityPropertyFlags()) const override;
virtual EntityItemProperties getProperties(const EntityPropertyFlags& desiredProperties, bool allowEmptyDesiredProperties) const override;
void doInitialModelSimulation();
void updateModelBounds();

View file

@ -97,10 +97,10 @@ void ShapeEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce
withWriteLock([&] {
auto entity = getEntity();
_position = entity->getWorldPosition();
_dimensions = entity->getScaledDimensions();
_dimensions = entity->getUnscaledDimensions(); // get unscaled to avoid scaling twice
_orientation = entity->getWorldOrientation();
updateModelTransformAndBound();
_renderTransform = getModelTransform();
_renderTransform = getModelTransform(); // contains parent scale, if this entity scales with its parent
if (_shape == entity::Sphere) {
_renderTransform.postScale(SPHERE_ENTITY_SCALE);
}

View file

@ -1251,9 +1251,10 @@ quint64 EntityItem::getExpiry() const {
return getCreated() + (quint64)(getLifetime() * (float)USECS_PER_SECOND);
}
EntityItemProperties EntityItem::getProperties(EntityPropertyFlags desiredProperties) const {
EntityItemProperties EntityItem::getProperties(const EntityPropertyFlags& desiredProperties, bool allowEmptyDesiredProperties) const {
EncodeBitstreamParams params; // unknown
EntityPropertyFlags propertyFlags = desiredProperties.isEmpty() ? getEntityProperties(params) : desiredProperties;
const EntityPropertyFlags propertyFlags = !allowEmptyDesiredProperties && desiredProperties.isEmpty() ?
getEntityProperties(params) : desiredProperties;
EntityItemProperties properties(propertyFlags);
properties._id = getID();
properties._idSet = true;
@ -3197,3 +3198,26 @@ void EntityItem::setCloneIDs(const QVector<QUuid>& cloneIDs) {
_cloneIDs = cloneIDs;
});
}
bool EntityItem::shouldPreloadScript() const {
return !_script.isEmpty() && ((_loadedScript != _script) || (_loadedScriptTimestamp != _scriptTimestamp));
}
void EntityItem::scriptHasPreloaded() {
_loadedScript = _script;
_loadedScriptTimestamp = _scriptTimestamp;
}
void EntityItem::scriptHasUnloaded() {
_loadedScript = "";
_loadedScriptTimestamp = 0;
_scriptPreloadFinished = false;
}
void EntityItem::setScriptHasFinishedPreload(bool value) {
_scriptPreloadFinished = value;
}
bool EntityItem::isScriptPreloadFinished() {
return _scriptPreloadFinished;
}

View file

@ -88,7 +88,7 @@ public:
EntityItemID getEntityItemID() const { return EntityItemID(_id); }
// methods for getting/setting all properties of an entity
virtual EntityItemProperties getProperties(EntityPropertyFlags desiredProperties = EntityPropertyFlags()) const;
virtual EntityItemProperties getProperties(const EntityPropertyFlags& desiredProperties = EntityPropertyFlags(), bool allowEmptyDesiredProperties = false) const;
/// returns true if something changed
// This function calls setSubClass properties and detects if any property changes value.
@ -441,6 +441,8 @@ public:
void setDynamicDataNeedsTransmit(bool value) const { _dynamicDataNeedsTransmit = value; }
bool dynamicDataNeedsTransmit() const { return _dynamicDataNeedsTransmit; }
void setTransitingWithAvatar(bool value) { _transitingWithAvatar = value; }
bool getTransitingWithAvatar() { return _transitingWithAvatar; }
bool shouldSuppressLocationEdits() const;
@ -470,10 +472,11 @@ public:
/// We only want to preload if:
/// there is some script, and either the script value or the scriptTimestamp
/// value have changed since our last preload
bool shouldPreloadScript() const { return !_script.isEmpty() &&
((_loadedScript != _script) || (_loadedScriptTimestamp != _scriptTimestamp)); }
void scriptHasPreloaded() { _loadedScript = _script; _loadedScriptTimestamp = _scriptTimestamp; }
void scriptHasUnloaded() { _loadedScript = ""; _loadedScriptTimestamp = 0; }
bool shouldPreloadScript() const;
void scriptHasPreloaded();
void scriptHasUnloaded();
void setScriptHasFinishedPreload(bool value);
bool isScriptPreloadFinished();
bool getClientOnly() const { return _clientOnly; }
virtual void setClientOnly(bool clientOnly) { _clientOnly = clientOnly; }
@ -584,6 +587,7 @@ protected:
QString _script { ENTITY_ITEM_DEFAULT_SCRIPT }; /// the value of the script property
QString _loadedScript; /// the value of _script when the last preload signal was sent
quint64 _scriptTimestamp { ENTITY_ITEM_DEFAULT_SCRIPT_TIMESTAMP }; /// the script loaded property used for forced reload
bool _scriptPreloadFinished { false };
QString _serverScripts;
/// keep track of time when _serverScripts property was last changed
@ -666,6 +670,7 @@ protected:
QUuid _sourceUUID; /// the server node UUID we came from
bool _clientOnly { false };
bool _transitingWithAvatar{ false };
QUuid _owningAvatarID;
// physics related changes from the network to suppress any duplicates and make

View file

@ -1216,7 +1216,9 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
* });
*/
QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool skipDefaults, bool allowUnknownCreateTime, bool strictSemantics) const {
QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool skipDefaults, bool allowUnknownCreateTime,
bool strictSemantics, EntityPsuedoPropertyFlags psueudoPropertyFlags) const {
// If strictSemantics is true and skipDefaults is false, then all and only those properties are copied for which the property flag
// is included in _desiredProperties, or is one of the specially enumerated ALWAYS properties below.
// (There may be exceptions, but if so, they are bugs.)
@ -1224,26 +1226,39 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool
QScriptValue properties = engine->newObject();
EntityItemProperties defaultEntityProperties;
const bool psuedoPropertyFlagsActive = psueudoPropertyFlags.test(EntityPsuedoPropertyFlag::FlagsActive);
// Fix to skip the default return all mechanism, when psuedoPropertyFlagsActive
const bool psuedoPropertyFlagsButDesiredEmpty = psuedoPropertyFlagsActive && _desiredProperties.isEmpty();
if (_created == UNKNOWN_CREATED_TIME && !allowUnknownCreateTime) {
// No entity properties can have been set so return without setting any default, zero property values.
return properties;
}
if (_idSet) {
if (_idSet && (!psuedoPropertyFlagsActive || psueudoPropertyFlags.test(EntityPsuedoPropertyFlag::ID))) {
COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER_ALWAYS(id, _id.toString());
}
COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER_ALWAYS(type, EntityTypes::getEntityTypeName(_type));
auto created = QDateTime::fromMSecsSinceEpoch(getCreated() / 1000.0f, Qt::UTC); // usec per msec
created.setTimeSpec(Qt::OffsetFromUTC);
COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER_ALWAYS(created, created.toString(Qt::ISODate));
if ((!skipDefaults || _lifetime != defaultEntityProperties._lifetime) && !strictSemantics) {
COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER_NO_SKIP(age, getAge()); // gettable, but not settable
COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER_NO_SKIP(ageAsText, formatSecondsElapsed(getAge())); // gettable, but not settable
if (!psuedoPropertyFlagsActive || psueudoPropertyFlags.test(EntityPsuedoPropertyFlag::Type)) {
COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER_ALWAYS(type, EntityTypes::getEntityTypeName(_type));
}
if (!psuedoPropertyFlagsActive || psueudoPropertyFlags.test(EntityPsuedoPropertyFlag::Created)) {
auto created = QDateTime::fromMSecsSinceEpoch(getCreated() / 1000.0f, Qt::UTC); // usec per msec
created.setTimeSpec(Qt::OffsetFromUTC);
COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER_ALWAYS(created, created.toString(Qt::ISODate));
}
properties.setProperty("lastEdited", convertScriptValue(engine, _lastEdited));
if ((!skipDefaults || _lifetime != defaultEntityProperties._lifetime) && !strictSemantics) {
if (!psuedoPropertyFlagsActive || psueudoPropertyFlags.test(EntityPsuedoPropertyFlag::Age)) {
COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER_NO_SKIP(age, getAge()); // gettable, but not settable
}
if (!psuedoPropertyFlagsActive || psueudoPropertyFlags.test(EntityPsuedoPropertyFlag::AgeAsText)) {
COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER_NO_SKIP(ageAsText, formatSecondsElapsed(getAge())); // gettable, but not settable
}
}
if (!psuedoPropertyFlagsActive || psueudoPropertyFlags.test(EntityPsuedoPropertyFlag::LastEdited)) {
properties.setProperty("lastEdited", convertScriptValue(engine, _lastEdited));
}
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_LAST_EDITED_BY, lastEditedBy);
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_POSITION, position);
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_DIMENSIONS, dimensions);
@ -1340,7 +1355,9 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool
// Models only
if (_type == EntityTypes::Model) {
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_MODEL_URL, modelURL);
_animation.copyToScriptValue(_desiredProperties, properties, engine, skipDefaults, defaultEntityProperties);
if (!psuedoPropertyFlagsButDesiredEmpty) {
_animation.copyToScriptValue(_desiredProperties, properties, engine, skipDefaults, defaultEntityProperties);
}
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_JOINT_ROTATIONS_SET, jointRotationsSet);
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_JOINT_ROTATIONS, jointRotations);
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_JOINT_TRANSLATIONS_SET, jointTranslationsSet);
@ -1393,21 +1410,24 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool
// Zones only
if (_type == EntityTypes::Zone) {
_keyLight.copyToScriptValue(_desiredProperties, properties, engine, skipDefaults, defaultEntityProperties);
_ambientLight.copyToScriptValue(_desiredProperties, properties, engine, skipDefaults, defaultEntityProperties);
_skybox.copyToScriptValue(_desiredProperties, properties, engine, skipDefaults, defaultEntityProperties);
if (!psuedoPropertyFlagsButDesiredEmpty) {
_keyLight.copyToScriptValue(_desiredProperties, properties, engine, skipDefaults, defaultEntityProperties);
_ambientLight.copyToScriptValue(_desiredProperties, properties, engine, skipDefaults, defaultEntityProperties);
_skybox.copyToScriptValue(_desiredProperties, properties, engine, skipDefaults, defaultEntityProperties);
}
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_FLYING_ALLOWED, flyingAllowed);
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_GHOSTING_ALLOWED, ghostingAllowed);
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_FILTER_URL, filterURL);
COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_HAZE_MODE, hazeMode, getHazeModeAsString());
_haze.copyToScriptValue(_desiredProperties, properties, engine, skipDefaults, defaultEntityProperties);
if (!psuedoPropertyFlagsButDesiredEmpty) {
_haze.copyToScriptValue(_desiredProperties, properties, engine, skipDefaults, defaultEntityProperties);
}
COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_BLOOM_MODE, bloomMode, getBloomModeAsString());
_bloom.copyToScriptValue(_desiredProperties, properties, engine, skipDefaults, defaultEntityProperties);
if (!psuedoPropertyFlagsButDesiredEmpty) {
_bloom.copyToScriptValue(_desiredProperties, properties, engine, skipDefaults, defaultEntityProperties);
}
COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_KEY_LIGHT_MODE, keyLightMode, getKeyLightModeAsString());
COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_AMBIENT_LIGHT_MODE, ambientLightMode, getAmbientLightModeAsString());
COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_SKYBOX_MODE, skyboxMode, getSkyboxModeAsString());
@ -1468,7 +1488,9 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool
* @property {Vec3} center - The center of the AA box.
* @property {Vec3} dimensions - The dimensions of the AA box.
*/
if (!skipDefaults && !strictSemantics) {
if (!skipDefaults && !strictSemantics &&
(!psuedoPropertyFlagsActive || psueudoPropertyFlags.test(EntityPsuedoPropertyFlag::BoundingBox))) {
AABox aaBox = getAABox();
QScriptValue boundingBox = engine->newObject();
QScriptValue bottomRightNear = vec3toScriptValue(engine, aaBox.getCorner());
@ -1483,7 +1505,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool
}
QString textureNamesStr = QJsonDocument::fromVariant(_textureNames).toJson();
if (!skipDefaults && !strictSemantics) {
if (!skipDefaults && !strictSemantics && (!psuedoPropertyFlagsActive || psueudoPropertyFlags.test(EntityPsuedoPropertyFlag::OriginalTextures))) {
COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER_NO_SKIP(originalTextures, textureNamesStr); // gettable, but not settable
}
@ -1509,7 +1531,9 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_CLONE_ORIGIN_ID, cloneOriginID);
// Rendering info
if (!skipDefaults && !strictSemantics) {
if (!skipDefaults && !strictSemantics &&
(!psuedoPropertyFlagsActive || psueudoPropertyFlags.test(EntityPsuedoPropertyFlag::RenderInfo))) {
QScriptValue renderInfo = engine->newObject();
/**jsdoc
@ -1535,8 +1559,12 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool
}
// FIXME: These properties should already have been set above.
properties.setProperty("clientOnly", convertScriptValue(engine, getClientOnly()));
properties.setProperty("owningAvatarID", convertScriptValue(engine, getOwningAvatarID()));
if (!psuedoPropertyFlagsActive || psueudoPropertyFlags.test(EntityPsuedoPropertyFlag::ClientOnly)) {
properties.setProperty("clientOnly", convertScriptValue(engine, getClientOnly()));
}
if (!psuedoPropertyFlagsActive || psueudoPropertyFlags.test(EntityPsuedoPropertyFlag::OwningAvatarID)) {
properties.setProperty("owningAvatarID", convertScriptValue(engine, getOwningAvatarID()));
}
// FIXME - I don't think these properties are supported any more
//COPY_PROPERTY_TO_QSCRIPTVALUE(localRenderAlpha);

View file

@ -35,6 +35,7 @@
#include "EntityItemPropertiesMacros.h"
#include "EntityTypes.h"
#include "EntityPropertyFlags.h"
#include "EntityPsuedoPropertyFlags.h"
#include "LightEntityItem.h"
#include "LineEntityItem.h"
#include "ParticleEffectEntityItem.h"
@ -86,7 +87,8 @@ public:
EntityTypes::EntityType getType() const { return _type; }
void setType(EntityTypes::EntityType type) { _type = type; }
virtual QScriptValue copyToScriptValue(QScriptEngine* engine, bool skipDefaults, bool allowUnknownCreateTime = false, bool strictSemantics = false) const;
virtual QScriptValue copyToScriptValue(QScriptEngine* engine, bool skipDefaults, bool allowUnknownCreateTime = false,
bool strictSemantics = false, EntityPsuedoPropertyFlags psueudoPropertyFlags = EntityPsuedoPropertyFlags()) const;
virtual void copyFromScriptValue(const QScriptValue& object, bool honorReadOnly);
static QScriptValue entityPropertyFlagsToScriptValue(QScriptEngine* engine, const EntityPropertyFlags& flags);

View file

@ -155,7 +155,7 @@ inline QScriptValue convertScriptValue(QScriptEngine* e, const AACube& v) { retu
}
#define COPY_PROPERTY_TO_QSCRIPTVALUE(p,P) \
if ((_desiredProperties.isEmpty() || _desiredProperties.getHasProperty(p)) && \
if (((!psuedoPropertyFlagsButDesiredEmpty && _desiredProperties.isEmpty()) || _desiredProperties.getHasProperty(p)) && \
(!skipDefaults || defaultEntityProperties._##P != _##P)) { \
QScriptValue V = convertScriptValue(engine, _##P); \
properties.setProperty(#P, V); \
@ -165,7 +165,7 @@ inline QScriptValue convertScriptValue(QScriptEngine* e, const AACube& v) { retu
properties.setProperty(#P, G);
#define COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(p, P, G) \
if ((_desiredProperties.isEmpty() || _desiredProperties.getHasProperty(p)) && \
if (((!psuedoPropertyFlagsButDesiredEmpty && _desiredProperties.isEmpty()) || _desiredProperties.getHasProperty(p)) && \
(!skipDefaults || defaultEntityProperties._##P != _##P)) { \
QScriptValue V = convertScriptValue(engine, G); \
properties.setProperty(#P, V); \
@ -173,7 +173,7 @@ inline QScriptValue convertScriptValue(QScriptEngine* e, const AACube& v) { retu
// same as COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER but uses #X instead of #P in the setProperty() step
#define COPY_PROXY_PROPERTY_TO_QSCRIPTVALUE_GETTER(p, P, X, G) \
if ((_desiredProperties.isEmpty() || _desiredProperties.getHasProperty(p)) && \
if (((!psuedoPropertyFlagsButDesiredEmpty && _desiredProperties.isEmpty()) || _desiredProperties.getHasProperty(p)) && \
(!skipDefaults || defaultEntityProperties._##P != _##P)) { \
QScriptValue V = convertScriptValue(engine, G); \
properties.setProperty(#X, V); \

View file

@ -0,0 +1,41 @@
//
// EntityPsuedoPropertyFlags.h
// libraries/entities/src
//
// Created by Thijs Wenker on 9/18/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
//
#pragma once
#ifndef hifi_EntityPsuedoPropertyFlag_h
#define hifi_EntityPsuedoPropertyFlag_h
#include <bitset>
#include <type_traits>
namespace EntityPsuedoPropertyFlag {
enum {
None = 0,
FlagsActive,
ID,
Type,
Created,
Age,
AgeAsText,
LastEdited,
BoundingBox,
OriginalTextures,
RenderInfo,
ClientOnly,
OwningAvatarID,
NumFlags
};
}
typedef std::bitset<EntityPsuedoPropertyFlag::NumFlags> EntityPsuedoPropertyFlags;
#endif // hifi_EntityPsuedoPropertyFlag_h

View file

@ -42,7 +42,7 @@ const QString GRABBABLE_USER_DATA = "{\"grabbableKey\":{\"grabbable\":true}}";
const QString NOT_GRABBABLE_USER_DATA = "{\"grabbableKey\":{\"grabbable\":false}}";
EntityScriptingInterface::EntityScriptingInterface(bool bidOnSimulationOwnership) :
_entityTree(NULL),
_entityTree(nullptr),
_bidOnSimulationOwnership(bidOnSimulationOwnership)
{
auto nodeList = DependencyManager::get<NodeList>();
@ -350,19 +350,19 @@ QUuid EntityScriptingInterface::cloneEntity(QUuid entityIDToClone) {
}
}
EntityItemProperties EntityScriptingInterface::getEntityProperties(QUuid identity) {
EntityPropertyFlags noSpecificProperties;
return getEntityProperties(identity, noSpecificProperties);
EntityItemProperties EntityScriptingInterface::getEntityProperties(QUuid entityID) {
const EntityPropertyFlags noSpecificProperties;
return getEntityProperties(entityID, noSpecificProperties);
}
EntityItemProperties EntityScriptingInterface::getEntityProperties(QUuid identity, EntityPropertyFlags desiredProperties) {
EntityItemProperties EntityScriptingInterface::getEntityProperties(QUuid entityID, EntityPropertyFlags desiredProperties) {
PROFILE_RANGE(script_entities, __FUNCTION__);
bool scalesWithParent { false };
EntityItemProperties results;
if (_entityTree) {
_entityTree->withReadLock([&] {
EntityItemPointer entity = _entityTree->findEntityByEntityItemID(EntityItemID(identity));
EntityItemPointer entity = _entityTree->findEntityByEntityItemID(EntityItemID(entityID));
if (entity) {
scalesWithParent = entity->getScalesWithParent();
if (desiredProperties.getHasProperty(PROP_POSITION) ||
@ -397,6 +397,134 @@ EntityItemProperties EntityScriptingInterface::getEntityProperties(QUuid identit
return convertPropertiesToScriptSemantics(results, scalesWithParent);
}
struct EntityPropertiesResult {
EntityPropertiesResult(const EntityItemProperties& properties, bool scalesWithParent) :
properties(properties),
scalesWithParent(scalesWithParent) {
}
EntityPropertiesResult() = default;
EntityItemProperties properties;
bool scalesWithParent{ false };
};
// Static method to make sure that we have the right script engine.
// Using sender() or QtScriptable::engine() does not work for classes used by multiple threads (script-engines)
QScriptValue EntityScriptingInterface::getMultipleEntityProperties(QScriptContext* context, QScriptEngine* engine) {
const int ARGUMENT_ENTITY_IDS = 0;
const int ARGUMENT_EXTENDED_DESIRED_PROPERTIES = 1;
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
const auto entityIDs = qScriptValueToValue<QVector<QUuid>>(context->argument(ARGUMENT_ENTITY_IDS));
return entityScriptingInterface->getMultipleEntityPropertiesInternal(engine, entityIDs, context->argument(ARGUMENT_EXTENDED_DESIRED_PROPERTIES));
}
QScriptValue EntityScriptingInterface::getMultipleEntityPropertiesInternal(QScriptEngine* engine, QVector<QUuid> entityIDs, const QScriptValue& extendedDesiredProperties) {
PROFILE_RANGE(script_entities, __FUNCTION__);
EntityPsuedoPropertyFlags psuedoPropertyFlags;
const auto readExtendedPropertyStringValue = [&](QScriptValue extendedProperty) {
const auto extendedPropertyString = extendedProperty.toString();
if (extendedPropertyString == "id") {
psuedoPropertyFlags.set(EntityPsuedoPropertyFlag::ID);
} else if (extendedPropertyString == "type") {
psuedoPropertyFlags.set(EntityPsuedoPropertyFlag::Type);
} else if (extendedPropertyString == "created") {
psuedoPropertyFlags.set(EntityPsuedoPropertyFlag::Created);
} else if (extendedPropertyString == "age") {
psuedoPropertyFlags.set(EntityPsuedoPropertyFlag::Age);
} else if (extendedPropertyString == "ageAsText") {
psuedoPropertyFlags.set(EntityPsuedoPropertyFlag::AgeAsText);
} else if (extendedPropertyString == "lastEdited") {
psuedoPropertyFlags.set(EntityPsuedoPropertyFlag::LastEdited);
} else if (extendedPropertyString == "boundingBox") {
psuedoPropertyFlags.set(EntityPsuedoPropertyFlag::BoundingBox);
} else if (extendedPropertyString == "originalTextures") {
psuedoPropertyFlags.set(EntityPsuedoPropertyFlag::OriginalTextures);
} else if (extendedPropertyString == "renderInfo") {
psuedoPropertyFlags.set(EntityPsuedoPropertyFlag::RenderInfo);
} else if (extendedPropertyString == "clientOnly") {
psuedoPropertyFlags.set(EntityPsuedoPropertyFlag::ClientOnly);
} else if (extendedPropertyString == "owningAvatarID") {
psuedoPropertyFlags.set(EntityPsuedoPropertyFlag::OwningAvatarID);
}
};
if (extendedDesiredProperties.isString()) {
readExtendedPropertyStringValue(extendedDesiredProperties);
psuedoPropertyFlags.set(EntityPsuedoPropertyFlag::FlagsActive);
} else if (extendedDesiredProperties.isArray()) {
const quint32 length = extendedDesiredProperties.property("length").toInt32();
for (quint32 i = 0; i < length; i++) {
readExtendedPropertyStringValue(extendedDesiredProperties.property(i));
}
psuedoPropertyFlags.set(EntityPsuedoPropertyFlag::FlagsActive);
}
EntityPropertyFlags desiredProperties = qScriptValueToValue<EntityPropertyFlags>(extendedDesiredProperties);
bool needsScriptSemantics = desiredProperties.getHasProperty(PROP_POSITION) ||
desiredProperties.getHasProperty(PROP_ROTATION) ||
desiredProperties.getHasProperty(PROP_LOCAL_POSITION) ||
desiredProperties.getHasProperty(PROP_LOCAL_ROTATION) ||
desiredProperties.getHasProperty(PROP_LOCAL_VELOCITY) ||
desiredProperties.getHasProperty(PROP_LOCAL_ANGULAR_VELOCITY) ||
desiredProperties.getHasProperty(PROP_LOCAL_DIMENSIONS);
if (needsScriptSemantics) {
// if we are explicitly getting position or rotation, we need parent information to make sense of them.
desiredProperties.setHasProperty(PROP_PARENT_ID);
desiredProperties.setHasProperty(PROP_PARENT_JOINT_INDEX);
}
QVector<EntityPropertiesResult> resultProperties;
if (_entityTree) {
PROFILE_RANGE(script_entities, "EntityScriptingInterface::getMultipleEntityProperties>Obtaining Properties");
int i = 0;
const int lockAmount = 500;
int size = entityIDs.size();
while (i < size) {
_entityTree->withReadLock([&] {
for (int j = 0; j < lockAmount && i < size; ++i, ++j) {
const auto& entityID = entityIDs.at(i);
const EntityItemPointer entity = _entityTree->findEntityByEntityItemID(EntityItemID(entityID));
if (entity) {
if (psuedoPropertyFlags.none() && desiredProperties.isEmpty()) {
// these are left out of EntityItem::getEntityProperties so that localPosition and localRotation
// don't end up in json saves, etc. We still want them here, though.
EncodeBitstreamParams params; // unknown
desiredProperties = entity->getEntityProperties(params);
desiredProperties.setHasProperty(PROP_LOCAL_POSITION);
desiredProperties.setHasProperty(PROP_LOCAL_ROTATION);
desiredProperties.setHasProperty(PROP_LOCAL_VELOCITY);
desiredProperties.setHasProperty(PROP_LOCAL_ANGULAR_VELOCITY);
desiredProperties.setHasProperty(PROP_LOCAL_DIMENSIONS);
psuedoPropertyFlags.set();
needsScriptSemantics = true;
}
auto properties = entity->getProperties(desiredProperties, true);
EntityPropertiesResult result(properties, entity->getScalesWithParent());
resultProperties.append(result);
}
}
});
}
}
QScriptValue finalResult = engine->newArray(resultProperties.size());
quint32 i = 0;
if (needsScriptSemantics) {
PROFILE_RANGE(script_entities, "EntityScriptingInterface::getMultipleEntityProperties>Script Semantics");
foreach(const auto& result, resultProperties) {
finalResult.setProperty(i++, convertPropertiesToScriptSemantics(result.properties, result.scalesWithParent)
.copyToScriptValue(engine, false, false, false, psuedoPropertyFlags));
}
} else {
PROFILE_RANGE(script_entities, "EntityScriptingInterface::getMultipleEntityProperties>Skip Script Semantics");
foreach(const auto& result, resultProperties) {
finalResult.setProperty(i++, result.properties.copyToScriptValue(engine, false, false, false, psuedoPropertyFlags));
}
}
return finalResult;
}
QUuid EntityScriptingInterface::editEntity(QUuid id, const EntityItemProperties& scriptSideProperties) {
PROFILE_RANGE(script_entities, __FUNCTION__);

View file

@ -149,6 +149,21 @@ public:
const QVector<EntityItemID>& entityIdsToInclude, const QVector<EntityItemID>& entityIdsToDiscard,
bool visibleOnly, bool collidableOnly);
/**jsdoc
* Get the properties of multiple entities.
* @function Entities.getMultipleEntityProperties
* @param {Uuid[]} entityIDs - The IDs of the entities to get the properties of.
* @param {string[]|string} [desiredProperties=[]] - Either string with property name or array of the names of the properties
* to get. If the array is empty, all properties are returned.
* @returns {Entities.EntityProperties[]} The properties of the entity if the entity can be found, otherwise an empty object.
* @example <caption>Retrieve the names of the nearby entities</caption>
* var SEARCH_RADIUS = 50; // meters
* var entityIds = Entities.findEntities(MyAvatar.position, SEARCH_RADIUS);
* var propertySets = Entities.getMultipleEntityProperties(entityIds, "name");
* print("Nearby entity names: " + JSON.stringify(propertySets));
*/
static QScriptValue getMultipleEntityProperties(QScriptContext* context, QScriptEngine* engine);
QScriptValue getMultipleEntityPropertiesInternal(QScriptEngine* engine, QVector<QUuid> entityIDs, const QScriptValue& extendedDesiredProperties);
public slots:
/**jsdoc
@ -270,7 +285,7 @@ public slots:
* print("Entity color: " + JSON.stringify(properties.color));
*/
Q_INVOKABLE EntityItemProperties getEntityProperties(QUuid entityID);
Q_INVOKABLE EntityItemProperties getEntityProperties(QUuid identity, EntityPropertyFlags desiredProperties);
Q_INVOKABLE EntityItemProperties getEntityProperties(QUuid entityID, EntityPropertyFlags desiredProperties);
/**jsdoc
* Update an entity with specified properties.

View file

@ -145,7 +145,7 @@ EntityItemID EntityTreeElement::findRayIntersection(const glm::vec3& origin, con
bool visibleOnly, bool collidableOnly, QVariantMap& extraInfo, bool precisionPicking) {
EntityItemID result;
BoxFace localFace;
BoxFace localFace { UNKNOWN_FACE };
glm::vec3 localSurfaceNormal;
if (!canPickIntersect()) {
@ -213,7 +213,7 @@ EntityItemID EntityTreeElement::findDetailedRayIntersection(const glm::vec3& ori
// we can use the AABox's ray intersection by mapping our origin and direction into the entity frame
// and testing intersection there.
float localDistance;
BoxFace localFace;
BoxFace localFace { UNKNOWN_FACE };
glm::vec3 localSurfaceNormal;
if (entityFrameBox.findRayIntersection(entityFrameOrigin, entityFrameDirection, 1.0f / entityFrameDirection, localDistance,
localFace, localSurfaceNormal)) {

View file

@ -13,7 +13,6 @@
#define hifi_EntityTypes_h
#include <memory>
#include <stdint.h>
#include <QHash>
#include <QString>

View file

@ -69,8 +69,8 @@ void LightEntityItem::dimensionsChanged() {
}
EntityItemProperties LightEntityItem::getProperties(EntityPropertyFlags desiredProperties) const {
EntityItemProperties properties = EntityItem::getProperties(desiredProperties); // get the properties from our base class
EntityItemProperties LightEntityItem::getProperties(const EntityPropertyFlags& desiredProperties, bool allowEmptyDesiredProperties) const {
EntityItemProperties properties = EntityItem::getProperties(desiredProperties, allowEmptyDesiredProperties); // get the properties from our base class
COPY_ENTITY_PROPERTY_TO_PROPERTIES(isSpotlight, getIsSpotlight);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(color, getXColor);

View file

@ -35,7 +35,7 @@ public:
virtual bool setSubClassProperties(const EntityItemProperties& properties) override;
// methods for getting/setting all properties of an entity
virtual EntityItemProperties getProperties(EntityPropertyFlags desiredProperties = EntityPropertyFlags()) const override;
virtual EntityItemProperties getProperties(const EntityPropertyFlags& desiredProperties, bool allowEmptyDesiredProperties) const override;
virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const override;

View file

@ -37,9 +37,9 @@ LineEntityItem::LineEntityItem(const EntityItemID& entityItemID) :
_type = EntityTypes::Line;
}
EntityItemProperties LineEntityItem::getProperties(EntityPropertyFlags desiredProperties) const {
EntityItemProperties LineEntityItem::getProperties(const EntityPropertyFlags& desiredProperties, bool allowEmptyDesiredProperties) const {
EntityItemProperties properties = EntityItem::getProperties(desiredProperties); // get the properties from our base class
EntityItemProperties properties = EntityItem::getProperties(desiredProperties, allowEmptyDesiredProperties); // get the properties from our base class
properties._color = getXColor();

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