Merge remote-tracking branch 'upstream/master' into feature/quest

This commit is contained in:
Brad Davis 2019-01-29 14:13:11 -08:00
commit 0d34030135
87 changed files with 4151 additions and 479 deletions

View file

@ -0,0 +1,44 @@
{
"avatars": [
{
"name": "Wooden Mannequin",
"preview_image": "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/previews/7fe80a1e-f445-4800-9e89-40e677b03bee/large/hifi-mp-7fe80a1e-f445-4800-9e89-40e677b03bee.jpg",
"url": "qrc:////meshes/defaultAvatar_full.fst"
},
{
"name": "Anime-Styled Boy",
"preview_image": "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/previews/46e0fd52-3cff-462f-ba97-927338d88295/thumbnail/hifi-mp-46e0fd52-3cff-462f-ba97-927338d88295.jpg",
"url": "http://mpassets.highfidelity.com/46e0fd52-3cff-462f-ba97-927338d88295-v1/AnimeBoy2.fst"
},
{
"name": "Anime-Styled Girl",
"preview_image": "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/previews/1e7e43f6-1757-44d3-baa4-756827d96311/large/hifi-mp-1e7e43f6-1757-44d3-baa4-756827d96311.jpg",
"url": "http://mpassets.highfidelity.com/0dce3426-55c8-4641-8dd5-d76eb575b64a-v1/Anime_F_Outfit.fst"
},
{
"name": "Last Legends: Male Avatar",
"preview_image": "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/previews/28569047-6f1a-4100-af67-8054ec397cc3/thumbnail/hifi-mp-28569047-6f1a-4100-af67-8054ec397cc3.jpg",
"url": "http://mpassets.highfidelity.com/28569047-6f1a-4100-af67-8054ec397cc3-v1/LLMale2.fst"
},
{
"name": "Last Legends: Female Avatar",
"preview_image": "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/previews/8d823be5-6197-4418-b984-eb94160ed956/thumbnail/hifi-mp-8d823be5-6197-4418-b984-eb94160ed956.jpg",
"url": "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/previews/46e0fd52-3cff-462f-ba97-927338d88295/thumbnail/hifi-mp-46e0fd52-3cff-462f-ba97-927338d88295.jpg"
},
{
"name": "Matthew: Photo-real avatar",
"preview_image": "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/previews/b652081b-a199-425e-ae5c-7815721bdc09/thumbnail/hifi-mp-b652081b-a199-425e-ae5c-7815721bdc09.jpg",
"url": "http://mpassets.highfidelity.com/b652081b-a199-425e-ae5c-7815721bdc09-v1/matthew.fst"
},
{
"name": "Priscilla: Photo real avatar",
"preview_image": "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/previews/e7565f93-8bc5-47c2-b6eb-b3b31d4a1339/thumbnail/hifi-mp-e7565f93-8bc5-47c2-b6eb-b3b31d4a1339.jpg",
"url": "http://mpassets.highfidelity.com/e7565f93-8bc5-47c2-b6eb-b3b31d4a1339-v1/priscilla.fst"
},
{
"name": "H1-F1 Optical Interpreter bot",
"preview_image": "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/previews/469c8b66-e3c2-47fb-9820-e306b1dd15c4/large/hifi-mp-469c8b66-e3c2-47fb-9820-e306b1dd15c4.jpg",
"url": "http://mpassets.highfidelity.com/469c8b66-e3c2-47fb-9820-e306b1dd15c4-v1/optical_interpreter[1].fst"
}
]
}

View file

@ -493,6 +493,34 @@ Java_io_highfidelity_hifiinterface_SplashActivity_registerLoadCompleteListener(J
}
JNIEXPORT jstring JNICALL
Java_io_highfidelity_hifiinterface_fragment_ProfileFragment_getDisplayName(JNIEnv *env,
jobject instance) {
QString displayName = AndroidHelper::instance().getDisplayName();
return env->NewStringUTF(displayName.toLatin1().data());
}
JNIEXPORT void JNICALL
Java_io_highfidelity_hifiinterface_fragment_ProfileFragment_setDisplayName(JNIEnv *env,
jobject instance,
jstring name_) {
const char *c_name = env->GetStringUTFChars(name_, 0);
const QString name = QString::fromUtf8(c_name);
env->ReleaseStringUTFChars(name_, c_name);
AndroidHelper::instance().setDisplayName(name);
}
JNIEXPORT void JNICALL
Java_io_highfidelity_hifiinterface_fragment_ProfileFragment_setAvatarUrl(JNIEnv *env,
jobject instance,
jstring url_) {
const char *url = env->GetStringUTFChars(url_, 0);
QString avatarUrl = QString::fromUtf8(url);
AndroidHelper::instance().setMyAvatarUrl(avatarUrl);
env->ReleaseStringUTFChars(url_, url);
}
JNIEXPORT void JNICALL
Java_io_highfidelity_hifiinterface_MainActivity_logout(JNIEnv *env, jobject instance) {
DependencyManager::get<AccountManager>()->logout();

View file

@ -33,13 +33,15 @@ import com.squareup.picasso.Picasso;
import io.highfidelity.hifiinterface.fragment.FriendsFragment;
import io.highfidelity.hifiinterface.fragment.HomeFragment;
import io.highfidelity.hifiinterface.fragment.PolicyFragment;
import io.highfidelity.hifiinterface.fragment.ProfileFragment;
import io.highfidelity.hifiinterface.fragment.SettingsFragment;
import io.highfidelity.hifiinterface.fragment.SignupFragment;
import io.highfidelity.hifiinterface.task.DownloadProfileImageTask;
public class MainActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener,
HomeFragment.OnHomeInteractionListener,
FriendsFragment.OnHomeInteractionListener {
FriendsFragment.OnHomeInteractionListener,
ProfileFragment.OnProfileInteractionListener {
private static final int PROFILE_PICTURE_PLACEHOLDER = R.drawable.default_profile_avatar;
public static final String DEFAULT_FRAGMENT = "Home";
@ -61,6 +63,7 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On
private View mProfilePanel;
private TextView mLogoutOption;
private MenuItem mPeopleMenuItem;
private MenuItem mProfileMenuItem;
private boolean backToScene;
private String backToUrl;
@ -83,6 +86,8 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On
mPeopleMenuItem = mNavigationView.getMenu().findItem(R.id.action_people);
mProfileMenuItem = mNavigationView.getMenu().findItem(R.id.action_profile);
updateDebugMenu(mNavigationView.getMenu());
Toolbar toolbar = findViewById(R.id.toolbar);
@ -162,6 +167,12 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On
loadFragment(fragment, getString(R.string.people), getString(R.string.tagFragmentPeople), true, true);
}
private void loadProfileFragment() {
Fragment fragment = ProfileFragment.newInstance();
loadFragment(fragment, getString(R.string.profile), getString(R.string.tagFragmentProfile), true, true);
}
private void loadSettingsFragment() {
SettingsFragment fragment = SettingsFragment.newInstance();
@ -261,6 +272,9 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On
case R.id.action_people:
loadPeopleFragment();
return true;
case R.id.action_profile:
loadProfileFragment();
break;
case R.id.action_debug_settings:
loadSettingsFragment();
return true;
@ -351,6 +365,21 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On
goToUser(username);
}
@Override
public void onCancelProfileEdit() {
loadHomeFragment(false);
}
@Override
public void onCompleteProfileEdit() {
loadHomeFragment(false);
}
@Override
public void onAvatarChosen() {
loadHomeFragment(false);
}
private class RoundProfilePictureCallback implements Callback {
@Override
public void onSuccess() {

View file

@ -0,0 +1,126 @@
package io.highfidelity.hifiinterface.fragment;
import android.app.Fragment;
import android.content.Context;
import android.os.Bundle;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.Button;
import android.widget.TextView;
import io.highfidelity.hifiinterface.R;
import io.highfidelity.hifiinterface.provider.AvatarProvider;
import io.highfidelity.hifiinterface.view.AvatarAdapter;
public class ProfileFragment extends Fragment {
private TextView mDisplayName;
private Button mOkButton;
private OnProfileInteractionListener mListener;
private AvatarProvider mAvatarsProvider;
private native String getDisplayName();
private native void setDisplayName(String name);
private native void setAvatarUrl(String url);
public ProfileFragment() {
// Required empty public constructor
}
public static ProfileFragment newInstance() {
ProfileFragment fragment = new ProfileFragment();
return fragment;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_profile, container, false);
mDisplayName = rootView.findViewById(R.id.displayName);
mDisplayName.setText(getDisplayName());
mDisplayName.setOnEditorActionListener((textView, actionId, keyEvent) -> onDisplayNameEditorAction(textView, actionId, keyEvent));
mOkButton = rootView.findViewById(R.id.okButton);
mOkButton.setOnClickListener(view -> onOkButtonClicked());
rootView.findViewById(R.id.cancel).setOnClickListener(view -> onCancelProfileEdit());
RecyclerView avatarsView = rootView.findViewById(R.id.gridview);
int numberOfColumns = 1;
mAvatarsProvider = new AvatarProvider(getContext());
GridLayoutManager gridLayoutMgr = new GridLayoutManager(getContext(), numberOfColumns);
avatarsView.setLayoutManager(gridLayoutMgr);
AvatarAdapter avatarAdapter = new AvatarAdapter(getContext(), mAvatarsProvider);
avatarsView.setAdapter(avatarAdapter);
avatarAdapter.loadAvatars();
avatarAdapter.setClickListener((view, position, avatar) -> {
setAvatarUrl(avatar.avatarUrl);
if (mListener != null) {
mListener.onAvatarChosen();
}
});
return rootView;
}
private void onOkButtonClicked() {
setDisplayName(mDisplayName.getText().toString());
View view = getActivity().getCurrentFocus();
InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
if (mListener != null) {
mListener.onCompleteProfileEdit();
}
}
private boolean onDisplayNameEditorAction(TextView textView, int actionId, KeyEvent keyEvent) {
if (actionId == EditorInfo.IME_ACTION_DONE) {
mOkButton.performClick();
return true;
}
return false;
}
private void onCancelProfileEdit() {
View view = getActivity().getCurrentFocus();
InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
if (mListener != null) {
mListener.onCancelProfileEdit();
}
}
/**
* Processes the back pressed event and returns true if it was managed by this Fragment
* @return
*/
public boolean onBackPressed() {
return false;
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
if (context instanceof OnProfileInteractionListener) {
mListener = (OnProfileInteractionListener) context;
} else {
throw new RuntimeException(context.toString()
+ " must implement OnProfileInteractionListener");
}
}
public interface OnProfileInteractionListener {
void onCancelProfileEdit();
void onCompleteProfileEdit();
void onAvatarChosen();
}
}

View file

@ -0,0 +1,70 @@
package io.highfidelity.hifiinterface.provider;
import android.content.Context;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import io.highfidelity.hifiinterface.view.AvatarAdapter;
/**
* Created by gcalero on 1/21/19
*/
public class AvatarProvider {
private static final String AVATARS_JSON = "avatars.json";
private static final String JSON_FIELD_NAME = "name";
private static final String JSON_FIELD_URL = "url";
private static final String JSON_FIELD_IMAGE = "preview_image";
private static final String JSON_FIELD_AVATARS_ARRAY = "avatars";
private final Context mContext;
public interface AvatarsCallback {
void retrieveOk(List<AvatarAdapter.Avatar> avatars);
void retrieveError(Exception e, String message);
}
public AvatarProvider(Context context) {
mContext = context;
}
public void retrieve(AvatarsCallback avatarsCallback)
{
try {
JSONObject obj = new JSONObject(loadJSONFromAssets());
JSONArray m_jArry = obj.getJSONArray(JSON_FIELD_AVATARS_ARRAY);
ArrayList<AvatarAdapter.Avatar> avatars = new ArrayList<>();
for (int i = 0; i < m_jArry.length(); i++) {
JSONObject jo_inside = m_jArry.getJSONObject(i);
AvatarAdapter.Avatar anAvatar = new AvatarAdapter.Avatar();
anAvatar.avatarName = jo_inside.getString(JSON_FIELD_NAME);
anAvatar.avatarPreviewUrl = jo_inside.getString(JSON_FIELD_IMAGE);
anAvatar.avatarUrl = jo_inside.getString(JSON_FIELD_URL);
avatars.add(anAvatar);
}
avatarsCallback.retrieveOk(avatars);
} catch (IOException e) {
avatarsCallback.retrieveError(e, "Failed retrieving avatar JSON");
} catch (JSONException e) {
avatarsCallback.retrieveError(e, "Failed parsing avatar JSON");
}
}
private String loadJSONFromAssets() throws IOException {
String json = null;
InputStream is = mContext.getAssets().open(AVATARS_JSON);
int size = is.available();
byte[] buffer = new byte[size];
is.read(buffer);
is.close();
json = new String(buffer, "UTF-8");
return json;
}
}

View file

@ -0,0 +1,111 @@
package io.highfidelity.hifiinterface.view;
import android.content.Context;
import android.net.Uri;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import com.squareup.picasso.Picasso;
import java.util.ArrayList;
import java.util.List;
import io.highfidelity.hifiinterface.R;
import io.highfidelity.hifiinterface.provider.AvatarProvider;
/**
* Created by gcalero on 1/21/19
*/
public class AvatarAdapter extends RecyclerView.Adapter<AvatarAdapter.ViewHolder> {
private static final String TAG = "Interface";
private final Context mContext;
private final LayoutInflater mInflater;
private final AvatarProvider mProvider;
private List<Avatar> mAvatars = new ArrayList<>();
private ItemClickListener mClickListener;
public AvatarAdapter(Context context, AvatarProvider provider) {
mContext = context;
mInflater = LayoutInflater.from(mContext);
mProvider = provider;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = mInflater.inflate(R.layout.avatar_item, parent, false);
return new AvatarAdapter.ViewHolder(view);
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
AvatarAdapter.Avatar anAvatar = mAvatars.get(position);
assert(holder.mName != null);
holder.mName.setText(anAvatar.avatarName);
Uri uri = Uri.parse(anAvatar.avatarPreviewUrl);
Picasso.get().load(uri).into(holder.mPreviewImage);
}
@Override
public int getItemCount() {
return mAvatars.size();
}
public void loadAvatars() {
mProvider.retrieve(new AvatarProvider.AvatarsCallback() {
@Override
public void retrieveOk(List<AvatarAdapter.Avatar> avatars) {
mAvatars = new ArrayList<>(avatars);
notifyDataSetChanged();
}
@Override
public void retrieveError(Exception e, String message) {
Log.e(TAG, message, e);
}
});
}
public void setClickListener(ItemClickListener clickListener) {
mClickListener = clickListener;
}
public interface ItemClickListener {
void onItemClick(View view, int position, Avatar avatar);
}
public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
TextView mName;
ImageView mPreviewImage;
public ViewHolder(View itemView) {
super(itemView);
mName = itemView.findViewById(R.id.avatarName);
assert (mName != null);
mPreviewImage = itemView.findViewById(R.id.avatarPreview);
itemView.setOnClickListener(this);
}
@Override
public void onClick(View view) {
int position= getAdapterPosition();
if (mClickListener != null) {
mClickListener.onItemClick(view, position, mAvatars.get(position));
}
}
}
public static class Avatar {
public String avatarName;
public String avatarUrl;
public String avatarPreviewUrl;
public Avatar() { }
}
}

View file

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:paddingTop="5dp"
android:paddingBottom="5dp">
<TextView
android:id="@+id/avatarName"
android:fontFamily="@font/raleway_bold"
android:textSize="20sp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:textColor="@color/colorButton1" />
<ImageView
android:id="@+id/avatarPreview"
android:background="@color/backgroundDark"
android:layout_width="match_parent"
android:layout_height="190dp"
android:scaleType="fitCenter"
app:layout_constraintTop_toBottomOf="@id/avatarName"/>
</android.support.constraint.ConstraintLayout>

View file

@ -0,0 +1,107 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/backgroundLight">
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@drawable/encourage_login_background"
android:scaleType="fitXY" />
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#B2000000" />
<ImageView
android:id="@+id/header"
android:layout_width="@dimen/header_hifi_width"
android:layout_height="@dimen/header_hifi_height"
android:layout_marginTop="@dimen/header_hifi_margin_top"
android:contentDescription="HighFidelity"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/hifi_header" />
<android.support.constraint.ConstraintLayout
android:id="@+id/profileForm"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginTop="@dimen/profile_margin_top"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@id/header"
app:layout_constraintBottom_toBottomOf="parent"
android:visibility="visible">
<EditText
android:id="@+id/displayName"
android:layout_width="match_parent"
android:layout_height="27dp"
android:layout_marginLeft="@dimen/profile_margin"
android:layout_marginRight="@dimen/profile_margin"
android:layout_marginBottom="@dimen/profile_margin"
android:background="@color/white_opaque"
android:paddingLeft="@dimen/edit_text_padding"
android:ems="10"
android:fontFamily="sans-serif"
android:textSize="@dimen/login_edit_text_size"
android:inputType="textCapWords"
android:textColor="@color/editTextColor"
android:textColorHint="@color/editTextColor"
android:gravity="left|center_vertical"
android:hint="@string/displayName"
app:layout_constraintTop_toTopOf="parent" />
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintTop_toBottomOf="@id/displayName"
app:layout_constraintBottom_toTopOf="@id/okButton"
android:layout_marginTop="@dimen/profile_margin_avatars_top"
android:layout_marginBottom="@dimen/profile_margin_avatars_bottom"
android:layout_marginLeft="@dimen/profile_margin"
android:layout_marginRight="@dimen/profile_margin">
<android.support.v7.widget.RecyclerView
android:id="@+id/gridview"
android:paddingTop="@dimen/list_vertical_padding"
android:paddingBottom="@dimen/list_vertical_padding"
android:clipToPadding="false"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</FrameLayout>
<TextView
android:id="@+id/cancel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="8dp"
app:layout_constraintLeft_toLeftOf="@id/displayName"
app:layout_constraintTop_toTopOf="@id/okButton"
app:layout_constraintBottom_toBottomOf="@id/okButton"
app:layout_constraintRight_toLeftOf="@id/okButton"
android:textColor="@color/white_opaque"
android:fontFamily="@font/raleway_bold"
android:textSize="@dimen/button_medium_text_size"
android:text="@string/cancel_uppercase" />
<Button
android:id="@+id/okButton"
android:layout_width="@dimen/button_medium_width"
android:layout_height="@dimen/button_medium_height"
android:background="@drawable/rounded_button_color3"
android:fontFamily="@font/raleway_bold"
android:layout_marginTop="@dimen/button_medium_margin"
android:layout_marginBottom="@dimen/button_medium_margin"
android:text="@string/ok"
android:textColor="@color/white_opaque"
android:textAllCaps="false"
android:textSize="@dimen/button_medium_text_size"
app:layout_constraintRight_toRightOf="@id/displayName"
app:layout_constraintBottom_toBottomOf="parent" />
</android.support.constraint.ConstraintLayout>
</android.support.constraint.ConstraintLayout>

View file

@ -9,6 +9,10 @@
android:id="@+id/action_people"
android:title="@string/people"
/>
<item
android:id="@+id/action_profile"
android:title="@string/profile"
/>
<item
android:id="@+id/action_debug_settings"
android:title="@string/settings"

View file

@ -54,5 +54,8 @@
<dimen name="login_menu_button_margin_top">77dp</dimen>
<dimen name="login_menu_text_size">20sp</dimen>
<dimen name="login_edit_text_padding">14dp</dimen>
<dimen name="profile_margin_top">20dp</dimen>
<dimen name="profile_margin">35dp</dimen>
<dimen name="profile_margin_avatars_top">40dp</dimen>
<dimen name="profile_margin_avatars_bottom">70dp</dimen>
</resources>

View file

@ -43,6 +43,7 @@
<string name="tagFragmentSignup">tagFragmentSignup</string>
<string name="tagFragmentPolicy">tagFragmentPolicy</string>
<string name="tagFragmentPeople">tagFragmentPeople</string>
<string name="tagFragmentProfile">tagFragmentProfile</string>
<string name="tagSettings">tagSettings</string>
<string name="tagFragmentSignedIn">tagFragmentSignedIn</string>
<string name="settings">Settings</string>
@ -54,4 +55,7 @@
<string name="take_me_in_world">No thanks, take me in-world!</string>
<string name="be_anywere">BE ANYWHERE, WITH ANYONE \nRIGHT NOW</string>
<string name="steam_log_in">STEAM LOG IN</string>
<string name="displayName">Name to display</string>
<string name="ok">OK</string>
<string name="profile">Avatar</string>
</resources>

View file

@ -7,15 +7,12 @@ docker build --build-arg BUILD_UID=`id -u` -t "${DOCKER_IMAGE_NAME}" -f docker/D
# The Jenkins PR builds use VERSION_CODE, but the release builds use VERSION
# So make sure we use VERSION_CODE consistently
if [-z "$VERSION_CODE"]; then
export VERSION_CODE=$VERSION
fi
test -z "$VERSION_CODE" && export VERSION_CODE=$VERSION
docker run \
--rm \
--security-opt seccomp:unconfined \
-v "${WORKSPACE}":/home/jenkins/hifi \
-v /home/jenkins/.gradle:/home/jenkins/.gradle \
-e RELEASE_NUMBER \
-e RELEASE_TYPE \
-e ANDROID_APP \

View file

@ -743,7 +743,7 @@ void AudioMixer::parseSettingsObject(const QJsonObject& settingsObject) {
float coefficient = coefficientObject.value(COEFFICIENT).toString().toFloat(&ok);
if (ok && coefficient >= 0.0f && coefficient <= 1.0f &&
if (ok && coefficient <= 1.0f &&
itSource != end(_audioZones) &&
itListener != end(_audioZones)) {

View file

@ -770,13 +770,29 @@ float computeGain(float masterListenerGain, const AvatarAudioStream& listeningNo
break;
}
}
// translate the zone setting to gain per log2(distance)
float g = glm::clamp(1.0f - attenuationPerDoublingInDistance, EPSILON, 1.0f);
// calculate the attenuation using the distance to this node
// reference attenuation of 0dB at distance = 1.0m
gain *= fastExp2f(fastLog2f(g) * fastLog2f(std::max(distance, HRTF_NEARFIELD_MIN)));
gain = std::min(gain, 1.0f / HRTF_NEARFIELD_MIN);
if (attenuationPerDoublingInDistance < 0.0f) {
// translate a negative zone setting to distance limit
const float MIN_DISTANCE_LIMIT = ATTN_DISTANCE_REF + 1.0f; // silent after 1m
float distanceLimit = std::max(-attenuationPerDoublingInDistance, MIN_DISTANCE_LIMIT);
// calculate the LINEAR attenuation using the distance to this node
// reference attenuation of 0dB at distance = ATTN_DISTANCE_REF
float d = distance - ATTN_DISTANCE_REF;
gain *= std::max(1.0f - d / (distanceLimit - ATTN_DISTANCE_REF), 0.0f);
gain = std::min(gain, ATTN_GAIN_MAX);
} else {
// translate a positive zone setting to gain per log2(distance)
const float MIN_ATTENUATION_COEFFICIENT = 0.001f; // -60dB per log2(distance)
float g = glm::clamp(1.0f - attenuationPerDoublingInDistance, MIN_ATTENUATION_COEFFICIENT, 1.0f);
// calculate the LOGARITHMIC attenuation using the distance to this node
// reference attenuation of 0dB at distance = ATTN_DISTANCE_REF
float d = (1.0f / ATTN_DISTANCE_REF) * std::max(distance, HRTF_NEARFIELD_MIN);
gain *= fastExp2f(fastLog2f(g) * fastLog2f(d));
gain = std::min(gain, ATTN_GAIN_MAX);
}
return gain;
}

View file

@ -15,8 +15,8 @@ print = functools.partial(print, flush=True)
# Encapsulates the vcpkg system
class VcpkgRepo:
CMAKE_TEMPLATE = """
set(CMAKE_TOOLCHAIN_FILE "{}" CACHE FILEPATH "Toolchain file")
set(CMAKE_TOOLCHAIN_FILE_UNCACHED "{}")
get_filename_component(CMAKE_TOOLCHAIN_FILE "{}" ABSOLUTE CACHE)
get_filename_component(CMAKE_TOOLCHAIN_FILE_UNCACHED "{}" ABSOLUTE)
set(VCPKG_INSTALL_ROOT "{}")
set(VCPKG_TOOLS_DIR "{}")
"""

View file

@ -15,6 +15,7 @@
#include <src/ui/LoginDialog.h>
#include "Application.h"
#include "Constants.h"
#include "avatar/AvatarManager.h"
#if defined(qApp)
#undef qApp
@ -152,3 +153,16 @@ void AndroidHelper::signupFailed(QNetworkReply* reply) {
emit handleSignupFailed(DEFAULT_SIGN_UP_FAILURE_MESSAGE);
}
}
QString AndroidHelper::getDisplayName() {
return DependencyManager::get<AvatarManager>()->getMyAvatar()->getDisplayName();
}
void AndroidHelper::setDisplayName(const QString &displayName) {
DependencyManager::get<AvatarManager>()->getMyAvatar()->setDisplayName(displayName);
}
void AndroidHelper::setMyAvatarUrl(const QString &avatarUrl) {
QUrl url = QUrl(avatarUrl);
DependencyManager::get<AvatarManager>()->getMyAvatar()->useFullAvatarURL(url);
}

View file

@ -41,6 +41,9 @@ public:
void operator=(AndroidHelper const&) = delete;
void signup(QString email, QString username, QString password);
QString getDisplayName();
void setDisplayName(const QString &displayName);
void setMyAvatarUrl(const QString &avatarUrl);
public slots:
void showLoginDialog(QUrl url);

View file

@ -162,6 +162,7 @@
#include "avatar/AvatarManager.h"
#include "avatar/MyHead.h"
#include "avatar/AvatarPackager.h"
#include "avatar/MyCharacterController.h"
#include "CrashRecoveryHandler.h"
#include "CrashHandler.h"
#include "devices/DdeFaceTracker.h"
@ -807,6 +808,10 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) {
if (auto steamClient = pluginManager->getSteamClientPlugin()) {
steamClient->init();
}
if (auto oculusPlatform = pluginManager->getOculusPlatformPlugin()) {
oculusPlatform->init();
}
PROFILE_SET_THREAD_NAME("Main Thread");
#if defined(Q_OS_WIN)
@ -2726,7 +2731,14 @@ Application::~Application() {
avatarManager->handleProcessedPhysicsTransaction(transaction);
avatarManager->deleteAllAvatars();
auto myCharacterController = getMyAvatar()->getCharacterController();
myCharacterController->clearDetailedMotionStates();
myCharacterController->buildPhysicsTransaction(transaction);
_physicsEngine->processTransaction(transaction);
myCharacterController->handleProcessedPhysicsTransaction(transaction);
_physicsEngine->setCharacterController(nullptr);
// the _shapeManager should have zero references
@ -2750,6 +2762,10 @@ Application::~Application() {
steamClient->shutdown();
}
if (auto oculusPlatform = PluginManager::getInstance()->getOculusPlatformPlugin()) {
oculusPlatform->shutdown();
}
DependencyManager::destroy<PluginManager>();
DependencyManager::destroy<CompositorHelper>(); // must be destroyed before the FramebufferCache
@ -4195,7 +4211,9 @@ void Application::keyPressEvent(QKeyEvent* event) {
if (!isShifted && !isMeta && !isOption && !event->isAutoRepeat()) {
AudioInjectorOptions options;
options.localOnly = true;
options.positionSet = false; // system sound
options.stereo = true;
Setting::Handle<bool> notificationSounds{ MenuOption::NotificationSounds, true };
Setting::Handle<bool> notificationSoundSnapshot{ MenuOption::NotificationSoundsSnapshot, true };
if (notificationSounds.get() && notificationSoundSnapshot.get()) {
@ -6312,7 +6330,9 @@ void Application::update(float deltaTime) {
avatarManager->buildPhysicsTransaction(transaction);
_physicsEngine->processTransaction(transaction);
avatarManager->handleProcessedPhysicsTransaction(transaction);
myAvatar->getCharacterController()->buildPhysicsTransaction(transaction);
_physicsEngine->processTransaction(transaction);
myAvatar->getCharacterController()->handleProcessedPhysicsTransaction(transaction);
myAvatar->prepareForPhysicsSimulation();
_physicsEngine->forEachDynamic([&](EntityDynamicPointer dynamic) {
dynamic->prepareForPhysicsSimulation();

View file

@ -288,39 +288,37 @@ void AvatarActionHold::doKinematicUpdate(float deltaTimeStep) {
glm::vec3 oneFrameVelocity = (_positionalTarget - _previousPositionalTarget) / deltaTimeStep;
_measuredLinearVelocities[_measuredLinearVelocitiesIndex++] = oneFrameVelocity;
if (_measuredLinearVelocitiesIndex >= AvatarActionHold::velocitySmoothFrames) {
_measuredLinearVelocitiesIndex = 0;
_measuredLinearVelocitiesIndex %= AvatarActionHold::velocitySmoothFrames;
}
if (_kinematicSetVelocity) {
glm::vec3 measuredLinearVelocity = _measuredLinearVelocities[0];
for (int i = 1; i < AvatarActionHold::velocitySmoothFrames; i++) {
// there is a bit of lag between when someone releases the trigger and when the software reacts to
// the release. we calculate the velocity from previous frames but we don't include several
// of the most recent.
//
// if _measuredLinearVelocitiesIndex is
// 0 -- ignore i of 3 4 5
// 1 -- ignore i of 4 5 0
// 2 -- ignore i of 5 0 1
// 3 -- ignore i of 0 1 2
// 4 -- ignore i of 1 2 3
// 5 -- ignore i of 2 3 4
// This code is now disabled, but I'm leaving it commented-out because I suspect it will come back.
// if ((i + 1) % AvatarActionHold::velocitySmoothFrames == _measuredLinearVelocitiesIndex ||
// (i + 2) % AvatarActionHold::velocitySmoothFrames == _measuredLinearVelocitiesIndex ||
// (i + 3) % AvatarActionHold::velocitySmoothFrames == _measuredLinearVelocitiesIndex) {
// continue;
// }
measuredLinearVelocity += _measuredLinearVelocities[i];
}
}
glm::vec3 measuredLinearVelocity;
for (int i = 0; i < AvatarActionHold::velocitySmoothFrames; i++) {
// there is a bit of lag between when someone releases the trigger and when the software reacts to
// the release. we calculate the velocity from previous frames but we don't include several
// of the most recent.
//
// if _measuredLinearVelocitiesIndex is
// 0 -- ignore i of 3 4 5
// 1 -- ignore i of 4 5 0
// 2 -- ignore i of 5 0 1
// 3 -- ignore i of 0 1 2
// 4 -- ignore i of 1 2 3
// 5 -- ignore i of 2 3 4
// This code is now disabled, but I'm leaving it commented-out because I suspect it will come back.
// if ((i + 1) % AvatarActionHold::velocitySmoothFrames == _measuredLinearVelocitiesIndex ||
// (i + 2) % AvatarActionHold::velocitySmoothFrames == _measuredLinearVelocitiesIndex ||
// (i + 3) % AvatarActionHold::velocitySmoothFrames == _measuredLinearVelocitiesIndex) {
// continue;
// }
measuredLinearVelocity += _measuredLinearVelocities[i];
}
measuredLinearVelocity /= (float)(AvatarActionHold::velocitySmoothFrames
measuredLinearVelocity /= (float)(AvatarActionHold::velocitySmoothFrames
// - 3 // 3 because of the 3 we skipped, above
);
if (_kinematicSetVelocity) {
rigidBody->setLinearVelocity(glmToBullet(measuredLinearVelocity));
rigidBody->setAngularVelocity(glmToBullet(_angularVelocityTarget));
}

View file

@ -43,6 +43,7 @@
#include "InterfaceLogging.h"
#include "Menu.h"
#include "MyAvatar.h"
#include "DebugDraw.h"
#include "SceneScriptingInterface.h"
// 50 times per second - target is 45hz, but this helps account for any small deviations
@ -396,21 +397,45 @@ void AvatarManager::buildPhysicsTransaction(PhysicsEngine::Transaction& transact
if (isInPhysics) {
transaction.objectsToRemove.push_back(avatar->_motionState);
avatar->_motionState = nullptr;
auto& detailedMotionStates = avatar->getDetailedMotionStates();
for (auto& mState : detailedMotionStates) {
transaction.objectsToRemove.push_back(mState);
}
avatar->resetDetailedMotionStates();
} else {
ShapeInfo shapeInfo;
avatar->computeShapeInfo(shapeInfo);
btCollisionShape* shape = const_cast<btCollisionShape*>(ObjectMotionState::getShapeManager()->getShape(shapeInfo));
if (shape) {
AvatarMotionState* motionState = new AvatarMotionState(avatar, shape);
motionState->setMass(avatar->computeMass());
avatar->_motionState = motionState;
transaction.objectsToAdd.push_back(motionState);
if (avatar->getDetailedMotionStates().size() == 0) {
avatar->createDetailedMotionStates(avatar);
for (auto dMotionState : avatar->getDetailedMotionStates()) {
transaction.objectsToAdd.push_back(dMotionState);
}
}
if (avatar->getDetailedMotionStates().size() > 0) {
ShapeInfo shapeInfo;
avatar->computeShapeInfo(shapeInfo);
btCollisionShape* shape = const_cast<btCollisionShape*>(ObjectMotionState::getShapeManager()->getShape(shapeInfo));
if (shape) {
AvatarMotionState* motionState = new AvatarMotionState(avatar, shape);
motionState->setMass(avatar->computeMass());
avatar->_motionState = motionState;
transaction.objectsToAdd.push_back(motionState);
} else {
failedShapeBuilds.insert(avatar);
}
} else {
failedShapeBuilds.insert(avatar);
}
}
} else if (isInPhysics) {
transaction.objectsToChange.push_back(avatar->_motionState);
auto& detailedMotionStates = avatar->getDetailedMotionStates();
for (auto& mState : detailedMotionStates) {
if (mState) {
transaction.objectsToChange.push_back(mState);
}
}
}
}
_avatarsToChangeInPhysics.swap(failedShapeBuilds);
@ -450,6 +475,7 @@ void AvatarManager::handleRemovedAvatar(const AvatarSharedPointer& removedAvatar
_spaceProxiesToDelete.push_back(avatar->getSpaceIndex());
}
AvatarHashMap::handleRemovedAvatar(avatar, removalReason);
avatar->tearDownGrabs();
avatar->die();
queuePhysicsChange(avatar);
@ -509,6 +535,7 @@ void AvatarManager::deleteAllAvatars() {
if (avatar != _myAvatar) {
auto otherAvatar = std::static_pointer_cast<OtherAvatar>(avatar);
assert(!otherAvatar->_motionState);
assert(otherAvatar->getDetailedMotionStates().size() == 0);
}
}
}
@ -601,103 +628,135 @@ AvatarSharedPointer AvatarManager::getAvatarBySessionID(const QUuid& sessionID)
RayToAvatarIntersectionResult AvatarManager::findRayIntersection(const PickRay& ray,
const QScriptValue& avatarIdsToInclude,
const QScriptValue& avatarIdsToDiscard) {
const QScriptValue& avatarIdsToDiscard,
bool pickAgainstMesh) {
QVector<EntityItemID> avatarsToInclude = qVectorEntityItemIDFromScriptValue(avatarIdsToInclude);
QVector<EntityItemID> avatarsToDiscard = qVectorEntityItemIDFromScriptValue(avatarIdsToDiscard);
return findRayIntersectionVector(ray, avatarsToInclude, avatarsToDiscard);
return findRayIntersectionVector(ray, avatarsToInclude, avatarsToDiscard, pickAgainstMesh);
}
RayToAvatarIntersectionResult AvatarManager::findRayIntersectionVector(const PickRay& ray,
const QVector<EntityItemID>& avatarsToInclude,
const QVector<EntityItemID>& avatarsToDiscard) {
const QVector<EntityItemID>& avatarsToDiscard,
bool pickAgainstMesh) {
RayToAvatarIntersectionResult result;
if (QThread::currentThread() != thread()) {
BLOCKING_INVOKE_METHOD(const_cast<AvatarManager*>(this), "findRayIntersectionVector",
Q_RETURN_ARG(RayToAvatarIntersectionResult, result),
Q_ARG(const PickRay&, ray),
Q_ARG(const QVector<EntityItemID>&, avatarsToInclude),
Q_ARG(const QVector<EntityItemID>&, avatarsToDiscard));
Q_ARG(const QVector<EntityItemID>&, avatarsToDiscard),
Q_ARG(bool, pickAgainstMesh));
return result;
}
// It's better to intersect the ray against the avatar's actual mesh, but this is currently difficult to
// do, because the transformed mesh data only exists over in GPU-land. As a compromise, this code
// intersects against the avatars capsule and then against the (T-pose) mesh. The end effect is that picking
// against the avatar is sort-of right, but you likely wont be able to pick against the arms.
PROFILE_RANGE(simulation_physics, __FUNCTION__);
// TODO -- find a way to extract transformed avatar mesh data from the rendering engine.
float distance = (float)INT_MAX; // with FLT_MAX bullet rayTest does not return results
glm::vec3 rayDirection = glm::normalize(ray.direction);
std::vector<MyCharacterController::RayAvatarResult> physicsResults = _myAvatar->getCharacterController()->rayTest(glmToBullet(ray.origin), glmToBullet(rayDirection), distance, QVector<uint>());
if (physicsResults.size() > 0) {
glm::vec3 rayDirectionInv = { rayDirection.x != 0.0f ? 1.0f / rayDirection.x : INFINITY,
rayDirection.y != 0.0f ? 1.0f / rayDirection.y : INFINITY,
rayDirection.z != 0.0f ? 1.0f / rayDirection.z : INFINITY };
std::vector<SortedAvatar> sortedAvatars;
auto avatarHashCopy = getHashCopy();
for (auto avatarData : avatarHashCopy) {
auto avatar = std::static_pointer_cast<Avatar>(avatarData);
if ((avatarsToInclude.size() > 0 && !avatarsToInclude.contains(avatar->getID())) ||
(avatarsToDiscard.size() > 0 && avatarsToDiscard.contains(avatar->getID()))) {
continue;
}
float distance = FLT_MAX;
#if 0
// if we weren't picking against the capsule, we would want to pick against the avatarBounds...
SkeletonModelPointer avatarModel = avatar->getSkeletonModel();
AABox avatarBounds = avatarModel->getRenderableMeshBound();
if (avatarBounds.contains(ray.origin)) {
distance = 0.0f;
} else {
float boundDistance = FLT_MAX;
BoxFace face;
glm::vec3 surfaceNormal;
if (avatarBounds.findRayIntersection(ray.origin, ray.direction, boundDistance, face, surfaceNormal)) {
distance = boundDistance;
}
}
#else
glm::vec3 start;
glm::vec3 end;
float radius;
avatar->getCapsule(start, end, radius);
findRayCapsuleIntersection(ray.origin, ray.direction, start, end, radius, distance);
#endif
if (distance < FLT_MAX) {
sortedAvatars.emplace_back(distance, avatar);
}
}
if (sortedAvatars.size() > 1) {
static auto comparator = [](const SortedAvatar& left, const SortedAvatar& right) { return left.first < right.first; };
std::sort(sortedAvatars.begin(), sortedAvatars.end(), comparator);
}
for (auto it = sortedAvatars.begin(); it != sortedAvatars.end(); ++it) {
const SortedAvatar& sortedAvatar = *it;
// We can exit once avatarCapsuleDistance > bestDistance
if (sortedAvatar.first > result.distance) {
break;
}
float distance = FLT_MAX;
BoxFace face;
MyCharacterController::RayAvatarResult rayAvatarResult;
AvatarPointer avatar = nullptr;
BoxFace face = BoxFace::UNKNOWN_FACE;
glm::vec3 surfaceNormal;
QVariantMap extraInfo;
SkeletonModelPointer avatarModel = sortedAvatar.second->getSkeletonModel();
if (avatarModel->findRayIntersectionAgainstSubMeshes(ray.origin, ray.direction, distance, face, surfaceNormal, extraInfo, true)) {
if (distance < result.distance) {
result.intersects = true;
result.avatarID = sortedAvatar.second->getID();
result.distance = distance;
result.face = face;
result.surfaceNormal = surfaceNormal;
result.extraInfo = extraInfo;
for (auto &hit : physicsResults) {
auto avatarID = hit._intersectWithAvatar;
if ((avatarsToInclude.size() > 0 && !avatarsToInclude.contains(avatarID)) ||
(avatarsToDiscard.size() > 0 && avatarsToDiscard.contains(avatarID))) {
continue;
}
if (_myAvatar->getSessionUUID() != avatarID) {
auto avatarMap = getHashCopy();
AvatarHash::iterator itr = avatarMap.find(avatarID);
if (itr != avatarMap.end()) {
avatar = std::static_pointer_cast<Avatar>(*itr);
}
} else {
avatar = _myAvatar;
}
if (!hit._isBound) {
rayAvatarResult = hit;
} else if (avatar) {
auto &multiSpheres = avatar->getMultiSphereShapes();
if (multiSpheres.size() > 0) {
std::vector<MyCharacterController::RayAvatarResult> boxHits;
for (size_t i = 0; i < hit._boundJoints.size(); i++) {
assert(hit._boundJoints[i] < multiSpheres.size());
auto &mSphere = multiSpheres[hit._boundJoints[i]];
if (mSphere.isValid()) {
float boundDistance = FLT_MAX;
BoxFace face;
glm::vec3 surfaceNormal;
auto &bbox = mSphere.getBoundingBox();
if (bbox.findRayIntersection(ray.origin, rayDirection, rayDirectionInv, boundDistance, face, surfaceNormal)) {
MyCharacterController::RayAvatarResult boxHit;
boxHit._distance = boundDistance;
boxHit._intersect = true;
boxHit._intersectionNormal = surfaceNormal;
boxHit._intersectionPoint = ray.origin + boundDistance * rayDirection;
boxHit._intersectWithAvatar = avatarID;
boxHit._intersectWithJoint = mSphere.getJointIndex();
boxHits.push_back(boxHit);
}
}
}
if (boxHits.size() > 0) {
if (boxHits.size() > 1) {
std::sort(boxHits.begin(), boxHits.end(), [](const MyCharacterController::RayAvatarResult& hitA,
const MyCharacterController::RayAvatarResult& hitB) {
return hitA._distance < hitB._distance;
});
}
rayAvatarResult = boxHits[0];
}
}
}
if (pickAgainstMesh) {
glm::vec3 localRayOrigin = avatar->worldToJointPoint(ray.origin, rayAvatarResult._intersectWithJoint);
glm::vec3 localRayPoint = avatar->worldToJointPoint(ray.origin + rayDirection, rayAvatarResult._intersectWithJoint);
auto avatarOrientation = avatar->getWorldOrientation();
auto avatarPosition = avatar->getWorldPosition();
auto jointOrientation = avatarOrientation * avatar->getAbsoluteDefaultJointRotationInObjectFrame(rayAvatarResult._intersectWithJoint);
auto jointPosition = avatarPosition + (avatarOrientation * avatar->getAbsoluteDefaultJointTranslationInObjectFrame(rayAvatarResult._intersectWithJoint));
auto defaultFrameRayOrigin = jointPosition + jointOrientation * localRayOrigin;
auto defaultFrameRayPoint = jointPosition + jointOrientation * localRayPoint;
auto defaultFrameRayDirection = defaultFrameRayPoint - defaultFrameRayOrigin;
if (avatar->getSkeletonModel()->findRayIntersectionAgainstSubMeshes(defaultFrameRayOrigin, defaultFrameRayDirection, distance, face, surfaceNormal, extraInfo, true, false)) {
auto newDistance = glm::length(vec3FromVariant(extraInfo["worldIntersectionPoint"]) - defaultFrameRayOrigin);
rayAvatarResult._distance = newDistance;
rayAvatarResult._intersectionPoint = ray.origin + newDistance * rayDirection;
rayAvatarResult._intersectionNormal = surfaceNormal;
extraInfo["worldIntersectionPoint"] = vec3toVariant(rayAvatarResult._intersectionPoint);
break;
}
} else if (rayAvatarResult._intersect){
break;
}
}
if (rayAvatarResult._intersect) {
result.intersects = true;
result.avatarID = rayAvatarResult._intersectWithAvatar;
result.distance = rayAvatarResult._distance;
result.surfaceNormal = rayAvatarResult._intersectionNormal;
result.jointIndex = rayAvatarResult._intersectWithJoint;
result.intersection = ray.origin + rayAvatarResult._distance * rayDirection;
result.extraInfo = extraInfo;
result.face = face;
}
}
if (result.intersects) {
result.intersection = ray.origin + ray.direction * result.distance;
}
return result;
}

View file

@ -29,6 +29,7 @@
#include <EntitySimulation.h> // for SetOfEntities
#include "AvatarMotionState.h"
#include "DetailedMotionState.h"
#include "MyAvatar.h"
#include "OtherAvatar.h"
@ -136,21 +137,25 @@ public:
* @param {PickRay} ray
* @param {Uuid[]} [avatarsToInclude=[]]
* @param {Uuid[]} [avatarsToDiscard=[]]
* @param {boolean} pickAgainstMesh
* @returns {RayToAvatarIntersectionResult}
*/
Q_INVOKABLE RayToAvatarIntersectionResult findRayIntersection(const PickRay& ray,
const QScriptValue& avatarIdsToInclude = QScriptValue(),
const QScriptValue& avatarIdsToDiscard = QScriptValue());
const QScriptValue& avatarIdsToDiscard = QScriptValue(),
bool pickAgainstMesh = true);
/**jsdoc
* @function AvatarManager.findRayIntersectionVector
* @param {PickRay} ray
* @param {Uuid[]} avatarsToInclude
* @param {Uuid[]} avatarsToDiscard
* @param {boolean} pickAgainstMesh
* @returns {RayToAvatarIntersectionResult}
*/
Q_INVOKABLE RayToAvatarIntersectionResult findRayIntersectionVector(const PickRay& ray,
const QVector<EntityItemID>& avatarsToInclude,
const QVector<EntityItemID>& avatarsToDiscard);
const QVector<EntityItemID>& avatarsToDiscard,
bool pickAgainstMesh);
/**jsdoc
* @function AvatarManager.findParabolaIntersectionVector

View file

@ -0,0 +1,188 @@
//
// DetailedMotionState.cpp
// interface/src/avatar/
//
// Created by Luis Cuenca 1/11/2019
// Copyright 2019 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "DetailedMotionState.h"
#include <PhysicsCollisionGroups.h>
#include <PhysicsEngine.h>
#include <PhysicsHelpers.h>
#include "MyAvatar.h"
DetailedMotionState::DetailedMotionState(AvatarPointer avatar, const btCollisionShape* shape, int jointIndex) :
ObjectMotionState(shape), _avatar(avatar), _jointIndex(jointIndex) {
assert(_avatar);
if (!_avatar->isMyAvatar()) {
_otherAvatar = std::static_pointer_cast<OtherAvatar>(_avatar);
}
_type = MOTIONSTATE_TYPE_DETAILED;
}
void DetailedMotionState::handleEasyChanges(uint32_t& flags) {
ObjectMotionState::handleEasyChanges(flags);
if (flags & Simulation::DIRTY_PHYSICS_ACTIVATION && !_body->isActive()) {
_body->activate();
}
}
bool DetailedMotionState::handleHardAndEasyChanges(uint32_t& flags, PhysicsEngine* engine) {
return ObjectMotionState::handleHardAndEasyChanges(flags, engine);
}
DetailedMotionState::~DetailedMotionState() {
assert(_avatar);
_avatar = nullptr;
}
// virtual
uint32_t DetailedMotionState::getIncomingDirtyFlags() {
return _body ? _dirtyFlags : 0;
}
void DetailedMotionState::clearIncomingDirtyFlags() {
if (_body) {
_dirtyFlags = 0;
}
}
PhysicsMotionType DetailedMotionState::computePhysicsMotionType() const {
// TODO?: support non-DYNAMIC motion for avatars? (e.g. when sitting)
return MOTION_TYPE_KINEMATIC;
}
// virtual and protected
const btCollisionShape* DetailedMotionState::computeNewShape() {
btCollisionShape* shape = nullptr;
if (!_avatar->isMyAvatar()) {
if (_otherAvatar != nullptr) {
shape = _otherAvatar->createCollisionShape(_jointIndex, _isBound, _boundJoints);
}
} else {
std::shared_ptr<MyAvatar> myAvatar = std::static_pointer_cast<MyAvatar>(_avatar);
if (myAvatar) {
shape = myAvatar->getCharacterController()->createDetailedCollisionShapeForJoint(_jointIndex);
}
}
return shape;
}
// virtual
bool DetailedMotionState::isMoving() const {
return false;
}
// virtual
void DetailedMotionState::getWorldTransform(btTransform& worldTrans) const {
worldTrans.setOrigin(glmToBullet(getObjectPosition()));
worldTrans.setRotation(glmToBullet(getObjectRotation()));
}
// virtual
void DetailedMotionState::setWorldTransform(const btTransform& worldTrans) {
_body->setWorldTransform(worldTrans);
}
// These pure virtual methods must be implemented for each MotionState type
// and make it possible to implement more complicated methods in this base class.
// virtual
float DetailedMotionState::getObjectRestitution() const {
return 0.5f;
}
// virtual
float DetailedMotionState::getObjectFriction() const {
return 0.5f;
}
// virtual
float DetailedMotionState::getObjectLinearDamping() const {
return 0.5f;
}
// virtual
float DetailedMotionState::getObjectAngularDamping() const {
return 0.5f;
}
// virtual
glm::vec3 DetailedMotionState::getObjectPosition() const {
if (_otherAvatar != nullptr) {
auto bodyLOD = _otherAvatar->getBodyLOD();
if (bodyLOD == OtherAvatar::BodyLOD::Sphere) {
return _avatar->getFitBounds().calcCenter();
}
}
return _avatar->getJointPosition(_jointIndex);
}
// virtual
glm::quat DetailedMotionState::getObjectRotation() const {
return _avatar->getWorldOrientation() * _avatar->getAbsoluteJointRotationInObjectFrame(_jointIndex);
}
// virtual
glm::vec3 DetailedMotionState::getObjectLinearVelocity() const {
return glm::vec3(0.0f);
}
// virtual
glm::vec3 DetailedMotionState::getObjectAngularVelocity() const {
return glm::vec3(0.0f);
}
// virtual
glm::vec3 DetailedMotionState::getObjectGravity() const {
return glm::vec3(0.0f);
}
// virtual
const QUuid DetailedMotionState::getObjectID() const {
return _avatar->getSessionUUID();
}
QString DetailedMotionState::getName() const {
return _avatar->getName() + "_" + _jointIndex;
}
// virtual
QUuid DetailedMotionState::getSimulatorID() const {
return _avatar->getSessionUUID();
}
// virtual
void DetailedMotionState::computeCollisionGroupAndMask(int32_t& group, int32_t& mask) const {
group = BULLET_COLLISION_GROUP_DETAILED_AVATAR;
mask = Physics::getDefaultCollisionMask(group);
}
// virtual
float DetailedMotionState::getMass() const {
return 0.0f;
}
void DetailedMotionState::setRigidBody(btRigidBody* body) {
ObjectMotionState::setRigidBody(body);
if (_body) {
// remove angular dynamics from this body
_body->setAngularFactor(0.0f);
}
}
void DetailedMotionState::setShape(const btCollisionShape* shape) {
ObjectMotionState::setShape(shape);
}
void DetailedMotionState::forceActive() {
if (_body && !_body->isActive()) {
_body->setActivationState(ACTIVE_TAG);
}
}

View file

@ -0,0 +1,100 @@
//
// DetailedMotionState.h
// interface/src/avatar/
//
// Created by Luis Cuenca 1/11/2019
// Copyright 2019 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_DetailedMotionState_h
#define hifi_DetailedMotionState_h
#include <QSet>
#include <ObjectMotionState.h>
#include <BulletUtil.h>
#include "OtherAvatar.h"
class DetailedMotionState : public ObjectMotionState {
public:
DetailedMotionState(AvatarPointer avatar, const btCollisionShape* shape, int jointIndex);
virtual void handleEasyChanges(uint32_t& flags) override;
virtual bool handleHardAndEasyChanges(uint32_t& flags, PhysicsEngine* engine) override;
virtual PhysicsMotionType getMotionType() const override { return _motionType; }
virtual uint32_t getIncomingDirtyFlags() override;
virtual void clearIncomingDirtyFlags() override;
virtual PhysicsMotionType computePhysicsMotionType() const override;
virtual bool isMoving() const override;
// this relays incoming position/rotation to the RigidBody
virtual void getWorldTransform(btTransform& worldTrans) const override;
// this relays outgoing position/rotation to the EntityItem
virtual void setWorldTransform(const btTransform& worldTrans) override;
// These pure virtual methods must be implemented for each MotionState type
// and make it possible to implement more complicated methods in this base class.
// pure virtual overrides from ObjectMotionState
virtual float getObjectRestitution() const override;
virtual float getObjectFriction() const override;
virtual float getObjectLinearDamping() const override;
virtual float getObjectAngularDamping() const override;
virtual glm::vec3 getObjectPosition() const override;
virtual glm::quat getObjectRotation() const override;
virtual glm::vec3 getObjectLinearVelocity() const override;
virtual glm::vec3 getObjectAngularVelocity() const override;
virtual glm::vec3 getObjectGravity() const override;
virtual const QUuid getObjectID() const override;
virtual QString getName() const override;
virtual QUuid getSimulatorID() const override;
void addDirtyFlags(uint32_t flags) { _dirtyFlags |= flags; }
virtual void computeCollisionGroupAndMask(int32_t& group, int32_t& mask) const override;
virtual float getMass() const override;
void forceActive();
QUuid getAvatarID() const { return _avatar->getID(); }
int getJointIndex() const { return _jointIndex; }
void setIsBound(bool isBound, std::vector<int> boundJoints) { _isBound = isBound; _boundJoints = boundJoints; }
bool getIsBound(std::vector<int>& boundJoints) const { boundJoints = _boundJoints; return _isBound; }
friend class AvatarManager;
friend class Avatar;
protected:
void setRigidBody(btRigidBody* body) override;
void setShape(const btCollisionShape* shape) override;
// the dtor had been made protected to force the compiler to verify that it is only
// ever called by the Avatar class dtor.
~DetailedMotionState();
virtual bool isReadyToComputeShape() const override { return true; }
virtual const btCollisionShape* computeNewShape() override;
AvatarPointer _avatar;
float _diameter { 0.0f };
uint32_t _dirtyFlags;
int _jointIndex { -1 };
OtherAvatarPointer _otherAvatar { nullptr };
bool _isBound { false };
std::vector<int> _boundJoints;
};
#endif // hifi_DetailedMotionState_h

View file

@ -134,7 +134,7 @@ MyAvatar::MyAvatar(QThread* thread) :
_scriptedMotorFrame(SCRIPTED_MOTOR_CAMERA_FRAME),
_scriptedMotorMode(SCRIPTED_MOTOR_SIMPLE_MODE),
_motionBehaviors(AVATAR_MOTION_DEFAULTS),
_characterController(this),
_characterController(std::shared_ptr<MyAvatar>(this)),
_eyeContactTarget(LEFT_EYE),
_realWorldFieldOfView("realWorldFieldOfView",
DEFAULT_REAL_WORLD_FIELD_OF_VIEW_DEGREES),
@ -866,7 +866,7 @@ void MyAvatar::simulate(float deltaTime, bool inView) {
// and all of its joints, now update our attachements.
Avatar::simulateAttachments(deltaTime);
relayJointDataToChildren();
if (updateGrabs()) {
if (applyGrabChanges()) {
_cauterizationNeedsUpdate = true;
}
@ -1171,77 +1171,6 @@ controller::Pose MyAvatar::getRightHandTipPose() const {
return pose;
}
glm::vec3 MyAvatar::worldToJointPoint(const glm::vec3& position, const int jointIndex) const {
glm::vec3 jointPos = getWorldPosition();//default value if no or invalid joint specified
glm::quat jointRot = getWorldOrientation();//default value if no or invalid joint specified
if (jointIndex != -1) {
if (_skeletonModel->getJointPositionInWorldFrame(jointIndex, jointPos)) {
_skeletonModel->getJointRotationInWorldFrame(jointIndex, jointRot);
} else {
qWarning() << "Invalid joint index specified: " << jointIndex;
}
}
glm::vec3 modelOffset = position - jointPos;
glm::vec3 jointSpacePosition = glm::inverse(jointRot) * modelOffset;
return jointSpacePosition;
}
glm::vec3 MyAvatar::worldToJointDirection(const glm::vec3& worldDir, const int jointIndex) const {
glm::quat jointRot = getWorldOrientation();//default value if no or invalid joint specified
if ((jointIndex != -1) && (!_skeletonModel->getJointRotationInWorldFrame(jointIndex, jointRot))) {
qWarning() << "Invalid joint index specified: " << jointIndex;
}
glm::vec3 jointSpaceDir = glm::inverse(jointRot) * worldDir;
return jointSpaceDir;
}
glm::quat MyAvatar::worldToJointRotation(const glm::quat& worldRot, const int jointIndex) const {
glm::quat jointRot = getWorldOrientation();//default value if no or invalid joint specified
if ((jointIndex != -1) && (!_skeletonModel->getJointRotationInWorldFrame(jointIndex, jointRot))) {
qWarning() << "Invalid joint index specified: " << jointIndex;
}
glm::quat jointSpaceRot = glm::inverse(jointRot) * worldRot;
return jointSpaceRot;
}
glm::vec3 MyAvatar::jointToWorldPoint(const glm::vec3& jointSpacePos, const int jointIndex) const {
glm::vec3 jointPos = getWorldPosition();//default value if no or invalid joint specified
glm::quat jointRot = getWorldOrientation();//default value if no or invalid joint specified
if (jointIndex != -1) {
if (_skeletonModel->getJointPositionInWorldFrame(jointIndex, jointPos)) {
_skeletonModel->getJointRotationInWorldFrame(jointIndex, jointRot);
} else {
qWarning() << "Invalid joint index specified: " << jointIndex;
}
}
glm::vec3 worldOffset = jointRot * jointSpacePos;
glm::vec3 worldPos = jointPos + worldOffset;
return worldPos;
}
glm::vec3 MyAvatar::jointToWorldDirection(const glm::vec3& jointSpaceDir, const int jointIndex) const {
glm::quat jointRot = getWorldOrientation();//default value if no or invalid joint specified
if ((jointIndex != -1) && (!_skeletonModel->getJointRotationInWorldFrame(jointIndex, jointRot))) {
qWarning() << "Invalid joint index specified: " << jointIndex;
}
glm::vec3 worldDir = jointRot * jointSpaceDir;
return worldDir;
}
glm::quat MyAvatar::jointToWorldRotation(const glm::quat& jointSpaceRot, const int jointIndex) const {
glm::quat jointRot = getWorldOrientation();//default value if no or invalid joint specified
if ((jointIndex != -1) && (!_skeletonModel->getJointRotationInWorldFrame(jointIndex, jointRot))) {
qWarning() << "Invalid joint index specified: " << jointIndex;
}
glm::quat worldRot = jointRot * jointSpaceRot;
return worldRot;
}
// virtual
void MyAvatar::render(RenderArgs* renderArgs) {
// don't render if we've been asked to disable local rendering
@ -3114,16 +3043,15 @@ void MyAvatar::postUpdate(float deltaTime, const render::ScenePointer& scene) {
if (_skeletonModel && _skeletonModel->isLoaded()) {
const Rig& rig = _skeletonModel->getRig();
const HFMModel& hfmModel = _skeletonModel->getHFMModel();
for (int i = 0; i < rig.getJointStateCount(); i++) {
AnimPose jointPose;
rig.getAbsoluteJointPoseInRigFrame(i, jointPose);
const HFMJointShapeInfo& shapeInfo = hfmModel.joints[i].shapeInfo;
const AnimPose pose = rigToWorldPose * jointPose;
for (size_t j = 0; j < shapeInfo.debugLines.size() / 2; j++) {
glm::vec3 pointA = pose.xformPoint(shapeInfo.debugLines[2 * j]);
glm::vec3 pointB = pose.xformPoint(shapeInfo.debugLines[2 * j + 1]);
DebugDraw::getInstance().drawRay(pointA, pointB, DEBUG_COLORS[i % NUM_DEBUG_COLORS]);
int jointCount = rig.getJointStateCount();
if (jointCount == (int)_multiSphereShapes.size()) {
for (int i = 0; i < jointCount; i++) {
AnimPose jointPose;
rig.getAbsoluteJointPoseInRigFrame(i, jointPose);
const AnimPose pose = rigToWorldPose * jointPose;
auto &multiSphere = _multiSphereShapes[i];
auto debugLines = multiSphere.getDebugLines();
DebugDraw::getInstance().drawRays(debugLines, DEBUG_COLORS[i % NUM_DEBUG_COLORS], pose.trans(), pose.rot());
}
}
}
@ -5375,7 +5303,7 @@ void MyAvatar::releaseGrab(const QUuid& grabID) {
_avatarGrabsLock.withWriteLock([&] {
if (_avatarGrabData.remove(grabID)) {
_deletedAvatarGrabs.insert(grabID);
_grabsToDelete.push_back(grabID);
tellHandler = true;
}
});
@ -5385,3 +5313,4 @@ void MyAvatar::releaseGrab(const QUuid& grabID) {
_clientTraitsHandler->markInstancedTraitDeleted(AvatarTraits::Grab, grabID);
}
}

View file

@ -37,6 +37,7 @@
class AvatarActionHold;
class ModelItemID;
class MyHead;
class DetailedMotionState;
enum eyeContactTarget {
LEFT_EYE,
@ -802,56 +803,6 @@ public:
*/
Q_INVOKABLE controller::Pose getRightHandTipPose() const;
// world-space to avatar-space rigconversion functions
/**jsdoc
* @function MyAvatar.worldToJointPoint
* @param {Vec3} position
* @param {number} [jointIndex=-1]
* @returns {Vec3}
*/
Q_INVOKABLE glm::vec3 worldToJointPoint(const glm::vec3& position, const int jointIndex = -1) const;
/**jsdoc
* @function MyAvatar.worldToJointDirection
* @param {Vec3} direction
* @param {number} [jointIndex=-1]
* @returns {Vec3}
*/
Q_INVOKABLE glm::vec3 worldToJointDirection(const glm::vec3& direction, const int jointIndex = -1) const;
/**jsdoc
* @function MyAvatar.worldToJointRotation
* @param {Quat} rotation
* @param {number} [jointIndex=-1]
* @returns {Quat}
*/
Q_INVOKABLE glm::quat worldToJointRotation(const glm::quat& rotation, const int jointIndex = -1) const;
/**jsdoc
* @function MyAvatar.jointToWorldPoint
* @param {vec3} position
* @param {number} [jointIndex=-1]
* @returns {Vec3}
*/
Q_INVOKABLE glm::vec3 jointToWorldPoint(const glm::vec3& position, const int jointIndex = -1) const;
/**jsdoc
* @function MyAvatar.jointToWorldDirection
* @param {Vec3} direction
* @param {number} [jointIndex=-1]
* @returns {Vec3}
*/
Q_INVOKABLE glm::vec3 jointToWorldDirection(const glm::vec3& direction, const int jointIndex = -1) const;
/**jsdoc
* @function MyAvatar.jointToWorldRotation
* @param {Quat} rotation
* @param {number} [jointIndex=-1]
* @returns {Quat}
*/
Q_INVOKABLE glm::quat jointToWorldRotation(const glm::quat& rotation, const int jointIndex = -1) const;
AvatarWeakPointer getLookAtTargetAvatar() const { return _lookAtTargetAvatar; }
void updateLookAtTargetAvatar();
void computeMyLookAtTarget(const AvatarHash& hash);

View file

@ -12,8 +12,10 @@
#include "MyCharacterController.h"
#include <BulletUtil.h>
#include "BulletCollision/NarrowPhaseCollision/btRaycastCallback.h"
#include "MyAvatar.h"
#include "DetailedMotionState.h"
// TODO: make avatars stand on steep slope
// TODO: make avatars not snag on low ceilings
@ -24,7 +26,7 @@ void MyCharacterController::RayShotgunResult::reset() {
walkable = true;
}
MyCharacterController::MyCharacterController(MyAvatar* avatar) {
MyCharacterController::MyCharacterController(std::shared_ptr<MyAvatar> avatar) {
assert(avatar);
_avatar = avatar;
@ -44,7 +46,6 @@ void MyCharacterController::setDynamicsWorld(btDynamicsWorld* world) {
void MyCharacterController::updateShapeIfNecessary() {
if (_pendingFlags & PENDING_FLAG_UPDATE_SHAPE) {
_pendingFlags &= ~PENDING_FLAG_UPDATE_SHAPE;
if (_radius > 0.0f) {
// create RigidBody if it doesn't exist
if (!_rigidBody) {
@ -375,3 +376,121 @@ void MyCharacterController::updateMassProperties() {
_rigidBody->setMassProps(mass, inertia);
}
btCollisionShape* MyCharacterController::createDetailedCollisionShapeForJoint(int jointIndex) {
ShapeInfo shapeInfo;
_avatar->computeDetailedShapeInfo(shapeInfo, jointIndex);
if (shapeInfo.getType() != SHAPE_TYPE_NONE) {
btCollisionShape* shape = const_cast<btCollisionShape*>(ObjectMotionState::getShapeManager()->getShape(shapeInfo));
if (shape) {
shape->setMargin(0.001f);
}
return shape;
}
return nullptr;
}
DetailedMotionState* MyCharacterController::createDetailedMotionStateForJoint(int jointIndex) {
auto shape = createDetailedCollisionShapeForJoint(jointIndex);
if (shape) {
DetailedMotionState* motionState = new DetailedMotionState(_avatar, shape, jointIndex);
motionState->setMass(_avatar->computeMass());
return motionState;
}
return nullptr;
}
void MyCharacterController::clearDetailedMotionStates() {
_pendingFlags |= PENDING_FLAG_REMOVE_DETAILED_FROM_SIMULATION;
// We make sure we don't add them again
_pendingFlags &= ~PENDING_FLAG_ADD_DETAILED_TO_SIMULATION;
}
void MyCharacterController::resetDetailedMotionStates() {
_detailedMotionStates.clear();
}
void MyCharacterController::buildPhysicsTransaction(PhysicsEngine::Transaction& transaction) {
for (size_t i = 0; i < _detailedMotionStates.size(); i++) {
_detailedMotionStates[i]->forceActive();
}
if (_pendingFlags & PENDING_FLAG_REMOVE_DETAILED_FROM_SIMULATION) {
_pendingFlags &= ~PENDING_FLAG_REMOVE_DETAILED_FROM_SIMULATION;
for (size_t i = 0; i < _detailedMotionStates.size(); i++) {
transaction.objectsToRemove.push_back(_detailedMotionStates[i]);
}
_detailedMotionStates.clear();
}
if (_pendingFlags & PENDING_FLAG_ADD_DETAILED_TO_SIMULATION) {
_pendingFlags &= ~PENDING_FLAG_ADD_DETAILED_TO_SIMULATION;
for (int i = 0; i < _avatar->getJointCount(); i++) {
auto dMotionState = createDetailedMotionStateForJoint(i);
if (dMotionState) {
_detailedMotionStates.push_back(dMotionState);
transaction.objectsToAdd.push_back(dMotionState);
}
}
}
}
void MyCharacterController::handleProcessedPhysicsTransaction(PhysicsEngine::Transaction& transaction) {
// things on objectsToRemove are ready for delete
for (auto object : transaction.objectsToRemove) {
delete object;
}
transaction.clear();
}
class DetailedRayResultCallback : public btCollisionWorld::AllHitsRayResultCallback {
public:
DetailedRayResultCallback()
: btCollisionWorld::AllHitsRayResultCallback(btVector3(0.0f, 0.0f, 0.0f), btVector3(0.0f, 0.0f, 0.0f)) {
// the RayResultCallback's group and mask must match MY_AVATAR
m_collisionFilterGroup = BULLET_COLLISION_GROUP_DETAILED_RAY;
m_collisionFilterMask = BULLET_COLLISION_MASK_DETAILED_RAY;
}
virtual btScalar addSingleResult(btCollisionWorld::LocalRayResult& rayResult, bool normalInWorldSpace) override {
return AllHitsRayResultCallback::addSingleResult(rayResult, normalInWorldSpace);
}
};
std::vector<MyCharacterController::RayAvatarResult> MyCharacterController::rayTest(const btVector3& origin, const btVector3& direction,
const btScalar& length, const QVector<uint>& jointsToExclude) const {
std::vector<RayAvatarResult> foundAvatars;
if (_dynamicsWorld) {
btVector3 end = origin + length * direction;
DetailedRayResultCallback rayCallback = DetailedRayResultCallback();
rayCallback.m_flags |= btTriangleRaycastCallback::kF_KeepUnflippedNormal;
rayCallback.m_flags |= btTriangleRaycastCallback::kF_UseSubSimplexConvexCastRaytest;
_dynamicsWorld->rayTest(origin, end, rayCallback);
if (rayCallback.m_hitFractions.size() > 0) {
foundAvatars.reserve(rayCallback.m_hitFractions.size());
for (int i = 0; i < rayCallback.m_hitFractions.size(); i++) {
auto object = rayCallback.m_collisionObjects[i];
ObjectMotionState* motionState = static_cast<ObjectMotionState*>(object->getUserPointer());
if (motionState && motionState->getType() == MOTIONSTATE_TYPE_DETAILED) {
DetailedMotionState* detailedMotionState = dynamic_cast<DetailedMotionState*>(motionState);
MyCharacterController::RayAvatarResult result;
result._intersect = true;
result._intersectWithAvatar = detailedMotionState->getAvatarID();
result._intersectionPoint = bulletToGLM(rayCallback.m_hitPointWorld[i]);
result._intersectionNormal = bulletToGLM(rayCallback.m_hitNormalWorld[i]);
result._distance = length * rayCallback.m_hitFractions[i];
result._intersectWithJoint = detailedMotionState->getJointIndex();
result._isBound = detailedMotionState->getIsBound(result._boundJoints);
btVector3 center;
btScalar radius;
detailedMotionState->getShape()->getBoundingSphere(center, radius);
result._maxDistance = (float)radius;
foundAvatars.push_back(result);
}
}
std::sort(foundAvatars.begin(), foundAvatars.end(), [](const RayAvatarResult& resultA, const RayAvatarResult& resultB) {
return resultA._distance < resultB._distance;
});
}
}
return foundAvatars;
}

33
interface/src/avatar/MyCharacterController.h Executable file → Normal file
View file

@ -15,13 +15,15 @@
#include <CharacterController.h>
//#include <SharedUtil.h>
#include <PhysicsEngine.h>
class btCollisionShape;
class MyAvatar;
class DetailedMotionState;
class MyCharacterController : public CharacterController {
public:
explicit MyCharacterController(MyAvatar* avatar);
explicit MyCharacterController(std::shared_ptr<MyAvatar> avatar);
~MyCharacterController ();
void setDynamicsWorld(btDynamicsWorld* world) override;
@ -42,6 +44,31 @@ public:
void setDensity(btScalar density) { _density = density; }
btCollisionShape* createDetailedCollisionShapeForJoint(int jointIndex);
DetailedMotionState* createDetailedMotionStateForJoint(int jointIndex);
std::vector<DetailedMotionState*>& getDetailedMotionStates() { return _detailedMotionStates; }
void clearDetailedMotionStates();
void resetDetailedMotionStates();
void buildPhysicsTransaction(PhysicsEngine::Transaction& transaction);
void handleProcessedPhysicsTransaction(PhysicsEngine::Transaction& transaction);
struct RayAvatarResult {
bool _intersect { false };
bool _isBound { false };
QUuid _intersectWithAvatar;
int _intersectWithJoint { -1 };
float _distance { 0.0f };
float _maxDistance { 0.0f };
QVariantMap _extraInfo;
glm::vec3 _intersectionPoint;
glm::vec3 _intersectionNormal;
std::vector<int> _boundJoints;
};
std::vector<RayAvatarResult> rayTest(const btVector3& origin, const btVector3& direction, const btScalar& length,
const QVector<uint>& jointsToExclude) const;
int32_t computeCollisionMask() const override;
void handleChangedCollisionMask() override;
@ -56,12 +83,14 @@ private:
btConvexHullShape* computeShape() const;
protected:
MyAvatar* _avatar { nullptr };
std::shared_ptr<MyAvatar> _avatar { nullptr };
// shotgun scan data
btAlignedObjectArray<btVector3> _topPoints;
btAlignedObjectArray<btVector3> _bottomPoints;
btScalar _density { 1.0f };
std::vector<DetailedMotionState*> _detailedMotionStates;
};
#endif // hifi_MyCharacterController_h

View file

@ -15,6 +15,7 @@
#include "Application.h"
#include "AvatarMotionState.h"
#include "DetailedMotionState.h"
const float DISPLAYNAME_FADE_TIME = 0.5f;
const float DISPLAYNAME_FADE_FACTOR = pow(0.01f, 1.0f / DISPLAYNAME_FADE_TIME);
@ -110,29 +111,155 @@ void OtherAvatar::updateSpaceProxy(workload::Transaction& transaction) const {
int OtherAvatar::parseDataFromBuffer(const QByteArray& buffer) {
int32_t bytesRead = Avatar::parseDataFromBuffer(buffer);
for (size_t i = 0; i < _detailedMotionStates.size(); i++) {
_detailedMotionStates[i]->forceActive();
}
if (_moving && _motionState) {
_motionState->addDirtyFlags(Simulation::DIRTY_POSITION);
}
return bytesRead;
}
btCollisionShape* OtherAvatar::createCollisionShape(int jointIndex, bool& isBound, std::vector<int>& boundJoints) {
ShapeInfo shapeInfo;
isBound = false;
QString jointName = "";
if (jointIndex > -1 && jointIndex < (int)_multiSphereShapes.size()) {
jointName = _multiSphereShapes[jointIndex].getJointName();
}
switch (_bodyLOD) {
case BodyLOD::Sphere:
shapeInfo.setSphere(0.5f * getFitBounds().getDimensions().y);
boundJoints.clear();
for (auto &spheres : _multiSphereShapes) {
if (spheres.isValid()) {
boundJoints.push_back(spheres.getJointIndex());
}
}
isBound = true;
break;
case BodyLOD::MultiSphereLow:
if (jointName.contains("RightHand", Qt::CaseInsensitive) || jointName.contains("LeftHand", Qt::CaseInsensitive)) {
if (jointName.size() <= QString("RightHand").size()) {
AABox handBound;
for (auto &spheres : _multiSphereShapes) {
if (spheres.isValid() && spheres.getJointName().contains(jointName, Qt::CaseInsensitive)) {
boundJoints.push_back(spheres.getJointIndex());
handBound += spheres.getBoundingBox();
}
}
shapeInfo.setSphere(0.5f * handBound.getLargestDimension());
glm::vec3 jointPosition;
glm::quat jointRotation;
_skeletonModel->getJointPositionInWorldFrame(jointIndex, jointPosition);
_skeletonModel->getJointRotationInWorldFrame(jointIndex, jointRotation);
glm::vec3 positionOffset = glm::inverse(jointRotation) * (handBound.calcCenter() - jointPosition);
shapeInfo.setOffset(positionOffset);
isBound = true;
}
break;
}
case BodyLOD::MultiSphereHigh:
computeDetailedShapeInfo(shapeInfo, jointIndex);
break;
default:
break;
}
if (shapeInfo.getType() != SHAPE_TYPE_NONE) {
auto shape = const_cast<btCollisionShape*>(ObjectMotionState::getShapeManager()->getShape(shapeInfo));
if (shape) {
shape->setMargin(0.001f);
}
return shape;
}
return nullptr;
}
DetailedMotionState* OtherAvatar::createMotionState(std::shared_ptr<OtherAvatar> avatar, int jointIndex) {
bool isBound = false;
std::vector<int> boundJoints;
btCollisionShape* shape = createCollisionShape(jointIndex, isBound, boundJoints);
if (shape) {
DetailedMotionState* motionState = new DetailedMotionState(avatar, shape, jointIndex);
motionState->setMass(computeMass());
motionState->setIsBound(isBound, boundJoints);
return motionState;
}
return nullptr;
}
void OtherAvatar::resetDetailedMotionStates() {
for (size_t i = 0; i < _detailedMotionStates.size(); i++) {
_detailedMotionStates[i] = nullptr;
}
_detailedMotionStates.clear();
}
void OtherAvatar::setWorkloadRegion(uint8_t region) {
_workloadRegion = region;
QString printRegion = "";
if (region == workload::Region::R1) {
printRegion = "R1";
} else if (region == workload::Region::R2) {
printRegion = "R2";
} else if (region == workload::Region::R3) {
printRegion = "R3";
} else {
printRegion = "invalid";
}
qCDebug(avatars) << "Setting workload region to " << printRegion;
computeShapeLOD();
}
void OtherAvatar::computeShapeLOD() {
// auto newBodyLOD = _workloadRegion < workload::Region::R3 ? BodyLOD::MultiSphereShapes : BodyLOD::CapsuleShape;
// auto newBodyLOD = BodyLOD::CapsuleShape;
BodyLOD newLOD;
switch (_workloadRegion) {
case workload::Region::R1:
newLOD = BodyLOD::MultiSphereHigh;
break;
case workload::Region::R2:
newLOD = BodyLOD::MultiSphereLow;
break;
case workload::Region::UNKNOWN:
case workload::Region::INVALID:
case workload::Region::R3:
default:
newLOD = BodyLOD::Sphere;
break;
}
if (newLOD != _bodyLOD) {
_bodyLOD = newLOD;
if (isInPhysicsSimulation()) {
qCDebug(avatars) << "Changing to body LOD " << newLOD;
_needsReinsertion = true;
}
}
}
bool OtherAvatar::isInPhysicsSimulation() const {
return _motionState != nullptr && _detailedMotionStates.size() > 0;
}
bool OtherAvatar::shouldBeInPhysicsSimulation() const {
return (_workloadRegion < workload::Region::R3 && !isDead());
return !isDead() && !(isInPhysicsSimulation() && _needsReinsertion);
}
bool OtherAvatar::needsPhysicsUpdate() const {
constexpr uint32_t FLAGS_OF_INTEREST = Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS | Simulation::DIRTY_POSITION | Simulation::DIRTY_COLLISION_GROUP;
return (_motionState && (bool)(_motionState->getIncomingDirtyFlags() & FLAGS_OF_INTEREST));
return (_needsReinsertion || (_motionState && (bool)(_motionState->getIncomingDirtyFlags() & FLAGS_OF_INTEREST)));
}
void OtherAvatar::rebuildCollisionShape() {
if (_motionState) {
_motionState->addDirtyFlags(Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS);
}
for (size_t i = 0; i < _detailedMotionStates.size(); i++) {
if (_detailedMotionStates[i]) {
_detailedMotionStates[i]->addDirtyFlags(Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS);
}
}
}
void OtherAvatar::setCollisionWithOtherAvatarsFlags() {
@ -141,6 +268,25 @@ void OtherAvatar::setCollisionWithOtherAvatarsFlags() {
}
}
void OtherAvatar::createDetailedMotionStates(const std::shared_ptr<OtherAvatar>& avatar) {
auto& detailedMotionStates = getDetailedMotionStates();
assert(detailedMotionStates.empty());
if (_bodyLOD == BodyLOD::Sphere) {
auto dMotionState = createMotionState(avatar, -1);
if (dMotionState) {
detailedMotionStates.push_back(dMotionState);
}
} else {
for (int i = 0; i < getJointCount(); i++) {
auto dMotionState = createMotionState(avatar, i);
if (dMotionState) {
detailedMotionStates.push_back(dMotionState);
}
}
}
_needsReinsertion = false;
}
void OtherAvatar::simulate(float deltaTime, bool inView) {
PROFILE_RANGE(simulation, "simulate");
@ -217,7 +363,7 @@ void OtherAvatar::simulate(float deltaTime, bool inView) {
{
PROFILE_RANGE(simulation, "grabs");
updateGrabs();
applyGrabChanges();
}
updateFadingStatus();

22
interface/src/avatar/OtherAvatar.h Executable file → Normal file
View file

@ -21,12 +21,19 @@
class AvatarManager;
class AvatarMotionState;
class DetailedMotionState;
class OtherAvatar : public Avatar {
public:
explicit OtherAvatar(QThread* thread);
virtual ~OtherAvatar();
enum BodyLOD {
Sphere = 0,
MultiSphereLow, // No finger joints
MultiSphereHigh // All joints
};
virtual void instantiableAvatar() override { };
virtual void createOrb() override;
virtual void indicateLoadingStatus(LoadingStatus loadingStatus) override;
@ -39,13 +46,22 @@ public:
int parseDataFromBuffer(const QByteArray& buffer) override;
bool isInPhysicsSimulation() const { return _motionState != nullptr; }
bool isInPhysicsSimulation() const;
void rebuildCollisionShape() override;
void setWorkloadRegion(uint8_t region);
bool shouldBeInPhysicsSimulation() const;
bool needsPhysicsUpdate() const;
btCollisionShape* createCollisionShape(int jointIndex, bool& isBound, std::vector<int>& boundJoints);
DetailedMotionState* createMotionState(std::shared_ptr<OtherAvatar> avatar, int jointIndex);
void createDetailedMotionStates(const std::shared_ptr<OtherAvatar>& avatar);
std::vector<DetailedMotionState*>& getDetailedMotionStates() { return _detailedMotionStates; }
void resetDetailedMotionStates();
BodyLOD getBodyLOD() { return _bodyLOD; }
void computeShapeLOD();
void updateCollisionGroup(bool myAvatarCollide);
bool getCollideWithOtherAvatars() const { return _collideWithOtherAvatars; }
void setCollisionWithOtherAvatarsFlags() override;
@ -64,10 +80,14 @@ protected:
std::shared_ptr<Sphere3DOverlay> _otherAvatarOrbMeshPlaceholder { nullptr };
OverlayID _otherAvatarOrbMeshPlaceholderID { UNKNOWN_OVERLAY_ID };
AvatarMotionState* _motionState { nullptr };
std::vector<DetailedMotionState*> _detailedMotionStates;
int32_t _spaceIndex { -1 };
uint8_t _workloadRegion { workload::Region::INVALID };
BodyLOD _bodyLOD { BodyLOD::Sphere };
bool _needsReinsertion { false };
};
using OtherAvatarPointer = std::shared_ptr<OtherAvatar>;
using AvatarPointer = std::shared_ptr<Avatar>;
#endif // hifi_OtherAvatar_h

View file

@ -56,7 +56,7 @@ PickResultPointer RayPick::getOverlayIntersection(const PickRay& pick) {
}
PickResultPointer RayPick::getAvatarIntersection(const PickRay& pick) {
RayToAvatarIntersectionResult avatarRes = DependencyManager::get<AvatarManager>()->findRayIntersectionVector(pick, getIncludeItemsAs<EntityItemID>(), getIgnoreItemsAs<EntityItemID>());
RayToAvatarIntersectionResult avatarRes = DependencyManager::get<AvatarManager>()->findRayIntersectionVector(pick, getIncludeItemsAs<EntityItemID>(), getIgnoreItemsAs<EntityItemID>(), true);
if (avatarRes.intersects) {
return std::make_shared<RayPickResult>(IntersectionType::AVATAR, avatarRes.avatarID, avatarRes.distance, avatarRes.intersection, pick, avatarRes.surfaceNormal, avatarRes.extraInfo);
} else {

View file

@ -1310,10 +1310,17 @@ bool AudioClient::mixLocalAudioInjectors(float* mixBuffer) {
memset(_localScratchBuffer, 0, bytesToRead);
if (0 < injectorBuffer->readData((char*)_localScratchBuffer, bytesToRead)) {
float gain = injector->getVolume();
if (injector->isAmbisonic()) {
// no distance attenuation
float gain = injector->getVolume();
if (injector->isPositionSet()) {
// distance attenuation
glm::vec3 relativePosition = injector->getPosition() - _positionGetter();
float distance = glm::max(glm::length(relativePosition), EPSILON);
gain = gainForSource(distance, gain);
}
//
// Calculate the soundfield orientation relative to the listener.
@ -1327,33 +1334,49 @@ bool AudioClient::mixLocalAudioInjectors(float* mixBuffer) {
float qy = -relativeOrientation.x;
float qz = relativeOrientation.y;
// Ambisonic gets spatialized into mixBuffer
// spatialize into mixBuffer
injector->getLocalFOA().render(_localScratchBuffer, mixBuffer, HRTF_DATASET_INDEX,
qw, qx, qy, qz, gain, AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL);
} else if (injector->isStereo()) {
// calculate distance, gain
glm::vec3 relativePosition = injector->getPosition() - _positionGetter();
float distance = glm::max(glm::length(relativePosition), EPSILON);
float gain = gainForSource(distance, injector->getVolume());
if (injector->isPositionSet()) {
// stereo gets directly mixed into mixBuffer
for (int i = 0; i < AudioConstants::NETWORK_FRAME_SAMPLES_STEREO; i++) {
mixBuffer[i] += convertToFloat(_localScratchBuffer[i]) * gain;
// distance attenuation
glm::vec3 relativePosition = injector->getPosition() - _positionGetter();
float distance = glm::max(glm::length(relativePosition), EPSILON);
gain = gainForSource(distance, gain);
}
} else {
// direct mix into mixBuffer
for (int i = 0; i < AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL; i++) {
mixBuffer[2*i+0] += convertToFloat(_localScratchBuffer[2*i+0]) * gain;
mixBuffer[2*i+1] += convertToFloat(_localScratchBuffer[2*i+1]) * gain;
}
// calculate distance, gain and azimuth for hrtf
glm::vec3 relativePosition = injector->getPosition() - _positionGetter();
float distance = glm::max(glm::length(relativePosition), EPSILON);
float gain = gainForSource(distance, injector->getVolume());
float azimuth = azimuthForSource(relativePosition);
} else { // injector is mono
// mono gets spatialized into mixBuffer
injector->getLocalHRTF().render(_localScratchBuffer, mixBuffer, HRTF_DATASET_INDEX,
azimuth, distance, gain, AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL);
if (injector->isPositionSet()) {
// distance attenuation
glm::vec3 relativePosition = injector->getPosition() - _positionGetter();
float distance = glm::max(glm::length(relativePosition), EPSILON);
gain = gainForSource(distance, gain);
float azimuth = azimuthForSource(relativePosition);
// spatialize into mixBuffer
injector->getLocalHRTF().render(_localScratchBuffer, mixBuffer, HRTF_DATASET_INDEX,
azimuth, distance, gain, AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL);
} else {
// direct mix into mixBuffer
for (int i = 0; i < AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL; i++) {
float sample = convertToFloat(_localScratchBuffer[i]) * gain;
mixBuffer[2*i+0] += sample;
mixBuffer[2*i+1] += sample;
}
}
}
} else {
@ -1956,8 +1979,10 @@ float AudioClient::azimuthForSource(const glm::vec3& relativePosition) {
float AudioClient::gainForSource(float distance, float volume) {
// attenuation = -6dB * log2(distance)
// reference attenuation of 0dB at distance = 1.0m
float gain = volume / std::max(distance, HRTF_NEARFIELD_MIN);
// reference attenuation of 0dB at distance = ATTN_DISTANCE_REF
float d = (1.0f / ATTN_DISTANCE_REF) * std::max(distance, HRTF_NEARFIELD_MIN);
float gain = volume / d;
gain = std::min(gain, ATTN_GAIN_MAX);
return gain;
}

View file

@ -30,6 +30,10 @@ static const float HRTF_NEARFIELD_MAX = 1.0f; // distance in meters
static const float HRTF_NEARFIELD_MIN = 0.125f; // distance in meters
static const float HRTF_HEAD_RADIUS = 0.0875f; // average human head in meters
// Distance attenuation
static const float ATTN_DISTANCE_REF = 2.0f; // distance where attn is 0dB
static const float ATTN_GAIN_MAX = 16.0f; // max gain allowed by distance attn (+24dB)
class AudioHRTF {
public:

View file

@ -67,6 +67,7 @@ public:
bool isLocalOnly() const { return _options.localOnly; }
float getVolume() const { return _options.volume; }
bool isPositionSet() const { return _options.positionSet; }
glm::vec3 getPosition() const { return _options.position; }
glm::quat getOrientation() const { return _options.orientation; }
bool isStereo() const { return _options.stereo; }

View file

@ -19,6 +19,7 @@
AudioInjectorOptions::AudioInjectorOptions() :
position(0.0f, 0.0f, 0.0f),
positionSet(true), // default to legacy behavior
volume(1.0f),
loop(false),
orientation(glm::vec3(0.0f, 0.0f, 0.0f)),
@ -29,12 +30,13 @@ AudioInjectorOptions::AudioInjectorOptions() :
secondOffset(0.0f),
pitch(1.0f)
{
}
QScriptValue injectorOptionsToScriptValue(QScriptEngine* engine, const AudioInjectorOptions& injectorOptions) {
QScriptValue obj = engine->newObject();
obj.setProperty("position", vec3ToScriptValue(engine, injectorOptions.position));
if (injectorOptions.positionSet) {
obj.setProperty("position", vec3ToScriptValue(engine, injectorOptions.position));
}
obj.setProperty("volume", injectorOptions.volume);
obj.setProperty("loop", injectorOptions.loop);
obj.setProperty("orientation", quatToScriptValue(engine, injectorOptions.orientation));
@ -68,12 +70,18 @@ void injectorOptionsFromScriptValue(const QScriptValue& object, AudioInjectorOpt
return;
}
if (injectorOptions.positionSet == false) {
qWarning() << "Audio injector options: injectorOptionsFromScriptValue() called more than once?";
}
injectorOptions.positionSet = false;
QScriptValueIterator it(object);
while (it.hasNext()) {
it.next();
if (it.name() == "position") {
vec3FromScriptValue(object.property("position"), injectorOptions.position);
injectorOptions.positionSet = true;
} else if (it.name() == "orientation") {
quatFromScriptValue(object.property("orientation"), injectorOptions.orientation);
} else if (it.name() == "volume") {

View file

@ -21,6 +21,7 @@ class AudioInjectorOptions {
public:
AudioInjectorOptions();
glm::vec3 position;
bool positionSet;
float volume;
bool loop;
glm::quat orientation;

View file

@ -298,7 +298,9 @@ void Avatar::setTargetScale(float targetScale) {
_scaleChanged = usecTimestampNow();
_avatarScaleChanged = _scaleChanged;
_isAnimatingScale = true;
for (auto& sphere : _multiSphereShapes) {
sphere.setScale(_targetScale);
}
emit targetScaleChanged(targetScale);
}
}
@ -324,88 +326,79 @@ void Avatar::removeAvatarEntitiesFromTree() {
}
}
bool Avatar::updateGrabs() {
bool Avatar::applyGrabChanges() {
if (!_avatarGrabDataChanged && _grabsToChange.empty() && _grabsToDelete.empty()) {
// early exit for most common case: nothing to do
return false;
}
bool grabAddedOrRemoved = false;
// update the Grabs according to any changes in _avatarGrabData
_avatarGrabsLock.withWriteLock([&] {
if (_avatarGrabDataChanged) {
// collect changes in _avatarGrabData
foreach (auto grabID, _avatarGrabData.keys()) {
AvatarGrabMap::iterator grabItr = _avatarGrabs.find(grabID);
if (grabItr == _avatarGrabs.end()) {
MapOfGrabs::iterator itr = _avatarGrabs.find(grabID);
if (itr == _avatarGrabs.end()) {
GrabPointer grab = std::make_shared<Grab>();
grab->fromByteArray(_avatarGrabData.value(grabID));
_avatarGrabs[grabID] = grab;
_changedAvatarGrabs.insert(grabID);
_grabsToChange.insert(grabID);
} else {
GrabPointer grab = grabItr.value();
bool changed = grab->fromByteArray(_avatarGrabData.value(grabID));
bool changed = itr->second->fromByteArray(_avatarGrabData.value(grabID));
if (changed) {
_changedAvatarGrabs.insert(grabID);
_grabsToChange.insert(grabID);
}
}
}
_avatarGrabDataChanged = false;
}
auto treeRenderer = DependencyManager::get<EntityTreeRenderer>();
auto entityTree = treeRenderer ? treeRenderer->getTree() : nullptr;
EntityEditPacketSender* packetSender = treeRenderer ? treeRenderer->getPacketSender() : nullptr;
auto sessionID = DependencyManager::get<NodeList>()->getSessionUUID();
QMutableSetIterator<QUuid> delItr(_deletedAvatarGrabs);
while (delItr.hasNext()) {
QUuid grabID = delItr.next();
GrabPointer grab = _avatarGrabs[grabID];
if (!grab) {
delItr.remove();
// delete _avatarGrabs
VectorOfIDs undeleted;
for (const auto& id : _grabsToDelete) {
MapOfGrabs::iterator itr = _avatarGrabs.find(id);
if (itr == _avatarGrabs.end()) {
continue;
}
bool success;
const GrabPointer& grab = itr->second;
SpatiallyNestablePointer target = SpatiallyNestable::findByID(grab->getTargetID(), success);
// only clear this entry from the _deletedAvatarGrabs if we found the entity.
if (success && target) {
bool iShouldTellServer = target->getEditSenderID() == sessionID;
EntityItemPointer entity = std::dynamic_pointer_cast<EntityItem>(target);
if (entity && entity->isAvatarEntity() && (entity->getOwningAvatarID() == sessionID ||
entity->getOwningAvatarID() == AVATAR_SELF_ID)) {
// this is our own avatar-entity, so we always tell the server about the release
iShouldTellServer = true;
}
target->removeGrab(grab);
delItr.remove();
// in case this is the last grab on an entity, we need to shrink the queryAACube and tell the server
// about the final position.
if (entityTree) {
bool force = true;
entityTree->withWriteLock([&] {
entityTree->updateEntityQueryAACube(target, packetSender, force, iShouldTellServer);
});
}
_avatarGrabs.erase(itr);
grabAddedOrRemoved = true;
} else {
undeleted.push_back(id);
}
_avatarGrabs.remove(grabID);
_changedAvatarGrabs.remove(grabID);
}
_grabsToDelete = std::move(undeleted);
QMutableSetIterator<QUuid> changeItr(_changedAvatarGrabs);
while (changeItr.hasNext()) {
QUuid grabID = changeItr.next();
GrabPointer& grab = _avatarGrabs[grabID];
// change _avatarGrabs and add Actions to target
SetOfIDs unchanged;
for (const auto& id : _grabsToChange) {
MapOfGrabs::iterator itr = _avatarGrabs.find(id);
if (itr == _avatarGrabs.end()) {
continue;
}
bool success;
const GrabPointer& grab = itr->second;
SpatiallyNestablePointer target = SpatiallyNestable::findByID(grab->getTargetID(), success);
if (success && target) {
target->addGrab(grab);
// only clear this entry from the _changedAvatarGrabs if we found the entity.
changeItr.remove();
if (isMyAvatar()) {
EntityItemPointer entity = std::dynamic_pointer_cast<EntityItem>(target);
if (entity) {
entity->upgradeScriptSimulationPriority(PERSONAL_SIMULATION_PRIORITY);
}
}
grabAddedOrRemoved = true;
} else {
unchanged.insert(id);
}
}
_grabsToChange = std::move(unchanged);
});
return grabAddedOrRemoved;
}
@ -413,8 +406,8 @@ bool Avatar::updateGrabs() {
void Avatar::accumulateGrabPositions(std::map<QUuid, GrabLocationAccumulator>& grabAccumulators) {
// relay avatar's joint position to grabbed target in a way that allows for averaging
_avatarGrabsLock.withReadLock([&] {
foreach (auto grabID, _avatarGrabs.keys()) {
const GrabPointer& grab = _avatarGrabs.value(grabID);
for (const auto& entry : _avatarGrabs) {
const GrabPointer& grab = entry.second;
if (!grab || !grab->getActionID().isNull()) {
continue; // the accumulated value isn't used, in this case.
@ -432,6 +425,20 @@ void Avatar::accumulateGrabPositions(std::map<QUuid, GrabLocationAccumulator>& g
});
}
void Avatar::tearDownGrabs() {
_avatarGrabsLock.withWriteLock([&] {
for (const auto& entry : _avatarGrabs) {
_grabsToDelete.push_back(entry.first);
}
_grabsToChange.clear();
});
applyGrabChanges();
if (!_grabsToDelete.empty()) {
// some grabs failed to delete, which is a possible "leak", so we log about it
qWarning() << "Failed to tearDown" << _grabsToDelete.size() << "grabs for Avatar" << getID();
}
}
void Avatar::relayJointDataToChildren() {
forEachChild([&](SpatiallyNestablePointer child) {
if (child->getNestableType() == NestableType::Entity) {
@ -717,6 +724,7 @@ void Avatar::postUpdate(float deltaTime, const render::ScenePointer& scene) {
}
fixupModelsInScene(scene);
updateFitBoundingBox();
}
void Avatar::render(RenderArgs* renderArgs) {
@ -1283,6 +1291,79 @@ glm::vec3 Avatar::getAbsoluteJointScaleInObjectFrame(int index) const {
}
}
glm::vec3 Avatar::worldToJointPoint(const glm::vec3& position, const int jointIndex) const {
glm::vec3 jointPos = getWorldPosition();//default value if no or invalid joint specified
glm::quat jointRot = getWorldOrientation();//default value if no or invalid joint specified
if (jointIndex != -1) {
if (_skeletonModel->getJointPositionInWorldFrame(jointIndex, jointPos)) {
_skeletonModel->getJointRotationInWorldFrame(jointIndex, jointRot);
} else {
qWarning() << "Invalid joint index specified: " << jointIndex;
}
}
glm::vec3 modelOffset = position - jointPos;
glm::vec3 jointSpacePosition = glm::inverse(jointRot) * modelOffset;
return jointSpacePosition;
}
glm::vec3 Avatar::worldToJointDirection(const glm::vec3& worldDir, const int jointIndex) const {
glm::quat jointRot = getWorldOrientation();//default value if no or invalid joint specified
if ((jointIndex != -1) && (!_skeletonModel->getJointRotationInWorldFrame(jointIndex, jointRot))) {
qWarning() << "Invalid joint index specified: " << jointIndex;
}
glm::vec3 jointSpaceDir = glm::inverse(jointRot) * worldDir;
return jointSpaceDir;
}
glm::quat Avatar::worldToJointRotation(const glm::quat& worldRot, const int jointIndex) const {
glm::quat jointRot = getWorldOrientation();//default value if no or invalid joint specified
if ((jointIndex != -1) && (!_skeletonModel->getJointRotationInWorldFrame(jointIndex, jointRot))) {
qWarning() << "Invalid joint index specified: " << jointIndex;
}
glm::quat jointSpaceRot = glm::inverse(jointRot) * worldRot;
return jointSpaceRot;
}
glm::vec3 Avatar::jointToWorldPoint(const glm::vec3& jointSpacePos, const int jointIndex) const {
glm::vec3 jointPos = getWorldPosition();//default value if no or invalid joint specified
glm::quat jointRot = getWorldOrientation();//default value if no or invalid joint specified
if (jointIndex != -1) {
if (_skeletonModel->getJointPositionInWorldFrame(jointIndex, jointPos)) {
_skeletonModel->getJointRotationInWorldFrame(jointIndex, jointRot);
} else {
qWarning() << "Invalid joint index specified: " << jointIndex;
}
}
glm::vec3 worldOffset = jointRot * jointSpacePos;
glm::vec3 worldPos = jointPos + worldOffset;
return worldPos;
}
glm::vec3 Avatar::jointToWorldDirection(const glm::vec3& jointSpaceDir, const int jointIndex) const {
glm::quat jointRot = getWorldOrientation();//default value if no or invalid joint specified
if ((jointIndex != -1) && (!_skeletonModel->getJointRotationInWorldFrame(jointIndex, jointRot))) {
qWarning() << "Invalid joint index specified: " << jointIndex;
}
glm::vec3 worldDir = jointRot * jointSpaceDir;
return worldDir;
}
glm::quat Avatar::jointToWorldRotation(const glm::quat& jointSpaceRot, const int jointIndex) const {
glm::quat jointRot = getWorldOrientation();//default value if no or invalid joint specified
if ((jointIndex != -1) && (!_skeletonModel->getJointRotationInWorldFrame(jointIndex, jointRot))) {
qWarning() << "Invalid joint index specified: " << jointIndex;
}
glm::quat worldRot = jointRot * jointSpaceRot;
return worldRot;
}
void Avatar::invalidateJointIndicesCache() const {
QWriteLocker writeLock(&_modelJointIndicesCacheLock);
_modelJointsCached = false;
@ -1420,6 +1501,7 @@ void Avatar::setModelURLFinished(bool success) {
// rig is ready
void Avatar::rigReady() {
buildUnscaledEyeHeightCache();
computeMultiSphereShapes();
}
// rig has been reset.
@ -1427,6 +1509,48 @@ void Avatar::rigReset() {
clearUnscaledEyeHeightCache();
}
void Avatar::computeMultiSphereShapes() {
const Rig& rig = getSkeletonModel()->getRig();
glm::vec3 scale = extractScale(rig.getGeometryOffsetPose());
const HFMModel& geometry = getSkeletonModel()->getHFMModel();
int jointCount = rig.getJointStateCount();
_multiSphereShapes.clear();
_multiSphereShapes.reserve(jointCount);
for (int i = 0; i < jointCount; i++) {
const HFMJointShapeInfo& shapeInfo = geometry.joints[i].shapeInfo;
std::vector<btVector3> btPoints;
int lineCount = (int)shapeInfo.debugLines.size();
btPoints.reserve(lineCount);
for (int j = 0; j < lineCount; j++) {
const glm::vec3 &point = shapeInfo.debugLines[j];
auto rigPoint = scale * point;
btVector3 btPoint = glmToBullet(rigPoint);
btPoints.push_back(btPoint);
}
auto jointName = rig.nameOfJoint(i).toUpper();
MultiSphereShape multiSphereShape;
if (multiSphereShape.computeMultiSphereShape(i, jointName, btPoints)) {
multiSphereShape.calculateDebugLines();
multiSphereShape.setScale(_targetScale);
}
_multiSphereShapes.push_back(multiSphereShape);
}
}
void Avatar::updateFitBoundingBox() {
_fitBoundingBox = AABox();
if (getJointCount() == (int)_multiSphereShapes.size()) {
for (int i = 0; i < getJointCount(); i++) {
auto &shape = _multiSphereShapes[i];
glm::vec3 jointPosition;
glm::quat jointRotation;
_skeletonModel->getJointPositionInWorldFrame(i, jointPosition);
_skeletonModel->getJointRotationInWorldFrame(i, jointRotation);
_fitBoundingBox += shape.updateBoundingBox(jointPosition, jointRotation);
}
}
}
// create new model, can return an instance of a SoftAttachmentModel rather then Model
static std::shared_ptr<Model> allocateAttachmentModel(bool isSoft, const Rig& rigOverride, bool isCauterized) {
if (isSoft) {
@ -1606,6 +1730,21 @@ void Avatar::computeShapeInfo(ShapeInfo& shapeInfo) {
shapeInfo.setOffset(offset);
}
void Avatar::computeDetailedShapeInfo(ShapeInfo& shapeInfo, int jointIndex) {
if (jointIndex > -1 && jointIndex < (int)_multiSphereShapes.size()) {
auto& data = _multiSphereShapes[jointIndex].getSpheresData();
std::vector<glm::vec3> positions;
std::vector<btScalar> radiuses;
positions.reserve(data.size());
radiuses.reserve(data.size());
for (auto& sphere : data) {
positions.push_back(sphere._position);
radiuses.push_back(sphere._radius);
}
shapeInfo.setMultiSphere(positions, radiuses);
}
}
void Avatar::getCapsule(glm::vec3& start, glm::vec3& end, float& radius) {
ShapeInfo shapeInfo;
computeShapeInfo(shapeInfo);
@ -1929,3 +2068,12 @@ scriptable::ScriptableModelBase Avatar::getScriptableModel() {
}
return result;
}
void Avatar::clearAvatarGrabData(const QUuid& id) {
AvatarData::clearAvatarGrabData(id);
_avatarGrabsLock.withWriteLock([&] {
if (_avatarGrabs.find(id) != _avatarGrabs.end()) {
_grabsToDelete.push_back(id);
}
});
}

View file

@ -14,6 +14,9 @@
#include <functional>
#include <glm/glm.hpp>
#include <glm/gtc/quaternion.hpp>
#include <map>
#include <set>
#include <vector>
#include <QtCore/QUuid>
@ -23,12 +26,15 @@
#include <graphics-scripting/Forward.h>
#include <GLMHelpers.h>
#include <Grab.h>
#include <ThreadSafeValueCache.h>
#include "Head.h"
#include "SkeletonModel.h"
#include "Rig.h"
#include <ThreadSafeValueCache.h>
#include "MetaModelPayload.h"
#include "MultiSphereShape.h"
namespace render {
template <> const ItemKey payloadGetKey(const AvatarSharedPointer& avatar);
@ -222,12 +228,63 @@ public:
*/
Q_INVOKABLE virtual glm::vec3 getAbsoluteDefaultJointTranslationInObjectFrame(int index) const;
virtual glm::vec3 getAbsoluteJointScaleInObjectFrame(int index) const override;
virtual glm::quat getAbsoluteJointRotationInObjectFrame(int index) const override;
virtual glm::vec3 getAbsoluteJointTranslationInObjectFrame(int index) const override;
virtual bool setAbsoluteJointRotationInObjectFrame(int index, const glm::quat& rotation) override { return false; }
virtual bool setAbsoluteJointTranslationInObjectFrame(int index, const glm::vec3& translation) override { return false; }
// world-space to avatar-space rigconversion functions
/**jsdoc
* @function MyAvatar.worldToJointPoint
* @param {Vec3} position
* @param {number} [jointIndex=-1]
* @returns {Vec3}
*/
Q_INVOKABLE glm::vec3 worldToJointPoint(const glm::vec3& position, const int jointIndex = -1) const;
/**jsdoc
* @function MyAvatar.worldToJointDirection
* @param {Vec3} direction
* @param {number} [jointIndex=-1]
* @returns {Vec3}
*/
Q_INVOKABLE glm::vec3 worldToJointDirection(const glm::vec3& direction, const int jointIndex = -1) const;
/**jsdoc
* @function MyAvatar.worldToJointRotation
* @param {Quat} rotation
* @param {number} [jointIndex=-1]
* @returns {Quat}
*/
Q_INVOKABLE glm::quat worldToJointRotation(const glm::quat& rotation, const int jointIndex = -1) const;
/**jsdoc
* @function MyAvatar.jointToWorldPoint
* @param {vec3} position
* @param {number} [jointIndex=-1]
* @returns {Vec3}
*/
Q_INVOKABLE glm::vec3 jointToWorldPoint(const glm::vec3& position, const int jointIndex = -1) const;
/**jsdoc
* @function MyAvatar.jointToWorldDirection
* @param {Vec3} direction
* @param {number} [jointIndex=-1]
* @returns {Vec3}
*/
Q_INVOKABLE glm::vec3 jointToWorldDirection(const glm::vec3& direction, const int jointIndex = -1) const;
/**jsdoc
* @function MyAvatar.jointToWorldRotation
* @param {Quat} rotation
* @param {number} [jointIndex=-1]
* @returns {Quat}
*/
Q_INVOKABLE glm::quat jointToWorldRotation(const glm::quat& rotation, const int jointIndex = -1) const;
virtual void setSkeletonModelURL(const QUrl& skeletonModelURL) override;
virtual void setAttachmentData(const QVector<AttachmentData>& attachmentData) override;
@ -315,6 +372,8 @@ public:
virtual void rebuildCollisionShape() = 0;
virtual void computeShapeInfo(ShapeInfo& shapeInfo);
virtual void computeDetailedShapeInfo(ShapeInfo& shapeInfo, int jointIndex);
void getCapsule(glm::vec3& start, glm::vec3& end, float& radius);
float computeMass();
/**jsdoc
@ -393,6 +452,7 @@ public:
float getBoundingRadius() const;
AABox getRenderBounds() const; // THis call is accessible from rendering thread only to report the bounding box of the avatar during the frame.
AABox getFitBounds() const { return _fitBoundingBox; }
void addToScene(AvatarSharedPointer self, const render::ScenePointer& scene);
void ensureInScene(AvatarSharedPointer self, const render::ScenePointer& scene);
@ -436,6 +496,9 @@ public:
void accumulateGrabPositions(std::map<QUuid, GrabLocationAccumulator>& grabAccumulators);
const std::vector<MultiSphereShape>& getMultiSphereShapes() const { return _multiSphereShapes; }
void tearDownGrabs();
signals:
void targetScaleChanged(float targetScale);
@ -503,6 +566,8 @@ protected:
virtual const QString& getSessionDisplayNameForTransport() const override { return _empty; } // Save a tiny bit of bandwidth. Mixer won't look at what we send.
QString _empty{};
virtual void maybeUpdateSessionDisplayNameFromTransport(const QString& sessionDisplayName) override { _sessionDisplayName = sessionDisplayName; } // don't use no-op setter!
void computeMultiSphereShapes();
void updateFitBoundingBox();
SkeletonModelPointer _skeletonModel;
@ -538,7 +603,7 @@ protected:
// protected methods...
bool isLookingAtMe(AvatarSharedPointer avatar) const;
bool updateGrabs();
bool applyGrabChanges();
void relayJointDataToChildren();
void fade(render::Transaction& transaction, render::Transition::Type type);
@ -625,8 +690,18 @@ protected:
static void metaBlendshapeOperator(render::ItemID renderItemID, int blendshapeNumber, const QVector<BlendshapeOffset>& blendshapeOffsets,
const QVector<int>& blendedMeshSizes, const render::ItemIDs& subItemIDs);
std::vector<MultiSphereShape> _multiSphereShapes;
AABox _fitBoundingBox;
void clearAvatarGrabData(const QUuid& grabID) override;
AvatarGrabMap _avatarGrabs;
using SetOfIDs = std::set<QUuid>;
using VectorOfIDs = std::vector<QUuid>;
using MapOfGrabs = std::map<QUuid, GrabPointer>;
MapOfGrabs _avatarGrabs;
SetOfIDs _grabsToChange; // updated grab IDs -- changes needed to entities or physics
VectorOfIDs _grabsToDelete; // deleted grab IDs -- changes needed to entities or physics
};
#endif // hifi_Avatar_h

View file

@ -2900,6 +2900,7 @@ QScriptValue RayToAvatarIntersectionResultToScriptValue(QScriptEngine* engine, c
obj.setProperty("intersection", intersection);
QScriptValue surfaceNormal = vec3ToScriptValue(engine, value.surfaceNormal);
obj.setProperty("surfaceNormal", surfaceNormal);
obj.setProperty("jointIndex", value.jointIndex);
obj.setProperty("extraInfo", engine->toScriptValue(value.extraInfo));
return obj;
}
@ -2919,6 +2920,7 @@ void RayToAvatarIntersectionResultFromScriptValue(const QScriptValue& object, Ra
if (surfaceNormal.isValid()) {
vec3FromScriptValue(surfaceNormal, value.surfaceNormal);
}
value.jointIndex = object.property("jointIndex").toInt32();
value.extraInfo = object.property("extraInfo").toVariant().toMap();
}
@ -3010,7 +3012,6 @@ void AvatarData::clearAvatarGrabData(const QUuid& grabID) {
_avatarGrabsLock.withWriteLock([&] {
if (_avatarGrabData.remove(grabID)) {
_avatarGrabDataChanged = true;
_deletedAvatarGrabs.insert(grabID);
}
});
}

View file

@ -54,7 +54,6 @@
#include "AvatarTraits.h"
#include "HeadData.h"
#include "PathUtils.h"
#include "Grab.h"
#include <graphics/Material.h>
@ -67,8 +66,6 @@ using PackedAvatarEntityMap = QMap<QUuid, QByteArray>; // similar to AvatarEntit
using AvatarEntityIDs = QSet<QUuid>;
using AvatarGrabDataMap = QMap<QUuid, QByteArray>;
using AvatarGrabIDs = QSet<QUuid>;
using AvatarGrabMap = QMap<QUuid, GrabPointer>;
using AvatarDataSequenceNumber = uint16_t;
@ -1475,8 +1472,6 @@ protected:
mutable ReadWriteLockable _avatarGrabsLock;
AvatarGrabDataMap _avatarGrabData;
bool _avatarGrabDataChanged { false }; // by network
AvatarGrabIDs _changedAvatarGrabs; // updated grab IDs -- changes needed to entities or physics
AvatarGrabIDs _deletedAvatarGrabs; // deleted grab IDs -- changes needed to entities or physics
// used to transform any sensor into world space, including the _hmdSensorMat, or hand controllers.
ThreadSafeValueCache<glm::mat4> _sensorToWorldMatrixCache { glm::mat4() };
@ -1539,7 +1534,7 @@ protected:
}
bool updateAvatarGrabData(const QUuid& grabID, const QByteArray& grabData);
void clearAvatarGrabData(const QUuid& grabID);
virtual void clearAvatarGrabData(const QUuid& grabID);
private:
friend void avatarStateFromFrame(const QByteArray& frameData, AvatarData* _avatar);
@ -1620,6 +1615,7 @@ public:
BoxFace face;
glm::vec3 intersection;
glm::vec3 surfaceNormal;
int jointIndex { -1 };
QVariantMap extraInfo;
};
Q_DECLARE_METATYPE(RayToAvatarIntersectionResult)

View file

@ -14,20 +14,20 @@
#include <ObjectMotionState.h>
#include "RenderableLightEntityItem.h"
#include "RenderableLineEntityItem.h"
#include "RenderableModelEntityItem.h"
#include "RenderableParticleEffectEntityItem.h"
#include "RenderablePolyVoxEntityItem.h"
#include "RenderablePolyLineEntityItem.h"
#include "RenderableShapeEntityItem.h"
#include "RenderableModelEntityItem.h"
#include "RenderableTextEntityItem.h"
#include "RenderableImageEntityItem.h"
#include "RenderableWebEntityItem.h"
#include "RenderableParticleEffectEntityItem.h"
#include "RenderableLineEntityItem.h"
#include "RenderablePolyLineEntityItem.h"
#include "RenderablePolyVoxEntityItem.h"
#include "RenderableGridEntityItem.h"
#include "RenderableGizmoEntityItem.h"
#include "RenderableLightEntityItem.h"
#include "RenderableZoneEntityItem.h"
#include "RenderableMaterialEntityItem.h"
#include "RenderableImageEntityItem.h"
#include "RenderableGridEntityItem.h"
using namespace render;
using namespace render::entities;
@ -284,6 +284,10 @@ EntityRenderer::Pointer EntityRenderer::addToScene(EntityTreeRenderer& renderer,
result = make_renderer<GridEntityRenderer>(entity);
break;
case Type::Gizmo:
result = make_renderer<GizmoEntityRenderer>(entity);
break;
case Type::Light:
result = make_renderer<LightEntityRenderer>(entity);
break;

View file

@ -0,0 +1,289 @@
//
// Created by Sam Gondelman on 1/22/19
// Copyright 2019 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "RenderableGizmoEntityItem.h"
#include <DependencyManager.h>
#include <GeometryCache.h>
using namespace render;
using namespace render::entities;
GizmoEntityRenderer::GizmoEntityRenderer(const EntityItemPointer& entity) : Parent(entity)
{
}
GizmoEntityRenderer::~GizmoEntityRenderer() {
auto geometryCache = DependencyManager::get<GeometryCache>();
if (geometryCache) {
if (_ringGeometryID) {
geometryCache->releaseID(_ringGeometryID);
}
if (_majorTicksGeometryID) {
geometryCache->releaseID(_majorTicksGeometryID);
}
if (_minorTicksGeometryID) {
geometryCache->releaseID(_minorTicksGeometryID);
}
}
}
bool GizmoEntityRenderer::isTransparent() const {
bool ringTransparent = _gizmoType == GizmoType::RING && (_ringProperties.getInnerStartAlpha() < 1.0f ||
_ringProperties.getInnerEndAlpha() < 1.0f || _ringProperties.getOuterStartAlpha() < 1.0f ||
_ringProperties.getOuterEndAlpha() < 1.0f);
return Parent::isTransparent() || ringTransparent;
}
bool GizmoEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPointer& entity) const {
bool needsUpdate = resultWithReadLock<bool>([&] {
if (_gizmoType != entity->getGizmoType()) {
return true;
}
if (_ringProperties != entity->getRingProperties()) {
return true;
}
return false;
});
return needsUpdate;
}
void GizmoEntityRenderer::doRenderUpdateAsynchronousTyped(const TypedEntityPointer& entity) {
bool dirty = false;
RingGizmoPropertyGroup ringProperties = entity->getRingProperties();
withWriteLock([&] {
_gizmoType = entity->getGizmoType();
if (_ringProperties != ringProperties) {
_ringProperties = ringProperties;
dirty = true;
}
});
if (dirty || _prevPrimitiveMode != _primitiveMode || !_ringGeometryID || !_majorTicksGeometryID || !_minorTicksGeometryID) {
_prevPrimitiveMode = _primitiveMode;
auto geometryCache = DependencyManager::get<GeometryCache>();
if (!_ringGeometryID) {
_ringGeometryID = geometryCache->allocateID();
}
const float FULL_CIRCLE = 360.0f;
const float SLICES = 180.0f;
const float SLICE_ANGLE_RADIANS = glm::radians(FULL_CIRCLE / SLICES);
if (_primitiveMode == PrimitiveMode::SOLID) {
QVector<glm::vec3> points;
QVector<glm::vec4> colors;
vec4 innerStartColor = vec4(toGlm(ringProperties.getInnerStartColor()), ringProperties.getInnerStartAlpha());
vec4 outerStartColor = vec4(toGlm(ringProperties.getOuterStartColor()), ringProperties.getOuterStartAlpha());
vec4 innerEndColor = vec4(toGlm(ringProperties.getInnerEndColor()), ringProperties.getInnerEndAlpha());
vec4 outerEndColor = vec4(toGlm(ringProperties.getOuterEndColor()), ringProperties.getOuterEndAlpha());
float startAtRadians = glm::radians(ringProperties.getStartAngle());
float endAtRadians = glm::radians(ringProperties.getEndAngle());
const auto totalRange = endAtRadians - startAtRadians;
if (ringProperties.getInnerRadius() <= 0) {
_solidPrimitive = gpu::TRIANGLE_FAN;
points << vec3();
colors << innerStartColor;
for (float angleRadians = startAtRadians; angleRadians < endAtRadians; angleRadians += SLICE_ANGLE_RADIANS) {
float range = (angleRadians - startAtRadians) / totalRange;
points << 0.5f * glm::vec3(cosf(angleRadians), 0.0f, sinf(angleRadians));
colors << glm::mix(outerStartColor, outerEndColor, range);
}
points << 0.5f * glm::vec3(cosf(endAtRadians), 0.0f, sinf(endAtRadians));
colors << outerEndColor;
} else {
_solidPrimitive = gpu::TRIANGLE_STRIP;
for (float angleRadians = startAtRadians; angleRadians < endAtRadians; angleRadians += SLICE_ANGLE_RADIANS) {
float range = (angleRadians - startAtRadians) / totalRange;
points << 0.5f * glm::vec3(cosf(angleRadians) * ringProperties.getInnerRadius(), 0.0f, sinf(angleRadians) * ringProperties.getInnerRadius());
colors << glm::mix(innerStartColor, innerEndColor, range);
points << 0.5f * glm::vec3(cosf(angleRadians), 0.0f, sinf(angleRadians));
colors << glm::mix(outerStartColor, outerEndColor, range);
}
points << 0.5f * glm::vec3(cosf(endAtRadians) * ringProperties.getInnerRadius(), 0.0f, sinf(endAtRadians) * ringProperties.getInnerRadius());
colors << innerEndColor;
points << 0.5f * glm::vec3(cosf(endAtRadians), 0.0f, sinf(endAtRadians));
colors << outerEndColor;
}
geometryCache->updateVertices(_ringGeometryID, points, colors);
} else {
_solidPrimitive = gpu::LINE_STRIP;
QVector<glm::vec3> points;
float startAtRadians = glm::radians(ringProperties.getStartAngle());
float endAtRadians = glm::radians(ringProperties.getEndAngle());
float angleRadians = startAtRadians;
glm::vec3 firstPoint = 0.5f * glm::vec3(cosf(angleRadians), 0.0f, sinf(angleRadians));
points << firstPoint;
while (angleRadians < endAtRadians) {
angleRadians += SLICE_ANGLE_RADIANS;
glm::vec3 thisPoint = 0.5f * glm::vec3(cosf(angleRadians), 0.0f, sinf(angleRadians));
points << thisPoint;
}
// get the last slice portion....
angleRadians = endAtRadians;
glm::vec3 lastPoint = 0.5f * glm::vec3(cosf(angleRadians), 0.0f, sinf(angleRadians));
points << lastPoint;
geometryCache->updateVertices(_ringGeometryID, points, glm::vec4(toGlm(ringProperties.getOuterStartColor()), ringProperties.getOuterStartAlpha()));
}
if (ringProperties.getHasTickMarks()) {
if (ringProperties.getMajorTickMarksAngle() > 0.0f && ringProperties.getMajorTickMarksLength() != 0.0f) {
QVector<glm::vec3> points;
if (!_majorTicksGeometryID) {
_majorTicksGeometryID = geometryCache->allocateID();
}
float startAngle = ringProperties.getStartAngle();
float tickMarkAngle = ringProperties.getMajorTickMarksAngle();
float angle = startAngle - fmodf(startAngle, tickMarkAngle) + tickMarkAngle;
float tickMarkLength = 0.5f * ringProperties.getMajorTickMarksLength();
float startRadius = (tickMarkLength > 0.0f) ? 0.5f * ringProperties.getInnerRadius() : 0.5f;
float endRadius = startRadius + tickMarkLength;
while (angle <= ringProperties.getEndAngle()) {
float angleInRadians = glm::radians(angle);
glm::vec3 thisPointA = startRadius * glm::vec3(cosf(angleInRadians), 0.0f, sinf(angleInRadians));
glm::vec3 thisPointB = endRadius * glm::vec3(cosf(angleInRadians), 0.0f, sinf(angleInRadians));
points << thisPointA << thisPointB;
angle += tickMarkAngle;
}
glm::vec4 color(toGlm(ringProperties.getMajorTickMarksColor()), 1.0f); // TODO: alpha
geometryCache->updateVertices(_majorTicksGeometryID, points, color);
}
if (ringProperties.getMinorTickMarksAngle() > 0.0f && ringProperties.getMinorTickMarksLength() != 0.0f) {
QVector<glm::vec3> points;
if (!_minorTicksGeometryID) {
_minorTicksGeometryID = geometryCache->allocateID();
}
float startAngle = ringProperties.getStartAngle();
float tickMarkAngle = ringProperties.getMinorTickMarksAngle();
float angle = startAngle - fmodf(startAngle, tickMarkAngle) + tickMarkAngle;
float tickMarkLength = 0.5f * ringProperties.getMinorTickMarksLength();
float startRadius = (tickMarkLength > 0.0f) ? 0.5f * ringProperties.getInnerRadius() : 0.5f;
float endRadius = startRadius + tickMarkLength;
while (angle <= ringProperties.getEndAngle()) {
float angleInRadians = glm::radians(angle);
glm::vec3 thisPointA = startRadius * glm::vec3(cosf(angleInRadians), 0.0f, sinf(angleInRadians));
glm::vec3 thisPointB = endRadius * glm::vec3(cosf(angleInRadians), 0.0f, sinf(angleInRadians));
points << thisPointA << thisPointB;
angle += tickMarkAngle;
}
glm::vec4 color(toGlm(ringProperties.getMinorTickMarksColor()), 1.0f); // TODO: alpha
geometryCache->updateVertices(_minorTicksGeometryID, points, color);
}
}
}
void* key = (void*)this;
AbstractViewStateInterface::instance()->pushPostUpdateLambda(key, [this, entity]() {
withWriteLock([&] {
updateModelTransformAndBound();
_renderTransform = getModelTransform();
_renderTransform.postScale(entity->getScaledDimensions());
});
});
}
Item::Bound GizmoEntityRenderer::getBound() {
auto bound = Parent::getBound();
if (_ringProperties.getHasTickMarks()) {
glm::vec3 scale = bound.getScale();
for (int i = 0; i < 3; i += 2) {
if (_ringProperties.getMajorTickMarksLength() + _ringProperties.getInnerRadius() > 1.0f) {
scale[i] *= _ringProperties.getMajorTickMarksLength() + _ringProperties.getInnerRadius();
} else if (_ringProperties.getMajorTickMarksLength() < -2.0f) {
scale[i] *= -_ringProperties.getMajorTickMarksLength() - 1.0f;
}
if (_ringProperties.getMinorTickMarksLength() + _ringProperties.getInnerRadius() > 1.0f) {
scale[i] *= _ringProperties.getMinorTickMarksLength() + _ringProperties.getInnerRadius();
} else if (_ringProperties.getMinorTickMarksLength() < -2.0f) {
scale[i] *= -_ringProperties.getMinorTickMarksLength() - 1.0f;
}
}
bound.setScaleStayCentered(scale);
return bound;
}
return bound;
}
ShapeKey GizmoEntityRenderer::getShapeKey() {
auto builder = render::ShapeKey::Builder().withoutCullFace();
if (isTransparent()) {
builder.withTranslucent();
}
if (_primitiveMode == PrimitiveMode::LINES) {
builder.withUnlit().withDepthBias();
}
return builder.build();
}
void GizmoEntityRenderer::doRender(RenderArgs* args) {
PerformanceTimer perfTimer("RenderableGizmoEntityItem::render");
Q_ASSERT(args->_batch);
gpu::Batch& batch = *args->_batch;
auto geometryCache = DependencyManager::get<GeometryCache>();
if (_gizmoType == GizmoType::RING) {
Transform transform;
bool hasTickMarks;
glm::vec4 tickProperties;
withReadLock([&] {
transform = _renderTransform;
hasTickMarks = _ringProperties.getHasTickMarks();
tickProperties = glm::vec4(_ringProperties.getMajorTickMarksAngle(), _ringProperties.getMajorTickMarksLength(),
_ringProperties.getMinorTickMarksAngle(), _ringProperties.getMinorTickMarksLength());
});
bool wireframe = render::ShapeKey(args->_globalShapeKey).isWireframe() || _primitiveMode == PrimitiveMode::LINES;
geometryCache->bindSimpleProgram(batch, false, isTransparent(), false, wireframe, true, true, _renderLayer != RenderLayer::WORLD);
batch.setModelTransform(transform);
// Background circle
geometryCache->renderVertices(batch, wireframe ? gpu::LINE_STRIP : _solidPrimitive, _ringGeometryID);
// Ticks
if (hasTickMarks) {
if (tickProperties.x > 0.0f && tickProperties.y != 0.0f) {
geometryCache->renderVertices(batch, gpu::LINES, _majorTicksGeometryID);
}
if (tickProperties.z > 0.0f && tickProperties.w != 0.0f) {
geometryCache->renderVertices(batch, gpu::LINES, _minorTicksGeometryID);
}
}
}
}

View file

@ -0,0 +1,48 @@
//
// Created by Sam Gondelman on 1/22/19
// Copyright 2019 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_RenderableGizmoEntityItem_h
#define hifi_RenderableGizmoEntityItem_h
#include "RenderableEntityItem.h"
#include <GizmoEntityItem.h>
namespace render { namespace entities {
class GizmoEntityRenderer : public TypedEntityRenderer<GizmoEntityItem> {
using Parent = TypedEntityRenderer<GizmoEntityItem>;
using Pointer = std::shared_ptr<GizmoEntityRenderer>;
public:
GizmoEntityRenderer(const EntityItemPointer& entity);
~GizmoEntityRenderer();
protected:
Item::Bound getBound() override;
ShapeKey getShapeKey() override;
bool isTransparent() const override;
private:
virtual bool needsRenderUpdateFromTypedEntity(const TypedEntityPointer& entity) const override;
virtual void doRenderUpdateAsynchronousTyped(const TypedEntityPointer& entity) override;
virtual void doRender(RenderArgs* args) override;
GizmoType _gizmoType;
RingGizmoPropertyGroup _ringProperties;
PrimitiveMode _prevPrimitiveMode;
int _ringGeometryID { 0 };
int _majorTicksGeometryID { 0 };
int _minorTicksGeometryID { 0 };
gpu::Primitive _solidPrimitive { gpu::TRIANGLE_FAN };
};
} }
#endif // hifi_RenderableGizmoEntityItem_h

View file

@ -3425,7 +3425,19 @@ void EntityItem::addGrab(GrabPointer grab) {
enableNoBootstrap();
SpatiallyNestable::addGrab(grab);
if (getDynamic() && getParentID().isNull()) {
if (!getParentID().isNull()) {
return;
}
int jointIndex = grab->getParentJointIndex();
bool isFarGrab = jointIndex == FARGRAB_RIGHTHAND_INDEX
|| jointIndex == FARGRAB_LEFTHAND_INDEX
|| jointIndex == FARGRAB_MOUSE_INDEX;
// GRAB HACK: FarGrab doesn't work on non-dynamic things yet, but we really really want NearGrab
// (aka Hold) to work for such ojects, hence we filter the useAction case like so:
bool useAction = getDynamic() || (_physicsInfo && !isFarGrab);
if (useAction) {
EntityTreePointer entityTree = getTree();
assert(entityTree);
EntitySimulationPointer simulation = entityTree ? entityTree->getSimulation() : nullptr;
@ -3436,13 +3448,11 @@ void EntityItem::addGrab(GrabPointer grab) {
EntityDynamicType dynamicType;
QVariantMap arguments;
int grabParentJointIndex =grab->getParentJointIndex();
if (grabParentJointIndex == FARGRAB_RIGHTHAND_INDEX || grabParentJointIndex == FARGRAB_LEFTHAND_INDEX ||
grabParentJointIndex == FARGRAB_MOUSE_INDEX) {
if (isFarGrab) {
// add a far-grab action
dynamicType = DYNAMIC_TYPE_FAR_GRAB;
arguments["otherID"] = grab->getOwnerID();
arguments["otherJointIndex"] = grabParentJointIndex;
arguments["otherJointIndex"] = jointIndex;
arguments["targetPosition"] = vec3ToQMap(grab->getPositionalOffset());
arguments["targetRotation"] = quatToQMap(grab->getRotationalOffset());
arguments["linearTimeScale"] = 0.05;
@ -3463,11 +3473,23 @@ void EntityItem::addGrab(GrabPointer grab) {
grab->setActionID(actionID);
_grabActions[actionID] = action;
simulation->addDynamic(action);
markDirtyFlags(Simulation::DIRTY_MOTION_TYPE);
simulation->changeEntity(getThisPointer());
}
}
void EntityItem::removeGrab(GrabPointer grab) {
int oldNumGrabs = _grabs.size();
SpatiallyNestable::removeGrab(grab);
if (!getDynamic() && _grabs.size() != oldNumGrabs) {
// GRAB HACK: the expected behavior is for non-dynamic grabbed things to NOT be throwable
// so we slam the velocities to zero here whenever the number of grabs change.
// NOTE: if there is still another grab in effect this shouldn't interfere with the object's motion
// because that grab will slam the position+velocities next frame.
setLocalVelocity(glm::vec3(0.0f));
setAngularVelocity(glm::vec3(0.0f));
}
markDirtyFlags(Simulation::DIRTY_MOTION_TYPE);
QUuid actionID = grab->getActionID();
if (!actionID.isNull()) {

View file

@ -43,6 +43,7 @@ KeyLightPropertyGroup EntityItemProperties::_staticKeyLight;
AmbientLightPropertyGroup EntityItemProperties::_staticAmbientLight;
GrabPropertyGroup EntityItemProperties::_staticGrab;
PulsePropertyGroup EntityItemProperties::_staticPulse;
RingGizmoPropertyGroup EntityItemProperties::_staticRing;
EntityPropertyList PROP_LAST_ITEM = (EntityPropertyList)(PROP_AFTER_LAST_ITEM - 1);
@ -431,6 +432,31 @@ void EntityItemProperties::setInputModeFromString(const QString& webInputMode) {
}
}
QHash<QString, GizmoType> stringToGizmoTypeLookup;
void addGizmoType(GizmoType mode) {
stringToGizmoTypeLookup[GizmoTypeHelpers::getNameForGizmoType(mode)] = mode;
}
void buildStringToGizmoTypeLookup() {
addGizmoType(GizmoType::RING);
}
QString EntityItemProperties::getGizmoTypeAsString() const {
return GizmoTypeHelpers::getNameForGizmoType(_gizmoType);
}
void EntityItemProperties::setGizmoTypeFromString(const QString& gizmoType) {
if (stringToGizmoTypeLookup.empty()) {
buildStringToGizmoTypeLookup();
}
auto gizmoTypeItr = stringToGizmoTypeLookup.find(gizmoType.toLower());
if (gizmoTypeItr != stringToGizmoTypeLookup.end()) {
_gizmoType = gizmoTypeItr.value();
_gizmoTypeChanged = true;
}
}
EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
EntityPropertyFlags changedProperties;
@ -651,6 +677,10 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
CHECK_PROPERTY_CHANGE(PROP_MAJOR_GRID_EVERY, majorGridEvery);
CHECK_PROPERTY_CHANGE(PROP_MINOR_GRID_EVERY, minorGridEvery);
// Gizmo
CHECK_PROPERTY_CHANGE(PROP_GIZMO_TYPE, gizmoType);
changedProperties += _ring.getChangedProperties();
return changedProperties;
}
@ -844,6 +874,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
* @see {@link Entities.EntityProperties-PolyLine|EntityProperties-PolyLine}
* @see {@link Entities.EntityProperties-PolyVox|EntityProperties-PolyVox}
* @see {@link Entities.EntityProperties-Grid|EntityProperties-Grid}
* @see {@link Entities.EntityProperties-Gizmo|EntityProperties-Gizmo}
* @see {@link Entities.EntityProperties-Light|EntityProperties-Light}
* @see {@link Entities.EntityProperties-Zone|EntityProperties-Zone}
* @see {@link Entities.EntityProperties-Material|EntityProperties-Material}
@ -1463,6 +1494,14 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
* });
*/
/**jsdoc
* The <code>"Gizmo"</code> {@link Entities.EntityType|EntityType} displays an entity that could be used as UI.
* It has properties in addition to the common {@link Entities.EntityProperties|EntityProperties}.
* @typedef {object} Entities.EntityProperties-Gizmo
* @property {GizmoType} gizmoType="ring" - The gizmo type of the entity.
* @property {Entities.RingGizmo} ring - The ring gizmo properties.
*/
QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool skipDefaults, bool allowUnknownCreateTime,
bool strictSemantics, EntityPsuedoPropertyFlags psueudoPropertyFlags) const {
@ -1682,9 +1721,9 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_TEXT, text);
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_LINE_HEIGHT, lineHeight);
COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER_TYPED(PROP_TEXT_COLOR, textColor, getTextColor(), u8vec3Color);
COPY_PROPERTY_TO_QSCRIPTVALUE_TYPED(PROP_TEXT_COLOR, textColor, u8vec3Color);
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_TEXT_ALPHA, textAlpha);
COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER_TYPED(PROP_BACKGROUND_COLOR, backgroundColor, getBackgroundColor(), u8vec3Color);
COPY_PROPERTY_TO_QSCRIPTVALUE_TYPED(PROP_BACKGROUND_COLOR, backgroundColor, u8vec3Color);
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_BACKGROUND_ALPHA, backgroundAlpha);
COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_BILLBOARD_MODE, billboardMode, getBillboardModeAsString());
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_LEFT_MARGIN, leftMargin);
@ -1814,6 +1853,12 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_MINOR_GRID_EVERY, minorGridEvery);
}
// Gizmo only
if (_type == EntityTypes::Gizmo) {
COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_GIZMO_TYPE, gizmoType, getGizmoTypeAsString());
_ring.copyToScriptValue(_desiredProperties, properties, engine, skipDefaults, defaultEntityProperties);
}
/**jsdoc
* The axis-aligned bounding box of an entity.
* @typedef {object} Entities.BoundingBox
@ -2124,6 +2169,10 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool
COPY_PROPERTY_FROM_QSCRIPTVALUE(majorGridEvery, uint32_t, setMajorGridEvery);
COPY_PROPERTY_FROM_QSCRIPTVALUE(minorGridEvery, float, setMinorGridEvery);
// Gizmo
COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(gizmoType, GizmoType);
_ring.copyFromScriptValue(object, _defaultSettings);
// Handle conversions from old 'textures' property to "imageURL"
{
QScriptValue V = object.property("textures");
@ -2398,6 +2447,10 @@ void EntityItemProperties::merge(const EntityItemProperties& other) {
COPY_PROPERTY_IF_CHANGED(majorGridEvery);
COPY_PROPERTY_IF_CHANGED(minorGridEvery);
// Gizmo
COPY_PROPERTY_IF_CHANGED(gizmoType);
_ring.merge(other._ring);
_lastEdited = usecTimestampNow();
}
@ -2777,6 +2830,32 @@ bool EntityItemProperties::getPropertyInfo(const QString& propertyName, EntityPr
ADD_PROPERTY_TO_MAP(PROP_GRID_FOLLOW_CAMERA, FollowCamera, followCamera, bool);
ADD_PROPERTY_TO_MAP(PROP_MAJOR_GRID_EVERY, MajorGridEvery, majorGridEvery, uint32_t);
ADD_PROPERTY_TO_MAP(PROP_MINOR_GRID_EVERY, MinorGridEvery, minorGridEvery, float);
// Gizmo
ADD_PROPERTY_TO_MAP(PROP_GIZMO_TYPE, GizmoType, gizmoType, GizmoType);
{ // RingGizmo
ADD_GROUP_PROPERTY_TO_MAP_WITH_RANGE(PROP_START_ANGLE, Ring, ring, StartAngle, startAngle, RingGizmoPropertyGroup::MIN_ANGLE, RingGizmoPropertyGroup::MAX_ANGLE);
ADD_GROUP_PROPERTY_TO_MAP_WITH_RANGE(PROP_END_ANGLE, Ring, ring, EndAngle, endAngle, RingGizmoPropertyGroup::MIN_ANGLE, RingGizmoPropertyGroup::MAX_ANGLE);
ADD_GROUP_PROPERTY_TO_MAP_WITH_RANGE(PROP_INNER_RADIUS, Ring, ring, InnerRadius, innerRadius, RingGizmoPropertyGroup::MIN_RADIUS, RingGizmoPropertyGroup::MAX_RADIUS);
ADD_GROUP_PROPERTY_TO_MAP(PROP_INNER_START_COLOR, Ring, ring, InnerStartColor, innerStartColor);
ADD_GROUP_PROPERTY_TO_MAP(PROP_INNER_END_COLOR, Ring, ring, InnerEndColor, innerEndColor);
ADD_GROUP_PROPERTY_TO_MAP(PROP_OUTER_START_COLOR, Ring, ring, OuterStartColor, outerStartColor);
ADD_GROUP_PROPERTY_TO_MAP(PROP_OUTER_END_COLOR, Ring, ring, OuterEndColor, outerEndColor);
ADD_GROUP_PROPERTY_TO_MAP_WITH_RANGE(PROP_INNER_START_ALPHA, Ring, ring, InnerStartAlpha, innerStartAlpha, RingGizmoPropertyGroup::MIN_ALPHA, RingGizmoPropertyGroup::MAX_ALPHA);
ADD_GROUP_PROPERTY_TO_MAP_WITH_RANGE(PROP_INNER_END_ALPHA, Ring, ring, InnerEndAlpha, innerEndAlpha, RingGizmoPropertyGroup::MIN_ALPHA, RingGizmoPropertyGroup::MAX_ALPHA);
ADD_GROUP_PROPERTY_TO_MAP_WITH_RANGE(PROP_OUTER_START_ALPHA, Ring, ring, OuterStartAlpha, outerStartAlpha, RingGizmoPropertyGroup::MIN_ALPHA, RingGizmoPropertyGroup::MAX_ALPHA);
ADD_GROUP_PROPERTY_TO_MAP_WITH_RANGE(PROP_OUTER_END_ALPHA, Ring, ring, OuterEndAlpha, outerEndAlpha, RingGizmoPropertyGroup::MIN_ALPHA, RingGizmoPropertyGroup::MAX_ALPHA);
ADD_GROUP_PROPERTY_TO_MAP(PROP_HAS_TICK_MARKS, Ring, ring, HasTickMarks, hasTickMarks);
ADD_GROUP_PROPERTY_TO_MAP_WITH_RANGE(PROP_MAJOR_TICK_MARKS_ANGLE, Ring, ring, MajorTickMarksAngle, majorTickMarksAngle, RingGizmoPropertyGroup::MIN_ANGLE, RingGizmoPropertyGroup::MAX_ANGLE);
ADD_GROUP_PROPERTY_TO_MAP_WITH_RANGE(PROP_MINOR_TICK_MARKS_ANGLE, Ring, ring, MinorTickMarksAngle, minorTickMarksAngle, RingGizmoPropertyGroup::MIN_ANGLE, RingGizmoPropertyGroup::MAX_ANGLE);
ADD_GROUP_PROPERTY_TO_MAP(PROP_MAJOR_TICK_MARKS_LENGTH, Ring, ring, MajorTickMarksLength, majorTickMarksLength);
ADD_GROUP_PROPERTY_TO_MAP(PROP_MINOR_TICK_MARKS_LENGTH, Ring, ring, MinorTickMarksLength, minorTickMarksLength);
ADD_GROUP_PROPERTY_TO_MAP(PROP_MAJOR_TICK_MARKS_COLOR, Ring, ring, MajorTickMarksColor, majorTickMarksColor);
ADD_GROUP_PROPERTY_TO_MAP(PROP_MINOR_TICK_MARKS_COLOR, Ring, ring, MinorTickMarksColor, minorTickMarksColor);
}
});
auto iter = _propertyInfos.find(propertyName);
@ -3199,6 +3278,13 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy
APPEND_ENTITY_PROPERTY(PROP_MAJOR_GRID_EVERY, properties.getMajorGridEvery());
APPEND_ENTITY_PROPERTY(PROP_MINOR_GRID_EVERY, properties.getMinorGridEvery());
}
if (properties.getType() == EntityTypes::Gizmo) {
APPEND_ENTITY_PROPERTY(PROP_GIZMO_TYPE, (uint32_t)properties.getGizmoType());
_staticRing.setProperties(properties);
_staticRing.appendToEditPacket(packetData, requestedProperties, propertyFlags,
propertiesDidntFit, propertyCount, appendState);
}
}
if (propertyCount > 0) {
@ -3648,6 +3734,11 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_MINOR_GRID_EVERY, float, setMinorGridEvery);
}
if (properties.getType() == EntityTypes::Gizmo) {
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_GIZMO_TYPE, GizmoType, setGizmoType);
properties.getRing().decodeFromEditPacket(propertyFlags, dataAt, processedBytes);
}
return valid;
}
@ -3981,6 +4072,10 @@ void EntityItemProperties::markAllChanged() {
_followCameraChanged = true;
_majorGridEveryChanged = true;
_minorGridEveryChanged = true;
// Gizmo
_gizmoTypeChanged = true;
_ring.markAllChanged();
}
// The minimum bounding box for the entity.
@ -4654,6 +4749,12 @@ QList<QString> EntityItemProperties::listChangedProperties() {
out += "minorGridEvery";
}
// Gizmo
if (gizmoTypeChanged()) {
out += "gizmoType";
}
getRing().listChangedProperties(out);
return out;
}

View file

@ -44,6 +44,7 @@
#include "LineEntityItem.h"
#include "PolyVoxEntityItem.h"
#include "GridEntityItem.h"
#include "GizmoEntityItem.h"
#include "LightEntityItem.h"
#include "ZoneEntityItem.h"
@ -52,12 +53,14 @@
#include "HazePropertyGroup.h"
#include "BloomPropertyGroup.h"
#include "PulsePropertyGroup.h"
#include "RingGizmoPropertyGroup.h"
#include "MaterialMappingMode.h"
#include "BillboardMode.h"
#include "RenderLayer.h"
#include "PrimitiveMode.h"
#include "WebInputMode.h"
#include "GizmoType.h"
const quint64 UNKNOWN_CREATED_TIME = 0;
@ -101,6 +104,7 @@ class EntityItemProperties {
friend class PolyLineEntityItem;
friend class PolyVoxEntityItem;
friend class GridEntityItem;
friend class GizmoEntityItem;
friend class LightEntityItem;
friend class ZoneEntityItem;
friend class MaterialEntityItem;
@ -234,8 +238,8 @@ public:
// Common
DEFINE_PROPERTY_REF_ENUM(PROP_SHAPE_TYPE, ShapeType, shapeType, ShapeType, SHAPE_TYPE_NONE);
DEFINE_PROPERTY_REF(PROP_COMPOUND_SHAPE_URL, CompoundShapeURL, compoundShapeURL, QString, "");
DEFINE_PROPERTY_REF(PROP_COLOR, Color, color, u8vec3Color, particle::DEFAULT_COLOR);
DEFINE_PROPERTY(PROP_ALPHA, Alpha, alpha, float, particle::DEFAULT_ALPHA);
DEFINE_PROPERTY_REF(PROP_COLOR, Color, color, u8vec3Color, ENTITY_ITEM_DEFAULT_COLOR);
DEFINE_PROPERTY(PROP_ALPHA, Alpha, alpha, float, ENTITY_ITEM_DEFAULT_ALPHA);
DEFINE_PROPERTY_GROUP(Pulse, pulse, PulsePropertyGroup);
DEFINE_PROPERTY_REF(PROP_TEXTURES, Textures, textures, QString, "");
@ -372,6 +376,10 @@ public:
DEFINE_PROPERTY(PROP_MAJOR_GRID_EVERY, MajorGridEvery, majorGridEvery, uint32_t, GridEntityItem::DEFAULT_MAJOR_GRID_EVERY);
DEFINE_PROPERTY(PROP_MINOR_GRID_EVERY, MinorGridEvery, minorGridEvery, float, GridEntityItem::DEFAULT_MINOR_GRID_EVERY);
// Gizmo
DEFINE_PROPERTY_REF_ENUM(PROP_GIZMO_TYPE, GizmoType, gizmoType, GizmoType, GizmoType::RING);
DEFINE_PROPERTY_GROUP(Ring, ring, RingGizmoPropertyGroup);
static QString getComponentModeAsString(uint32_t mode);
std::array<ComponentPair, COMPONENT_MODE_ITEM_COUNT>::const_iterator findComponent(const QString& mode);

View file

@ -43,6 +43,7 @@ const quint32 ENTITY_ITEM_DEFAULT_ENTITY_INSTANCE_NUMBER = 0;
const QString ENTITY_ITEM_DEFAULT_CERTIFICATE_ID = QString("");
const quint32 ENTITY_ITEM_DEFAULT_STATIC_CERTIFICATE_VERSION = 0;
const glm::u8vec3 ENTITY_ITEM_DEFAULT_COLOR = { 255, 255, 255 };
const float ENTITY_ITEM_DEFAULT_ALPHA = 1.0f;
const bool ENTITY_ITEM_DEFAULT_VISIBLE = true;
const bool ENTITY_ITEM_DEFAULT_VISIBLE_IN_SECONDARY_CAMERA = true;

View file

@ -331,6 +331,28 @@ enum EntityPropertyList {
PROP_MAJOR_GRID_EVERY = PROP_DERIVED_1,
PROP_MINOR_GRID_EVERY = PROP_DERIVED_2,
// Gizmo
PROP_GIZMO_TYPE = PROP_DERIVED_0,
// Ring
PROP_START_ANGLE = PROP_DERIVED_1,
PROP_END_ANGLE = PROP_DERIVED_2,
PROP_INNER_RADIUS = PROP_DERIVED_3,
PROP_INNER_START_COLOR = PROP_DERIVED_4,
PROP_INNER_END_COLOR = PROP_DERIVED_5,
PROP_OUTER_START_COLOR = PROP_DERIVED_6,
PROP_OUTER_END_COLOR = PROP_DERIVED_7,
PROP_INNER_START_ALPHA = PROP_DERIVED_8,
PROP_INNER_END_ALPHA = PROP_DERIVED_9,
PROP_OUTER_START_ALPHA = PROP_DERIVED_10,
PROP_OUTER_END_ALPHA = PROP_DERIVED_11,
PROP_HAS_TICK_MARKS = PROP_DERIVED_12,
PROP_MAJOR_TICK_MARKS_ANGLE = PROP_DERIVED_13,
PROP_MINOR_TICK_MARKS_ANGLE = PROP_DERIVED_14,
PROP_MAJOR_TICK_MARKS_LENGTH = PROP_DERIVED_15,
PROP_MINOR_TICK_MARKS_LENGTH = PROP_DERIVED_16,
PROP_MAJOR_TICK_MARKS_COLOR = PROP_DERIVED_17,
PROP_MINOR_TICK_MARKS_COLOR = PROP_DERIVED_18,
// WARNING!!! DO NOT ADD PROPS_xxx here unless you really really meant to.... Add them UP above
};

View file

@ -30,6 +30,7 @@
#include "PolyLineEntityItem.h"
#include "PolyVoxEntityItem.h"
#include "GridEntityItem.h"
#include "GizmoEntityItem.h"
#include "LightEntityItem.h"
#include "ZoneEntityItem.h"
#include "MaterialEntityItem.h"
@ -54,6 +55,7 @@ REGISTER_ENTITY_TYPE(Line)
REGISTER_ENTITY_TYPE(PolyLine)
REGISTER_ENTITY_TYPE(PolyVox)
REGISTER_ENTITY_TYPE(Grid)
REGISTER_ENTITY_TYPE(Gizmo)
REGISTER_ENTITY_TYPE(Light)
REGISTER_ENTITY_TYPE(Zone)
REGISTER_ENTITY_TYPE(Material)

View file

@ -78,6 +78,8 @@ public:
* <td>{@link Entities.EntityProperties-PolyVox|EntityProperties-PolyVox}</td></tr>
* <tr><td><code>"Grid"</code></td><td>A grid of lines in a plane.</td>
* <td>{@link Entities.EntityProperties-Grid|EntityProperties-Grid}</td></tr>
* <tr><td><code>"Gizmo"</code></td><td>An entity with various UI-related properties.</td>
* <td>{@link Entities.EntityProperties-Gizmo|EntityProperties-Gizmo}</td></tr>
* <tr><td><code>"Light"</code></td><td>A local lighting effect.</td>
* <td>{@link Entities.EntityProperties-Light|EntityProperties-Light}</td></tr>
* <tr><td><code>"Zone"</code></td><td>A volume of lighting effects and avatar permissions.</td>
@ -103,6 +105,7 @@ public:
PolyLine,
PolyVox,
Grid,
Gizmo,
Light,
Zone,
Material,

View file

@ -0,0 +1,200 @@
//
// Created by Sam Gondelman on 1/22/19
// Copyright 2019 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "GizmoEntityItem.h"
#include "EntityItemProperties.h"
EntityItemPointer GizmoEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) {
Pointer entity(new GizmoEntityItem(entityID), [](EntityItem* ptr) { ptr->deleteLater(); });
entity->setProperties(properties);
return entity;
}
// our non-pure virtual subclass for now...
GizmoEntityItem::GizmoEntityItem(const EntityItemID& entityItemID) : EntityItem(entityItemID) {
_type = EntityTypes::Gizmo;
}
void GizmoEntityItem::setUnscaledDimensions(const glm::vec3& value) {
// NOTE: Gizmo Entities always have a "height" of 1mm.
EntityItem::setUnscaledDimensions(glm::vec3(value.x, ENTITY_ITEM_MIN_DIMENSION, value.z));
}
EntityItemProperties GizmoEntityItem::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(gizmoType, getGizmoType);
withReadLock([&] {
_ringProperties.getProperties(properties);
});
return properties;
}
bool GizmoEntityItem::setProperties(const EntityItemProperties& properties) {
bool somethingChanged = EntityItem::setProperties(properties); // set the properties in our base class
SET_ENTITY_PROPERTY_FROM_PROPERTIES(gizmoType, setGizmoType);
withWriteLock([&] {
bool ringPropertiesChanged = _ringProperties.setProperties(properties);
somethingChanged |= ringPropertiesChanged;
});
if (somethingChanged) {
bool wantDebug = false;
if (wantDebug) {
uint64_t now = usecTimestampNow();
int elapsed = now - getLastEdited();
qCDebug(entities) << "GizmoEntityItem::setProperties() AFTER update... edited AGO=" << elapsed <<
"now=" << now << " getLastEdited()=" << getLastEdited();
}
setLastEdited(properties.getLastEdited());
}
return somethingChanged;
}
int GizmoEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead,
ReadBitstreamToTreeParams& args,
EntityPropertyFlags& propertyFlags, bool overwriteLocalData,
bool& somethingChanged) {
int bytesRead = 0;
const unsigned char* dataAt = data;
READ_ENTITY_PROPERTY(PROP_GIZMO_TYPE, GizmoType, setGizmoType);
withWriteLock([&] {
int bytesFromRing = _ringProperties.readEntitySubclassDataFromBuffer(dataAt, (bytesLeftToRead - bytesRead), args,
propertyFlags, overwriteLocalData,
somethingChanged);
bytesRead += bytesFromRing;
dataAt += bytesFromRing;
});
return bytesRead;
}
EntityPropertyFlags GizmoEntityItem::getEntityProperties(EncodeBitstreamParams& params) const {
EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params);
requestedProperties += PROP_GIZMO_TYPE;
requestedProperties += _ringProperties.getEntityProperties(params);
return requestedProperties;
}
void GizmoEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params,
EntityTreeElementExtraEncodeDataPointer entityTreeElementExtraEncodeData,
EntityPropertyFlags& requestedProperties,
EntityPropertyFlags& propertyFlags,
EntityPropertyFlags& propertiesDidntFit,
int& propertyCount,
OctreeElement::AppendState& appendState) const {
bool successPropertyFits = true;
APPEND_ENTITY_PROPERTY(PROP_GIZMO_TYPE, (uint32_t)getGizmoType());
withReadLock([&] {
_ringProperties.appendSubclassData(packetData, params, entityTreeElementExtraEncodeData, requestedProperties,
propertyFlags, propertiesDidntFit, propertyCount, appendState);
});
}
bool GizmoEntityItem::supportsDetailedIntersection() const {
return _gizmoType == GizmoType::RING;
}
#include <qmath.h>
bool GizmoEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
OctreeElementPointer& element,
float& distance, BoxFace& face, glm::vec3& surfaceNormal,
QVariantMap& extraInfo, bool precisionPicking) const {
glm::vec3 dimensions = getScaledDimensions();
glm::vec2 xyDimensions(dimensions.x, dimensions.z);
glm::quat rotation = glm::angleAxis((float)M_PI_2, Vectors::RIGHT) * getWorldOrientation();
glm::vec3 position = getWorldPosition() + rotation * (dimensions * (ENTITY_ITEM_DEFAULT_REGISTRATION_POINT - getRegistrationPoint()));
if (findRayRectangleIntersection(origin, direction, rotation, position, xyDimensions, distance)) {
glm::vec3 hitPosition = origin + (distance * direction);
glm::vec3 localHitPosition = glm::inverse(rotation) * (hitPosition - getWorldPosition());
localHitPosition.x /= xyDimensions.x;
localHitPosition.y /= xyDimensions.y;
float distanceToHit = glm::length(localHitPosition);
if (0.5f * _ringProperties.getInnerRadius() <= distanceToHit && distanceToHit <= 0.5f) {
glm::vec3 forward = rotation * Vectors::FRONT;
if (glm::dot(forward, direction) > 0.0f) {
face = MAX_Z_FACE;
surfaceNormal = -forward;
} else {
face = MIN_Z_FACE;
surfaceNormal = forward;
}
return true;
}
}
return false;
}
bool GizmoEntityItem::findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration,
OctreeElementPointer& element, float& parabolicDistance,
BoxFace& face, glm::vec3& surfaceNormal,
QVariantMap& extraInfo, bool precisionPicking) const {
//// Scale the dimensions by the diameter
glm::vec3 dimensions = getScaledDimensions();
glm::vec2 xyDimensions(dimensions.x, dimensions.z);
glm::quat rotation = glm::angleAxis((float)M_PI_2, Vectors::RIGHT) * getWorldOrientation();
glm::vec3 position = getWorldPosition();
glm::quat inverseRot = glm::inverse(rotation);
glm::vec3 localOrigin = inverseRot * (origin - position);
glm::vec3 localVelocity = inverseRot * velocity;
glm::vec3 localAcceleration = inverseRot * acceleration;
if (findParabolaRectangleIntersection(localOrigin, localVelocity, localAcceleration, xyDimensions, parabolicDistance)) {
glm::vec3 localHitPosition = localOrigin + localVelocity * parabolicDistance + 0.5f * localAcceleration * parabolicDistance * parabolicDistance;
localHitPosition.x /= xyDimensions.x;
localHitPosition.y /= xyDimensions.y;
float distanceToHit = glm::length(localHitPosition);
if (0.5f * _ringProperties.getInnerRadius() <= distanceToHit && distanceToHit <= 0.5f) {
float localIntersectionVelocityZ = localVelocity.z + localAcceleration.z * parabolicDistance;
glm::vec3 forward = rotation * Vectors::FRONT;
if (localIntersectionVelocityZ > 0.0f) {
face = MIN_Z_FACE;
surfaceNormal = forward;
} else {
face = MAX_Z_FACE;
surfaceNormal = -forward;
}
return true;
}
}
return false;
}
void GizmoEntityItem::setGizmoType(GizmoType value) {
withWriteLock([&] {
_gizmoType = value;
});
}
GizmoType GizmoEntityItem::getGizmoType() const {
return resultWithReadLock<GizmoType>([&] {
return _gizmoType;
});
}
RingGizmoPropertyGroup GizmoEntityItem::getRingProperties() const {
return resultWithReadLock<RingGizmoPropertyGroup>([&] {
return _ringProperties;
});
}

View file

@ -0,0 +1,67 @@
//
// Created by Sam Gondelman on 1/22/19
// Copyright 2019 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_GizmoEntityItem_h
#define hifi_GizmoEntityItem_h
#include "EntityItem.h"
#include "RingGizmoPropertyGroup.h"
class GizmoEntityItem : public EntityItem {
using Pointer = std::shared_ptr<GizmoEntityItem>;
public:
static EntityItemPointer factory(const EntityItemID& entityID, const EntityItemProperties& properties);
GizmoEntityItem(const EntityItemID& entityItemID);
ALLOW_INSTANTIATION // This class can be instantiated
virtual void setUnscaledDimensions(const glm::vec3& value) override;
// methods for getting/setting all properties of an entity
EntityItemProperties getProperties(const EntityPropertyFlags& desiredProperties, bool allowEmptyDesiredProperties) const override;
bool setProperties(const EntityItemProperties& properties) override;
EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const override;
void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params,
EntityTreeElementExtraEncodeDataPointer entityTreeElementExtraEncodeData,
EntityPropertyFlags& requestedProperties,
EntityPropertyFlags& propertyFlags,
EntityPropertyFlags& propertiesDidntFit,
int& propertyCount,
OctreeElement::AppendState& appendState) const override;
int readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead,
ReadBitstreamToTreeParams& args,
EntityPropertyFlags& propertyFlags, bool overwriteLocalData,
bool& somethingChanged) override;
bool supportsDetailedIntersection() const override;
bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
OctreeElementPointer& element, float& distance,
BoxFace& face, glm::vec3& surfaceNormal,
QVariantMap& extraInfo, bool precisionPicking) const override;
bool findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity,
const glm::vec3& acceleration, OctreeElementPointer& element, float& parabolicDistance,
BoxFace& face, glm::vec3& surfaceNormal,
QVariantMap& extraInfo, bool precisionPicking) const override;
GizmoType getGizmoType() const;
void setGizmoType(GizmoType value);
RingGizmoPropertyGroup getRingProperties() const;
protected:
GizmoType _gizmoType;
RingGizmoPropertyGroup _ringProperties;
};
#endif // hifi_GizmoEntityItem_h

View file

@ -21,10 +21,10 @@
namespace particle {
static const float SCRIPT_MAXIMUM_PI = 3.1416f; // Round up so that reasonable property values work
static const float UNINITIALIZED = NAN;
static const u8vec3 DEFAULT_COLOR = { 255, 255, 255 };
static const u8vec3 DEFAULT_COLOR = ENTITY_ITEM_DEFAULT_COLOR;
static const vec3 DEFAULT_COLOR_UNINITIALIZED = { UNINITIALIZED, UNINITIALIZED, UNINITIALIZED };
static const u8vec3 DEFAULT_COLOR_SPREAD = { 0, 0, 0 };
static const float DEFAULT_ALPHA = 1.0f;
static const float DEFAULT_ALPHA = ENTITY_ITEM_DEFAULT_ALPHA;
static const float DEFAULT_ALPHA_SPREAD = 0.0f;
static const float DEFAULT_ALPHA_START = UNINITIALIZED;
static const float DEFAULT_ALPHA_FINISH = UNINITIALIZED;

View file

@ -0,0 +1,489 @@
//
// Created by Sam Gondelman on 1/22/19
// Copyright 2019 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "RingGizmoPropertyGroup.h"
#include <OctreePacketData.h>
#include "EntityItemProperties.h"
#include "EntityItemPropertiesMacros.h"
const float RingGizmoPropertyGroup::MIN_ANGLE = 0.0f;
const float RingGizmoPropertyGroup::MAX_ANGLE = 360.0f;
const float RingGizmoPropertyGroup::MIN_ALPHA = 0.0f;
const float RingGizmoPropertyGroup::MAX_ALPHA = 1.0f;
const float RingGizmoPropertyGroup::MIN_RADIUS = 0.0f;
const float RingGizmoPropertyGroup::MAX_RADIUS = 0.5f;
void RingGizmoPropertyGroup::copyToScriptValue(const EntityPropertyFlags& desiredProperties, QScriptValue& properties,
QScriptEngine* engine, bool skipDefaults,
EntityItemProperties& defaultEntityProperties) const {
COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_START_ANGLE, Ring, ring, StartAngle, startAngle);
COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_END_ANGLE, Ring, ring, EndAngle, endAngle);
COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_INNER_RADIUS, Ring, ring, InnerRadius, innerRadius);
COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE_TYPED(PROP_INNER_START_COLOR, Ring, ring, InnerStartColor, innerStartColor, u8vec3Color);
COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE_TYPED(PROP_INNER_END_COLOR, Ring, ring, InnerEndColor, innerEndColor, u8vec3Color);
COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE_TYPED(PROP_OUTER_START_COLOR, Ring, ring, OuterStartColor, outerStartColor, u8vec3Color);
COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE_TYPED(PROP_OUTER_END_COLOR, Ring, ring, OuterEndColor, outerEndColor, u8vec3Color);
COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_INNER_START_ALPHA, Ring, ring, InnerStartAlpha, innerStartAlpha);
COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_INNER_END_ALPHA, Ring, ring, InnerEndAlpha, innerEndAlpha);
COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_OUTER_START_ALPHA, Ring, ring, OuterStartAlpha, outerStartAlpha);
COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_OUTER_END_ALPHA, Ring, ring, OuterEndAlpha, outerEndAlpha);
COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_HAS_TICK_MARKS, Ring, ring, HasTickMarks, hasTickMarks);
COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_MAJOR_TICK_MARKS_ANGLE, Ring, ring, MajorTickMarksAngle, majorTickMarksAngle);
COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_MINOR_TICK_MARKS_ANGLE, Ring, ring, MinorTickMarksAngle, minorTickMarksAngle);
COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_MAJOR_TICK_MARKS_LENGTH, Ring, ring, MajorTickMarksLength, majorTickMarksLength);
COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_MINOR_TICK_MARKS_LENGTH, Ring, ring, MinorTickMarksLength, minorTickMarksLength);
COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE_TYPED(PROP_MAJOR_TICK_MARKS_COLOR, Ring, ring, MajorTickMarksColor, majorTickMarksColor, u8vec3Color);
COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE_TYPED(PROP_MINOR_TICK_MARKS_COLOR, Ring, ring, MinorTickMarksColor, minorTickMarksColor, u8vec3Color);
}
void RingGizmoPropertyGroup::copyFromScriptValue(const QScriptValue& object, bool& _defaultSettings) {
COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(ring, startAngle, float, setStartAngle);
COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(ring, endAngle, float, setEndAngle);
COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(ring, innerRadius, float, setInnerRadius);
COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(ring, innerStartColor, u8vec3Color, setInnerStartColor);
COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(ring, innerEndColor, u8vec3Color, setInnerEndColor);
COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(ring, outerStartColor, u8vec3Color, setOuterStartColor);
COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(ring, outerEndColor, u8vec3Color, setOuterEndColor);
COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(ring, innerStartAlpha, float, setInnerStartAlpha);
COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(ring, innerEndAlpha, float, setInnerEndAlpha);
COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(ring, outerStartAlpha, float, setOuterStartAlpha);
COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(ring, outerEndAlpha, float, setOuterEndAlpha);
COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(ring, hasTickMarks, bool, setHasTickMarks);
COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(ring, majorTickMarksAngle, float, setMajorTickMarksAngle);
COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(ring, minorTickMarksAngle, float, setMinorTickMarksAngle);
COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(ring, majorTickMarksLength, float, setMajorTickMarksLength);
COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(ring, minorTickMarksLength, float, setMinorTickMarksLength);
COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(ring, majorTickMarksColor, u8vec3Color, setMajorTickMarksColor);
COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(ring, minorTickMarksColor, u8vec3Color, setMinorTickMarksColor);
}
void RingGizmoPropertyGroup::merge(const RingGizmoPropertyGroup& other) {
COPY_PROPERTY_IF_CHANGED(startAngle);
COPY_PROPERTY_IF_CHANGED(endAngle);
COPY_PROPERTY_IF_CHANGED(innerRadius);
COPY_PROPERTY_IF_CHANGED(innerStartColor);
COPY_PROPERTY_IF_CHANGED(innerEndColor);
COPY_PROPERTY_IF_CHANGED(outerStartColor);
COPY_PROPERTY_IF_CHANGED(outerEndColor);
COPY_PROPERTY_IF_CHANGED(innerStartAlpha);
COPY_PROPERTY_IF_CHANGED(innerEndAlpha);
COPY_PROPERTY_IF_CHANGED(outerStartAlpha);
COPY_PROPERTY_IF_CHANGED(outerEndAlpha);
COPY_PROPERTY_IF_CHANGED(hasTickMarks);
COPY_PROPERTY_IF_CHANGED(majorTickMarksAngle);
COPY_PROPERTY_IF_CHANGED(minorTickMarksAngle);
COPY_PROPERTY_IF_CHANGED(majorTickMarksLength);
COPY_PROPERTY_IF_CHANGED(minorTickMarksLength);
COPY_PROPERTY_IF_CHANGED(majorTickMarksColor);
COPY_PROPERTY_IF_CHANGED(minorTickMarksColor);
}
void RingGizmoPropertyGroup::debugDump() const {
qCDebug(entities) << " RingGizmoPropertyGroup: ---------------------------------------------";
qCDebug(entities) << " _startAngle:" << _startAngle;
qCDebug(entities) << " _endAngle:" << _endAngle;
qCDebug(entities) << " _innerRadius:" << _innerRadius;
qCDebug(entities) << " _innerStartColor:" << _innerStartColor;
qCDebug(entities) << " _innerEndColor:" << _innerEndColor;
qCDebug(entities) << " _outerStartColor:" << _outerStartColor;
qCDebug(entities) << " _outerEndColor:" << _outerEndColor;
qCDebug(entities) << " _innerStartAlpha:" << _innerStartAlpha;
qCDebug(entities) << " _innerEndAlpha:" << _innerEndAlpha;
qCDebug(entities) << " _outerStartAlpha:" << _outerStartAlpha;
qCDebug(entities) << " _outerEndAlpha:" << _outerEndAlpha;
qCDebug(entities) << " _hasTickMarks:" << _hasTickMarks;
qCDebug(entities) << " _majorTickMarksAngle:" << _majorTickMarksAngle;
qCDebug(entities) << " _minorTickMarksAngle:" << _minorTickMarksAngle;
qCDebug(entities) << " _majorTickMarksLength:" << _majorTickMarksLength;
qCDebug(entities) << " _minorTickMarksLength:" << _minorTickMarksLength;
qCDebug(entities) << " _majorTickMarksColor:" << _majorTickMarksColor;
qCDebug(entities) << " _minorTickMarksColor:" << _minorTickMarksColor;
}
void RingGizmoPropertyGroup::listChangedProperties(QList<QString>& out) {
if (startAngleChanged()) {
out << "ring-startAngle";
}
if (endAngleChanged()) {
out << "ring-endAngle";
}
if (innerRadiusChanged()) {
out << "ring-innerRadius";
}
if (innerStartColorChanged()) {
out << "ring-innerStartColor";
}
if (innerEndColorChanged()) {
out << "ring-innerEndColor";
}
if (outerStartColorChanged()) {
out << "ring-outerStartColor";
}
if (outerEndColorChanged()) {
out << "ring-outerEndColor";
}
if (innerStartAlphaChanged()) {
out << "ring-innerStartAlpha";
}
if (innerEndAlphaChanged()) {
out << "ring-innerEndAlpha";
}
if (outerStartAlphaChanged()) {
out << "ring-outerStartAlpha";
}
if (outerEndAlphaChanged()) {
out << "ring-outerEndAlpha";
}
if (hasTickMarksChanged()) {
out << "ring-hasTickMarks";
}
if (majorTickMarksAngleChanged()) {
out << "ring-majorTickMarksAngle";
}
if (minorTickMarksAngleChanged()) {
out << "ring-minorTickMarksAngle";
}
if (majorTickMarksLengthChanged()) {
out << "ring-majorTickMarksLength";
}
if (minorTickMarksLengthChanged()) {
out << "ring-minorTickMarksLength";
}
if (majorTickMarksColorChanged()) {
out << "ring-majorTickMarksColor";
}
if (minorTickMarksColorChanged()) {
out << "ring-minorTickMarksColor";
}
}
bool RingGizmoPropertyGroup::appendToEditPacket(OctreePacketData* packetData,
EntityPropertyFlags& requestedProperties,
EntityPropertyFlags& propertyFlags,
EntityPropertyFlags& propertiesDidntFit,
int& propertyCount,
OctreeElement::AppendState& appendState) const {
bool successPropertyFits = true;
APPEND_ENTITY_PROPERTY(PROP_START_ANGLE, getStartAngle());
APPEND_ENTITY_PROPERTY(PROP_END_ANGLE, getEndAngle());
APPEND_ENTITY_PROPERTY(PROP_INNER_RADIUS, getInnerRadius());
APPEND_ENTITY_PROPERTY(PROP_INNER_START_COLOR, getInnerStartColor());
APPEND_ENTITY_PROPERTY(PROP_INNER_END_COLOR, getInnerEndColor());
APPEND_ENTITY_PROPERTY(PROP_OUTER_START_COLOR, getOuterStartColor());
APPEND_ENTITY_PROPERTY(PROP_OUTER_END_COLOR, getOuterEndColor());
APPEND_ENTITY_PROPERTY(PROP_INNER_START_ALPHA, getInnerStartAlpha());
APPEND_ENTITY_PROPERTY(PROP_INNER_END_ALPHA, getInnerEndAlpha());
APPEND_ENTITY_PROPERTY(PROP_OUTER_START_ALPHA, getOuterStartAlpha());
APPEND_ENTITY_PROPERTY(PROP_OUTER_END_ALPHA, getOuterEndAlpha());
APPEND_ENTITY_PROPERTY(PROP_HAS_TICK_MARKS, getHasTickMarks());
APPEND_ENTITY_PROPERTY(PROP_MAJOR_TICK_MARKS_ANGLE, getMajorTickMarksAngle());
APPEND_ENTITY_PROPERTY(PROP_MINOR_TICK_MARKS_ANGLE, getMinorTickMarksAngle());
APPEND_ENTITY_PROPERTY(PROP_MAJOR_TICK_MARKS_LENGTH, getMajorTickMarksLength());
APPEND_ENTITY_PROPERTY(PROP_MINOR_TICK_MARKS_LENGTH, getMinorTickMarksLength());
APPEND_ENTITY_PROPERTY(PROP_MAJOR_TICK_MARKS_COLOR, getMajorTickMarksColor());
APPEND_ENTITY_PROPERTY(PROP_MINOR_TICK_MARKS_COLOR, getMinorTickMarksColor());
return true;
}
bool RingGizmoPropertyGroup::decodeFromEditPacket(EntityPropertyFlags& propertyFlags,
const unsigned char*& dataAt , int& processedBytes) {
int bytesRead = 0;
bool overwriteLocalData = true;
bool somethingChanged = false;
READ_ENTITY_PROPERTY(PROP_START_ANGLE, float, setStartAngle);
READ_ENTITY_PROPERTY(PROP_END_ANGLE, float, setEndAngle);
READ_ENTITY_PROPERTY(PROP_INNER_RADIUS, float, setInnerRadius);
READ_ENTITY_PROPERTY(PROP_INNER_START_COLOR, u8vec3Color, setInnerStartColor);
READ_ENTITY_PROPERTY(PROP_INNER_END_COLOR, u8vec3Color, setInnerEndColor);
READ_ENTITY_PROPERTY(PROP_OUTER_START_COLOR, u8vec3Color, setOuterStartColor);
READ_ENTITY_PROPERTY(PROP_OUTER_END_COLOR, u8vec3Color, setOuterEndColor);
READ_ENTITY_PROPERTY(PROP_INNER_START_ALPHA, float, setInnerStartAlpha);
READ_ENTITY_PROPERTY(PROP_INNER_END_ALPHA, float, setInnerEndAlpha);
READ_ENTITY_PROPERTY(PROP_OUTER_START_ALPHA, float, setOuterStartAlpha);
READ_ENTITY_PROPERTY(PROP_OUTER_END_ALPHA, float, setOuterEndAlpha);
READ_ENTITY_PROPERTY(PROP_HAS_TICK_MARKS, bool, setHasTickMarks);
READ_ENTITY_PROPERTY(PROP_MAJOR_TICK_MARKS_ANGLE, float, setMajorTickMarksAngle);
READ_ENTITY_PROPERTY(PROP_MINOR_TICK_MARKS_ANGLE, float, setMinorTickMarksAngle);
READ_ENTITY_PROPERTY(PROP_MAJOR_TICK_MARKS_LENGTH, float, setMajorTickMarksLength);
READ_ENTITY_PROPERTY(PROP_MINOR_TICK_MARKS_LENGTH, float, setMinorTickMarksLength);
READ_ENTITY_PROPERTY(PROP_MAJOR_TICK_MARKS_COLOR, u8vec3Color, setMajorTickMarksColor);
READ_ENTITY_PROPERTY(PROP_MINOR_TICK_MARKS_COLOR, u8vec3Color, setMinorTickMarksColor);
DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_START_ANGLE, StartAngle);
DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_END_ANGLE, EndAngle);
DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_INNER_RADIUS, InnerRadius);
DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_INNER_START_COLOR, InnerStartColor);
DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_INNER_END_COLOR, InnerEndColor);
DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_OUTER_START_COLOR, OuterStartColor);
DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_OUTER_END_COLOR, OuterEndColor);
DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_INNER_START_ALPHA, InnerStartAlpha);
DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_INNER_END_ALPHA, InnerEndAlpha);
DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_OUTER_START_ALPHA, OuterStartAlpha);
DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_OUTER_END_ALPHA, OuterEndAlpha);
DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_HAS_TICK_MARKS, HasTickMarks);
DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_MAJOR_TICK_MARKS_ANGLE, MajorTickMarksAngle);
DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_MINOR_TICK_MARKS_ANGLE, MinorTickMarksAngle);
DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_MAJOR_TICK_MARKS_LENGTH, MajorTickMarksLength);
DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_MINOR_TICK_MARKS_LENGTH, MinorTickMarksLength);
DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_MAJOR_TICK_MARKS_COLOR, MajorTickMarksColor);
DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_MINOR_TICK_MARKS_COLOR, MinorTickMarksColor);
processedBytes += bytesRead;
Q_UNUSED(somethingChanged);
return true;
}
void RingGizmoPropertyGroup::markAllChanged() {
_startAngleChanged = true;
_endAngleChanged = true;
_innerRadiusChanged = true;
_innerStartColorChanged = true;
_innerEndColorChanged = true;
_outerStartColorChanged = true;
_outerEndColorChanged = true;
_innerStartAlphaChanged = true;
_innerEndAlphaChanged = true;
_outerStartAlphaChanged = true;
_outerEndAlphaChanged = true;
_hasTickMarksChanged = true;
_majorTickMarksAngleChanged = true;
_minorTickMarksAngleChanged = true;
_majorTickMarksLengthChanged = true;
_minorTickMarksLengthChanged = true;
_majorTickMarksColorChanged = true;
_minorTickMarksColorChanged = true;
}
EntityPropertyFlags RingGizmoPropertyGroup::getChangedProperties() const {
EntityPropertyFlags changedProperties;
CHECK_PROPERTY_CHANGE(PROP_START_ANGLE, startAngle);
CHECK_PROPERTY_CHANGE(PROP_END_ANGLE, endAngle);
CHECK_PROPERTY_CHANGE(PROP_INNER_RADIUS, innerRadius);
CHECK_PROPERTY_CHANGE(PROP_INNER_START_COLOR, innerStartColor);
CHECK_PROPERTY_CHANGE(PROP_INNER_END_COLOR, innerEndColor);
CHECK_PROPERTY_CHANGE(PROP_OUTER_START_COLOR, outerStartColor);
CHECK_PROPERTY_CHANGE(PROP_OUTER_END_COLOR, outerEndColor);
CHECK_PROPERTY_CHANGE(PROP_INNER_START_ALPHA, innerStartAlpha);
CHECK_PROPERTY_CHANGE(PROP_INNER_END_ALPHA, innerEndAlpha);
CHECK_PROPERTY_CHANGE(PROP_OUTER_START_ALPHA, outerStartAlpha);
CHECK_PROPERTY_CHANGE(PROP_OUTER_END_ALPHA, outerEndAlpha);
CHECK_PROPERTY_CHANGE(PROP_HAS_TICK_MARKS, hasTickMarks);
CHECK_PROPERTY_CHANGE(PROP_MAJOR_TICK_MARKS_ANGLE, majorTickMarksAngle);
CHECK_PROPERTY_CHANGE(PROP_MINOR_TICK_MARKS_ANGLE, minorTickMarksAngle);
CHECK_PROPERTY_CHANGE(PROP_MAJOR_TICK_MARKS_LENGTH, majorTickMarksLength);
CHECK_PROPERTY_CHANGE(PROP_MINOR_TICK_MARKS_LENGTH, minorTickMarksLength);
CHECK_PROPERTY_CHANGE(PROP_MAJOR_TICK_MARKS_COLOR, majorTickMarksColor);
CHECK_PROPERTY_CHANGE(PROP_MINOR_TICK_MARKS_COLOR, minorTickMarksColor);
return changedProperties;
}
void RingGizmoPropertyGroup::getProperties(EntityItemProperties& properties) const {
COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Ring, StartAngle, getStartAngle);
COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Ring, EndAngle, getEndAngle);
COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Ring, InnerRadius, getInnerRadius);
COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Ring, InnerStartColor, getInnerStartColor);
COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Ring, InnerEndColor, getInnerEndColor);
COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Ring, OuterStartColor, getOuterStartColor);
COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Ring, OuterEndColor, getOuterEndColor);
COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Ring, InnerStartAlpha, getInnerStartAlpha);
COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Ring, InnerEndAlpha, getInnerEndAlpha);
COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Ring, OuterStartAlpha, getOuterStartAlpha);
COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Ring, OuterEndAlpha, getOuterEndAlpha);
COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Ring, HasTickMarks, getHasTickMarks);
COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Ring, MajorTickMarksAngle, getMajorTickMarksAngle);
COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Ring, MinorTickMarksAngle, getMinorTickMarksAngle);
COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Ring, MajorTickMarksLength, getMajorTickMarksLength);
COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Ring, MinorTickMarksLength, getMinorTickMarksLength);
COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Ring, MajorTickMarksColor, getMajorTickMarksColor);
COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Ring, MinorTickMarksColor, getMinorTickMarksColor);
}
bool RingGizmoPropertyGroup::setProperties(const EntityItemProperties& properties) {
bool somethingChanged = false;
SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Ring, StartAngle, startAngle, setStartAngle);
SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Ring, EndAngle, endAngle, setEndAngle);
SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Ring, InnerRadius, innerRadius, setInnerRadius);
SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Ring, InnerStartColor, innerStartColor, setInnerStartColor);
SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Ring, InnerEndColor, innerEndColor, setInnerEndColor);
SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Ring, OuterStartColor, outerStartColor, setOuterStartColor);
SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Ring, OuterEndColor, outerEndColor, setOuterEndColor);
SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Ring, InnerStartAlpha, innerStartAlpha, setInnerStartAlpha);
SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Ring, InnerEndAlpha, innerEndAlpha, setInnerEndAlpha);
SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Ring, OuterStartAlpha, outerStartAlpha, setOuterStartAlpha);
SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Ring, OuterEndAlpha, outerEndAlpha, setOuterEndAlpha);
SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Ring, HasTickMarks, hasTickMarks, setHasTickMarks);
SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Ring, MajorTickMarksAngle, majorTickMarksAngle, setMajorTickMarksAngle);
SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Ring, MinorTickMarksAngle, minorTickMarksAngle, setMinorTickMarksAngle);
SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Ring, MajorTickMarksLength, majorTickMarksLength, setMajorTickMarksLength);
SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Ring, MinorTickMarksLength, minorTickMarksLength, setMinorTickMarksLength);
SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Ring, MajorTickMarksColor, majorTickMarksColor, setMajorTickMarksColor);
SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Ring, MinorTickMarksColor, minorTickMarksColor, setMinorTickMarksColor);
return somethingChanged;
}
EntityPropertyFlags RingGizmoPropertyGroup::getEntityProperties(EncodeBitstreamParams& params) const {
EntityPropertyFlags requestedProperties;
requestedProperties += PROP_START_ANGLE;
requestedProperties += PROP_END_ANGLE;
requestedProperties += PROP_INNER_RADIUS;
requestedProperties += PROP_INNER_START_COLOR;
requestedProperties += PROP_INNER_END_COLOR;
requestedProperties += PROP_OUTER_START_COLOR;
requestedProperties += PROP_OUTER_END_COLOR;
requestedProperties += PROP_INNER_START_ALPHA;
requestedProperties += PROP_INNER_END_ALPHA;
requestedProperties += PROP_OUTER_START_ALPHA;
requestedProperties += PROP_OUTER_END_ALPHA;
requestedProperties += PROP_HAS_TICK_MARKS;
requestedProperties += PROP_MAJOR_TICK_MARKS_ANGLE;
requestedProperties += PROP_MINOR_TICK_MARKS_ANGLE;
requestedProperties += PROP_MAJOR_TICK_MARKS_LENGTH;
requestedProperties += PROP_MINOR_TICK_MARKS_LENGTH;
requestedProperties += PROP_MAJOR_TICK_MARKS_COLOR;
requestedProperties += PROP_MINOR_TICK_MARKS_COLOR;
return requestedProperties;
}
void RingGizmoPropertyGroup::appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params,
EntityTreeElementExtraEncodeDataPointer entityTreeElementExtraEncodeData,
EntityPropertyFlags& requestedProperties,
EntityPropertyFlags& propertyFlags,
EntityPropertyFlags& propertiesDidntFit,
int& propertyCount,
OctreeElement::AppendState& appendState) const {
bool successPropertyFits = true;
APPEND_ENTITY_PROPERTY(PROP_START_ANGLE, getStartAngle());
APPEND_ENTITY_PROPERTY(PROP_END_ANGLE, getEndAngle());
APPEND_ENTITY_PROPERTY(PROP_INNER_RADIUS, getInnerRadius());
APPEND_ENTITY_PROPERTY(PROP_INNER_START_COLOR, getInnerStartColor());
APPEND_ENTITY_PROPERTY(PROP_INNER_END_COLOR, getInnerEndColor());
APPEND_ENTITY_PROPERTY(PROP_OUTER_START_COLOR, getOuterStartColor());
APPEND_ENTITY_PROPERTY(PROP_OUTER_END_COLOR, getOuterEndColor());
APPEND_ENTITY_PROPERTY(PROP_INNER_START_ALPHA, getInnerStartAlpha());
APPEND_ENTITY_PROPERTY(PROP_INNER_END_ALPHA, getInnerEndAlpha());
APPEND_ENTITY_PROPERTY(PROP_OUTER_START_ALPHA, getOuterStartAlpha());
APPEND_ENTITY_PROPERTY(PROP_OUTER_END_ALPHA, getOuterEndAlpha());
APPEND_ENTITY_PROPERTY(PROP_HAS_TICK_MARKS, getHasTickMarks());
APPEND_ENTITY_PROPERTY(PROP_MAJOR_TICK_MARKS_ANGLE, getMajorTickMarksAngle());
APPEND_ENTITY_PROPERTY(PROP_MINOR_TICK_MARKS_ANGLE, getMinorTickMarksAngle());
APPEND_ENTITY_PROPERTY(PROP_MAJOR_TICK_MARKS_LENGTH, getMajorTickMarksLength());
APPEND_ENTITY_PROPERTY(PROP_MINOR_TICK_MARKS_LENGTH, getMinorTickMarksLength());
APPEND_ENTITY_PROPERTY(PROP_MAJOR_TICK_MARKS_COLOR, getMajorTickMarksColor());
APPEND_ENTITY_PROPERTY(PROP_MINOR_TICK_MARKS_COLOR, getMinorTickMarksColor());
}
int RingGizmoPropertyGroup::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead,
ReadBitstreamToTreeParams& args,
EntityPropertyFlags& propertyFlags, bool overwriteLocalData,
bool& somethingChanged) {
int bytesRead = 0;
const unsigned char* dataAt = data;
READ_ENTITY_PROPERTY(PROP_START_ANGLE, float, setStartAngle);
READ_ENTITY_PROPERTY(PROP_END_ANGLE, float, setEndAngle);
READ_ENTITY_PROPERTY(PROP_INNER_RADIUS, float, setInnerRadius);
READ_ENTITY_PROPERTY(PROP_INNER_START_COLOR, u8vec3Color, setInnerStartColor);
READ_ENTITY_PROPERTY(PROP_INNER_END_COLOR, u8vec3Color, setInnerEndColor);
READ_ENTITY_PROPERTY(PROP_OUTER_START_COLOR, u8vec3Color, setOuterStartColor);
READ_ENTITY_PROPERTY(PROP_OUTER_END_COLOR, u8vec3Color, setOuterEndColor);
READ_ENTITY_PROPERTY(PROP_INNER_START_ALPHA, float, setInnerStartAlpha);
READ_ENTITY_PROPERTY(PROP_INNER_END_ALPHA, float, setInnerEndAlpha);
READ_ENTITY_PROPERTY(PROP_OUTER_START_ALPHA, float, setOuterStartAlpha);
READ_ENTITY_PROPERTY(PROP_OUTER_END_ALPHA, float, setOuterEndAlpha);
READ_ENTITY_PROPERTY(PROP_HAS_TICK_MARKS, bool, setHasTickMarks);
READ_ENTITY_PROPERTY(PROP_MAJOR_TICK_MARKS_ANGLE, float, setMajorTickMarksAngle);
READ_ENTITY_PROPERTY(PROP_MINOR_TICK_MARKS_ANGLE, float, setMinorTickMarksAngle);
READ_ENTITY_PROPERTY(PROP_MAJOR_TICK_MARKS_LENGTH, float, setMajorTickMarksLength);
READ_ENTITY_PROPERTY(PROP_MINOR_TICK_MARKS_LENGTH, float, setMinorTickMarksLength);
READ_ENTITY_PROPERTY(PROP_MAJOR_TICK_MARKS_COLOR, u8vec3Color, setMajorTickMarksColor);
READ_ENTITY_PROPERTY(PROP_MINOR_TICK_MARKS_COLOR, u8vec3Color, setMinorTickMarksColor);
return bytesRead;
}
bool RingGizmoPropertyGroup::operator==(const RingGizmoPropertyGroup& a) const {
return (a._startAngle == _startAngle) &&
(a._endAngle == _endAngle) &&
(a._innerRadius == _innerRadius) &&
(a._innerStartColor == _innerStartColor) &&
(a._innerEndColor == _innerEndColor) &&
(a._outerStartColor == _outerStartColor) &&
(a._outerEndColor == _outerEndColor) &&
(a._innerStartAlpha == _innerStartAlpha) &&
(a._innerEndAlpha == _innerEndAlpha) &&
(a._outerStartAlpha == _outerStartAlpha) &&
(a._outerEndAlpha == _outerEndAlpha) &&
(a._hasTickMarks == _hasTickMarks) &&
(a._majorTickMarksAngle == _majorTickMarksAngle) &&
(a._minorTickMarksAngle == _minorTickMarksAngle) &&
(a._majorTickMarksLength == _majorTickMarksLength) &&
(a._minorTickMarksLength == _minorTickMarksLength) &&
(a._majorTickMarksColor == _majorTickMarksColor) &&
(a._minorTickMarksColor == _minorTickMarksColor);
}

View file

@ -0,0 +1,135 @@
//
// Created by Sam Gondelman on 1/22/19
// Copyright 2019 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_RingGizmoPropertyGroup_h
#define hifi_RingGizmoPropertyGroup_h
#include <stdint.h>
#include <QtScript/QScriptEngine>
#include "PropertyGroup.h"
#include "EntityItemPropertiesMacros.h"
#include "EntityItemPropertiesDefaults.h"
class EntityItemProperties;
class EncodeBitstreamParams;
class OctreePacketData;
class ReadBitstreamToTreeParams;
using u8vec3Color = glm::u8vec3;
/**jsdoc
* A RingGizmo is defined by the following properties.
* @typedef {object} Entities.RingGizmo
*
* @property {number} startAngle=0 - The angle at which the ring will start, in degrees.
* @property {number} endAngle=360 - The angle at which the ring will end, in degrees.
* @property {number} innerRadius=0 - The inner radius of the ring as a fraction of the total radius. 0-1.
* @property {Color} innerStartColor - The color at the inner start point of the ring.
* @property {Color} innerEndColor - The color at the inner end point of the ring.
* @property {Color} outerStartColor - The color at the outer start point of the ring.
* @property {Color} outerEndColor - The color at the outer end point of the ring.
* @property {number} innerStartAlpha=1 - The alpha at the inner start point of the ring.
* @property {number} innerEndAlpha=1 - The alpha at the inner end point of the ring.
* @property {number} outerStartAlpha=1 - The alpha at the outer start point of the ring.
* @property {number} outerEndAlpha=1 - The alpha at the outer end point of the ring.
* @property {boolean} hasTickMarks=false - Whether or not to render tick marks.
* @property {number} majorTickMarksAngle - The angle between major tick marks, in degrees.
* @property {number} minorTickMarksAngle - The angle between minor tick marks, in degrees.
* @property {number} majorTickMarksLength - The length of the major tick marks, as a fraction of the radius. A positive value draws tick marks
* outwards from the inner radius; a negative value draws tick marks inwards from the outer radius.
* @property {number} minorTickMarksLength - The length of the minor tick marks, as a fraction of the radius. A positive value draws tick marks
* outwards from the inner radius; a negative value draws tick marks inwards from the outer radius.
* @property {Color} majorTickMarksColor - The color of the major tick marks.
* @property {Color} minorTickMarksColor - The color of the minor tick marks.
*/
class RingGizmoPropertyGroup : public PropertyGroup {
public:
// EntityItemProperty related helpers
virtual void copyToScriptValue(const EntityPropertyFlags& desiredProperties, QScriptValue& properties,
QScriptEngine* engine, bool skipDefaults,
EntityItemProperties& defaultEntityProperties) const override;
virtual void copyFromScriptValue(const QScriptValue& object, bool& _defaultSettings) override;
void merge(const RingGizmoPropertyGroup& other);
virtual void debugDump() const override;
virtual void listChangedProperties(QList<QString>& out) override;
virtual bool appendToEditPacket(OctreePacketData* packetData,
EntityPropertyFlags& requestedProperties,
EntityPropertyFlags& propertyFlags,
EntityPropertyFlags& propertiesDidntFit,
int& propertyCount,
OctreeElement::AppendState& appendState) const override;
virtual bool decodeFromEditPacket(EntityPropertyFlags& propertyFlags,
const unsigned char*& dataAt, int& processedBytes) override;
virtual void markAllChanged() override;
virtual EntityPropertyFlags getChangedProperties() const override;
// EntityItem related helpers
// methods for getting/setting all properties of an entity
virtual void getProperties(EntityItemProperties& propertiesOut) const override;
// returns true if something changed
virtual bool setProperties(const EntityItemProperties& properties) override;
virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const override;
virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params,
EntityTreeElementExtraEncodeDataPointer entityTreeElementExtraEncodeData,
EntityPropertyFlags& requestedProperties,
EntityPropertyFlags& propertyFlags,
EntityPropertyFlags& propertiesDidntFit,
int& propertyCount,
OctreeElement::AppendState& appendState) const override;
virtual int readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead,
ReadBitstreamToTreeParams& args,
EntityPropertyFlags& propertyFlags, bool overwriteLocalData,
bool& somethingChanged) override;
bool operator==(const RingGizmoPropertyGroup& a) const;
bool operator!=(const RingGizmoPropertyGroup& a) const { return !(*this == a); }
static const float MIN_ANGLE;
static const float MAX_ANGLE;
static const float MIN_ALPHA;
static const float MAX_ALPHA;
static const float MIN_RADIUS;
static const float MAX_RADIUS;
DEFINE_PROPERTY(PROP_START_ANGLE, StartAngle, startAngle, float, 0.0f);
DEFINE_PROPERTY(PROP_END_ANGLE, EndAngle, endAngle, float, 360.0f);
DEFINE_PROPERTY(PROP_INNER_RADIUS, InnerRadius, innerRadius, float, 0.0f);
DEFINE_PROPERTY_REF(PROP_INNER_START_COLOR, InnerStartColor, innerStartColor, u8vec3Color, ENTITY_ITEM_DEFAULT_COLOR);
DEFINE_PROPERTY_REF(PROP_INNER_END_COLOR, InnerEndColor, innerEndColor, u8vec3Color, ENTITY_ITEM_DEFAULT_COLOR);
DEFINE_PROPERTY_REF(PROP_OUTER_START_COLOR, OuterStartColor, outerStartColor, u8vec3Color, ENTITY_ITEM_DEFAULT_COLOR);
DEFINE_PROPERTY_REF(PROP_OUTER_END_COLOR, OuterEndColor, outerEndColor, u8vec3Color, ENTITY_ITEM_DEFAULT_COLOR);
DEFINE_PROPERTY(PROP_INNER_START_ALPHA, InnerStartAlpha, innerStartAlpha, float, ENTITY_ITEM_DEFAULT_ALPHA);
DEFINE_PROPERTY(PROP_INNER_END_ALPHA, InnerEndAlpha, innerEndAlpha, float, ENTITY_ITEM_DEFAULT_ALPHA);
DEFINE_PROPERTY(PROP_OUTER_START_ALPHA, OuterStartAlpha, outerStartAlpha, float, ENTITY_ITEM_DEFAULT_ALPHA);
DEFINE_PROPERTY(PROP_OUTER_END_ALPHA, OuterEndAlpha, outerEndAlpha, float, ENTITY_ITEM_DEFAULT_ALPHA);
DEFINE_PROPERTY(PROP_HAS_TICK_MARKS, HasTickMarks, hasTickMarks, bool, false);
DEFINE_PROPERTY(PROP_MAJOR_TICK_MARKS_ANGLE, MajorTickMarksAngle, majorTickMarksAngle, float, 0.0f);
DEFINE_PROPERTY(PROP_MINOR_TICK_MARKS_ANGLE, MinorTickMarksAngle, minorTickMarksAngle, float, 0.0f);
DEFINE_PROPERTY(PROP_MAJOR_TICK_MARKS_LENGTH, MajorTickMarksLength, majorTickMarksLength, float, 0.0f);
DEFINE_PROPERTY(PROP_MINOR_TICK_MARKS_LENGTH, MinorTickMarksLength, minorTickMarksLength, float, 0.0f);
DEFINE_PROPERTY_REF(PROP_MAJOR_TICK_MARKS_COLOR, MajorTickMarksColor, majorTickMarksColor, u8vec3Color, ENTITY_ITEM_DEFAULT_COLOR);
DEFINE_PROPERTY_REF(PROP_MINOR_TICK_MARKS_COLOR, MinorTickMarksColor, minorTickMarksColor, u8vec3Color, ENTITY_ITEM_DEFAULT_COLOR);
};
#endif // hifi_RingGizmoPropertyGroup_h

View file

@ -96,11 +96,11 @@ const uint8_t RECRUIT_SIMULATION_PRIORITY = VOLUNTEER_SIMULATION_PRIORITY + 1;
// When poking objects with scripts an observer will bid at SCRIPT_EDIT priority.
const uint8_t SCRIPT_GRAB_SIMULATION_PRIORITY = 128;
const uint8_t SCRIPT_POKE_SIMULATION_PRIORITY = SCRIPT_GRAB_SIMULATION_PRIORITY - 1;
const uint8_t AVATAR_ENTITY_SIMULATION_PRIORITY = 255;
// PERSONAL priority (needs a better name) is the level at which a simulation observer owns its own avatar
// which really just means: things that collide with it will be bid at a priority level one lower
const uint8_t PERSONAL_SIMULATION_PRIORITY = SCRIPT_GRAB_SIMULATION_PRIORITY;
const uint8_t AVATAR_ENTITY_SIMULATION_PRIORITY = PERSONAL_SIMULATION_PRIORITY;
class SimulationOwner {

View file

@ -259,6 +259,7 @@ enum class EntityVersion : PacketVersion {
MigrateOverlayRenderProperties,
MissingWebEntityProperties,
PulseProperties,
RingGizmoEntities,
// Add new versions above here
NUM_PACKET_TYPE,

View file

@ -39,6 +39,7 @@
#include "PrimitiveMode.h"
#include "WebInputMode.h"
#include "PulseMode.h"
#include "GizmoType.h"
#include "OctreeConstants.h"
#include "OctreeElement.h"
@ -271,6 +272,7 @@ public:
static int unpackDataFromBytes(const unsigned char* dataBytes, PrimitiveMode& result) { memcpy(&result, dataBytes, sizeof(result)); return sizeof(result); }
static int unpackDataFromBytes(const unsigned char* dataBytes, WebInputMode& result) { memcpy(&result, dataBytes, sizeof(result)); return sizeof(result); }
static int unpackDataFromBytes(const unsigned char* dataBytes, PulseMode& result) { memcpy(&result, dataBytes, sizeof(result)); return sizeof(result); }
static int unpackDataFromBytes(const unsigned char* dataBytes, GizmoType& result) { memcpy(&result, dataBytes, sizeof(result)); return sizeof(result); }
static int unpackDataFromBytes(const unsigned char* dataBytes, glm::vec2& result);
static int unpackDataFromBytes(const unsigned char* dataBytes, glm::vec3& result);
static int unpackDataFromBytes(const unsigned char* dataBytes, glm::u8vec3& result);

View file

@ -138,7 +138,8 @@ void CharacterController::setDynamicsWorld(btDynamicsWorld* world) {
if (_dynamicsWorld) {
if (_pendingFlags & PENDING_FLAG_UPDATE_SHAPE) {
// shouldn't fall in here, but if we do make sure both ADD and REMOVE bits are still set
_pendingFlags |= PENDING_FLAG_ADD_TO_SIMULATION | PENDING_FLAG_REMOVE_FROM_SIMULATION;
_pendingFlags |= PENDING_FLAG_ADD_TO_SIMULATION | PENDING_FLAG_REMOVE_FROM_SIMULATION |
PENDING_FLAG_ADD_DETAILED_TO_SIMULATION | PENDING_FLAG_REMOVE_DETAILED_FROM_SIMULATION;
} else {
_pendingFlags &= ~PENDING_FLAG_ADD_TO_SIMULATION;
}
@ -446,10 +447,10 @@ void CharacterController::setLocalBoundingBox(const glm::vec3& minCorner, const
if (_dynamicsWorld) {
// must REMOVE from world prior to shape update
_pendingFlags |= PENDING_FLAG_REMOVE_FROM_SIMULATION;
_pendingFlags |= PENDING_FLAG_REMOVE_FROM_SIMULATION | PENDING_FLAG_REMOVE_DETAILED_FROM_SIMULATION;
}
_pendingFlags |= PENDING_FLAG_UPDATE_SHAPE;
_pendingFlags |= PENDING_FLAG_ADD_TO_SIMULATION;
_pendingFlags |= PENDING_FLAG_ADD_TO_SIMULATION | PENDING_FLAG_ADD_DETAILED_TO_SIMULATION;
}
// it's ok to change offset immediately -- there are no thread safety issues here

View file

@ -32,6 +32,9 @@ const uint32_t PENDING_FLAG_UPDATE_SHAPE = 1U << 2;
const uint32_t PENDING_FLAG_JUMP = 1U << 3;
const uint32_t PENDING_FLAG_UPDATE_COLLISION_MASK = 1U << 4;
const uint32_t PENDING_FLAG_RECOMPUTE_FLYING = 1U << 5;
const uint32_t PENDING_FLAG_ADD_DETAILED_TO_SIMULATION = 1U << 6;
const uint32_t PENDING_FLAG_REMOVE_DETAILED_FROM_SIMULATION = 1U << 7;
const float DEFAULT_MIN_FLOOR_NORMAL_DOT_UP = cosf(PI / 3.0f);
class btRigidBody;

View file

@ -81,7 +81,7 @@ EntityMotionState::EntityMotionState(btCollisionShape* shape, EntityItemPointer
setShape(shape);
if (_entity->isAvatarEntity() && _entity->getOwningAvatarID() != Physics::getSessionUUID()) {
// avatar entities entities are always thus, so we cache this fact in _ownershipState
// avatar entities are always thus, so we cache this fact in _ownershipState
_ownershipState = EntityMotionState::OwnershipState::Unownable;
}
@ -211,6 +211,7 @@ PhysicsMotionType EntityMotionState::computePhysicsMotionType() const {
}
if (_entity->isMovingRelativeToParent() ||
_entity->hasActions() ||
_entity->hasGrabs() ||
_entity->hasAncestorOfType(NestableType::Avatar)) {
return MOTION_TYPE_KINEMATIC;
}
@ -235,11 +236,19 @@ void EntityMotionState::getWorldTransform(btTransform& worldTrans) const {
assert(entityTreeIsLocked());
if (_motionType == MOTION_TYPE_KINEMATIC) {
BT_PROFILE("kinematicIntegration");
uint32_t thisStep = ObjectMotionState::getWorldSimulationStep();
if (hasInternalKinematicChanges()) {
// ACTION_CAN_CONTROL_KINEMATIC_OBJECT_HACK: This kinematic body was moved by an Action
// and doesn't require transform update because the body is authoritative and its transform
// has already been copied out --> do no kinematic integration.
clearInternalKinematicChanges();
_lastKinematicStep = thisStep;
return;
}
// This is physical kinematic motion which steps strictly by the subframe count
// of the physics simulation and uses full gravity for acceleration.
_entity->setAcceleration(_entity->getGravity());
uint32_t thisStep = ObjectMotionState::getWorldSimulationStep();
float dt = (thisStep - _lastKinematicStep) * PHYSICS_ENGINE_FIXED_SUBSTEP;
_lastKinematicStep = thisStep;
_entity->stepKinematicMotion(dt);
@ -767,6 +776,7 @@ bool EntityMotionState::shouldSendBid() const {
// NOTE: this method is only ever called when the entity's simulation is NOT locally owned
return _body->isActive()
&& (_region == workload::Region::R1)
&& _ownershipState != EntityMotionState::OwnershipState::Unownable
&& glm::max(glm::max(VOLUNTEER_SIMULATION_PRIORITY, _bumpedPriority), _entity->getScriptSimulationPriority()) >= _entity->getSimulationPriority()
&& !_entity->getLocked();
}

View file

@ -88,7 +88,6 @@ public:
virtual void computeCollisionGroupAndMask(int32_t& group, int32_t& mask) const override;
bool shouldSendBid() const;
uint8_t computeFinalBidPriority() const;
bool isLocallyOwned() const override;
bool isLocallyOwnedOrShouldBe() const override; // aka shouldEmitCollisionEvents()
@ -100,6 +99,7 @@ public:
void saveKinematicState(btScalar timeStep) override;
protected:
uint8_t computeFinalBidPriority() const;
void updateSendVelocities();
uint64_t getNextBidExpiry() const { return _nextBidExpiry; }
void initForBid();

View file

@ -0,0 +1,538 @@
//
// MultiSphereShape.cpp
// libraries/physics/src
//
// Created by Luis Cuenca 5/11/2018
// Copyright 2018 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "MultiSphereShape.h"
void SphereRegion::translate(const glm::vec3& translation) {
for (auto &line : _lines) {
line.first += translation;
line.second += translation;
}
}
void SphereRegion::dump(std::vector<std::pair<glm::vec3, glm::vec3>>& outLines) {
for (auto &line : _lines) {
outLines.push_back(line);
}
}
void SphereRegion::insertUnique(const glm::vec3& point, std::vector<glm::vec3>& pointSet) {
auto hit = std::find_if(pointSet.begin(), pointSet.end(), [point](const glm::vec3& pointFromSet) -> bool {
return (glm::length(pointFromSet-point) < FLT_EPSILON);
});
if (hit == pointSet.end()) {
pointSet.push_back(point);
}
}
void SphereRegion::extractEdges(bool reverseY) {
if (_lines.size() == 0) {
return;
}
float yVal = _lines[0].first.y;
for (size_t i = 0; i < _lines.size(); i++) {
yVal = reverseY ? glm::max(yVal, _lines[i].first.y) : glm::min(yVal, _lines[i].first.y);
}
for (size_t i = 0; i < _lines.size(); i++) {
auto line = _lines[i];
auto p1 = line.first;
auto p2 = line.second;
auto vec = p1 - p2;
if (vec.z == 0.0f) {
insertUnique(p1, _edgesX);
insertUnique(p2, _edgesX);
} else if (vec.y == 0.0f && p1.y == yVal && p2.y == yVal) {
insertUnique(p1, _edgesY);
insertUnique(p2, _edgesY);
} else if (vec.x == 0.0f) {
insertUnique(p1, _edgesZ);
insertUnique(p2, _edgesZ);
}
}
}
void SphereRegion::extractSphereRegion(std::vector<std::pair<glm::vec3, glm::vec3>>& outLines) {
for (size_t i = 0; i < outLines.size(); i++) {
auto &line = outLines[i];
auto &p1 = line.first;
auto &p2 = line.second;
p1.x = glm::abs(p1.x) < 0.001f ? 0.0f : p1.x;
p1.y = glm::abs(p1.y) < 0.001f ? 0.0f : p1.y;
p1.z = glm::abs(p1.z) < 0.001f ? 0.0f : p1.z;
p2.x = glm::abs(p2.x) < 0.001f ? 0.0f : p2.x;
p2.y = glm::abs(p2.y) < 0.001f ? 0.0f : p2.y;
p2.z = glm::abs(p2.z) < 0.001f ? 0.0f : p2.z;
glm::vec3 point1 = { p1.x != 0.0f ? glm::abs(p1.x) / p1.x : _direction.x,
p1.y != 0.0f ? glm::abs(p1.y) / p1.y : _direction.y,
p1.z != 0.0f ? glm::abs(p1.z) / p1.z : _direction.z };
glm::vec3 point2 = { p2.x != 0.0f ? glm::abs(p2.x) / p2.x : _direction.x,
p2.y != 0.0f ? glm::abs(p2.y) / p2.y : _direction.y,
p2.z != 0.0f ? glm::abs(p2.z) / p2.z : _direction.z };
if (point1 == _direction && point2 == _direction) {
_lines.push_back(line);
}
}
}
CollisionShapeExtractionMode MultiSphereShape::getExtractionModeByName(const QString& name) {
CollisionShapeExtractionMode mode = CollisionShapeExtractionMode::Automatic;
bool isSim = name.indexOf("SIM") == 0;
bool isFlow = name.indexOf("FLOW") == 0;
bool isEye = name.indexOf("EYE") > -1;
bool isToe = name.indexOf("TOE") > -1;
bool isShoulder = name.indexOf("SHOULDER") > -1;
bool isNeck = name.indexOf("NECK") > -1;
bool isRightHand = name == "RIGHTHAND";
bool isLeftHand = name == "LEFTHAND";
bool isRightFinger = name.indexOf("RIGHTHAND") == 0 && !isRightHand;
bool isLeftFinger = name.indexOf("LEFTHAND") == 0 && !isLeftHand;
//bool isFinger =
if (isNeck || isLeftFinger || isRightFinger) {
mode = CollisionShapeExtractionMode::SpheresY;
} else if (isShoulder) {
mode = CollisionShapeExtractionMode::SphereCollapse;
} else if (isRightHand || isLeftHand) {
mode = CollisionShapeExtractionMode::SpheresXY;
} else if (isSim || isFlow || isEye || isToe) {
mode = CollisionShapeExtractionMode::None;
}
return mode;
}
void MultiSphereShape::filterUniquePoints(const std::vector<btVector3>& kdop, std::vector<glm::vec3>& uniquePoints) {
for (size_t j = 0; j < kdop.size(); j++) {
btVector3 btPoint = kdop[j];
auto hit = std::find_if(uniquePoints.begin(), uniquePoints.end(), [btPoint](const glm::vec3& point) -> bool {
return (glm::length(btPoint.getX() - point.x) < FLT_EPSILON
&& glm::length(btPoint.getY() - point.y) < FLT_EPSILON
&& glm::length(btPoint.getZ() - point.z) < FLT_EPSILON);
});
if (hit == uniquePoints.end()) {
uniquePoints.push_back(bulletToGLM(btPoint));
}
}
}
bool MultiSphereShape::computeMultiSphereShape(int jointIndex, const QString& name, const std::vector<btVector3>& kdop, float scale) {
_scale = scale;
_jointIndex = jointIndex;
_name = name;
_mode = getExtractionModeByName(_name);
if (_mode == CollisionShapeExtractionMode::None || kdop.size() < 4 || kdop.size() > 200) {
return false;
}
std::vector<glm::vec3> points;
filterUniquePoints(kdop, points);
glm::vec3 min = glm::vec3(100.0f, 100.0f, 100.0f);
glm::vec3 max = glm::vec3(-100.0f, -100.0f, -100.0f);
_midPoint = glm::vec3(0.0f, 0.0f, 0.0f);
std::vector<glm::vec3> relPoints;
for (size_t i = 0; i < points.size(); i++) {
min.x = points[i].x < min.x ? points[i].x : min.x;
min.y = points[i].y < min.y ? points[i].y : min.y;
min.z = points[i].z < min.z ? points[i].z : min.z;
max.x = points[i].x > max.x ? points[i].x : max.x;
max.y = points[i].y > max.y ? points[i].y : max.y;
max.z = points[i].z > max.z ? points[i].z : max.z;
_midPoint += points[i];
}
_midPoint /= (int)points.size();
glm::vec3 dimensions = max - min;
for (size_t i = 0; i < points.size(); i++) {
glm::vec3 relPoint = points[i] - _midPoint;
relPoints.push_back(relPoint);
}
CollisionShapeExtractionMode applyMode = _mode;
float xCorrector = dimensions.x > dimensions.y && dimensions.x > dimensions.z ? -1.0f + (dimensions.x / (0.5f * (dimensions.y + dimensions.z))) : 0.0f;
float yCorrector = dimensions.y > dimensions.x && dimensions.y > dimensions.z ? -1.0f + (dimensions.y / (0.5f * (dimensions.x + dimensions.z))) : 0.0f;
float zCorrector = dimensions.z > dimensions.x && dimensions.z > dimensions.y ? -1.0f + (dimensions.z / (0.5f * (dimensions.x + dimensions.y))) : 0.0f;
float xyDif = glm::abs(dimensions.x - dimensions.y);
float xzDif = glm::abs(dimensions.x - dimensions.z);
float yzDif = glm::abs(dimensions.y - dimensions.z);
float xyEpsilon = (0.05f + zCorrector) * glm::max(dimensions.x, dimensions.y);
float xzEpsilon = (0.05f + yCorrector) * glm::max(dimensions.x, dimensions.z);
float yzEpsilon = (0.05f + xCorrector) * glm::max(dimensions.y, dimensions.z);
if (xyDif < 0.5f * xyEpsilon && xzDif < 0.5f * xzEpsilon && yzDif < 0.5f * yzEpsilon) {
applyMode = CollisionShapeExtractionMode::Sphere;
} else if (xzDif < xzEpsilon) {
applyMode = dimensions.y > dimensions.z ? CollisionShapeExtractionMode::SpheresY : CollisionShapeExtractionMode::SpheresXZ;
} else if (xyDif < xyEpsilon) {
applyMode = dimensions.z > dimensions.y ? CollisionShapeExtractionMode::SpheresZ : CollisionShapeExtractionMode::SpheresXY;
} else if (yzDif < yzEpsilon) {
applyMode = dimensions.x > dimensions.y ? CollisionShapeExtractionMode::SpheresX : CollisionShapeExtractionMode::SpheresYZ;
} else {
applyMode = CollisionShapeExtractionMode::SpheresXYZ;
}
if (_mode != CollisionShapeExtractionMode::Automatic && applyMode != _mode) {
bool isModeSphereAxis = (_mode >= CollisionShapeExtractionMode::SpheresX && _mode <= CollisionShapeExtractionMode::SpheresZ);
bool isApplyModeComplex = (applyMode >= CollisionShapeExtractionMode::SpheresXY && applyMode <= CollisionShapeExtractionMode::SpheresXYZ);
applyMode = (isModeSphereAxis && isApplyModeComplex) ? CollisionShapeExtractionMode::Sphere : _mode;
}
std::vector<glm::vec3> axes;
glm::vec3 axis, axis1, axis2;
SphereShapeData sphere;
switch (applyMode) {
case CollisionShapeExtractionMode::None:
break;
case CollisionShapeExtractionMode::Automatic:
break;
case CollisionShapeExtractionMode::Box:
break;
case CollisionShapeExtractionMode::Sphere:
sphere._radius = 0.5f * (dimensions.x + dimensions.y + dimensions.z) / 3.0f;
sphere._position = glm::vec3(0.0f);
_spheres.push_back(sphere);
break;
case CollisionShapeExtractionMode::SphereCollapse:
sphere._radius = 0.5f * glm::min(glm::min(dimensions.x, dimensions.y), dimensions.z);
sphere._position = glm::vec3(0.0f);
_spheres.push_back(sphere);
break;
case CollisionShapeExtractionMode::SpheresX:
axis = 0.5f* dimensions.x * Vectors::UNIT_NEG_X;
axes = { axis, -axis };
break;
case CollisionShapeExtractionMode::SpheresY:
axis = 0.5f* dimensions.y * Vectors::UNIT_NEG_Y;
axes = { axis, -axis };
break;
case CollisionShapeExtractionMode::SpheresZ:
axis = 0.5f* dimensions.z * Vectors::UNIT_NEG_Z;
axes = { axis, -axis };
break;
case CollisionShapeExtractionMode::SpheresXY:
axis1 = glm::vec3(0.5f * dimensions.x, 0.5f * dimensions.y, 0.0f);
axis2 = glm::vec3(0.5f * dimensions.x, -0.5f * dimensions.y, 0.0f);
axes = { axis1, axis2, -axis1, -axis2 };
break;
case CollisionShapeExtractionMode::SpheresYZ:
axis1 = glm::vec3(0.0f, 0.5f * dimensions.y, 0.5f * dimensions.z);
axis2 = glm::vec3(0.0f, 0.5f * dimensions.y, -0.5f * dimensions.z);
axes = { axis1, axis2, -axis1, -axis2 };
break;
case CollisionShapeExtractionMode::SpheresXZ:
axis1 = glm::vec3(0.5f * dimensions.x, 0.0f, 0.5f * dimensions.z);
axis2 = glm::vec3(-0.5f * dimensions.x, 0.0f, 0.5f * dimensions.z);
axes = { axis1, axis2, -axis1, -axis2 };
break;
case CollisionShapeExtractionMode::SpheresXYZ:
for (size_t i = 0; i < CORNER_SIGNS.size(); i++) {
axes.push_back(0.5f * (dimensions * CORNER_SIGNS[i]));
}
break;
default:
break;
}
if (axes.size() > 0) {
spheresFromAxes(relPoints, axes, _spheres);
}
for (size_t i = 0; i < _spheres.size(); i++) {
_spheres[i]._position += _midPoint;
}
return _mode != CollisionShapeExtractionMode::None;
}
void MultiSphereShape::spheresFromAxes(const std::vector<glm::vec3>& points, const std::vector<glm::vec3>& axes, std::vector<SphereShapeData>& spheres) {
float maxRadius = 0.0f;
float maxAverageRadius = 0.0f;
float minAverageRadius = glm::length(points[0]);
size_t sphereCount = axes.size();
spheres.clear();
for (size_t j = 0; j < sphereCount; j++) {
SphereShapeData sphere = SphereShapeData();
sphere._axis = axes[j];
spheres.push_back(sphere);
}
for (size_t j = 0; j < sphereCount; j++) {
auto axis = _spheres[j]._axis;
float averageRadius = 0.0f;
float maxDistance = glm::length(axis);
glm::vec3 axisDir = glm::normalize(axis);
for (size_t i = 0; i < points.size(); i++) {
float dot = glm::dot(points[i], axisDir);
if (dot > 0.0f) {
float distancePow = glm::distance2(Vectors::ZERO, points[i]);
float dotPow = glm::pow(dot, 2);
float radius = (dot / maxDistance) * glm::sqrt(distancePow - dotPow);
averageRadius += radius;
maxRadius = radius > maxRadius ? radius : maxRadius;
}
}
if (points.size() > 0) {
averageRadius /= (int)points.size();
}
maxAverageRadius = glm::max(averageRadius, maxAverageRadius);
minAverageRadius = glm::min(averageRadius, minAverageRadius);
spheres[j]._radius = averageRadius;
}
if (maxAverageRadius == 0.0f) {
maxAverageRadius = 1.0f;
}
float radiusRatio = maxRadius / maxAverageRadius;
// Push the sphere into the bounding box
float contractionRatio = 0.8f;
for (size_t j = 0; j < sphereCount; j++) {
auto axis = _spheres[j]._axis;
float distance = glm::length(axis);
float correntionRatio = radiusRatio * (spheres[j]._radius / maxAverageRadius);
float radius = (correntionRatio < 0.8f * radiusRatio ? 0.8f * radiusRatio : correntionRatio) * spheres[j]._radius;
if (sphereCount > 3) {
distance = contractionRatio * distance;
}
spheres[j]._radius = radius;
if (distance - radius > 0.0f) {
spheres[j]._position = ((distance - radius) / distance) * axis;
} else {
spheres[j]._position = glm::vec3(0.0f);
}
}
// Collapse spheres if too close
if (sphereCount == 2) {
int maxRadiusIndex = spheres[0]._radius > spheres[1]._radius ? 0 : 1;
if (glm::length(spheres[0]._position - spheres[1]._position) < 0.2f * spheres[maxRadiusIndex]._radius) {
SphereShapeData newSphere;
newSphere._position = 0.5f * (spheres[0]._position + spheres[1]._position);
newSphere._radius = spheres[maxRadiusIndex]._radius;
spheres.clear();
spheres.push_back(newSphere);
}
}
}
void MultiSphereShape::connectSpheres(int index1, int index2, bool onlyEdges) {
auto sphere1 = _spheres[index1]._radius > _spheres[index2]._radius ? _spheres[index1] : _spheres[index2];
auto sphere2 = _spheres[index1]._radius <= _spheres[index2]._radius ? _spheres[index1] : _spheres[index2];
float distance = glm::length(sphere1._position - sphere2._position);
auto axis = sphere1._position - sphere2._position;
float angleOffset = glm::asin((sphere1._radius - sphere2._radius) / distance);
float ratio1 = ((0.5f * PI) + angleOffset) / PI;
float ratio2 = ((0.5f * PI) - angleOffset) / PI;
std::vector<glm::vec3> edge1, edge2;
if (onlyEdges) {
std::vector<std::pair<glm::vec3, glm::vec3>> debugLines;
calculateSphereLines(debugLines, sphere1._position, sphere1._radius, DEFAULT_SPHERE_SUBDIVISIONS, glm::normalize(axis), ratio1, &edge1);
calculateSphereLines(debugLines, sphere2._position, sphere2._radius, DEFAULT_SPHERE_SUBDIVISIONS, glm::normalize(-axis), ratio2, &edge2);
} else {
calculateSphereLines(_debugLines, sphere1._position, sphere1._radius, DEFAULT_SPHERE_SUBDIVISIONS, glm::normalize(axis), ratio1, &edge1);
calculateSphereLines(_debugLines, sphere2._position, sphere2._radius, DEFAULT_SPHERE_SUBDIVISIONS, glm::normalize(-axis), ratio2, &edge2);
}
connectEdges(_debugLines, edge1, edge2);
}
void MultiSphereShape::calculateDebugLines() {
if (_spheres.size() == 1) {
auto sphere = _spheres[0];
calculateSphereLines(_debugLines, sphere._position, sphere._radius);
} else if (_spheres.size() == 2) {
connectSpheres(0, 1);
} else if (_spheres.size() == 4) {
std::vector<glm::vec3> axes;
axes.resize(8);
for (size_t i = 0; i < CORNER_SIGNS.size(); i++) {
for (size_t j = 0; j < 4; j++) {
auto axis = _spheres[j]._position - _midPoint;
glm::vec3 sign = { axis.x != 0.0f ? glm::abs(axis.x) / axis.x : 0.0f,
axis.x != 0.0f ? glm::abs(axis.y) / axis.y : 0.0f ,
axis.z != 0.0f ? glm::abs(axis.z) / axis.z : 0.0f };
bool add = false;
if (sign.x == 0.0f) {
if (sign.y == CORNER_SIGNS[i].y && sign.z == CORNER_SIGNS[i].z) {
add = true;
}
} else if (sign.y == 0.0f) {
if (sign.x == CORNER_SIGNS[i].x && sign.z == CORNER_SIGNS[i].z) {
add = true;
}
} else if (sign.z == 0.0f) {
if (sign.x == CORNER_SIGNS[i].x && sign.y == CORNER_SIGNS[i].y) {
add = true;
}
} else if (sign == CORNER_SIGNS[i]) {
add = true;
}
if (add) {
axes[i] = axis;
break;
}
}
}
calculateChamferBox(_debugLines, _spheres[0]._radius, axes, _midPoint);
} else if (_spheres.size() == 8) {
std::vector<glm::vec3> axes;
for (size_t i = 0; i < _spheres.size(); i++) {
axes.push_back(_spheres[i]._position - _midPoint);
}
calculateChamferBox(_debugLines, _spheres[0]._radius, axes, _midPoint);
}
}
void MultiSphereShape::connectEdges(std::vector<std::pair<glm::vec3, glm::vec3>>& outLines, const std::vector<glm::vec3>& edge1, const std::vector<glm::vec3>& edge2, bool reverse) {
if (edge1.size() == edge2.size()) {
for (size_t i = 0; i < edge1.size(); i++) {
size_t j = reverse ? edge1.size() - i - 1 : i;
outLines.push_back({ edge1[i], edge2[j] });
}
}
}
void MultiSphereShape::calculateChamferBox(std::vector<std::pair<glm::vec3, glm::vec3>>& outLines, const float& radius, const std::vector<glm::vec3>& axes, const glm::vec3& translation) {
std::vector<std::pair<glm::vec3, glm::vec3>> sphereLines;
calculateSphereLines(sphereLines, glm::vec3(0.0f), radius);
std::vector<SphereRegion> regions = {
SphereRegion({ 1.0f, 1.0f, 1.0f }),
SphereRegion({ -1.0f, 1.0f, 1.0f }),
SphereRegion({ -1.0f, 1.0f, -1.0f }),
SphereRegion({ 1.0f, 1.0f, -1.0f }),
SphereRegion({ 1.0f, -1.0f, 1.0f }),
SphereRegion({ -1.0f, -1.0f, 1.0f }),
SphereRegion({ -1.0f, -1.0f, -1.0f }),
SphereRegion({ 1.0f, -1.0f, -1.0f })
};
assert(axes.size() == regions.size());
for (size_t i = 0; i < regions.size(); i++) {
regions[i].extractSphereRegion(sphereLines);
regions[i].translate(translation + axes[i]);
regions[i].extractEdges(axes[i].y < 0);
regions[i].dump(outLines);
}
connectEdges(outLines, regions[0].getEdgesZ(), regions[1].getEdgesZ());
connectEdges(outLines, regions[1].getEdgesX(), regions[2].getEdgesX());
connectEdges(outLines, regions[2].getEdgesZ(), regions[3].getEdgesZ());
connectEdges(outLines, regions[3].getEdgesX(), regions[0].getEdgesX());
connectEdges(outLines, regions[4].getEdgesZ(), regions[5].getEdgesZ());
connectEdges(outLines, regions[5].getEdgesX(), regions[6].getEdgesX());
connectEdges(outLines, regions[6].getEdgesZ(), regions[7].getEdgesZ());
connectEdges(outLines, regions[7].getEdgesX(), regions[4].getEdgesX());
connectEdges(outLines, regions[0].getEdgesY(), regions[4].getEdgesY());
connectEdges(outLines, regions[1].getEdgesY(), regions[5].getEdgesY());
connectEdges(outLines, regions[2].getEdgesY(), regions[6].getEdgesY());
connectEdges(outLines, regions[3].getEdgesY(), regions[7].getEdgesY());
}
void MultiSphereShape::calculateSphereLines(std::vector<std::pair<glm::vec3, glm::vec3>>& outLines, const glm::vec3& center, const float& radius,
const int& subdivisions, const glm::vec3& direction, const float& percentage, std::vector<glm::vec3>* edge) {
float uTotalAngle = percentage * PI;
float vTotalAngle = 2.0f * PI;
int uSubdivisions = (int)glm::ceil(subdivisions * 0.5f * percentage);
int vSubdivisions = subdivisions;
float uDeltaAngle = uTotalAngle / uSubdivisions;
float vDeltaAngle = vTotalAngle / vSubdivisions;
float uAngle = 0.0f;
glm::vec3 uAxis, vAxis;
glm::vec3 mainAxis = glm::normalize(direction);
if (mainAxis.y == 1.0f || mainAxis.y == -1.0f) {
uAxis = glm::vec3(1.0f, 0.0f, 0.0f);
vAxis = glm::vec3(0.0f, 0.0f, 1.0f);
} else {
uAxis = glm::normalize(glm::cross(glm::vec3(0.0f, 1.0f, 0.0f), mainAxis));
vAxis = glm::normalize(glm::cross(mainAxis, uAxis));
if ((uAxis.z == 0.0f && uAxis.x < 0.0f) || (uAxis.x == 0.0f && uAxis.z < 0.0f)) {
uAxis = -uAxis;
} else if (uAxis.x < 0.0f) {
uAxis = -uAxis;
}
if ((vAxis.z == 0.0f && vAxis.x < 0.0f) || (vAxis.x == 0.0f && vAxis.z < 0.0f)) {
vAxis = -vAxis;
} else if (vAxis.x < 0.0f) {
vAxis = -vAxis;
}
}
std::vector<std::vector<glm::vec3>> arcs;
auto origin = center;
for (int u = 0; u < uSubdivisions + 1; u++) {
std::vector<glm::vec3> arc;
glm::vec3 arcCenter = origin + mainAxis * (glm::cos(uAngle) * radius);
float vAngle = 0.0f;
for (int v = 0; v < vSubdivisions + 1; v++) {
float arcRadius = glm::abs(glm::sin(uAngle) * radius);
glm::vec3 arcPoint = arcCenter + (arcRadius * glm::cos(vAngle)) * uAxis + (arcRadius * glm::sin(vAngle)) * vAxis;
arc.push_back(arcPoint);
if (u == uSubdivisions && edge != nullptr) {
edge->push_back(arcPoint);
}
vAngle += vDeltaAngle;
}
arc.push_back(arc[0]);
arcs.push_back(arc);
uAngle += uDeltaAngle;
}
for (size_t i = 1; i < arcs.size(); i++) {
auto arc1 = arcs[i];
auto arc2 = arcs[i - 1];
for (size_t j = 1; j < arc1.size(); j++) {
auto point1 = arc1[j];
auto point2 = arc1[j - 1];
auto point3 = arc2[j];
std::pair<glm::vec3, glm::vec3> line1 = { point1, point2 };
std::pair<glm::vec3, glm::vec3> line2 = { point1, point3 };
outLines.push_back(line1);
outLines.push_back(line2);
}
}
}
void MultiSphereShape::setScale(float scale) {
if (scale != _scale) {
float deltaScale = scale / _scale;
for (auto& sphere : _spheres) {
sphere._axis *= deltaScale;
sphere._position *= deltaScale;
sphere._radius *= deltaScale;
}
for (auto& line : _debugLines) {
line.first *= deltaScale;
line.second *= deltaScale;
}
_scale = scale;
}
}
AABox& MultiSphereShape::updateBoundingBox(const glm::vec3& position, const glm::quat& rotation) {
_boundingBox = AABox();
auto spheres = getSpheresData();
for (size_t i = 0; i < spheres.size(); i++) {
auto sphere = spheres[i];
auto worldPosition = position + rotation * sphere._position;
glm::vec3 corner = worldPosition - glm::vec3(sphere._radius);
glm::vec3 dimensions = glm::vec3(2.0f * sphere._radius);
_boundingBox += AABox(corner, dimensions);
}
return _boundingBox;
}

View file

@ -0,0 +1,112 @@
//
// MultiSphereShape.h
// libraries/physics/src
//
// Created by Luis Cuenca 5/11/2018
// Copyright 2018 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_MultiSphereShape_h
#define hifi_MultiSphereShape_h
#include <stdint.h>
#include <btBulletDynamicsCommon.h>
#include <GLMHelpers.h>
#include <AABox.h>
#include "BulletUtil.h"
enum CollisionShapeExtractionMode {
None = 0,
Automatic,
Box,
Sphere,
SphereCollapse,
SpheresX,
SpheresY,
SpheresZ,
SpheresXY,
SpheresYZ,
SpheresXZ,
SpheresXYZ
};
struct SphereShapeData {
SphereShapeData() {}
glm::vec3 _position;
glm::vec3 _axis;
float _radius;
};
class SphereRegion {
public:
SphereRegion() {}
SphereRegion(const glm::vec3& direction) : _direction(direction) {}
void extractSphereRegion(std::vector<std::pair<glm::vec3, glm::vec3>>& outLines);
void extractEdges(bool reverseY = false);
void translate(const glm::vec3& translation);
void dump(std::vector<std::pair<glm::vec3, glm::vec3>>& outLines);
const glm::vec3& getDirection() const { return _direction; }
const std::vector<glm::vec3>& getEdgesX() const { return _edgesX; }
const std::vector<glm::vec3>& getEdgesY() const { return _edgesY; }
const std::vector<glm::vec3>& getEdgesZ() const { return _edgesZ; }
private:
void insertUnique(const glm::vec3& point, std::vector<glm::vec3>& pointSet);
std::vector<std::pair<glm::vec3, glm::vec3>> _lines;
std::vector<glm::vec3> _edgesX;
std::vector<glm::vec3> _edgesY;
std::vector<glm::vec3> _edgesZ;
glm::vec3 _direction;
};
const int DEFAULT_SPHERE_SUBDIVISIONS = 16;
const std::vector<glm::vec3> CORNER_SIGNS = {
glm::vec3(1.0f, 1.0f, 1.0f), glm::vec3(-1.0f, 1.0f, 1.0f),
glm::vec3(-1.0f, 1.0f, -1.0f), glm::vec3(1.0f, 1.0f, -1.0f),
glm::vec3(1.0f, -1.0f, 1.0f), glm::vec3(-1.0f, -1.0f, 1.0f),
glm::vec3(-1.0f, -1.0f, -1.0f), glm::vec3(1.0f, -1.0f, -1.0f) };
class MultiSphereShape {
public:
MultiSphereShape() {};
bool computeMultiSphereShape(int jointIndex, const QString& name, const std::vector<btVector3>& points, float scale = 1.0f);
void calculateDebugLines();
const std::vector<SphereShapeData>& getSpheresData() const { return _spheres; }
const std::vector<std::pair<glm::vec3, glm::vec3>>& getDebugLines() const { return _debugLines; }
void setScale(float scale);
AABox& updateBoundingBox(const glm::vec3& position, const glm::quat& rotation);
const AABox& getBoundingBox() const { return _boundingBox; }
int getJointIndex() const { return _jointIndex; }
QString getJointName() const { return _name; }
bool isValid() const { return _spheres.size() > 0; }
private:
CollisionShapeExtractionMode getExtractionModeByName(const QString& name);
void filterUniquePoints(const std::vector<btVector3>& kdop, std::vector<glm::vec3>& uniquePoints);
void spheresFromAxes(const std::vector<glm::vec3>& points, const std::vector<glm::vec3>& axes,
std::vector<SphereShapeData>& spheres);
void calculateSphereLines(std::vector<std::pair<glm::vec3, glm::vec3>>& outLines, const glm::vec3& center, const float& radius,
const int& subdivisions = DEFAULT_SPHERE_SUBDIVISIONS, const glm::vec3& direction = Vectors::UNIT_Y,
const float& percentage = 1.0f, std::vector<glm::vec3>* edge = nullptr);
void calculateChamferBox(std::vector<std::pair<glm::vec3, glm::vec3>>& outLines, const float& radius, const std::vector<glm::vec3>& axes, const glm::vec3& translation);
void connectEdges(std::vector<std::pair<glm::vec3, glm::vec3>>& outLines, const std::vector<glm::vec3>& edge1,
const std::vector<glm::vec3>& edge2, bool reverse = false);
void connectSpheres(int index1, int index2, bool onlyEdges = false);
int _jointIndex { -1 };
QString _name;
std::vector<SphereShapeData> _spheres;
std::vector<std::pair<glm::vec3, glm::vec3>> _debugLines;
CollisionShapeExtractionMode _mode;
glm::vec3 _midPoint;
float _scale { 1.0f };
AABox _boundingBox;
};
#endif // hifi_MultiSphereShape_h

View file

@ -198,7 +198,7 @@ void ObjectMotionState::setShape(const btCollisionShape* shape) {
getShapeManager()->releaseShape(_shape);
}
_shape = shape;
if (_body) {
if (_body && _type != MOTIONSTATE_TYPE_DETAILED) {
updateCCDConfiguration();
}
}
@ -310,7 +310,7 @@ bool ObjectMotionState::handleHardAndEasyChanges(uint32_t& flags, PhysicsEngine*
// the shape didn't actually change, so we clear the DIRTY_SHAPE flag
flags &= ~Simulation::DIRTY_SHAPE;
// and clear the reference we just created
getShapeManager()->releaseShape(_shape);
getShapeManager()->releaseShape(_shape);
} else {
_body->setCollisionShape(const_cast<btCollisionShape*>(newShape));
setShape(newShape);

View file

@ -58,7 +58,8 @@ inline QString motionTypeToString(PhysicsMotionType motionType) {
enum MotionStateType {
MOTIONSTATE_TYPE_INVALID,
MOTIONSTATE_TYPE_ENTITY,
MOTIONSTATE_TYPE_AVATAR
MOTIONSTATE_TYPE_AVATAR,
MOTIONSTATE_TYPE_DETAILED
};
// The update flags trigger two varieties of updates: "hard" which require the body to be pulled
@ -160,8 +161,9 @@ public:
bool hasInternalKinematicChanges() const { return _hasInternalKinematicChanges; }
void dirtyInternalKinematicChanges() { _hasInternalKinematicChanges = true; }
void clearInternalKinematicChanges() { _hasInternalKinematicChanges = false; }
// these methods are declared const so they can be called inside other const methods
void dirtyInternalKinematicChanges() const { _hasInternalKinematicChanges = true; }
void clearInternalKinematicChanges() const { _hasInternalKinematicChanges = false; }
virtual bool isLocallyOwned() const { return false; }
virtual bool isLocallyOwnedOrShouldBe() const { return false; } // aka shouldEmitCollisionEvents()
@ -185,8 +187,11 @@ protected:
btRigidBody* _body { nullptr };
float _density { 1.0f };
// ACTION_CAN_CONTROL_KINEMATIC_OBJECT_HACK: These date members allow an Action
// to operate on a kinematic object without screwing up our default kinematic integration
// which is done in the MotionState::getWorldTransform().
mutable uint32_t _lastKinematicStep;
bool _hasInternalKinematicChanges { false };
mutable bool _hasInternalKinematicChanges { false };
};
using SetOfMotionStates = QSet<ObjectMotionState*>;

View file

@ -327,7 +327,7 @@ void PhysicsEngine::processTransaction(PhysicsEngine::Transaction& transaction)
}
if (object->getMotionType() == MOTION_TYPE_STATIC && object->isActive()) {
_activeStaticBodies.insert(object->getRigidBody());
}
}
}
// activeStaticBodies have changed (in an Easy way) and need their Aabbs updated
// but we've configured Bullet to NOT update them automatically (for improved performance)

View file

@ -284,6 +284,17 @@ const btCollisionShape* ShapeFactory::createShapeFromInfo(const ShapeInfo& info)
shape = new btSphereShape(radius);
}
break;
case SHAPE_TYPE_MULTISPHERE: {
std::vector<btVector3> positions;
std::vector<float> radiuses;
auto sphereCollection = info.getSphereCollection();
for (auto &sphereData : sphereCollection) {
positions.push_back(glmToBullet(glm::vec3(sphereData)));
radiuses.push_back(sphereData.w);
}
shape = new btMultiSphereShape(positions.data(), radiuses.data(), (int)positions.size());
}
break;
case SHAPE_TYPE_ELLIPSOID: {
glm::vec3 halfExtents = info.getHalfExtents();
float radius = halfExtents.x;

View file

@ -100,9 +100,11 @@ void ThreadSafeDynamicsWorld::synchronizeMotionState(btRigidBody* body) {
if (body->isKinematicObject()) {
ObjectMotionState* objectMotionState = static_cast<ObjectMotionState*>(body->getMotionState());
if (objectMotionState->hasInternalKinematicChanges()) {
// this is a special case where the kinematic motion has been updated by an Action
// so we supply the body's current transform to the MotionState
objectMotionState->clearInternalKinematicChanges();
// ACTION_CAN_CONTROL_KINEMATIC_OBJECT_HACK:
// This is a special case where the kinematic motion has been updated by an Action
// so we supply the body's current transform to the MotionState,
// but we DON'T clear the internalKinematicChanges bit here because
// objectMotionState.getWorldTransform() will use and clear it later
body->getMotionState()->setWorldTransform(body->getWorldTransform());
}
return;
@ -172,3 +174,46 @@ void ThreadSafeDynamicsWorld::saveKinematicState(btScalar timeStep) {
}
}
}
void ThreadSafeDynamicsWorld::drawConnectedSpheres(btIDebugDraw* drawer, btScalar radius1, btScalar radius2, const btVector3& position1, const btVector3& position2, const btVector3& color) {
float stepRadians = PI/6.0f; // 30 degrees
btVector3 direction = position2 - position1;
btVector3 xAxis = direction.cross(btVector3(0.0f, 1.0f, 0.0f));
xAxis = xAxis.length() < EPSILON ? btVector3(1.0f, 0.0f, 0.0f) : xAxis.normalize();
btVector3 zAxis = xAxis.cross(btVector3(0.0f, 1.0f, 0.0f));
zAxis = (direction.normalize().getY() < EPSILON) ? btVector3(0.0f, 1.0f, 0.0f) : zAxis.normalize();
float fullCircle = 2.0f * PI;
for (float i = 0; i < fullCircle; i += stepRadians) {
float x1 = btSin(btScalar(i)) * radius1;
float z1 = btCos(btScalar(i)) * radius1;
float x2 = btSin(btScalar(i)) * radius2;
float z2 = btCos(btScalar(i)) * radius2;
btVector3 addVector1 = xAxis * x1 + zAxis * z1;
btVector3 addVector2 = xAxis * x2 + zAxis * z2;
drawer->drawLine(position1 + addVector1, position2 + addVector2, color);
}
}
void ThreadSafeDynamicsWorld::debugDrawObject(const btTransform& worldTransform, const btCollisionShape* shape, const btVector3& color) {
btCollisionWorld::debugDrawObject(worldTransform, shape, color);
if (shape->getShapeType() == MULTI_SPHERE_SHAPE_PROXYTYPE) {
const btMultiSphereShape* multiSphereShape = static_cast<const btMultiSphereShape*>(shape);
for (int i = multiSphereShape->getSphereCount() - 1; i >= 0; i--) {
btTransform sphereTransform1, sphereTransform2;
sphereTransform1.setIdentity();
sphereTransform2.setIdentity();
int sphereIndex1 = i;
int sphereIndex2 = i > 0 ? i - 1 : multiSphereShape->getSphereCount() - 1;
sphereTransform1.setOrigin(multiSphereShape->getSpherePosition(sphereIndex1));
sphereTransform2.setOrigin(multiSphereShape->getSpherePosition(sphereIndex2));
sphereTransform1 = worldTransform * sphereTransform1;
sphereTransform2 = worldTransform * sphereTransform2;
getDebugDrawer()->drawSphere(multiSphereShape->getSphereRadius(sphereIndex1), sphereTransform1, color);
drawConnectedSpheres(getDebugDrawer(), multiSphereShape->getSphereRadius(sphereIndex1), multiSphereShape->getSphereRadius(sphereIndex2), sphereTransform1.getOrigin(), sphereTransform2.getOrigin(), color);
}
} else {
btCollisionWorld::debugDrawObject(worldTransform, shape, color);
}
}

View file

@ -53,10 +53,13 @@ public:
const VectorOfMotionStates& getDeactivatedMotionStates() const { return _deactivatedStates; }
void addChangedMotionState(ObjectMotionState* motionState) { _changedMotionStates.push_back(motionState); }
virtual void debugDrawObject(const btTransform& worldTransform, const btCollisionShape* shape, const btVector3& color) override;
private:
// call this instead of non-virtual btDiscreteDynamicsWorld::synchronizeSingleMotionState()
void synchronizeMotionState(btRigidBody* body);
void drawConnectedSpheres(btIDebugDraw* drawer, btScalar radius1, btScalar radius2, const btVector3& position1,
const btVector3& position2, const btVector3& color);
VectorOfMotionStates _changedMotionStates;
VectorOfMotionStates _deactivatedStates;

View file

@ -20,6 +20,9 @@ public:
virtual QString getName() const = 0;
virtual QString getOculusUserID() const = 0;
virtual bool init() = 0;
virtual void shutdown() = 0;
virtual bool isRunning() const = 0;
virtual void requestNonceAndUserID(NonceUserIDCallback callback) = 0;

View file

@ -2255,8 +2255,8 @@ gpu::PipelinePointer GeometryCache::getWebBrowserProgram(bool transparent) {
return transparent ? _simpleTransparentWebBrowserPipeline : _simpleOpaqueWebBrowserPipeline;
}
void GeometryCache::bindSimpleProgram(gpu::Batch& batch, bool textured, bool transparent, bool culled, bool unlit, bool depthBiased, bool isAntiAliased) {
batch.setPipeline(getSimplePipeline(textured, transparent, culled, unlit, depthBiased, false, isAntiAliased));
void GeometryCache::bindSimpleProgram(gpu::Batch& batch, bool textured, bool transparent, bool culled, bool unlit, bool depthBiased, bool isAntiAliased, bool forward) {
batch.setPipeline(getSimplePipeline(textured, transparent, culled, unlit, depthBiased, false, isAntiAliased, forward));
// If not textured, set a default albedo map
if (!textured) {

View file

@ -171,7 +171,7 @@ public:
// Bind the pipeline and get the state to render static geometry
void bindSimpleProgram(gpu::Batch& batch, bool textured = false, bool transparent = false, bool culled = true,
bool unlit = false, bool depthBias = false, bool isAntiAliased = true);
bool unlit = false, bool depthBias = false, bool isAntiAliased = true, bool forward = false);
// Get the pipeline to render static geometry
static gpu::PipelinePointer getSimplePipeline(bool textured = false, bool transparent = false, bool culled = true,
bool unlit = false, bool depthBias = false, bool fading = false, bool isAntiAliased = true, bool forward = false);

View file

@ -38,10 +38,10 @@ void AudioScriptingInterface::setLocalAudioInterface(AbstractAudioInterface* aud
}
}
ScriptAudioInjector* AudioScriptingInterface::playSystemSound(SharedSoundPointer sound, const QVector3D& position) {
ScriptAudioInjector* AudioScriptingInterface::playSystemSound(SharedSoundPointer sound) {
AudioInjectorOptions options;
options.position = glm::vec3(position.x(), position.y(), position.z());
options.localOnly = true;
options.positionSet = false; // system sound
return playSound(sound, options);
}

View file

@ -102,11 +102,9 @@ protected:
* @function Audio.playSystemSound
* @param {SoundObject} sound - The content of an audio file, loaded using {@link SoundCache.getSound}. See
* {@link SoundObject} for supported formats.
* @param {Vec3} position - The position in the domain to play the sound.
* @returns {AudioInjector} The audio injector that plays the audio file.
*/
// FIXME: there is no way to play a positionless sound
Q_INVOKABLE ScriptAudioInjector* playSystemSound(SharedSoundPointer sound, const QVector3D& position);
Q_INVOKABLE ScriptAudioInjector* playSystemSound(SharedSoundPointer sound);
/**jsdoc
* Set whether or not the audio input should be used in stereo. If the audio input does not support stereo then setting a

View file

@ -43,8 +43,9 @@ static inline float fastLog2f(float x) {
}
//
// for -127 <= x < 128, returns exp2(x)
// otherwise, returns undefined
// for -126 <= x < 128, returns exp2(x)
// for x < -126, returns 0
// for x >= 128, returns undefined
//
// rel |error| < 9e-6, smooth (exact for x=N)
//
@ -60,6 +61,7 @@ static inline float fastExp2f(float x) {
x -= xi.i;
// construct exp2(xi) as a float
xi.i &= ~(xi.i >> 31); // MAX(xi.i, 0)
xi.i <<= IEEE754_MANT_BITS;
// polynomial for exp2(x) over x=[0,1]

View file

@ -74,3 +74,13 @@ void DebugDraw::clearRays() {
Lock lock(_mapMutex);
_rays.clear();
}
void DebugDraw::drawRays(const std::vector<std::pair<glm::vec3, glm::vec3>>& lines,
const glm::vec4& color, const glm::vec3& translation, const glm::quat& rotation) {
Lock lock(_mapMutex);
for (std::pair<glm::vec3, glm::vec3> line : lines) {
auto point1 = translation + rotation * line.first;
auto point2 = translation + rotation * line.second;
_rays.push_back(Ray(point1, point2, color));
}
}

View file

@ -47,6 +47,16 @@ public:
* @param {Vec4} color - color of line, each component should be in the zero to one range. x = red, y = blue, z = green, w = alpha.
*/
Q_INVOKABLE void drawRay(const glm::vec3& start, const glm::vec3& end, const glm::vec4& color);
/**jsdoc
* Draws a line in world space, but it will only be visible for a single frame.
* @function DebugDraw.drawRay
* @param {Vec3} start - start position of line in world space.
* @param {Vec3} end - end position of line in world space.
* @param {Vec4} color - color of line, each component should be in the zero to one range. x = red, y = blue, z = green, w = alpha.
*/
Q_INVOKABLE void drawRays(const std::vector<std::pair<glm::vec3, glm::vec3>>& lines, const glm::vec4& color,
const glm::vec3& translation = glm::vec3(0.0f, 0.0f, 0.0f), const glm::quat& rotation = glm::quat(1.0f, 0.0f, 0.0f, 0.0f));
/**jsdoc
* Adds a debug marker to the world. This marker will be drawn every frame until it is removed with DebugDraw.removeMarker.

View file

@ -0,0 +1,23 @@
//
// Created by Sam Gondelman on 1/22/19.
// Copyright 2019 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "GizmoType.h"
const char* gizmoTypeNames[] = {
"ring",
};
static const size_t GIZMO_TYPE_NAMES = (sizeof(gizmoTypeNames) / sizeof(gizmoTypeNames[0]));
QString GizmoTypeHelpers::getNameForGizmoType(GizmoType mode) {
if (((int)mode <= 0) || ((int)mode >= (int)GIZMO_TYPE_NAMES)) {
mode = (GizmoType)0;
}
return gizmoTypeNames[(int)mode];
}

View file

@ -0,0 +1,37 @@
//
// Created by Sam Gondelman on 1/22/19.
// Copyright 2019 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_GizmoType_h
#define hifi_GizmoType_h
#include "QString"
/**jsdoc
* <p>Controls how the Gizmo behaves and renders</p>
* <table>
* <thead>
* <tr><th>Value</th><th>Description</th></tr>
* </thead>
* <tbody>
* <tr><td><code>ring</code></td><td>A ring gizmo.</td></tr>
* </tbody>
* </table>
* @typedef {string} GizmoType
*/
enum GizmoType {
RING = 0,
};
class GizmoTypeHelpers {
public:
static QString getNameForGizmoType(GizmoType mode);
};
#endif // hifi_GizmoType_h

View file

@ -39,6 +39,8 @@ const int32_t BULLET_COLLISION_GROUP_DYNAMIC = 1 << 1;
const int32_t BULLET_COLLISION_GROUP_KINEMATIC = 1 << 2;
const int32_t BULLET_COLLISION_GROUP_MY_AVATAR = 1 << 3;
const int32_t BULLET_COLLISION_GROUP_OTHER_AVATAR = 1 << 4;
const int32_t BULLET_COLLISION_GROUP_DETAILED_AVATAR = 1 << 5;
const int32_t BULLET_COLLISION_GROUP_DETAILED_RAY = 1 << 6;
// ...
const int32_t BULLET_COLLISION_GROUP_COLLISIONLESS = 1 << 31;
@ -64,7 +66,8 @@ const int32_t BULLET_COLLISION_MASK_MY_AVATAR = ~(BULLET_COLLISION_GROUP_COLLISI
// to move its avatar around correctly and to communicate its motion through the avatar-mixer.
// Therefore, they only need to collide against things that can be affected by their motion: dynamic and MyAvatar
const int32_t BULLET_COLLISION_MASK_OTHER_AVATAR = BULLET_COLLISION_GROUP_DYNAMIC | BULLET_COLLISION_GROUP_MY_AVATAR;
const int32_t BULLET_COLLISION_MASK_DETAILED_AVATAR = BULLET_COLLISION_GROUP_DETAILED_RAY;
const int32_t BULLET_COLLISION_MASK_DETAILED_RAY = BULLET_COLLISION_GROUP_DETAILED_AVATAR;
// COLLISIONLESS gets an empty mask.
const int32_t BULLET_COLLISION_MASK_COLLISIONLESS = 0;

View file

@ -78,6 +78,10 @@ int32_t Physics::getDefaultCollisionMask(int32_t group) {
return BULLET_COLLISION_MASK_MY_AVATAR;
case BULLET_COLLISION_GROUP_OTHER_AVATAR:
return BULLET_COLLISION_MASK_OTHER_AVATAR;
case BULLET_COLLISION_GROUP_DETAILED_AVATAR:
return BULLET_COLLISION_MASK_DETAILED_AVATAR;
case BULLET_COLLISION_GROUP_DETAILED_RAY:
return BULLET_COLLISION_MASK_DETAILED_RAY;
default:
break;
};

View file

@ -38,6 +38,7 @@
* sub-meshes.</td></tr>
* <tr><td><code>"static-mesh"</code></td><td>The exact shape of the model.</td></tr>
* <tr><td><code>"plane"</code></td><td>A plane.</td></tr>
* <tr><td><code>"multisphere"</code></td><td>A convex hull generated from a set of spheres.</td></tr>
* </tbody>
* </table>
* @typedef {string} ShapeType
@ -59,7 +60,9 @@ const char* shapeTypeNames[] = {
"simple-hull",
"simple-compound",
"static-mesh",
"ellipsoid"
"ellipsoid",
"circle",
"multisphere"
};
static const size_t SHAPETYPE_NAME_COUNT = (sizeof(shapeTypeNames) / sizeof((shapeTypeNames)[0]));
@ -90,6 +93,7 @@ void ShapeInfo::clear() {
_url.clear();
_pointCollection.clear();
_triangleIndices.clear();
_sphereCollection.clear();
_halfExtents = glm::vec3(0.0f);
_offset = glm::vec3(0.0f);
_hashKey.clear();
@ -106,6 +110,7 @@ void ShapeInfo::setParams(ShapeType type, const glm::vec3& halfExtents, QString
break;
case SHAPE_TYPE_BOX:
case SHAPE_TYPE_HULL:
case SHAPE_TYPE_MULTISPHERE:
break;
case SHAPE_TYPE_SPHERE: {
float radius = glm::length(halfExtents) / SQUARE_ROOT_OF_3;
@ -144,6 +149,17 @@ void ShapeInfo::setSphere(float radius) {
_hashKey.clear();
}
void ShapeInfo::setMultiSphere(const std::vector<glm::vec3>& centers, const std::vector<float>& radiuses) {
_url = "";
_type = SHAPE_TYPE_MULTISPHERE;
assert(centers.size() == radiuses.size() && centers.size() > 0);
for (size_t i = 0; i < centers.size(); i++) {
SphereData sphere = SphereData(centers[i], radiuses[i]);
_sphereCollection.push_back(sphere);
}
_hashKey.clear();
}
void ShapeInfo::setPointCollection(const ShapeInfo::PointCollection& pointCollection) {
_pointCollection = pointCollection;
_hashKey.clear();
@ -170,6 +186,7 @@ uint32_t ShapeInfo::getNumSubShapes() const {
case SHAPE_TYPE_COMPOUND:
case SHAPE_TYPE_SIMPLE_COMPOUND:
return _pointCollection.size();
case SHAPE_TYPE_MULTISPHERE:
case SHAPE_TYPE_SIMPLE_HULL:
case SHAPE_TYPE_STATIC_MESH:
assert(_pointCollection.size() == 1);
@ -257,7 +274,12 @@ const HashKey& ShapeInfo::getHash() const {
// The key is not yet cached therefore we must compute it.
_hashKey.hashUint64((uint64_t)_type);
if (_type != SHAPE_TYPE_SIMPLE_HULL) {
if (_type == SHAPE_TYPE_MULTISPHERE) {
for (auto &sphereData : _sphereCollection) {
_hashKey.hashVec3(glm::vec3(sphereData));
_hashKey.hashFloat(sphereData.w);
}
} else if (_type != SHAPE_TYPE_SIMPLE_HULL) {
_hashKey.hashVec3(_halfExtents);
_hashKey.hashVec3(_offset);
} else {
@ -283,9 +305,12 @@ const HashKey& ShapeInfo::getHash() const {
if (_type == SHAPE_TYPE_COMPOUND || _type == SHAPE_TYPE_SIMPLE_COMPOUND) {
uint64_t numHulls = (uint64_t)_pointCollection.size();
_hashKey.hashUint64(numHulls);
} else if (_type == SHAPE_TYPE_MULTISPHERE) {
uint64_t numSpheres = (uint64_t)_sphereCollection.size();
_hashKey.hashUint64(numSpheres);
} else if (_type == SHAPE_TYPE_SIMPLE_HULL) {
_hashKey.hashUint64(1);
}
}
}
return _hashKey;
}

View file

@ -47,7 +47,8 @@ enum ShapeType {
SHAPE_TYPE_SIMPLE_COMPOUND,
SHAPE_TYPE_STATIC_MESH,
SHAPE_TYPE_ELLIPSOID,
SHAPE_TYPE_CIRCLE
SHAPE_TYPE_CIRCLE,
SHAPE_TYPE_MULTISPHERE
};
class ShapeInfo {
@ -57,6 +58,8 @@ public:
using PointList = QVector<glm::vec3>;
using PointCollection = QVector<PointList>;
using TriangleIndices = QVector<int32_t>;
using SphereData = glm::vec4;
using SphereCollection = QVector<SphereData>;
static QString getNameForShapeType(ShapeType type);
static ShapeType getShapeTypeForName(QString string);
@ -68,7 +71,8 @@ public:
void setSphere(float radius);
void setPointCollection(const PointCollection& pointCollection);
void setCapsuleY(float radius, float cylinderHalfHeight);
void setOffset(const glm::vec3& offset);
void setMultiSphere(const std::vector<glm::vec3>& centers, const std::vector<float>& radiuses);
void setOffset(const glm::vec3& offset);
ShapeType getType() const { return _type; }
@ -78,6 +82,7 @@ public:
PointCollection& getPointCollection() { return _pointCollection; }
const PointCollection& getPointCollection() const { return _pointCollection; }
const SphereCollection& getSphereCollection() const { return _sphereCollection; }
TriangleIndices& getTriangleIndices() { return _triangleIndices; }
const TriangleIndices& getTriangleIndices() const { return _triangleIndices; }
@ -92,6 +97,7 @@ protected:
void setHalfExtents(const glm::vec3& halfExtents);
QUrl _url; // url for model of convex collision hulls
SphereCollection _sphereCollection;
PointCollection _pointCollection;
TriangleIndices _triangleIndices;
glm::vec3 _halfExtents = glm::vec3(0.0f);

View file

@ -210,6 +210,7 @@ void TabletScriptingInterface::playSound(TabletAudioEvents aEvent) {
options.stereo = sound->isStereo();
options.ambisonic = sound->isAmbisonic();
options.localOnly = true;
options.positionSet = false; // system sound
AudioInjectorPointer injector = AudioInjector::playSoundAndDelete(sound, options);
}

View file

@ -16,20 +16,31 @@
QString OculusAPIPlugin::NAME { "Oculus Rift" };
OculusAPIPlugin::OculusAPIPlugin() {
_session = hifi::ovr::acquireRenderSession();
bool OculusAPIPlugin::init() {
if (qApp->property(hifi::properties::OCULUS_STORE).toBool()) {
_session = hifi::ovr::acquireRenderSession();
}
return _session;
}
OculusAPIPlugin::~OculusAPIPlugin() {
hifi::ovr::releaseRenderSession(_session);
void OculusAPIPlugin::shutdown() {
if (isRunning()) {
hifi::ovr::releaseRenderSession(_session);
}
}
bool OculusAPIPlugin::isRunning() const {
return (qApp->property(hifi::properties::OCULUS_STORE).toBool());
return _session;
}
void OculusAPIPlugin::requestNonceAndUserID(NonceUserIDCallback callback) {
#ifdef OCULUS_APP_ID
if (!isRunning()) {
qCWarning(oculusLog) << "Oculus request failed: Oculus platform plugin not running";
callback("", "");
return;
}
_nonceUserIDCallback = callback;
ovr_User_GetUserProof();
ovr_User_GetLoggedInUser();
@ -38,72 +49,74 @@ void OculusAPIPlugin::requestNonceAndUserID(NonceUserIDCallback callback) {
void OculusAPIPlugin::handleOVREvents() {
#ifdef OCULUS_APP_ID
if (qApp->property(hifi::properties::OCULUS_STORE).toBool()) {
// pop messages to see if we got a return for an entitlement check
ovrMessageHandle message { nullptr };
if (!isRunning()) {
return;
}
// pop the next message to check, if there is one
while ((message = ovr_PopMessage())) {
switch (ovr_Message_GetType(message)) {
case ovrMessage_Entitlement_GetIsViewerEntitled: {
if (!ovr_Message_IsError(message)) {
// this viewer is entitled, no need to flag anything
qCDebug(oculusLog) << "Oculus Platform entitlement check succeeded, proceeding normally";
} else {
// we failed the entitlement check, quit
qCDebug(oculusLog) << "Oculus Platform entitlement check failed, app will now quit" << OCULUS_APP_ID;
QMetaObject::invokeMethod(qApp, "quit");
}
break;
}
case ovrMessage_User_Get: {
if (!ovr_Message_IsError(message)) {
qCDebug(oculusLog) << "Oculus Platform user retrieval succeeded";
ovrUserHandle user = ovr_Message_GetUser(message);
_user = ovr_User_GetOculusID(user);
// went all the way through the `requestNonceAndUserID()` pipeline successfully.
} else {
qCDebug(oculusLog) << "Oculus Platform user retrieval failed" << QString(ovr_Error_GetMessage(ovr_Message_GetError(message)));
// emit the signal so we don't hang for it anywhere else.
_user = "";
}
break;
}
case ovrMessage_User_GetLoggedInUser: {
if (!ovr_Message_IsError(message)) {
ovrUserHandle user = ovr_Message_GetUser(message);
_userID = ovr_User_GetID(user);
ovr_User_Get(_userID);
} else {
qCDebug(oculusLog) << "Oculus Platform user ID retrieval failed" << QString(ovr_Error_GetMessage(ovr_Message_GetError(message)));
// emit the signal so we don't hang for it anywhere else.
}
_userIDChanged = true;
break;
}
case ovrMessage_User_GetUserProof: {
if (!ovr_Message_IsError(message)) {
ovrUserProofHandle userProof = ovr_Message_GetUserProof(message);
_nonce = ovr_UserProof_GetNonce(userProof);
qCDebug(oculusLog) << "Oculus Platform nonce retrieval succeeded: " << _nonce;
} else {
qCDebug(oculusLog) << "Oculus Platform nonce retrieval failed" << QString(ovr_Error_GetMessage(ovr_Message_GetError(message)));
_nonce = "";
// emit the signal so we don't hang for it anywhere else.
}
_nonceChanged = true;
break;
// pop messages to see if we got a return for an entitlement check
ovrMessageHandle message { nullptr };
// pop the next message to check, if there is one
while ((message = ovr_PopMessage())) {
switch (ovr_Message_GetType(message)) {
case ovrMessage_Entitlement_GetIsViewerEntitled: {
if (!ovr_Message_IsError(message)) {
// this viewer is entitled, no need to flag anything
qCDebug(oculusLog) << "Oculus Platform entitlement check succeeded, proceeding normally";
} else {
// we failed the entitlement check, quit
qCDebug(oculusLog) << "Oculus Platform entitlement check failed, app will now quit" << OCULUS_APP_ID;
QMetaObject::invokeMethod(qApp, "quit");
}
break;
}
if (_nonceChanged && _userIDChanged) {
_nonceUserIDCallback(_nonce, QString::number(_userID));
_nonceChanged = _userIDChanged = false;
case ovrMessage_User_Get: {
if (!ovr_Message_IsError(message)) {
qCDebug(oculusLog) << "Oculus Platform user retrieval succeeded";
ovrUserHandle user = ovr_Message_GetUser(message);
_user = ovr_User_GetOculusID(user);
// went all the way through the `requestNonceAndUserID()` pipeline successfully.
} else {
qCDebug(oculusLog) << "Oculus Platform user retrieval failed" << QString(ovr_Error_GetMessage(ovr_Message_GetError(message)));
// emit the signal so we don't hang for it anywhere else.
_user = "";
}
break;
}
case ovrMessage_User_GetLoggedInUser: {
if (!ovr_Message_IsError(message)) {
ovrUserHandle user = ovr_Message_GetUser(message);
_userID = ovr_User_GetID(user);
ovr_User_Get(_userID);
} else {
qCDebug(oculusLog) << "Oculus Platform user ID retrieval failed" << QString(ovr_Error_GetMessage(ovr_Message_GetError(message)));
// emit the signal so we don't hang for it anywhere else.
}
_userIDChanged = true;
break;
}
case ovrMessage_User_GetUserProof: {
if (!ovr_Message_IsError(message)) {
ovrUserProofHandle userProof = ovr_Message_GetUserProof(message);
_nonce = ovr_UserProof_GetNonce(userProof);
qCDebug(oculusLog) << "Oculus Platform nonce retrieval succeeded: " << _nonce;
} else {
qCDebug(oculusLog) << "Oculus Platform nonce retrieval failed" << QString(ovr_Error_GetMessage(ovr_Message_GetError(message)));
_nonce = "";
// emit the signal so we don't hang for it anywhere else.
}
_nonceChanged = true;
break;
}
// free the message handle to cleanup and not leak
ovr_FreeMessage(message);
}
if (_nonceChanged && _userIDChanged) {
_nonceUserIDCallback(_nonce, QString::number(_userID));
_nonceChanged = _userIDChanged = false;
}
// free the message handle to cleanup and not leak
ovr_FreeMessage(message);
}
#endif
}

View file

@ -16,13 +16,17 @@
class OculusAPIPlugin : public OculusPlatformPlugin {
public:
OculusAPIPlugin();
virtual ~OculusAPIPlugin();
OculusAPIPlugin() = default;
virtual ~OculusAPIPlugin() = default;
QString getName() const { return NAME; }
QString getOculusUserID() const { return _user; };
bool isRunning() const;
bool init();
void shutdown();
virtual void requestNonceAndUserID(NonceUserIDCallback callback);
virtual void handleOVREvents();

View file

@ -68,14 +68,23 @@ var window = new OverlayWindow({
if (hasPosition) {
window.setPosition(windowX, windowY);
};
function recenterWindow() {
window.setPosition(100, 100);
}
window.visibleChanged.connect(function() {
if (!window.visible) {
window.setVisible(true);
recenterWindow();
}
});
HMD.displayModeChanged.connect(function(isHMDMode) {
recenterWindow();
});
window.closed.connect(function () { Script.stop(); });
function shouldLogMessage(scriptFileName) {