mirror of
https://github.com/overte-org/overte.git
synced 2025-08-09 18:13:05 +02:00
Merge branch 'master' into M17853
This commit is contained in:
commit
ecb58a9629
79 changed files with 1695 additions and 1007 deletions
|
@ -98,13 +98,17 @@ public class FriendsFragment extends Fragment {
|
||||||
|
|
||||||
mUsersAdapter.setListener(new UserListAdapter.AdapterListener() {
|
mUsersAdapter.setListener(new UserListAdapter.AdapterListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onEmptyAdapter() {
|
public void onEmptyAdapter(boolean shouldStopRefreshing) {
|
||||||
mSwipeRefreshLayout.setRefreshing(false);
|
if (shouldStopRefreshing) {
|
||||||
|
mSwipeRefreshLayout.setRefreshing(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onNonEmptyAdapter() {
|
public void onNonEmptyAdapter(boolean shouldStopRefreshing) {
|
||||||
mSwipeRefreshLayout.setRefreshing(false);
|
if (shouldStopRefreshing) {
|
||||||
|
mSwipeRefreshLayout.setRefreshing(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -115,6 +119,8 @@ public class FriendsFragment extends Fragment {
|
||||||
|
|
||||||
mUsersView.setAdapter(mUsersAdapter);
|
mUsersView.setAdapter(mUsersAdapter);
|
||||||
|
|
||||||
|
mUsersAdapter.startLoad();
|
||||||
|
|
||||||
mSlidingUpPanelLayout.setFadeOnClickListener(new View.OnClickListener() {
|
mSlidingUpPanelLayout.setFadeOnClickListener(new View.OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View view) {
|
public void onClick(View view) {
|
||||||
|
|
|
@ -76,18 +76,22 @@ public class HomeFragment extends Fragment {
|
||||||
});
|
});
|
||||||
mDomainAdapter.setListener(new DomainAdapter.AdapterListener() {
|
mDomainAdapter.setListener(new DomainAdapter.AdapterListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onEmptyAdapter() {
|
public void onEmptyAdapter(boolean shouldStopRefreshing) {
|
||||||
searchNoResultsView.setText(R.string.search_no_results);
|
searchNoResultsView.setText(R.string.search_no_results);
|
||||||
searchNoResultsView.setVisibility(View.VISIBLE);
|
searchNoResultsView.setVisibility(View.VISIBLE);
|
||||||
mDomainsView.setVisibility(View.GONE);
|
mDomainsView.setVisibility(View.GONE);
|
||||||
mSwipeRefreshLayout.setRefreshing(false);
|
if (shouldStopRefreshing) {
|
||||||
|
mSwipeRefreshLayout.setRefreshing(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onNonEmptyAdapter() {
|
public void onNonEmptyAdapter(boolean shouldStopRefreshing) {
|
||||||
searchNoResultsView.setVisibility(View.GONE);
|
searchNoResultsView.setVisibility(View.GONE);
|
||||||
mDomainsView.setVisibility(View.VISIBLE);
|
mDomainsView.setVisibility(View.VISIBLE);
|
||||||
mSwipeRefreshLayout.setRefreshing(false);
|
if (shouldStopRefreshing) {
|
||||||
|
mSwipeRefreshLayout.setRefreshing(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -96,11 +100,20 @@ public class HomeFragment extends Fragment {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
mDomainsView.setAdapter(mDomainAdapter);
|
mDomainsView.setAdapter(mDomainAdapter);
|
||||||
|
mDomainAdapter.startLoad();
|
||||||
|
|
||||||
mSearchView = rootView.findViewById(R.id.searchView);
|
mSearchView = rootView.findViewById(R.id.searchView);
|
||||||
mSearchIconView = rootView.findViewById(R.id.search_mag_icon);
|
mSearchIconView = rootView.findViewById(R.id.search_mag_icon);
|
||||||
mClearSearch = rootView.findViewById(R.id.search_clear);
|
mClearSearch = rootView.findViewById(R.id.search_clear);
|
||||||
|
|
||||||
|
getActivity().getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
|
||||||
|
|
||||||
|
return rootView;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStart() {
|
||||||
|
super.onStart();
|
||||||
mSearchView.addTextChangedListener(new TextWatcher() {
|
mSearchView.addTextChangedListener(new TextWatcher() {
|
||||||
@Override
|
@Override
|
||||||
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {}
|
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {}
|
||||||
|
@ -142,10 +155,6 @@ public class HomeFragment extends Fragment {
|
||||||
mDomainAdapter.loadDomains(mSearchView.getText().toString(), true);
|
mDomainAdapter.loadDomains(mSearchView.getText().toString(), true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
getActivity().getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
|
|
||||||
|
|
||||||
return rootView;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -12,6 +12,7 @@ import android.widget.TextView;
|
||||||
|
|
||||||
import com.squareup.picasso.Picasso;
|
import com.squareup.picasso.Picasso;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import io.highfidelity.hifiinterface.R;
|
import io.highfidelity.hifiinterface.R;
|
||||||
|
@ -36,19 +37,41 @@ public class DomainAdapter extends RecyclerView.Adapter<DomainAdapter.ViewHolder
|
||||||
// references to our domains
|
// references to our domains
|
||||||
private Domain[] mDomains = {};
|
private Domain[] mDomains = {};
|
||||||
|
|
||||||
|
private static Domain[] DOMAINS_TMP_CACHE = {};
|
||||||
|
|
||||||
public DomainAdapter(Context c, String protocol, String lastLocation) {
|
public DomainAdapter(Context c, String protocol, String lastLocation) {
|
||||||
mContext = c;
|
mContext = c;
|
||||||
this.mInflater = LayoutInflater.from(mContext);
|
this.mInflater = LayoutInflater.from(mContext);
|
||||||
mProtocol = protocol;
|
mProtocol = protocol;
|
||||||
mLastLocation = lastLocation;
|
mLastLocation = lastLocation;
|
||||||
domainProvider = new UserStoryDomainProvider(mProtocol);
|
domainProvider = new UserStoryDomainProvider(mProtocol);
|
||||||
loadDomains("", true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setListener(AdapterListener adapterListener) {
|
public void setListener(AdapterListener adapterListener) {
|
||||||
mAdapterListener = adapterListener;
|
mAdapterListener = adapterListener;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void startLoad() {
|
||||||
|
useTmpCachedDomains();
|
||||||
|
loadDomains("", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void useTmpCachedDomains() {
|
||||||
|
synchronized (this) {
|
||||||
|
if (DOMAINS_TMP_CACHE != null && DOMAINS_TMP_CACHE.length > 0) {
|
||||||
|
mDomains = Arrays.copyOf(DOMAINS_TMP_CACHE, DOMAINS_TMP_CACHE.length);
|
||||||
|
notifyDataSetChanged();
|
||||||
|
if (mAdapterListener != null) {
|
||||||
|
if (mDomains.length == 0) {
|
||||||
|
mAdapterListener.onEmptyAdapter(false);
|
||||||
|
} else {
|
||||||
|
mAdapterListener.onNonEmptyAdapter(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void loadDomains(String filterText, boolean forceRefresh) {
|
public void loadDomains(String filterText, boolean forceRefresh) {
|
||||||
domainProvider.retrieve(filterText, new DomainProvider.DomainCallback() {
|
domainProvider.retrieve(filterText, new DomainProvider.DomainCallback() {
|
||||||
@Override
|
@Override
|
||||||
|
@ -60,13 +83,18 @@ public class DomainAdapter extends RecyclerView.Adapter<DomainAdapter.ViewHolder
|
||||||
overrideDefaultThumbnails(domain);
|
overrideDefaultThumbnails(domain);
|
||||||
|
|
||||||
mDomains = new Domain[domain.size()];
|
mDomains = new Domain[domain.size()];
|
||||||
mDomains = domain.toArray(mDomains);
|
synchronized (this) {
|
||||||
notifyDataSetChanged();
|
domain.toArray(mDomains);
|
||||||
if (mAdapterListener != null) {
|
if (filterText.isEmpty()) {
|
||||||
if (mDomains.length == 0) {
|
DOMAINS_TMP_CACHE = Arrays.copyOf(mDomains, mDomains.length);
|
||||||
mAdapterListener.onEmptyAdapter();
|
}
|
||||||
} else {
|
notifyDataSetChanged();
|
||||||
mAdapterListener.onNonEmptyAdapter();
|
if (mAdapterListener != null) {
|
||||||
|
if (mDomains.length == 0) {
|
||||||
|
mAdapterListener.onEmptyAdapter(true);
|
||||||
|
} else {
|
||||||
|
mAdapterListener.onNonEmptyAdapter(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -112,8 +140,6 @@ public class DomainAdapter extends RecyclerView.Adapter<DomainAdapter.ViewHolder
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBindViewHolder(ViewHolder holder, int position) {
|
public void onBindViewHolder(ViewHolder holder, int position) {
|
||||||
// TODO
|
|
||||||
//holder.thumbnail.setImageResource(mDomains[position].thumbnail);
|
|
||||||
Domain domain = mDomains[position];
|
Domain domain = mDomains[position];
|
||||||
holder.mDomainName.setText(domain.name);
|
holder.mDomainName.setText(domain.name);
|
||||||
Uri uri = Uri.parse(domain.thumbnail);
|
Uri uri = Uri.parse(domain.thumbnail);
|
||||||
|
@ -164,8 +190,8 @@ public class DomainAdapter extends RecyclerView.Adapter<DomainAdapter.ViewHolder
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface AdapterListener {
|
public interface AdapterListener {
|
||||||
void onEmptyAdapter();
|
void onEmptyAdapter(boolean shouldStopRefreshing);
|
||||||
void onNonEmptyAdapter();
|
void onNonEmptyAdapter(boolean shouldStopRefreshing);
|
||||||
void onError(Exception e, String message);
|
void onError(Exception e, String message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,28 +37,57 @@ public class UserListAdapter extends RecyclerView.Adapter<UserListAdapter.ViewHo
|
||||||
private ItemClickListener mClickListener;
|
private ItemClickListener mClickListener;
|
||||||
private AdapterListener mAdapterListener;
|
private AdapterListener mAdapterListener;
|
||||||
|
|
||||||
|
private static List<User> USERS_TMP_CACHE;
|
||||||
|
|
||||||
public UserListAdapter(Context c, UsersProvider usersProvider) {
|
public UserListAdapter(Context c, UsersProvider usersProvider) {
|
||||||
mContext = c;
|
mContext = c;
|
||||||
mInflater = LayoutInflater.from(mContext);
|
mInflater = LayoutInflater.from(mContext);
|
||||||
mProvider = usersProvider;
|
mProvider = usersProvider;
|
||||||
loadUsers();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setListener(AdapterListener adapterListener) {
|
public void setListener(AdapterListener adapterListener) {
|
||||||
mAdapterListener = adapterListener;
|
mAdapterListener = adapterListener;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void startLoad() {
|
||||||
|
useTmpCachedUsers();
|
||||||
|
loadUsers();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void useTmpCachedUsers() {
|
||||||
|
synchronized (this) {
|
||||||
|
if (USERS_TMP_CACHE != null && USERS_TMP_CACHE.size() > 0) {
|
||||||
|
mUsers = new ArrayList<>(USERS_TMP_CACHE.size());
|
||||||
|
mUsers.addAll(USERS_TMP_CACHE);
|
||||||
|
notifyDataSetChanged();
|
||||||
|
if (mAdapterListener != null) {
|
||||||
|
if (mUsers.isEmpty()) {
|
||||||
|
mAdapterListener.onEmptyAdapter(false);
|
||||||
|
} else {
|
||||||
|
mAdapterListener.onNonEmptyAdapter(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void loadUsers() {
|
public void loadUsers() {
|
||||||
mProvider.retrieve(new UsersProvider.UsersCallback() {
|
mProvider.retrieve(new UsersProvider.UsersCallback() {
|
||||||
@Override
|
@Override
|
||||||
public void retrieveOk(List<User> users) {
|
public void retrieveOk(List<User> users) {
|
||||||
mUsers = new ArrayList<>(users);
|
mUsers = new ArrayList<>(users);
|
||||||
notifyDataSetChanged();
|
notifyDataSetChanged();
|
||||||
if (mAdapterListener != null) {
|
|
||||||
if (mUsers.isEmpty()) {
|
synchronized (this) {
|
||||||
mAdapterListener.onEmptyAdapter();
|
USERS_TMP_CACHE = new ArrayList<>(mUsers.size());
|
||||||
} else {
|
USERS_TMP_CACHE.addAll(mUsers);
|
||||||
mAdapterListener.onNonEmptyAdapter();
|
|
||||||
|
if (mAdapterListener != null) {
|
||||||
|
if (mUsers.isEmpty()) {
|
||||||
|
mAdapterListener.onEmptyAdapter(true);
|
||||||
|
} else {
|
||||||
|
mAdapterListener.onNonEmptyAdapter(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -240,8 +269,9 @@ public class UserListAdapter extends RecyclerView.Adapter<UserListAdapter.ViewHo
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface AdapterListener {
|
public interface AdapterListener {
|
||||||
void onEmptyAdapter();
|
void onEmptyAdapter(boolean shouldStopRefreshing);
|
||||||
void onNonEmptyAdapter();
|
void onNonEmptyAdapter(boolean shouldStopRefreshing);
|
||||||
void onError(Exception e, String message);
|
void onError(Exception e, String message);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -463,19 +463,15 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect
|
||||||
limitedNodeList->eachNodeBreakable([nodeConnection, username, &existingNodeID](const SharedNodePointer& node){
|
limitedNodeList->eachNodeBreakable([nodeConnection, username, &existingNodeID](const SharedNodePointer& node){
|
||||||
|
|
||||||
if (node->getPublicSocket() == nodeConnection.publicSockAddr && node->getLocalSocket() == nodeConnection.localSockAddr) {
|
if (node->getPublicSocket() == nodeConnection.publicSockAddr && node->getLocalSocket() == nodeConnection.localSockAddr) {
|
||||||
// we have a node that already has these exact sockets - this can occur if a node
|
// we have a node that already has these exact sockets
|
||||||
// is failing to connect to the domain
|
// this can occur if a node is failing to connect to the domain
|
||||||
|
|
||||||
// we'll re-use the existing node ID
|
// remove the old node before adding the new node
|
||||||
// as long as the user hasn't changed their username (by logging in or logging out)
|
qDebug() << "Deleting existing connection from same sockaddr: " << node->getUUID();
|
||||||
auto existingNodeData = static_cast<DomainServerNodeData*>(node->getLinkedData());
|
existingNodeID = node->getUUID();
|
||||||
|
return false;
|
||||||
if (existingNodeData->getUsername() == username) {
|
|
||||||
qDebug() << "Deleting existing connection from same sockaddr: " << node->getUUID();
|
|
||||||
existingNodeID = node->getUUID();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -119,6 +119,22 @@ Item {
|
||||||
visible: root.expanded
|
visible: root.expanded
|
||||||
text: "Avatars NOT Updated: " + root.notUpdatedAvatarCount
|
text: "Avatars NOT Updated: " + root.notUpdatedAvatarCount
|
||||||
}
|
}
|
||||||
|
StatText {
|
||||||
|
visible: root.expanded
|
||||||
|
text: "Total picks:\n " +
|
||||||
|
root.stylusPicksCount + " styluses\n " +
|
||||||
|
root.rayPicksCount + " rays\n " +
|
||||||
|
root.parabolaPicksCount + " parabolas\n " +
|
||||||
|
root.collisionPicksCount + " colliders"
|
||||||
|
}
|
||||||
|
StatText {
|
||||||
|
visible: root.expanded
|
||||||
|
text: "Intersection calls: Entities/Overlays/Avatars/HUD\n " +
|
||||||
|
"Styluses:\t" + root.stylusPicksUpdated.x + "/" + root.stylusPicksUpdated.y + "/" + root.stylusPicksUpdated.z + "/" + root.stylusPicksUpdated.w + "\n " +
|
||||||
|
"Rays:\t" + root.rayPicksUpdated.x + "/" + root.rayPicksUpdated.y + "/" + root.rayPicksUpdated.z + "/" + root.rayPicksUpdated.w + "\n " +
|
||||||
|
"Parabolas:\t" + root.parabolaPicksUpdated.x + "/" + root.parabolaPicksUpdated.y + "/" + root.parabolaPicksUpdated.z + "/" + root.parabolaPicksUpdated.w + "\n " +
|
||||||
|
"Colliders:\t" + root.collisionPicksUpdated.x + "/" + root.collisionPicksUpdated.y + "/" + root.collisionPicksUpdated.z + "/" + root.collisionPicksUpdated.w
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -52,12 +52,18 @@ Item {
|
||||||
id: back
|
id: back
|
||||||
enabledColor: hifi.colors.darkGray
|
enabledColor: hifi.colors.darkGray
|
||||||
disabledColor: hifi.colors.lightGrayText
|
disabledColor: hifi.colors.lightGrayText
|
||||||
enabled: historyIndex > 0
|
enabled: true
|
||||||
text: "BACK"
|
text: "BACK"
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
onClicked: goBack()
|
onClicked: {
|
||||||
|
if (historyIndex > 0) {
|
||||||
|
goBack();
|
||||||
|
} else {
|
||||||
|
closeWebEngine();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -726,6 +726,9 @@ static const QString STATE_SNAP_TURN = "SnapTurn";
|
||||||
static const QString STATE_ADVANCED_MOVEMENT_CONTROLS = "AdvancedMovement";
|
static const QString STATE_ADVANCED_MOVEMENT_CONTROLS = "AdvancedMovement";
|
||||||
static const QString STATE_GROUNDED = "Grounded";
|
static const QString STATE_GROUNDED = "Grounded";
|
||||||
static const QString STATE_NAV_FOCUSED = "NavigationFocused";
|
static const QString STATE_NAV_FOCUSED = "NavigationFocused";
|
||||||
|
static const QString STATE_PLATFORM_WINDOWS = "PlatformWindows";
|
||||||
|
static const QString STATE_PLATFORM_MAC = "PlatformMac";
|
||||||
|
static const QString STATE_PLATFORM_ANDROID = "PlatformAndroid";
|
||||||
|
|
||||||
// Statically provided display and input plugins
|
// Statically provided display and input plugins
|
||||||
extern DisplayPluginList getDisplayPlugins();
|
extern DisplayPluginList getDisplayPlugins();
|
||||||
|
@ -909,7 +912,8 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) {
|
||||||
DependencyManager::set<MessagesClient>();
|
DependencyManager::set<MessagesClient>();
|
||||||
controller::StateController::setStateVariables({ { STATE_IN_HMD, STATE_CAMERA_FULL_SCREEN_MIRROR,
|
controller::StateController::setStateVariables({ { STATE_IN_HMD, STATE_CAMERA_FULL_SCREEN_MIRROR,
|
||||||
STATE_CAMERA_FIRST_PERSON, STATE_CAMERA_THIRD_PERSON, STATE_CAMERA_ENTITY, STATE_CAMERA_INDEPENDENT,
|
STATE_CAMERA_FIRST_PERSON, STATE_CAMERA_THIRD_PERSON, STATE_CAMERA_ENTITY, STATE_CAMERA_INDEPENDENT,
|
||||||
STATE_SNAP_TURN, STATE_ADVANCED_MOVEMENT_CONTROLS, STATE_GROUNDED, STATE_NAV_FOCUSED } });
|
STATE_SNAP_TURN, STATE_ADVANCED_MOVEMENT_CONTROLS, STATE_GROUNDED, STATE_NAV_FOCUSED,
|
||||||
|
STATE_PLATFORM_WINDOWS, STATE_PLATFORM_MAC, STATE_PLATFORM_WINDOWS } });
|
||||||
DependencyManager::set<UserInputMapper>();
|
DependencyManager::set<UserInputMapper>();
|
||||||
DependencyManager::set<controller::ScriptingInterface, ControllerScriptingInterface>();
|
DependencyManager::set<controller::ScriptingInterface, ControllerScriptingInterface>();
|
||||||
DependencyManager::set<InterfaceParentFinder>();
|
DependencyManager::set<InterfaceParentFinder>();
|
||||||
|
@ -1683,6 +1687,27 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
||||||
_applicationStateDevice->setInputVariant(STATE_NAV_FOCUSED, []() -> float {
|
_applicationStateDevice->setInputVariant(STATE_NAV_FOCUSED, []() -> float {
|
||||||
return DependencyManager::get<OffscreenUi>()->navigationFocused() ? 1 : 0;
|
return DependencyManager::get<OffscreenUi>()->navigationFocused() ? 1 : 0;
|
||||||
});
|
});
|
||||||
|
_applicationStateDevice->setInputVariant(STATE_PLATFORM_WINDOWS, []() -> float {
|
||||||
|
#if defined(Q_OS_WIN)
|
||||||
|
return 1;
|
||||||
|
#else
|
||||||
|
return 0;
|
||||||
|
#endif
|
||||||
|
});
|
||||||
|
_applicationStateDevice->setInputVariant(STATE_PLATFORM_MAC, []() -> float {
|
||||||
|
#if defined(Q_OS_MAC)
|
||||||
|
return 1;
|
||||||
|
#else
|
||||||
|
return 0;
|
||||||
|
#endif
|
||||||
|
});
|
||||||
|
_applicationStateDevice->setInputVariant(STATE_PLATFORM_ANDROID, []() -> float {
|
||||||
|
#if defined(Q_OS_ANDROID)
|
||||||
|
return 1;
|
||||||
|
#else
|
||||||
|
return 0;
|
||||||
|
#endif
|
||||||
|
});
|
||||||
|
|
||||||
// Setup the _keyboardMouseDevice, _touchscreenDevice, _touchscreenVirtualPadDevice and the user input mapper with the default bindings
|
// Setup the _keyboardMouseDevice, _touchscreenDevice, _touchscreenVirtualPadDevice and the user input mapper with the default bindings
|
||||||
userInputMapper->registerDevice(_keyboardMouseDevice->getInputDevice());
|
userInputMapper->registerDevice(_keyboardMouseDevice->getInputDevice());
|
||||||
|
@ -1735,11 +1760,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
||||||
QTimer* settingsTimer = new QTimer();
|
QTimer* settingsTimer = new QTimer();
|
||||||
moveToNewNamedThread(settingsTimer, "Settings Thread", [this, settingsTimer]{
|
moveToNewNamedThread(settingsTimer, "Settings Thread", [this, settingsTimer]{
|
||||||
connect(qApp, &Application::beforeAboutToQuit, [this, settingsTimer]{
|
connect(qApp, &Application::beforeAboutToQuit, [this, settingsTimer]{
|
||||||
bool autoLogout = Setting::Handle<bool>(AUTO_LOGOUT_SETTING_NAME, false).get();
|
|
||||||
if (autoLogout) {
|
|
||||||
auto accountManager = DependencyManager::get<AccountManager>();
|
|
||||||
accountManager->logout();
|
|
||||||
}
|
|
||||||
// Disconnect the signal from the save settings
|
// Disconnect the signal from the save settings
|
||||||
QObject::disconnect(settingsTimer, &QTimer::timeout, this, &Application::saveSettings);
|
QObject::disconnect(settingsTimer, &QTimer::timeout, this, &Application::saveSettings);
|
||||||
// Stop the settings timer
|
// Stop the settings timer
|
||||||
|
@ -1841,6 +1861,10 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
||||||
});
|
});
|
||||||
|
|
||||||
EntityTree::setAddMaterialToEntityOperator([this](const QUuid& entityID, graphics::MaterialLayer material, const std::string& parentMaterialName) {
|
EntityTree::setAddMaterialToEntityOperator([this](const QUuid& entityID, graphics::MaterialLayer material, const std::string& parentMaterialName) {
|
||||||
|
if (_aboutToQuit) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// try to find the renderable
|
// try to find the renderable
|
||||||
auto renderable = getEntities()->renderableForEntityId(entityID);
|
auto renderable = getEntities()->renderableForEntityId(entityID);
|
||||||
if (renderable) {
|
if (renderable) {
|
||||||
|
@ -1856,6 +1880,10 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
EntityTree::setRemoveMaterialFromEntityOperator([this](const QUuid& entityID, graphics::MaterialPointer material, const std::string& parentMaterialName) {
|
EntityTree::setRemoveMaterialFromEntityOperator([this](const QUuid& entityID, graphics::MaterialPointer material, const std::string& parentMaterialName) {
|
||||||
|
if (_aboutToQuit) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// try to find the renderable
|
// try to find the renderable
|
||||||
auto renderable = getEntities()->renderableForEntityId(entityID);
|
auto renderable = getEntities()->renderableForEntityId(entityID);
|
||||||
if (renderable) {
|
if (renderable) {
|
||||||
|
@ -2483,6 +2511,11 @@ void Application::cleanupBeforeQuit() {
|
||||||
}
|
}
|
||||||
DependencyManager::destroy<ScriptEngines>();
|
DependencyManager::destroy<ScriptEngines>();
|
||||||
|
|
||||||
|
bool autoLogout = Setting::Handle<bool>(AUTO_LOGOUT_SETTING_NAME, false).get();
|
||||||
|
if (autoLogout) {
|
||||||
|
DependencyManager::get<AccountManager>()->removeAccountFromFile();
|
||||||
|
}
|
||||||
|
|
||||||
_displayPlugin.reset();
|
_displayPlugin.reset();
|
||||||
PluginManager::getInstance()->shutdown();
|
PluginManager::getInstance()->shutdown();
|
||||||
|
|
||||||
|
|
|
@ -312,6 +312,9 @@ public:
|
||||||
|
|
||||||
Q_INVOKABLE void copyToClipboard(const QString& text);
|
Q_INVOKABLE void copyToClipboard(const QString& text);
|
||||||
|
|
||||||
|
int getOtherAvatarsReplicaCount() { return DependencyManager::get<AvatarHashMap>()->getReplicaCount(); }
|
||||||
|
void setOtherAvatarsReplicaCount(int count) { DependencyManager::get<AvatarHashMap>()->setReplicaCount(count); }
|
||||||
|
|
||||||
#if defined(Q_OS_ANDROID)
|
#if defined(Q_OS_ANDROID)
|
||||||
void beforeEnterBackground();
|
void beforeEnterBackground();
|
||||||
void enterBackground();
|
void enterBackground();
|
||||||
|
|
|
@ -145,20 +145,9 @@ void AvatarBookmarks::removeBookmark(const QString& bookmarkName) {
|
||||||
emit bookmarkDeleted(bookmarkName);
|
emit bookmarkDeleted(bookmarkName);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isWearableEntity(const EntityItemPointer& entity) {
|
|
||||||
return entity->isVisible() && (entity->getParentJointIndex() != INVALID_JOINT_INDEX || (entity->getType() == EntityTypes::Model && (std::static_pointer_cast<ModelEntityItem>(entity))->getRelayParentJoints()))
|
|
||||||
&& (entity->getParentID() == DependencyManager::get<NodeList>()->getSessionUUID() || entity->getParentID() == DependencyManager::get<AvatarManager>()->getMyAvatar()->getSelfID());
|
|
||||||
}
|
|
||||||
|
|
||||||
void AvatarBookmarks::updateAvatarEntities(const QVariantList &avatarEntities) {
|
void AvatarBookmarks::updateAvatarEntities(const QVariantList &avatarEntities) {
|
||||||
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
|
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
|
||||||
auto treeRenderer = DependencyManager::get<EntityTreeRenderer>();
|
myAvatar->removeWearableAvatarEntities();
|
||||||
EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr;
|
|
||||||
myAvatar->removeAvatarEntities([&](const QUuid& entityID) {
|
|
||||||
auto entity = entityTree->findEntityByID(entityID);
|
|
||||||
return entity && isWearableEntity(entity);
|
|
||||||
});
|
|
||||||
|
|
||||||
addAvatarEntities(avatarEntities);
|
addAvatarEntities(avatarEntities);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -183,10 +172,7 @@ void AvatarBookmarks::loadBookmark(const QString& bookmarkName) {
|
||||||
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
|
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
|
||||||
auto treeRenderer = DependencyManager::get<EntityTreeRenderer>();
|
auto treeRenderer = DependencyManager::get<EntityTreeRenderer>();
|
||||||
EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr;
|
EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr;
|
||||||
myAvatar->removeAvatarEntities([&](const QUuid& entityID) {
|
myAvatar->removeWearableAvatarEntities();
|
||||||
auto entity = entityTree->findEntityByID(entityID);
|
|
||||||
return entity && isWearableEntity(entity);
|
|
||||||
});
|
|
||||||
const QString& avatarUrl = bookmark.value(ENTRY_AVATAR_URL, "").toString();
|
const QString& avatarUrl = bookmark.value(ENTRY_AVATAR_URL, "").toString();
|
||||||
myAvatar->useFullAvatarURL(avatarUrl);
|
myAvatar->useFullAvatarURL(avatarUrl);
|
||||||
qCDebug(interfaceapp) << "Avatar On " << avatarUrl;
|
qCDebug(interfaceapp) << "Avatar On " << avatarUrl;
|
||||||
|
|
|
@ -46,6 +46,7 @@
|
||||||
#include "InterfaceLogging.h"
|
#include "InterfaceLogging.h"
|
||||||
#include "LocationBookmarks.h"
|
#include "LocationBookmarks.h"
|
||||||
#include "DeferredLightingEffect.h"
|
#include "DeferredLightingEffect.h"
|
||||||
|
#include "PickManager.h"
|
||||||
|
|
||||||
#include "AmbientOcclusionEffect.h"
|
#include "AmbientOcclusionEffect.h"
|
||||||
#include "RenderShadowTask.h"
|
#include "RenderShadowTask.h"
|
||||||
|
@ -451,6 +452,9 @@ Menu::Menu() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::ComputeBlendshapes, 0, true,
|
||||||
|
DependencyManager::get<ModelBlender>().data(), SLOT(setComputeBlendshapes(bool)));
|
||||||
|
|
||||||
// Developer > Assets >>>
|
// Developer > Assets >>>
|
||||||
// Menu item is not currently needed but code should be kept in case it proves useful again at some stage.
|
// Menu item is not currently needed but code should be kept in case it proves useful again at some stage.
|
||||||
//#define WANT_ASSET_MIGRATION
|
//#define WANT_ASSET_MIGRATION
|
||||||
|
@ -688,6 +692,11 @@ Menu::Menu() {
|
||||||
addCheckableActionToQMenuAndActionHash(physicsOptionsMenu, MenuOption::PhysicsShowBulletConstraints, 0, false, qApp, SLOT(setShowBulletConstraints(bool)));
|
addCheckableActionToQMenuAndActionHash(physicsOptionsMenu, MenuOption::PhysicsShowBulletConstraints, 0, false, qApp, SLOT(setShowBulletConstraints(bool)));
|
||||||
addCheckableActionToQMenuAndActionHash(physicsOptionsMenu, MenuOption::PhysicsShowBulletConstraintLimits, 0, false, qApp, SLOT(setShowBulletConstraintLimits(bool)));
|
addCheckableActionToQMenuAndActionHash(physicsOptionsMenu, MenuOption::PhysicsShowBulletConstraintLimits, 0, false, qApp, SLOT(setShowBulletConstraintLimits(bool)));
|
||||||
|
|
||||||
|
// Developer > Picking >>>
|
||||||
|
MenuWrapper* pickingOptionsMenu = developerMenu->addMenu("Picking");
|
||||||
|
addCheckableActionToQMenuAndActionHash(pickingOptionsMenu, MenuOption::ForceCoarsePicking, 0, false,
|
||||||
|
DependencyManager::get<PickManager>().data(), SLOT(setForceCoarsePicking(bool)));
|
||||||
|
|
||||||
// Developer > Display Crash Options
|
// Developer > Display Crash Options
|
||||||
addCheckableActionToQMenuAndActionHash(developerMenu, MenuOption::DisplayCrashOptions, 0, true);
|
addCheckableActionToQMenuAndActionHash(developerMenu, MenuOption::DisplayCrashOptions, 0, true);
|
||||||
// Developer > Crash >>>
|
// Developer > Crash >>>
|
||||||
|
|
|
@ -221,6 +221,8 @@ namespace MenuOption {
|
||||||
const QString NotificationSounds = "play_notification_sounds";
|
const QString NotificationSounds = "play_notification_sounds";
|
||||||
const QString NotificationSoundsSnapshot = "play_notification_sounds_snapshot";
|
const QString NotificationSoundsSnapshot = "play_notification_sounds_snapshot";
|
||||||
const QString NotificationSoundsTablet = "play_notification_sounds_tablet";
|
const QString NotificationSoundsTablet = "play_notification_sounds_tablet";
|
||||||
|
const QString ForceCoarsePicking = "Force Coarse Picking";
|
||||||
|
const QString ComputeBlendshapes = "Compute Blendshapes";
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif // hifi_Menu_h
|
#endif // hifi_Menu_h
|
||||||
|
|
|
@ -359,21 +359,21 @@ void AvatarManager::simulateAvatarFades(float deltaTime) {
|
||||||
QReadLocker locker(&_hashLock);
|
QReadLocker locker(&_hashLock);
|
||||||
QVector<AvatarSharedPointer>::iterator avatarItr = _avatarsToFade.begin();
|
QVector<AvatarSharedPointer>::iterator avatarItr = _avatarsToFade.begin();
|
||||||
const render::ScenePointer& scene = qApp->getMain3DScene();
|
const render::ScenePointer& scene = qApp->getMain3DScene();
|
||||||
|
render::Transaction transaction;
|
||||||
while (avatarItr != _avatarsToFade.end()) {
|
while (avatarItr != _avatarsToFade.end()) {
|
||||||
auto avatar = std::static_pointer_cast<Avatar>(*avatarItr);
|
auto avatar = std::static_pointer_cast<Avatar>(*avatarItr);
|
||||||
avatar->updateFadingStatus(scene);
|
avatar->updateFadingStatus(scene);
|
||||||
if (!avatar->isFading()) {
|
if (!avatar->isFading()) {
|
||||||
// fading to zero is such a rare event we push a unique transaction for each
|
// fading to zero is such a rare event we push a unique transaction for each
|
||||||
if (avatar->isInScene()) {
|
if (avatar->isInScene()) {
|
||||||
render::Transaction transaction;
|
|
||||||
avatar->removeFromScene(*avatarItr, scene, transaction);
|
avatar->removeFromScene(*avatarItr, scene, transaction);
|
||||||
scene->enqueueTransaction(transaction);
|
|
||||||
}
|
}
|
||||||
avatarItr = _avatarsToFade.erase(avatarItr);
|
avatarItr = _avatarsToFade.erase(avatarItr);
|
||||||
} else {
|
} else {
|
||||||
++avatarItr;
|
++avatarItr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
scene->enqueueTransaction(transaction);
|
||||||
}
|
}
|
||||||
|
|
||||||
AvatarSharedPointer AvatarManager::newSharedAvatar() {
|
AvatarSharedPointer AvatarManager::newSharedAvatar() {
|
||||||
|
@ -583,8 +583,14 @@ RayToAvatarIntersectionResult AvatarManager::findRayIntersectionVector(const Pic
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
glm::vec3 normDirection = glm::normalize(ray.direction);
|
// 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.
|
||||||
|
|
||||||
|
// TODO -- find a way to extract transformed avatar mesh data from the rendering engine.
|
||||||
|
|
||||||
|
std::vector<SortedAvatar> sortedAvatars;
|
||||||
auto avatarHashCopy = getHashCopy();
|
auto avatarHashCopy = getHashCopy();
|
||||||
for (auto avatarData : avatarHashCopy) {
|
for (auto avatarData : avatarHashCopy) {
|
||||||
auto avatar = std::static_pointer_cast<Avatar>(avatarData);
|
auto avatar = std::static_pointer_cast<Avatar>(avatarData);
|
||||||
|
@ -593,52 +599,65 @@ RayToAvatarIntersectionResult AvatarManager::findRayIntersectionVector(const Pic
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
float distance;
|
float distance = FLT_MAX;
|
||||||
BoxFace face;
|
#if 0
|
||||||
glm::vec3 surfaceNormal;
|
|
||||||
|
|
||||||
SkeletonModelPointer avatarModel = avatar->getSkeletonModel();
|
|
||||||
|
|
||||||
// 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.
|
|
||||||
|
|
||||||
// TODO -- find a way to extract transformed avatar mesh data from the rendering engine.
|
|
||||||
|
|
||||||
// if we weren't picking against the capsule, we would want to pick against the avatarBounds...
|
// if we weren't picking against the capsule, we would want to pick against the avatarBounds...
|
||||||
// AABox avatarBounds = avatarModel->getRenderableMeshBound();
|
SkeletonModelPointer avatarModel = avatar->getSkeletonModel();
|
||||||
// if (!avatarBounds.findRayIntersection(ray.origin, normDirection, distance, face, surfaceNormal)) {
|
AABox avatarBounds = avatarModel->getRenderableMeshBound();
|
||||||
// // ray doesn't intersect avatar's bounding-box
|
if (avatarBounds.contains(ray.origin)) {
|
||||||
// continue;
|
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 start;
|
||||||
glm::vec3 end;
|
glm::vec3 end;
|
||||||
float radius;
|
float radius;
|
||||||
avatar->getCapsule(start, end, radius);
|
avatar->getCapsule(start, end, radius);
|
||||||
bool intersects = findRayCapsuleIntersection(ray.origin, normDirection, start, end, radius, distance);
|
findRayCapsuleIntersection(ray.origin, ray.direction, start, end, radius, distance);
|
||||||
if (!intersects) {
|
#endif
|
||||||
// ray doesn't intersect avatar's capsule
|
|
||||||
continue;
|
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;
|
||||||
|
glm::vec3 surfaceNormal;
|
||||||
QVariantMap extraInfo;
|
QVariantMap extraInfo;
|
||||||
intersects = avatarModel->findRayIntersectionAgainstSubMeshes(ray.origin, normDirection,
|
SkeletonModelPointer avatarModel = sortedAvatar.second->getSkeletonModel();
|
||||||
distance, face, surfaceNormal, extraInfo, true);
|
if (avatarModel->findRayIntersectionAgainstSubMeshes(ray.origin, ray.direction, distance, face, surfaceNormal, extraInfo, true)) {
|
||||||
|
if (distance < result.distance) {
|
||||||
if (intersects && (!result.intersects || distance < result.distance)) {
|
result.intersects = true;
|
||||||
result.intersects = true;
|
result.avatarID = sortedAvatar.second->getID();
|
||||||
result.avatarID = avatar->getID();
|
result.distance = distance;
|
||||||
result.distance = distance;
|
result.face = face;
|
||||||
result.face = face;
|
result.surfaceNormal = surfaceNormal;
|
||||||
result.surfaceNormal = surfaceNormal;
|
result.extraInfo = extraInfo;
|
||||||
result.extraInfo = extraInfo;
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result.intersects) {
|
if (result.intersects) {
|
||||||
result.intersection = ray.origin + normDirection * result.distance;
|
result.intersection = ray.origin + ray.direction * result.distance;
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
@ -657,6 +676,14 @@ ParabolaToAvatarIntersectionResult AvatarManager::findParabolaIntersectionVector
|
||||||
return result;
|
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.
|
||||||
|
|
||||||
|
// TODO -- find a way to extract transformed avatar mesh data from the rendering engine.
|
||||||
|
|
||||||
|
std::vector<SortedAvatar> sortedAvatars;
|
||||||
auto avatarHashCopy = getHashCopy();
|
auto avatarHashCopy = getHashCopy();
|
||||||
for (auto avatarData : avatarHashCopy) {
|
for (auto avatarData : avatarHashCopy) {
|
||||||
auto avatar = std::static_pointer_cast<Avatar>(avatarData);
|
auto avatar = std::static_pointer_cast<Avatar>(avatarData);
|
||||||
|
@ -665,47 +692,60 @@ ParabolaToAvatarIntersectionResult AvatarManager::findParabolaIntersectionVector
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
float parabolicDistance;
|
float distance = FLT_MAX;
|
||||||
BoxFace face;
|
#if 0
|
||||||
glm::vec3 surfaceNormal;
|
|
||||||
|
|
||||||
SkeletonModelPointer avatarModel = avatar->getSkeletonModel();
|
|
||||||
|
|
||||||
// It's better to intersect the parabola 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.
|
|
||||||
|
|
||||||
// TODO -- find a way to extract transformed avatar mesh data from the rendering engine.
|
|
||||||
|
|
||||||
// if we weren't picking against the capsule, we would want to pick against the avatarBounds...
|
// if we weren't picking against the capsule, we would want to pick against the avatarBounds...
|
||||||
// AABox avatarBounds = avatarModel->getRenderableMeshBound();
|
SkeletonModelPointer avatarModel = avatar->getSkeletonModel();
|
||||||
// if (!avatarBounds.findParabolaIntersection(pick.origin, pick.velocity, pick.acceleration, parabolicDistance, face, surfaceNormal)) {
|
AABox avatarBounds = avatarModel->getRenderableMeshBound();
|
||||||
// // parabola doesn't intersect avatar's bounding-box
|
if (avatarBounds.contains(pick.origin)) {
|
||||||
// continue;
|
distance = 0.0f;
|
||||||
// }
|
} else {
|
||||||
|
float boundDistance = FLT_MAX;
|
||||||
|
BoxFace face;
|
||||||
|
glm::vec3 surfaceNormal;
|
||||||
|
if (avatarBounds.findParabolaIntersection(pick.origin, pick.velocity, pick.acceleration, boundDistance, face, surfaceNormal)) {
|
||||||
|
distance = boundDistance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#else
|
||||||
glm::vec3 start;
|
glm::vec3 start;
|
||||||
glm::vec3 end;
|
glm::vec3 end;
|
||||||
float radius;
|
float radius;
|
||||||
avatar->getCapsule(start, end, radius);
|
avatar->getCapsule(start, end, radius);
|
||||||
bool intersects = findParabolaCapsuleIntersection(pick.origin, pick.velocity, pick.acceleration, start, end, radius, avatar->getWorldOrientation(), parabolicDistance);
|
findParabolaCapsuleIntersection(pick.origin, pick.velocity, pick.acceleration, start, end, radius, avatar->getWorldOrientation(), distance);
|
||||||
if (!intersects) {
|
#endif
|
||||||
// ray doesn't intersect avatar's capsule
|
|
||||||
continue;
|
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.parabolicDistance) {
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
float parabolicDistance = FLT_MAX;
|
||||||
|
BoxFace face;
|
||||||
|
glm::vec3 surfaceNormal;
|
||||||
QVariantMap extraInfo;
|
QVariantMap extraInfo;
|
||||||
intersects = avatarModel->findParabolaIntersectionAgainstSubMeshes(pick.origin, pick.velocity, pick.acceleration,
|
SkeletonModelPointer avatarModel = sortedAvatar.second->getSkeletonModel();
|
||||||
parabolicDistance, face, surfaceNormal, extraInfo, true);
|
if (avatarModel->findParabolaIntersectionAgainstSubMeshes(pick.origin, pick.velocity, pick.acceleration, parabolicDistance, face, surfaceNormal, extraInfo, true)) {
|
||||||
|
if (parabolicDistance < result.parabolicDistance) {
|
||||||
if (intersects && (!result.intersects || parabolicDistance < result.parabolicDistance)) {
|
result.intersects = true;
|
||||||
result.intersects = true;
|
result.avatarID = sortedAvatar.second->getID();
|
||||||
result.avatarID = avatar->getID();
|
result.parabolicDistance = parabolicDistance;
|
||||||
result.parabolicDistance = parabolicDistance;
|
result.face = face;
|
||||||
result.face = face;
|
result.surfaceNormal = surfaceNormal;
|
||||||
result.surfaceNormal = surfaceNormal;
|
result.extraInfo = extraInfo;
|
||||||
result.extraInfo = extraInfo;
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -31,6 +31,8 @@
|
||||||
#include "MyAvatar.h"
|
#include "MyAvatar.h"
|
||||||
#include "OtherAvatar.h"
|
#include "OtherAvatar.h"
|
||||||
|
|
||||||
|
using SortedAvatar = std::pair<float, std::shared_ptr<Avatar>>;
|
||||||
|
|
||||||
/**jsdoc
|
/**jsdoc
|
||||||
* The AvatarManager API has properties and methods which manage Avatars within the same domain.
|
* The AvatarManager API has properties and methods which manage Avatars within the same domain.
|
||||||
*
|
*
|
||||||
|
|
|
@ -1703,18 +1703,50 @@ void MyAvatar::setSkeletonModelURL(const QUrl& skeletonModelURL) {
|
||||||
emit skeletonChanged();
|
emit skeletonChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
void MyAvatar::removeAvatarEntities(const std::function<bool(const QUuid& entityID)>& condition) {
|
bool isWearableEntity(const EntityItemPointer& entity) {
|
||||||
|
return entity->isVisible()
|
||||||
|
&& (entity->getParentJointIndex() != INVALID_JOINT_INDEX
|
||||||
|
|| (entity->getType() == EntityTypes::Model && (std::static_pointer_cast<ModelEntityItem>(entity))->getRelayParentJoints()))
|
||||||
|
&& (entity->getParentID() == DependencyManager::get<NodeList>()->getSessionUUID()
|
||||||
|
|| entity->getParentID() == AVATAR_SELF_ID);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MyAvatar::clearAvatarEntities() {
|
||||||
auto treeRenderer = DependencyManager::get<EntityTreeRenderer>();
|
auto treeRenderer = DependencyManager::get<EntityTreeRenderer>();
|
||||||
EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr;
|
EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr;
|
||||||
if (entityTree) {
|
|
||||||
entityTree->withWriteLock([&] {
|
AvatarEntityMap avatarEntities = getAvatarEntityData();
|
||||||
AvatarEntityMap avatarEntities = getAvatarEntityData();
|
for (auto entityID : avatarEntities.keys()) {
|
||||||
for (auto entityID : avatarEntities.keys()) {
|
entityTree->withWriteLock([&entityID, &entityTree] {
|
||||||
if (!condition || condition(entityID)) {
|
// remove this entity first from the entity tree
|
||||||
entityTree->deleteEntity(entityID, true, true);
|
entityTree->deleteEntity(entityID, true, true);
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// remove the avatar entity from our internal list
|
||||||
|
// (but indicate it doesn't need to be pulled from the tree)
|
||||||
|
clearAvatarEntity(entityID, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MyAvatar::removeWearableAvatarEntities() {
|
||||||
|
auto treeRenderer = DependencyManager::get<EntityTreeRenderer>();
|
||||||
|
EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr;
|
||||||
|
|
||||||
|
if (entityTree) {
|
||||||
|
AvatarEntityMap avatarEntities = getAvatarEntityData();
|
||||||
|
for (auto entityID : avatarEntities.keys()) {
|
||||||
|
auto entity = entityTree->findEntityByID(entityID);
|
||||||
|
if (entity && isWearableEntity(entity)) {
|
||||||
|
entityTree->withWriteLock([&entityID, &entityTree] {
|
||||||
|
// remove this entity first from the entity tree
|
||||||
|
entityTree->deleteEntity(entityID, true, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
// remove the avatar entity from our internal list
|
||||||
|
// (but indicate it doesn't need to be pulled from the tree)
|
||||||
|
clearAvatarEntity(entityID, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2116,7 +2148,7 @@ void MyAvatar::setAttachmentData(const QVector<AttachmentData>& attachmentData)
|
||||||
}
|
}
|
||||||
|
|
||||||
// clear any existing avatar entities
|
// clear any existing avatar entities
|
||||||
setAvatarEntityData(AvatarEntityMap());
|
clearAvatarEntities();
|
||||||
|
|
||||||
for (auto& properties : newEntitiesProperties) {
|
for (auto& properties : newEntitiesProperties) {
|
||||||
DependencyManager::get<EntityScriptingInterface>()->addEntity(properties, true);
|
DependencyManager::get<EntityScriptingInterface>()->addEntity(properties, true);
|
||||||
|
|
|
@ -931,7 +931,8 @@ public:
|
||||||
* @returns {object[]}
|
* @returns {object[]}
|
||||||
*/
|
*/
|
||||||
Q_INVOKABLE QVariantList getAvatarEntitiesVariant();
|
Q_INVOKABLE QVariantList getAvatarEntitiesVariant();
|
||||||
void removeAvatarEntities(const std::function<bool(const QUuid& entityID)>& condition = {});
|
void clearAvatarEntities();
|
||||||
|
void removeWearableAvatarEntities();
|
||||||
|
|
||||||
/**jsdoc
|
/**jsdoc
|
||||||
* @function MyAvatar.isFlying
|
* @function MyAvatar.isFlying
|
||||||
|
@ -1782,4 +1783,6 @@ void audioListenModeFromScriptValue(const QScriptValue& object, AudioListenerMod
|
||||||
QScriptValue driveKeysToScriptValue(QScriptEngine* engine, const MyAvatar::DriveKeys& driveKeys);
|
QScriptValue driveKeysToScriptValue(QScriptEngine* engine, const MyAvatar::DriveKeys& driveKeys);
|
||||||
void driveKeysFromScriptValue(const QScriptValue& object, MyAvatar::DriveKeys& driveKeys);
|
void driveKeysFromScriptValue(const QScriptValue& object, MyAvatar::DriveKeys& driveKeys);
|
||||||
|
|
||||||
|
bool isWearableEntity(const EntityItemPointer& entity);
|
||||||
|
|
||||||
#endif // hifi_MyAvatar_h
|
#endif // hifi_MyAvatar_h
|
||||||
|
|
|
@ -42,6 +42,9 @@ glm::vec3 LaserPointer::getPickOrigin(const PickResultPointer& pickResult) const
|
||||||
|
|
||||||
glm::vec3 LaserPointer::getPickEnd(const PickResultPointer& pickResult, float distance) const {
|
glm::vec3 LaserPointer::getPickEnd(const PickResultPointer& pickResult, float distance) const {
|
||||||
auto rayPickResult = std::static_pointer_cast<RayPickResult>(pickResult);
|
auto rayPickResult = std::static_pointer_cast<RayPickResult>(pickResult);
|
||||||
|
if (!rayPickResult) {
|
||||||
|
return glm::vec3(0.0f);
|
||||||
|
}
|
||||||
if (distance > 0.0f) {
|
if (distance > 0.0f) {
|
||||||
PickRay pick = PickRay(rayPickResult->pickVariant);
|
PickRay pick = PickRay(rayPickResult->pickVariant);
|
||||||
return pick.origin + distance * pick.direction;
|
return pick.origin + distance * pick.direction;
|
||||||
|
|
|
@ -13,11 +13,13 @@
|
||||||
#include "avatar/AvatarManager.h"
|
#include "avatar/AvatarManager.h"
|
||||||
#include "scripting/HMDScriptingInterface.h"
|
#include "scripting/HMDScriptingInterface.h"
|
||||||
#include "DependencyManager.h"
|
#include "DependencyManager.h"
|
||||||
|
#include "PickManager.h"
|
||||||
|
|
||||||
PickResultPointer ParabolaPick::getEntityIntersection(const PickParabola& pick) {
|
PickResultPointer ParabolaPick::getEntityIntersection(const PickParabola& pick) {
|
||||||
if (glm::length2(pick.acceleration) > EPSILON && glm::length2(pick.velocity) > EPSILON) {
|
if (glm::length2(pick.acceleration) > EPSILON && glm::length2(pick.velocity) > EPSILON) {
|
||||||
|
bool precisionPicking = !(getFilter().doesPickCoarse() || DependencyManager::get<PickManager>()->getForceCoarsePicking());
|
||||||
ParabolaToEntityIntersectionResult entityRes =
|
ParabolaToEntityIntersectionResult entityRes =
|
||||||
DependencyManager::get<EntityScriptingInterface>()->findParabolaIntersectionVector(pick, !getFilter().doesPickCoarse(),
|
DependencyManager::get<EntityScriptingInterface>()->findParabolaIntersectionVector(pick, precisionPicking,
|
||||||
getIncludeItemsAs<EntityItemID>(), getIgnoreItemsAs<EntityItemID>(), !getFilter().doesPickInvisible(), !getFilter().doesPickNonCollidable());
|
getIncludeItemsAs<EntityItemID>(), getIgnoreItemsAs<EntityItemID>(), !getFilter().doesPickInvisible(), !getFilter().doesPickNonCollidable());
|
||||||
if (entityRes.intersects) {
|
if (entityRes.intersects) {
|
||||||
return std::make_shared<ParabolaPickResult>(IntersectionType::ENTITY, entityRes.entityID, entityRes.distance, entityRes.parabolicDistance, entityRes.intersection, pick, entityRes.surfaceNormal, entityRes.extraInfo);
|
return std::make_shared<ParabolaPickResult>(IntersectionType::ENTITY, entityRes.entityID, entityRes.distance, entityRes.parabolicDistance, entityRes.intersection, pick, entityRes.surfaceNormal, entityRes.extraInfo);
|
||||||
|
@ -28,8 +30,9 @@ PickResultPointer ParabolaPick::getEntityIntersection(const PickParabola& pick)
|
||||||
|
|
||||||
PickResultPointer ParabolaPick::getOverlayIntersection(const PickParabola& pick) {
|
PickResultPointer ParabolaPick::getOverlayIntersection(const PickParabola& pick) {
|
||||||
if (glm::length2(pick.acceleration) > EPSILON && glm::length2(pick.velocity) > EPSILON) {
|
if (glm::length2(pick.acceleration) > EPSILON && glm::length2(pick.velocity) > EPSILON) {
|
||||||
|
bool precisionPicking = !(getFilter().doesPickCoarse() || DependencyManager::get<PickManager>()->getForceCoarsePicking());
|
||||||
ParabolaToOverlayIntersectionResult overlayRes =
|
ParabolaToOverlayIntersectionResult overlayRes =
|
||||||
qApp->getOverlays().findParabolaIntersectionVector(pick, !getFilter().doesPickCoarse(),
|
qApp->getOverlays().findParabolaIntersectionVector(pick, precisionPicking,
|
||||||
getIncludeItemsAs<OverlayID>(), getIgnoreItemsAs<OverlayID>(), !getFilter().doesPickInvisible(), !getFilter().doesPickNonCollidable());
|
getIncludeItemsAs<OverlayID>(), getIgnoreItemsAs<OverlayID>(), !getFilter().doesPickInvisible(), !getFilter().doesPickNonCollidable());
|
||||||
if (overlayRes.intersects) {
|
if (overlayRes.intersects) {
|
||||||
return std::make_shared<ParabolaPickResult>(IntersectionType::OVERLAY, overlayRes.overlayID, overlayRes.distance, overlayRes.parabolicDistance, overlayRes.intersection, pick, overlayRes.surfaceNormal, overlayRes.extraInfo);
|
return std::make_shared<ParabolaPickResult>(IntersectionType::OVERLAY, overlayRes.overlayID, overlayRes.distance, overlayRes.parabolicDistance, overlayRes.intersection, pick, overlayRes.surfaceNormal, overlayRes.extraInfo);
|
||||||
|
|
|
@ -67,6 +67,9 @@ glm::vec3 ParabolaPointer::getPickOrigin(const PickResultPointer& pickResult) co
|
||||||
|
|
||||||
glm::vec3 ParabolaPointer::getPickEnd(const PickResultPointer& pickResult, float distance) const {
|
glm::vec3 ParabolaPointer::getPickEnd(const PickResultPointer& pickResult, float distance) const {
|
||||||
auto parabolaPickResult = std::static_pointer_cast<ParabolaPickResult>(pickResult);
|
auto parabolaPickResult = std::static_pointer_cast<ParabolaPickResult>(pickResult);
|
||||||
|
if (!parabolaPickResult) {
|
||||||
|
return glm::vec3(0.0f);
|
||||||
|
}
|
||||||
if (distance > 0.0f) {
|
if (distance > 0.0f) {
|
||||||
PickParabola pick = PickParabola(parabolaPickResult->pickVariant);
|
PickParabola pick = PickParabola(parabolaPickResult->pickVariant);
|
||||||
return pick.origin + pick.velocity * distance + 0.5f * pick.acceleration * distance * distance;
|
return pick.origin + pick.velocity * distance + 0.5f * pick.acceleration * distance * distance;
|
||||||
|
|
|
@ -54,11 +54,13 @@ PathPointer::~PathPointer() {
|
||||||
void PathPointer::setRenderState(const std::string& state) {
|
void PathPointer::setRenderState(const std::string& state) {
|
||||||
withWriteLock([&] {
|
withWriteLock([&] {
|
||||||
if (!_currentRenderState.empty() && state != _currentRenderState) {
|
if (!_currentRenderState.empty() && state != _currentRenderState) {
|
||||||
if (_renderStates.find(_currentRenderState) != _renderStates.end()) {
|
auto renderState = _renderStates.find(_currentRenderState);
|
||||||
_renderStates[_currentRenderState]->disable();
|
if (renderState != _renderStates.end()) {
|
||||||
|
renderState->second->disable();
|
||||||
}
|
}
|
||||||
if (_defaultRenderStates.find(_currentRenderState) != _defaultRenderStates.end()) {
|
auto defaultRenderState = _defaultRenderStates.find(_currentRenderState);
|
||||||
_defaultRenderStates[_currentRenderState].second->disable();
|
if (defaultRenderState != _defaultRenderStates.end()) {
|
||||||
|
defaultRenderState->second.second->disable();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_currentRenderState = state;
|
_currentRenderState = state;
|
||||||
|
@ -105,7 +107,7 @@ PickResultPointer PathPointer::getVisualPickResult(const PickResultPointer& pick
|
||||||
glm::mat4 entityMat = createMatFromQuatAndPos(props.getRotation(), props.getPosition());
|
glm::mat4 entityMat = createMatFromQuatAndPos(props.getRotation(), props.getPosition());
|
||||||
glm::mat4 finalPosAndRotMat = entityMat * _lockEndObject.offsetMat;
|
glm::mat4 finalPosAndRotMat = entityMat * _lockEndObject.offsetMat;
|
||||||
pos = extractTranslation(finalPosAndRotMat);
|
pos = extractTranslation(finalPosAndRotMat);
|
||||||
rot = glmExtractRotation(finalPosAndRotMat);
|
rot = props.getRotation();
|
||||||
dim = props.getDimensions();
|
dim = props.getDimensions();
|
||||||
registrationPoint = props.getRegistrationPoint();
|
registrationPoint = props.getRegistrationPoint();
|
||||||
}
|
}
|
||||||
|
@ -142,52 +144,57 @@ PickResultPointer PathPointer::getVisualPickResult(const PickResultPointer& pick
|
||||||
|
|
||||||
void PathPointer::updateVisuals(const PickResultPointer& pickResult) {
|
void PathPointer::updateVisuals(const PickResultPointer& pickResult) {
|
||||||
IntersectionType type = getPickedObjectType(pickResult);
|
IntersectionType type = getPickedObjectType(pickResult);
|
||||||
if (_enabled && !_currentRenderState.empty() && _renderStates.find(_currentRenderState) != _renderStates.end() &&
|
auto renderState = _renderStates.find(_currentRenderState);
|
||||||
|
auto defaultRenderState = _defaultRenderStates.find(_currentRenderState);
|
||||||
|
if (_enabled && !_currentRenderState.empty() && renderState != _renderStates.end() &&
|
||||||
(type != IntersectionType::NONE || _pathLength > 0.0f)) {
|
(type != IntersectionType::NONE || _pathLength > 0.0f)) {
|
||||||
glm::vec3 origin = getPickOrigin(pickResult);
|
glm::vec3 origin = getPickOrigin(pickResult);
|
||||||
glm::vec3 end = getPickEnd(pickResult, _pathLength);
|
glm::vec3 end = getPickEnd(pickResult, _pathLength);
|
||||||
glm::vec3 surfaceNormal = getPickedObjectNormal(pickResult);
|
glm::vec3 surfaceNormal = getPickedObjectNormal(pickResult);
|
||||||
_renderStates[_currentRenderState]->update(origin, end, surfaceNormal, _scaleWithAvatar, _distanceScaleEnd, _centerEndY, _faceAvatar,
|
renderState->second->update(origin, end, surfaceNormal, _scaleWithAvatar, _distanceScaleEnd, _centerEndY, _faceAvatar,
|
||||||
_followNormal, _followNormalStrength, _pathLength, pickResult);
|
_followNormal, _followNormalStrength, _pathLength, pickResult);
|
||||||
if (_defaultRenderStates.find(_currentRenderState) != _defaultRenderStates.end()) {
|
if (defaultRenderState != _defaultRenderStates.end() && defaultRenderState->second.second->isEnabled()) {
|
||||||
_defaultRenderStates[_currentRenderState].second->disable();
|
defaultRenderState->second.second->disable();
|
||||||
}
|
}
|
||||||
} else if (_enabled && !_currentRenderState.empty() && _defaultRenderStates.find(_currentRenderState) != _defaultRenderStates.end()) {
|
} else if (_enabled && !_currentRenderState.empty() && defaultRenderState != _defaultRenderStates.end()) {
|
||||||
if (_renderStates.find(_currentRenderState) != _renderStates.end()) {
|
if (renderState != _renderStates.end() && renderState->second->isEnabled()) {
|
||||||
_renderStates[_currentRenderState]->disable();
|
renderState->second->disable();
|
||||||
}
|
}
|
||||||
glm::vec3 origin = getPickOrigin(pickResult);
|
glm::vec3 origin = getPickOrigin(pickResult);
|
||||||
glm::vec3 end = getPickEnd(pickResult, _defaultRenderStates[_currentRenderState].first);
|
glm::vec3 end = getPickEnd(pickResult, defaultRenderState->second.first);
|
||||||
_defaultRenderStates[_currentRenderState].second->update(origin, end, Vectors::UP, _scaleWithAvatar, _distanceScaleEnd, _centerEndY,
|
defaultRenderState->second.second->update(origin, end, Vectors::UP, _scaleWithAvatar, _distanceScaleEnd, _centerEndY,
|
||||||
_faceAvatar, _followNormal, _followNormalStrength, _defaultRenderStates[_currentRenderState].first, pickResult);
|
_faceAvatar, _followNormal, _followNormalStrength, defaultRenderState->second.first, pickResult);
|
||||||
} else if (!_currentRenderState.empty()) {
|
} else if (!_currentRenderState.empty()) {
|
||||||
if (_renderStates.find(_currentRenderState) != _renderStates.end()) {
|
if (renderState != _renderStates.end() && renderState->second->isEnabled()) {
|
||||||
_renderStates[_currentRenderState]->disable();
|
renderState->second->disable();
|
||||||
}
|
}
|
||||||
if (_defaultRenderStates.find(_currentRenderState) != _defaultRenderStates.end()) {
|
if (defaultRenderState != _defaultRenderStates.end() && defaultRenderState->second.second->isEnabled()) {
|
||||||
_defaultRenderStates[_currentRenderState].second->disable();
|
defaultRenderState->second.second->disable();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void PathPointer::editRenderState(const std::string& state, const QVariant& startProps, const QVariant& pathProps, const QVariant& endProps) {
|
void PathPointer::editRenderState(const std::string& state, const QVariant& startProps, const QVariant& pathProps, const QVariant& endProps) {
|
||||||
withWriteLock([&] {
|
withWriteLock([&] {
|
||||||
updateRenderStateOverlay(_renderStates[state]->getStartID(), startProps);
|
auto renderState = _renderStates.find(state);
|
||||||
updateRenderStateOverlay(_renderStates[state]->getEndID(), endProps);
|
if (renderState != _renderStates.end()) {
|
||||||
QVariant startDim = startProps.toMap()["dimensions"];
|
updateRenderStateOverlay(renderState->second->getStartID(), startProps);
|
||||||
if (startDim.isValid()) {
|
updateRenderStateOverlay(renderState->second->getEndID(), endProps);
|
||||||
_renderStates[state]->setStartDim(vec3FromVariant(startDim));
|
QVariant startDim = startProps.toMap()["dimensions"];
|
||||||
}
|
if (startDim.isValid()) {
|
||||||
QVariant endDim = endProps.toMap()["dimensions"];
|
renderState->second->setStartDim(vec3FromVariant(startDim));
|
||||||
if (endDim.isValid()) {
|
}
|
||||||
_renderStates[state]->setEndDim(vec3FromVariant(endDim));
|
QVariant endDim = endProps.toMap()["dimensions"];
|
||||||
}
|
if (endDim.isValid()) {
|
||||||
QVariant rotation = endProps.toMap()["rotation"];
|
renderState->second->setEndDim(vec3FromVariant(endDim));
|
||||||
if (rotation.isValid()) {
|
}
|
||||||
_renderStates[state]->setEndRot(quatFromVariant(rotation));
|
QVariant rotation = endProps.toMap()["rotation"];
|
||||||
}
|
if (rotation.isValid()) {
|
||||||
|
renderState->second->setEndRot(quatFromVariant(rotation));
|
||||||
|
}
|
||||||
|
|
||||||
editRenderStatePath(state, pathProps);
|
editRenderStatePath(state, pathProps);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -271,6 +278,7 @@ void StartEndRenderState::disable() {
|
||||||
endProps.insert("ignoreRayIntersection", true);
|
endProps.insert("ignoreRayIntersection", true);
|
||||||
qApp->getOverlays().editOverlay(getEndID(), endProps);
|
qApp->getOverlays().editOverlay(getEndID(), endProps);
|
||||||
}
|
}
|
||||||
|
_enabled = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void StartEndRenderState::update(const glm::vec3& origin, const glm::vec3& end, const glm::vec3& surfaceNormal, bool scaleWithAvatar, bool distanceScaleEnd, bool centerEndY,
|
void StartEndRenderState::update(const glm::vec3& origin, const glm::vec3& end, const glm::vec3& surfaceNormal, bool scaleWithAvatar, bool distanceScaleEnd, bool centerEndY,
|
||||||
|
@ -337,6 +345,7 @@ void StartEndRenderState::update(const glm::vec3& origin, const glm::vec3& end,
|
||||||
endProps.insert("ignoreRayIntersection", doesEndIgnoreRays());
|
endProps.insert("ignoreRayIntersection", doesEndIgnoreRays());
|
||||||
qApp->getOverlays().editOverlay(getEndID(), endProps);
|
qApp->getOverlays().editOverlay(getEndID(), endProps);
|
||||||
}
|
}
|
||||||
|
_enabled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
glm::vec2 PathPointer::findPos2D(const PickedObject& pickedObject, const glm::vec3& origin) {
|
glm::vec2 PathPointer::findPos2D(const PickedObject& pickedObject, const glm::vec3& origin) {
|
||||||
|
@ -350,4 +359,4 @@ glm::vec2 PathPointer::findPos2D(const PickedObject& pickedObject, const glm::ve
|
||||||
default:
|
default:
|
||||||
return glm::vec2(NAN);
|
return glm::vec2(NAN);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,6 +47,8 @@ public:
|
||||||
virtual void update(const glm::vec3& origin, const glm::vec3& end, const glm::vec3& surfaceNormal, bool scaleWithAvatar, bool distanceScaleEnd, bool centerEndY,
|
virtual void update(const glm::vec3& origin, const glm::vec3& end, const glm::vec3& surfaceNormal, bool scaleWithAvatar, bool distanceScaleEnd, bool centerEndY,
|
||||||
bool faceAvatar, bool followNormal, float followNormalStrength, float distance, const PickResultPointer& pickResult);
|
bool faceAvatar, bool followNormal, float followNormalStrength, float distance, const PickResultPointer& pickResult);
|
||||||
|
|
||||||
|
bool isEnabled() const { return _enabled; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
OverlayID _startID;
|
OverlayID _startID;
|
||||||
OverlayID _endID;
|
OverlayID _endID;
|
||||||
|
@ -59,6 +61,8 @@ protected:
|
||||||
|
|
||||||
glm::quat _avgEndRot;
|
glm::quat _avgEndRot;
|
||||||
bool _avgEndRotInitialized { false };
|
bool _avgEndRotInitialized { false };
|
||||||
|
|
||||||
|
bool _enabled { true };
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef std::unordered_map<std::string, std::shared_ptr<StartEndRenderState>> RenderStateMap;
|
typedef std::unordered_map<std::string, std::shared_ptr<StartEndRenderState>> RenderStateMap;
|
||||||
|
|
|
@ -13,10 +13,12 @@
|
||||||
#include "avatar/AvatarManager.h"
|
#include "avatar/AvatarManager.h"
|
||||||
#include "scripting/HMDScriptingInterface.h"
|
#include "scripting/HMDScriptingInterface.h"
|
||||||
#include "DependencyManager.h"
|
#include "DependencyManager.h"
|
||||||
|
#include "PickManager.h"
|
||||||
|
|
||||||
PickResultPointer RayPick::getEntityIntersection(const PickRay& pick) {
|
PickResultPointer RayPick::getEntityIntersection(const PickRay& pick) {
|
||||||
|
bool precisionPicking = !(getFilter().doesPickCoarse() || DependencyManager::get<PickManager>()->getForceCoarsePicking());
|
||||||
RayToEntityIntersectionResult entityRes =
|
RayToEntityIntersectionResult entityRes =
|
||||||
DependencyManager::get<EntityScriptingInterface>()->findRayIntersectionVector(pick, !getFilter().doesPickCoarse(),
|
DependencyManager::get<EntityScriptingInterface>()->findRayIntersectionVector(pick, precisionPicking,
|
||||||
getIncludeItemsAs<EntityItemID>(), getIgnoreItemsAs<EntityItemID>(), !getFilter().doesPickInvisible(), !getFilter().doesPickNonCollidable());
|
getIncludeItemsAs<EntityItemID>(), getIgnoreItemsAs<EntityItemID>(), !getFilter().doesPickInvisible(), !getFilter().doesPickNonCollidable());
|
||||||
if (entityRes.intersects) {
|
if (entityRes.intersects) {
|
||||||
return std::make_shared<RayPickResult>(IntersectionType::ENTITY, entityRes.entityID, entityRes.distance, entityRes.intersection, pick, entityRes.surfaceNormal, entityRes.extraInfo);
|
return std::make_shared<RayPickResult>(IntersectionType::ENTITY, entityRes.entityID, entityRes.distance, entityRes.intersection, pick, entityRes.surfaceNormal, entityRes.extraInfo);
|
||||||
|
@ -26,8 +28,9 @@ PickResultPointer RayPick::getEntityIntersection(const PickRay& pick) {
|
||||||
}
|
}
|
||||||
|
|
||||||
PickResultPointer RayPick::getOverlayIntersection(const PickRay& pick) {
|
PickResultPointer RayPick::getOverlayIntersection(const PickRay& pick) {
|
||||||
|
bool precisionPicking = !(getFilter().doesPickCoarse() || DependencyManager::get<PickManager>()->getForceCoarsePicking());
|
||||||
RayToOverlayIntersectionResult overlayRes =
|
RayToOverlayIntersectionResult overlayRes =
|
||||||
qApp->getOverlays().findRayIntersectionVector(pick, !getFilter().doesPickCoarse(),
|
qApp->getOverlays().findRayIntersectionVector(pick, precisionPicking,
|
||||||
getIncludeItemsAs<OverlayID>(), getIgnoreItemsAs<OverlayID>(), !getFilter().doesPickInvisible(), !getFilter().doesPickNonCollidable());
|
getIncludeItemsAs<OverlayID>(), getIgnoreItemsAs<OverlayID>(), !getFilter().doesPickInvisible(), !getFilter().doesPickNonCollidable());
|
||||||
if (overlayRes.intersects) {
|
if (overlayRes.intersects) {
|
||||||
return std::make_shared<RayPickResult>(IntersectionType::OVERLAY, overlayRes.overlayID, overlayRes.distance, overlayRes.intersection, pick, overlayRes.surfaceNormal, overlayRes.extraInfo);
|
return std::make_shared<RayPickResult>(IntersectionType::OVERLAY, overlayRes.overlayID, overlayRes.distance, overlayRes.intersection, pick, overlayRes.surfaceNormal, overlayRes.extraInfo);
|
||||||
|
|
|
@ -64,7 +64,9 @@ void StylusPointer::updateVisuals(const PickResultPointer& pickResult) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
hide();
|
if (_showing) {
|
||||||
|
hide();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void StylusPointer::show(const StylusTip& tip) {
|
void StylusPointer::show(const StylusTip& tip) {
|
||||||
|
@ -80,6 +82,7 @@ void StylusPointer::show(const StylusTip& tip) {
|
||||||
props["visible"] = true;
|
props["visible"] = true;
|
||||||
qApp->getOverlays().editOverlay(_stylusOverlay, props);
|
qApp->getOverlays().editOverlay(_stylusOverlay, props);
|
||||||
}
|
}
|
||||||
|
_showing = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void StylusPointer::hide() {
|
void StylusPointer::hide() {
|
||||||
|
@ -88,6 +91,7 @@ void StylusPointer::hide() {
|
||||||
props.insert("visible", false);
|
props.insert("visible", false);
|
||||||
qApp->getOverlays().editOverlay(_stylusOverlay, props);
|
qApp->getOverlays().editOverlay(_stylusOverlay, props);
|
||||||
}
|
}
|
||||||
|
_showing = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool StylusPointer::shouldHover(const PickResultPointer& pickResult) {
|
bool StylusPointer::shouldHover(const PickResultPointer& pickResult) {
|
||||||
|
|
|
@ -76,6 +76,8 @@ private:
|
||||||
static glm::vec3 findIntersection(const PickedObject& pickedObject, const glm::vec3& origin, const glm::vec3& direction);
|
static glm::vec3 findIntersection(const PickedObject& pickedObject, const glm::vec3& origin, const glm::vec3& direction);
|
||||||
static glm::vec2 findPos2D(const PickedObject& pickedObject, const glm::vec3& origin);
|
static glm::vec2 findPos2D(const PickedObject& pickedObject, const glm::vec3& origin);
|
||||||
|
|
||||||
|
bool _showing { true };
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // hifi_StylusPointer_h
|
#endif // hifi_StylusPointer_h
|
||||||
|
|
|
@ -190,4 +190,12 @@ void TestScriptingInterface::saveObject(QVariant variant, const QString& filenam
|
||||||
|
|
||||||
void TestScriptingInterface::showMaximized() {
|
void TestScriptingInterface::showMaximized() {
|
||||||
qApp->getWindow()->showMaximized();
|
qApp->getWindow()->showMaximized();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestScriptingInterface::setOtherAvatarsReplicaCount(int count) {
|
||||||
|
qApp->setOtherAvatarsReplicaCount(count);
|
||||||
|
}
|
||||||
|
|
||||||
|
int TestScriptingInterface::getOtherAvatarsReplicaCount() {
|
||||||
|
return qApp->getOtherAvatarsReplicaCount();
|
||||||
}
|
}
|
|
@ -149,6 +149,20 @@ public slots:
|
||||||
*/
|
*/
|
||||||
void showMaximized();
|
void showMaximized();
|
||||||
|
|
||||||
|
/**jsdoc
|
||||||
|
* Values higher than 0 will create replicas of other-avatars when entering a domain for testing purpouses
|
||||||
|
* @function Test.setOtherAvatarsReplicaCount
|
||||||
|
* @param {number} count - Number of replicas we want to create
|
||||||
|
*/
|
||||||
|
Q_INVOKABLE void setOtherAvatarsReplicaCount(int count);
|
||||||
|
|
||||||
|
/**jsdoc
|
||||||
|
* Return the number of replicas that are being created of other-avatars when entering a domain
|
||||||
|
* @function Test.getOtherAvatarsReplicaCount
|
||||||
|
* @returns {number} Current number of replicas of other-avatars.
|
||||||
|
*/
|
||||||
|
Q_INVOKABLE int getOtherAvatarsReplicaCount();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool waitForCondition(qint64 maxWaitMs, std::function<bool()> condition);
|
bool waitForCondition(qint64 maxWaitMs, std::function<bool()> condition);
|
||||||
QString _testResultsLocation;
|
QString _testResultsLocation;
|
||||||
|
|
|
@ -26,6 +26,7 @@
|
||||||
#include <OffscreenUi.h>
|
#include <OffscreenUi.h>
|
||||||
#include <PerfStat.h>
|
#include <PerfStat.h>
|
||||||
#include <plugins/DisplayPlugin.h>
|
#include <plugins/DisplayPlugin.h>
|
||||||
|
#include <PickManager.h>
|
||||||
|
|
||||||
#include <gl/Context.h>
|
#include <gl/Context.h>
|
||||||
|
|
||||||
|
@ -147,6 +148,20 @@ void Stats::updateStats(bool force) {
|
||||||
}
|
}
|
||||||
STAT_UPDATE(gameLoopRate, (int)qApp->getGameLoopRate());
|
STAT_UPDATE(gameLoopRate, (int)qApp->getGameLoopRate());
|
||||||
|
|
||||||
|
auto pickManager = DependencyManager::get<PickManager>();
|
||||||
|
if (pickManager && (_expanded || force)) {
|
||||||
|
std::vector<int> totalPicks = pickManager->getTotalPickCounts();
|
||||||
|
STAT_UPDATE(stylusPicksCount, totalPicks[PickQuery::Stylus]);
|
||||||
|
STAT_UPDATE(rayPicksCount, totalPicks[PickQuery::Ray]);
|
||||||
|
STAT_UPDATE(parabolaPicksCount, totalPicks[PickQuery::Parabola]);
|
||||||
|
STAT_UPDATE(collisionPicksCount, totalPicks[PickQuery::Collision]);
|
||||||
|
std::vector<QVector4D> updatedPicks = pickManager->getUpdatedPickCounts();
|
||||||
|
STAT_UPDATE(stylusPicksUpdated, updatedPicks[PickQuery::Stylus]);
|
||||||
|
STAT_UPDATE(rayPicksUpdated, updatedPicks[PickQuery::Ray]);
|
||||||
|
STAT_UPDATE(parabolaPicksUpdated, updatedPicks[PickQuery::Parabola]);
|
||||||
|
STAT_UPDATE(collisionPicksUpdated, updatedPicks[PickQuery::Collision]);
|
||||||
|
}
|
||||||
|
|
||||||
auto bandwidthRecorder = DependencyManager::get<BandwidthRecorder>();
|
auto bandwidthRecorder = DependencyManager::get<BandwidthRecorder>();
|
||||||
STAT_UPDATE(packetInCount, (int)bandwidthRecorder->getCachedTotalAverageInputPacketsPerSecond());
|
STAT_UPDATE(packetInCount, (int)bandwidthRecorder->getCachedTotalAverageInputPacketsPerSecond());
|
||||||
STAT_UPDATE(packetOutCount, (int)bandwidthRecorder->getCachedTotalAverageOutputPacketsPerSecond());
|
STAT_UPDATE(packetOutCount, (int)bandwidthRecorder->getCachedTotalAverageOutputPacketsPerSecond());
|
||||||
|
@ -286,7 +301,7 @@ void Stats::updateStats(bool force) {
|
||||||
// downloads << (int)(resource->getProgress() * 100.0f) << "% ";
|
// downloads << (int)(resource->getProgress() * 100.0f) << "% ";
|
||||||
//}
|
//}
|
||||||
//downloads << "(" << << " pending)";
|
//downloads << "(" << << " pending)";
|
||||||
} // expanded avatar column
|
}
|
||||||
|
|
||||||
// Fourth column, octree stats
|
// Fourth column, octree stats
|
||||||
int serverCount = 0;
|
int serverCount = 0;
|
||||||
|
|
|
@ -22,7 +22,6 @@ public: \
|
||||||
private: \
|
private: \
|
||||||
type _##name{ initialValue };
|
type _##name{ initialValue };
|
||||||
|
|
||||||
|
|
||||||
/**jsdoc
|
/**jsdoc
|
||||||
* @namespace Stats
|
* @namespace Stats
|
||||||
*
|
*
|
||||||
|
@ -169,6 +168,15 @@ private: \
|
||||||
* @property {number} implicitHeight
|
* @property {number} implicitHeight
|
||||||
*
|
*
|
||||||
* @property {object} layer - <em>Read-only.</em>
|
* @property {object} layer - <em>Read-only.</em>
|
||||||
|
|
||||||
|
* @property {number} stylusPicksCount - <em>Read-only.</em>
|
||||||
|
* @property {number} rayPicksCount - <em>Read-only.</em>
|
||||||
|
* @property {number} parabolaPicksCount - <em>Read-only.</em>
|
||||||
|
* @property {number} collisionPicksCount - <em>Read-only.</em>
|
||||||
|
* @property {Vec4} stylusPicksUpdated - <em>Read-only.</em>
|
||||||
|
* @property {Vec4} rayPicksUpdated - <em>Read-only.</em>
|
||||||
|
* @property {Vec4} parabolaPicksUpdated - <em>Read-only.</em>
|
||||||
|
* @property {Vec4} collisionPicksUpdated - <em>Read-only.</em>
|
||||||
*/
|
*/
|
||||||
// Properties from x onwards are QQuickItem properties.
|
// Properties from x onwards are QQuickItem properties.
|
||||||
|
|
||||||
|
@ -287,6 +295,15 @@ class Stats : public QQuickItem {
|
||||||
STATS_PROPERTY(float, avatarSimulationTime, 0)
|
STATS_PROPERTY(float, avatarSimulationTime, 0)
|
||||||
Q_PROPERTY(QStringList animStackNames READ animStackNames NOTIFY animStackNamesChanged)
|
Q_PROPERTY(QStringList animStackNames READ animStackNames NOTIFY animStackNamesChanged)
|
||||||
|
|
||||||
|
STATS_PROPERTY(int, stylusPicksCount, 0)
|
||||||
|
STATS_PROPERTY(int, rayPicksCount, 0)
|
||||||
|
STATS_PROPERTY(int, parabolaPicksCount, 0)
|
||||||
|
STATS_PROPERTY(int, collisionPicksCount, 0)
|
||||||
|
STATS_PROPERTY(QVector4D, stylusPicksUpdated, QVector4D(0, 0, 0, 0))
|
||||||
|
STATS_PROPERTY(QVector4D, rayPicksUpdated, QVector4D(0, 0, 0, 0))
|
||||||
|
STATS_PROPERTY(QVector4D, parabolaPicksUpdated, QVector4D(0, 0, 0, 0))
|
||||||
|
STATS_PROPERTY(QVector4D, collisionPicksUpdated, QVector4D(0, 0, 0, 0))
|
||||||
|
|
||||||
public:
|
public:
|
||||||
static Stats* getInstance();
|
static Stats* getInstance();
|
||||||
|
|
||||||
|
@ -1254,6 +1271,62 @@ signals:
|
||||||
* @function Stats.update
|
* @function Stats.update
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**jsdoc
|
||||||
|
* Triggered when the value of the <code>stylusPicksCount</code> property changes.
|
||||||
|
* @function Stats.stylusPicksCountChanged
|
||||||
|
* @returns {Signal}
|
||||||
|
*/
|
||||||
|
void stylusPicksCountChanged();
|
||||||
|
|
||||||
|
/**jsdoc
|
||||||
|
* Triggered when the value of the <code>rayPicksCount</code> property changes.
|
||||||
|
* @function Stats.rayPicksCountChanged
|
||||||
|
* @returns {Signal}
|
||||||
|
*/
|
||||||
|
void rayPicksCountChanged();
|
||||||
|
|
||||||
|
/**jsdoc
|
||||||
|
* Triggered when the value of the <code>parabolaPicksCount</code> property changes.
|
||||||
|
* @function Stats.parabolaPicksCountChanged
|
||||||
|
* @returns {Signal}
|
||||||
|
*/
|
||||||
|
void parabolaPicksCountChanged();
|
||||||
|
|
||||||
|
/**jsdoc
|
||||||
|
* Triggered when the value of the <code>collisionPicksCount</code> property changes.
|
||||||
|
* @function Stats.collisionPicksCountChanged
|
||||||
|
* @returns {Signal}
|
||||||
|
*/
|
||||||
|
void collisionPicksCountChanged();
|
||||||
|
|
||||||
|
/**jsdoc
|
||||||
|
* Triggered when the value of the <code>stylusPicksUpdated</code> property changes.
|
||||||
|
* @function Stats.stylusPicksUpdatedChanged
|
||||||
|
* @returns {Signal}
|
||||||
|
*/
|
||||||
|
void stylusPicksUpdatedChanged();
|
||||||
|
|
||||||
|
/**jsdoc
|
||||||
|
* Triggered when the value of the <code>rayPicksUpdated</code> property changes.
|
||||||
|
* @function Stats.rayPicksUpdatedChanged
|
||||||
|
* @returns {Signal}
|
||||||
|
*/
|
||||||
|
void rayPicksUpdatedChanged();
|
||||||
|
|
||||||
|
/**jsdoc
|
||||||
|
* Triggered when the value of the <code>parabolaPicksUpdated</code> property changes.
|
||||||
|
* @function Stats.parabolaPicksUpdatedChanged
|
||||||
|
* @returns {Signal}
|
||||||
|
*/
|
||||||
|
void parabolaPicksUpdatedChanged();
|
||||||
|
|
||||||
|
/**jsdoc
|
||||||
|
* Triggered when the value of the <code>collisionPicksUpdated</code> property changes.
|
||||||
|
* @function Stats.collisionPicksUpdatedChanged
|
||||||
|
* @returns {Signal}
|
||||||
|
*/
|
||||||
|
void collisionPicksUpdatedChanged();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
int _recentMaxPackets{ 0 } ; // recent max incoming voxel packets to process
|
int _recentMaxPackets{ 0 } ; // recent max incoming voxel packets to process
|
||||||
bool _resetRecentMaxPacketsSoon{ true };
|
bool _resetRecentMaxPacketsSoon{ true };
|
||||||
|
|
|
@ -180,7 +180,7 @@ bool ContextOverlayInterface::createOrDestroyContextOverlay(const EntityItemID&
|
||||||
float distance;
|
float distance;
|
||||||
BoxFace face;
|
BoxFace face;
|
||||||
glm::vec3 normal;
|
glm::vec3 normal;
|
||||||
boundingBox.findRayIntersection(cameraPosition, direction, distance, face, normal);
|
boundingBox.findRayIntersection(cameraPosition, direction, 1.0f / direction, distance, face, normal);
|
||||||
float offsetAngle = -CONTEXT_OVERLAY_OFFSET_ANGLE;
|
float offsetAngle = -CONTEXT_OVERLAY_OFFSET_ANGLE;
|
||||||
if (event.getID() == 1) { // "1" is left hand
|
if (event.getID() == 1) { // "1" is left hand
|
||||||
offsetAngle *= -1.0f;
|
offsetAngle *= -1.0f;
|
||||||
|
|
|
@ -88,7 +88,7 @@ bool Volume3DOverlay::findRayIntersection(const glm::vec3& origin, const glm::ve
|
||||||
|
|
||||||
// we can use the AABox's ray intersection by mapping our origin and direction into the overlays frame
|
// we can use the AABox's ray intersection by mapping our origin and direction into the overlays frame
|
||||||
// and testing intersection there.
|
// and testing intersection there.
|
||||||
bool hit = _localBoundingBox.findRayIntersection(overlayFrameOrigin, overlayFrameDirection, distance, face, surfaceNormal);
|
bool hit = _localBoundingBox.findRayIntersection(overlayFrameOrigin, overlayFrameDirection, 1.0f / overlayFrameDirection, distance, face, surfaceNormal);
|
||||||
|
|
||||||
if (hit) {
|
if (hit) {
|
||||||
surfaceNormal = transform.getRotation() * surfaceNormal;
|
surfaceNormal = transform.getRotation() * surfaceNormal;
|
||||||
|
|
|
@ -156,6 +156,10 @@ void AnimBlendLinearMove::setFrameAndPhase(float dt, float alpha, int prevPoseIn
|
||||||
// integrate phase forward in time.
|
// integrate phase forward in time.
|
||||||
_phase += omega * dt;
|
_phase += omega * dt;
|
||||||
|
|
||||||
|
if (_phase < 0.0f) {
|
||||||
|
_phase = 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
// detect loop trigger events
|
// detect loop trigger events
|
||||||
if (_phase >= 1.0f) {
|
if (_phase >= 1.0f) {
|
||||||
triggersOut.setTrigger(_id + "Loop");
|
triggersOut.setTrigger(_id + "Loop");
|
||||||
|
|
|
@ -458,7 +458,6 @@ protected:
|
||||||
glm::vec3 _lastAngularVelocity;
|
glm::vec3 _lastAngularVelocity;
|
||||||
glm::vec3 _angularAcceleration;
|
glm::vec3 _angularAcceleration;
|
||||||
glm::quat _lastOrientation;
|
glm::quat _lastOrientation;
|
||||||
|
|
||||||
glm::vec3 _worldUpDirection { Vectors::UP };
|
glm::vec3 _worldUpDirection { Vectors::UP };
|
||||||
bool _moving { false }; ///< set when position is changing
|
bool _moving { false }; ///< set when position is changing
|
||||||
|
|
||||||
|
|
|
@ -918,7 +918,18 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) {
|
||||||
|
|
||||||
PACKET_READ_CHECK(AvatarGlobalPosition, sizeof(AvatarDataPacket::AvatarGlobalPosition));
|
PACKET_READ_CHECK(AvatarGlobalPosition, sizeof(AvatarDataPacket::AvatarGlobalPosition));
|
||||||
auto data = reinterpret_cast<const AvatarDataPacket::AvatarGlobalPosition*>(sourceBuffer);
|
auto data = reinterpret_cast<const AvatarDataPacket::AvatarGlobalPosition*>(sourceBuffer);
|
||||||
auto newValue = glm::vec3(data->globalPosition[0], data->globalPosition[1], data->globalPosition[2]);
|
|
||||||
|
glm::vec3 offset = glm::vec3(0.0f, 0.0f, 0.0f);
|
||||||
|
|
||||||
|
if (_replicaIndex > 0) {
|
||||||
|
const float SPACE_BETWEEN_AVATARS = 2.0f;
|
||||||
|
const int AVATARS_PER_ROW = 3;
|
||||||
|
int row = _replicaIndex % AVATARS_PER_ROW;
|
||||||
|
int col = floor(_replicaIndex / AVATARS_PER_ROW);
|
||||||
|
offset = glm::vec3(row * SPACE_BETWEEN_AVATARS, 0.0f, col * SPACE_BETWEEN_AVATARS);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto newValue = glm::vec3(data->globalPosition[0], data->globalPosition[1], data->globalPosition[2]) + offset;
|
||||||
if (_globalPosition != newValue) {
|
if (_globalPosition != newValue) {
|
||||||
_globalPosition = newValue;
|
_globalPosition = newValue;
|
||||||
_globalPositionChanged = usecTimestampNow();
|
_globalPositionChanged = usecTimestampNow();
|
||||||
|
|
|
@ -337,6 +337,7 @@ enum KillAvatarReason : uint8_t {
|
||||||
TheirAvatarEnteredYourBubble,
|
TheirAvatarEnteredYourBubble,
|
||||||
YourAvatarEnteredTheirBubble
|
YourAvatarEnteredTheirBubble
|
||||||
};
|
};
|
||||||
|
|
||||||
Q_DECLARE_METATYPE(KillAvatarReason);
|
Q_DECLARE_METATYPE(KillAvatarReason);
|
||||||
|
|
||||||
class QDataStream;
|
class QDataStream;
|
||||||
|
@ -1186,6 +1187,8 @@ public:
|
||||||
|
|
||||||
virtual void addMaterial(graphics::MaterialLayer material, const std::string& parentMaterialName) {}
|
virtual void addMaterial(graphics::MaterialLayer material, const std::string& parentMaterialName) {}
|
||||||
virtual void removeMaterial(graphics::MaterialPointer material, const std::string& parentMaterialName) {}
|
virtual void removeMaterial(graphics::MaterialPointer material, const std::string& parentMaterialName) {}
|
||||||
|
void setReplicaIndex(int replicaIndex) { _replicaIndex = replicaIndex; }
|
||||||
|
int getReplicaIndex() { return _replicaIndex; }
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
|
|
||||||
|
@ -1445,6 +1448,7 @@ protected:
|
||||||
udt::SequenceNumber _identitySequenceNumber { 0 };
|
udt::SequenceNumber _identitySequenceNumber { 0 };
|
||||||
bool _hasProcessedFirstIdentity { false };
|
bool _hasProcessedFirstIdentity { false };
|
||||||
float _density;
|
float _density;
|
||||||
|
int _replicaIndex { 0 };
|
||||||
|
|
||||||
// null unless MyAvatar or ScriptableAvatar sending traits data to mixer
|
// null unless MyAvatar or ScriptableAvatar sending traits data to mixer
|
||||||
std::unique_ptr<ClientTraitsHandler> _clientTraitsHandler;
|
std::unique_ptr<ClientTraitsHandler> _clientTraitsHandler;
|
||||||
|
@ -1561,7 +1565,7 @@ class RayToAvatarIntersectionResult {
|
||||||
public:
|
public:
|
||||||
bool intersects { false };
|
bool intersects { false };
|
||||||
QUuid avatarID;
|
QUuid avatarID;
|
||||||
float distance { 0.0f };
|
float distance { FLT_MAX };
|
||||||
BoxFace face;
|
BoxFace face;
|
||||||
glm::vec3 intersection;
|
glm::vec3 intersection;
|
||||||
glm::vec3 surfaceNormal;
|
glm::vec3 surfaceNormal;
|
||||||
|
|
|
@ -21,6 +21,84 @@
|
||||||
#include "AvatarLogging.h"
|
#include "AvatarLogging.h"
|
||||||
#include "AvatarTraits.h"
|
#include "AvatarTraits.h"
|
||||||
|
|
||||||
|
|
||||||
|
void AvatarReplicas::addReplica(const QUuid& parentID, AvatarSharedPointer replica) {
|
||||||
|
if (parentID == QUuid()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (_replicasMap.find(parentID) == _replicasMap.end()) {
|
||||||
|
std::vector<AvatarSharedPointer> emptyReplicas = std::vector<AvatarSharedPointer>();
|
||||||
|
_replicasMap.insert(std::pair<QUuid, std::vector<AvatarSharedPointer>>(parentID, emptyReplicas));
|
||||||
|
}
|
||||||
|
auto &replicas = _replicasMap[parentID];
|
||||||
|
replica->setReplicaIndex((int)replicas.size() + 1);
|
||||||
|
replicas.push_back(replica);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<QUuid> AvatarReplicas::getReplicaIDs(const QUuid& parentID) {
|
||||||
|
std::vector<QUuid> ids;
|
||||||
|
if (_replicasMap.find(parentID) != _replicasMap.end()) {
|
||||||
|
auto &replicas = _replicasMap[parentID];
|
||||||
|
for (int i = 0; i < (int)replicas.size(); i++) {
|
||||||
|
ids.push_back(replicas[i]->getID());
|
||||||
|
}
|
||||||
|
} else if (_replicaCount > 0) {
|
||||||
|
for (int i = 0; i < _replicaCount; i++) {
|
||||||
|
ids.push_back(QUuid::createUuid());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ids;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AvatarReplicas::parseDataFromBuffer(const QUuid& parentID, const QByteArray& buffer) {
|
||||||
|
if (_replicasMap.find(parentID) != _replicasMap.end()) {
|
||||||
|
auto &replicas = _replicasMap[parentID];
|
||||||
|
for (auto avatar : replicas) {
|
||||||
|
avatar->parseDataFromBuffer(buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AvatarReplicas::removeReplicas(const QUuid& parentID) {
|
||||||
|
if (_replicasMap.find(parentID) != _replicasMap.end()) {
|
||||||
|
_replicasMap.erase(parentID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AvatarReplicas::processAvatarIdentity(const QUuid& parentID, const QByteArray& identityData, bool& identityChanged, bool& displayNameChanged) {
|
||||||
|
if (_replicasMap.find(parentID) != _replicasMap.end()) {
|
||||||
|
auto &replicas = _replicasMap[parentID];
|
||||||
|
for (auto avatar : replicas) {
|
||||||
|
avatar->processAvatarIdentity(identityData, identityChanged, displayNameChanged);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void AvatarReplicas::processTrait(const QUuid& parentID, AvatarTraits::TraitType traitType, QByteArray traitBinaryData) {
|
||||||
|
if (_replicasMap.find(parentID) != _replicasMap.end()) {
|
||||||
|
auto &replicas = _replicasMap[parentID];
|
||||||
|
for (auto avatar : replicas) {
|
||||||
|
avatar->processTrait(traitType, traitBinaryData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void AvatarReplicas::processDeletedTraitInstance(const QUuid& parentID, AvatarTraits::TraitType traitType, AvatarTraits::TraitInstanceID instanceID) {
|
||||||
|
if (_replicasMap.find(parentID) != _replicasMap.end()) {
|
||||||
|
auto &replicas = _replicasMap[parentID];
|
||||||
|
for (auto avatar : replicas) {
|
||||||
|
avatar->processDeletedTraitInstance(traitType, instanceID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void AvatarReplicas::processTraitInstance(const QUuid& parentID, AvatarTraits::TraitType traitType,
|
||||||
|
AvatarTraits::TraitInstanceID instanceID, QByteArray traitBinaryData) {
|
||||||
|
if (_replicasMap.find(parentID) != _replicasMap.end()) {
|
||||||
|
auto &replicas = _replicasMap[parentID];
|
||||||
|
for (auto avatar : replicas) {
|
||||||
|
avatar->processTraitInstance(traitType, instanceID, traitBinaryData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
AvatarHashMap::AvatarHashMap() {
|
AvatarHashMap::AvatarHashMap() {
|
||||||
auto nodeList = DependencyManager::get<NodeList>();
|
auto nodeList = DependencyManager::get<NodeList>();
|
||||||
|
|
||||||
|
@ -64,6 +142,21 @@ bool AvatarHashMap::isAvatarInRange(const glm::vec3& position, const float range
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AvatarHashMap::setReplicaCount(int count) {
|
||||||
|
_replicas.setReplicaCount(count);
|
||||||
|
auto avatars = getAvatarIdentifiers();
|
||||||
|
for (int i = 0; i < avatars.size(); i++) {
|
||||||
|
KillAvatarReason reason = KillAvatarReason::NoReason;
|
||||||
|
if (avatars[i] != QUuid()) {
|
||||||
|
removeAvatar(avatars[i], reason);
|
||||||
|
auto replicaIDs = _replicas.getReplicaIDs(avatars[i]);
|
||||||
|
for (auto id : replicaIDs) {
|
||||||
|
removeAvatar(id, reason);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
int AvatarHashMap::numberOfAvatarsInRange(const glm::vec3& position, float rangeMeters) {
|
int AvatarHashMap::numberOfAvatarsInRange(const glm::vec3& position, float rangeMeters) {
|
||||||
auto hashCopy = getHashCopy();
|
auto hashCopy = getHashCopy();
|
||||||
auto rangeMeters2 = rangeMeters * rangeMeters;
|
auto rangeMeters2 = rangeMeters * rangeMeters;
|
||||||
|
@ -135,18 +228,25 @@ AvatarSharedPointer AvatarHashMap::parseAvatarData(QSharedPointer<ReceivedMessag
|
||||||
|
|
||||||
// make sure this isn't our own avatar data or for a previously ignored node
|
// make sure this isn't our own avatar data or for a previously ignored node
|
||||||
auto nodeList = DependencyManager::get<NodeList>();
|
auto nodeList = DependencyManager::get<NodeList>();
|
||||||
|
|
||||||
bool isNewAvatar;
|
bool isNewAvatar;
|
||||||
if (sessionUUID != _lastOwnerSessionUUID && (!nodeList->isIgnoringNode(sessionUUID) || nodeList->getRequestsDomainListData())) {
|
if (sessionUUID != _lastOwnerSessionUUID && (!nodeList->isIgnoringNode(sessionUUID) || nodeList->getRequestsDomainListData())) {
|
||||||
auto avatar = newOrExistingAvatar(sessionUUID, sendingNode, isNewAvatar);
|
auto avatar = newOrExistingAvatar(sessionUUID, sendingNode, isNewAvatar);
|
||||||
|
|
||||||
if (isNewAvatar) {
|
if (isNewAvatar) {
|
||||||
QWriteLocker locker(&_hashLock);
|
QWriteLocker locker(&_hashLock);
|
||||||
_pendingAvatars.insert(sessionUUID, { std::chrono::steady_clock::now(), 0, avatar });
|
_pendingAvatars.insert(sessionUUID, { std::chrono::steady_clock::now(), 0, avatar });
|
||||||
}
|
auto replicaIDs = _replicas.getReplicaIDs(sessionUUID);
|
||||||
|
for (auto replicaID : replicaIDs) {
|
||||||
|
auto replicaAvatar = addAvatar(replicaID, sendingNode);
|
||||||
|
_replicas.addReplica(sessionUUID, replicaAvatar);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// have the matching (or new) avatar parse the data from the packet
|
// have the matching (or new) avatar parse the data from the packet
|
||||||
int bytesRead = avatar->parseDataFromBuffer(byteArray);
|
int bytesRead = avatar->parseDataFromBuffer(byteArray);
|
||||||
message->seek(positionBeforeRead + bytesRead);
|
message->seek(positionBeforeRead + bytesRead);
|
||||||
|
_replicas.parseDataFromBuffer(sessionUUID, byteArray);
|
||||||
|
|
||||||
return avatar;
|
return avatar;
|
||||||
} else {
|
} else {
|
||||||
// create a dummy AvatarData class to throw this data on the ground
|
// create a dummy AvatarData class to throw this data on the ground
|
||||||
|
@ -191,10 +291,13 @@ void AvatarHashMap::processAvatarIdentityPacket(QSharedPointer<ReceivedMessage>
|
||||||
bool displayNameChanged = false;
|
bool displayNameChanged = false;
|
||||||
// In this case, the "sendingNode" is the Avatar Mixer.
|
// In this case, the "sendingNode" is the Avatar Mixer.
|
||||||
avatar->processAvatarIdentity(message->getMessage(), identityChanged, displayNameChanged);
|
avatar->processAvatarIdentity(message->getMessage(), identityChanged, displayNameChanged);
|
||||||
|
_replicas.processAvatarIdentity(identityUUID, message->getMessage(), identityChanged, displayNameChanged);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AvatarHashMap::processBulkAvatarTraits(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode) {
|
void AvatarHashMap::processBulkAvatarTraits(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode) {
|
||||||
|
|
||||||
while (message->getBytesLeftToRead()) {
|
while (message->getBytesLeftToRead()) {
|
||||||
// read the avatar ID to figure out which avatar this is for
|
// read the avatar ID to figure out which avatar this is for
|
||||||
auto avatarID = QUuid::fromRfc4122(message->readWithoutCopy(NUM_BYTES_RFC4122_UUID));
|
auto avatarID = QUuid::fromRfc4122(message->readWithoutCopy(NUM_BYTES_RFC4122_UUID));
|
||||||
|
@ -202,7 +305,6 @@ void AvatarHashMap::processBulkAvatarTraits(QSharedPointer<ReceivedMessage> mess
|
||||||
// grab the avatar so we can ask it to process trait data
|
// grab the avatar so we can ask it to process trait data
|
||||||
bool isNewAvatar;
|
bool isNewAvatar;
|
||||||
auto avatar = newOrExistingAvatar(avatarID, sendingNode, isNewAvatar);
|
auto avatar = newOrExistingAvatar(avatarID, sendingNode, isNewAvatar);
|
||||||
|
|
||||||
// read the first trait type for this avatar
|
// read the first trait type for this avatar
|
||||||
AvatarTraits::TraitType traitType;
|
AvatarTraits::TraitType traitType;
|
||||||
message->readPrimitive(&traitType);
|
message->readPrimitive(&traitType);
|
||||||
|
@ -217,13 +319,14 @@ void AvatarHashMap::processBulkAvatarTraits(QSharedPointer<ReceivedMessage> mess
|
||||||
AvatarTraits::TraitWireSize traitBinarySize;
|
AvatarTraits::TraitWireSize traitBinarySize;
|
||||||
bool skipBinaryTrait = false;
|
bool skipBinaryTrait = false;
|
||||||
|
|
||||||
|
|
||||||
if (AvatarTraits::isSimpleTrait(traitType)) {
|
if (AvatarTraits::isSimpleTrait(traitType)) {
|
||||||
message->readPrimitive(&traitBinarySize);
|
message->readPrimitive(&traitBinarySize);
|
||||||
|
|
||||||
// check if this trait version is newer than what we already have for this avatar
|
// check if this trait version is newer than what we already have for this avatar
|
||||||
if (packetTraitVersion > lastProcessedVersions[traitType]) {
|
if (packetTraitVersion > lastProcessedVersions[traitType]) {
|
||||||
avatar->processTrait(traitType, message->read(traitBinarySize));
|
auto traitData = message->read(traitBinarySize);
|
||||||
|
avatar->processTrait(traitType, traitData);
|
||||||
|
_replicas.processTrait(avatarID, traitType, traitData);
|
||||||
lastProcessedVersions[traitType] = packetTraitVersion;
|
lastProcessedVersions[traitType] = packetTraitVersion;
|
||||||
} else {
|
} else {
|
||||||
skipBinaryTrait = true;
|
skipBinaryTrait = true;
|
||||||
|
@ -238,8 +341,11 @@ void AvatarHashMap::processBulkAvatarTraits(QSharedPointer<ReceivedMessage> mess
|
||||||
if (packetTraitVersion > processedInstanceVersion) {
|
if (packetTraitVersion > processedInstanceVersion) {
|
||||||
if (traitBinarySize == AvatarTraits::DELETED_TRAIT_SIZE) {
|
if (traitBinarySize == AvatarTraits::DELETED_TRAIT_SIZE) {
|
||||||
avatar->processDeletedTraitInstance(traitType, traitInstanceID);
|
avatar->processDeletedTraitInstance(traitType, traitInstanceID);
|
||||||
|
_replicas.processDeletedTraitInstance(avatarID, traitType, traitInstanceID);
|
||||||
} else {
|
} else {
|
||||||
avatar->processTraitInstance(traitType, traitInstanceID, message->read(traitBinarySize));
|
auto traitData = message->read(traitBinarySize);
|
||||||
|
avatar->processTraitInstance(traitType, traitInstanceID, traitData);
|
||||||
|
_replicas.processTraitInstance(avatarID, traitType, traitInstanceID, traitData);
|
||||||
}
|
}
|
||||||
processedInstanceVersion = packetTraitVersion;
|
processedInstanceVersion = packetTraitVersion;
|
||||||
} else {
|
} else {
|
||||||
|
@ -265,17 +371,31 @@ void AvatarHashMap::processKillAvatar(QSharedPointer<ReceivedMessage> message, S
|
||||||
KillAvatarReason reason;
|
KillAvatarReason reason;
|
||||||
message->readPrimitive(&reason);
|
message->readPrimitive(&reason);
|
||||||
removeAvatar(sessionUUID, reason);
|
removeAvatar(sessionUUID, reason);
|
||||||
|
auto replicaIDs = _replicas.getReplicaIDs(sessionUUID);
|
||||||
|
for (auto id : replicaIDs) {
|
||||||
|
removeAvatar(id, reason);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AvatarHashMap::removeAvatar(const QUuid& sessionUUID, KillAvatarReason removalReason) {
|
void AvatarHashMap::removeAvatar(const QUuid& sessionUUID, KillAvatarReason removalReason) {
|
||||||
QWriteLocker locker(&_hashLock);
|
QWriteLocker locker(&_hashLock);
|
||||||
|
|
||||||
|
auto replicaIDs = _replicas.getReplicaIDs(sessionUUID);
|
||||||
|
_replicas.removeReplicas(sessionUUID);
|
||||||
|
for (auto id : replicaIDs) {
|
||||||
|
auto removedReplica = _avatarHash.take(id);
|
||||||
|
if (removedReplica) {
|
||||||
|
handleRemovedAvatar(removedReplica, removalReason);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_pendingAvatars.remove(sessionUUID);
|
_pendingAvatars.remove(sessionUUID);
|
||||||
auto removedAvatar = _avatarHash.take(sessionUUID);
|
auto removedAvatar = _avatarHash.take(sessionUUID);
|
||||||
|
|
||||||
if (removedAvatar) {
|
if (removedAvatar) {
|
||||||
handleRemovedAvatar(removedAvatar, removalReason);
|
handleRemovedAvatar(removedAvatar, removalReason);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AvatarHashMap::handleRemovedAvatar(const AvatarSharedPointer& removedAvatar, KillAvatarReason removalReason) {
|
void AvatarHashMap::handleRemovedAvatar(const AvatarSharedPointer& removedAvatar, KillAvatarReason removalReason) {
|
||||||
|
|
|
@ -41,6 +41,27 @@
|
||||||
* @hifi-assignment-client
|
* @hifi-assignment-client
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
class AvatarReplicas {
|
||||||
|
public:
|
||||||
|
AvatarReplicas() {}
|
||||||
|
void addReplica(const QUuid& parentID, AvatarSharedPointer replica);
|
||||||
|
std::vector<QUuid> getReplicaIDs(const QUuid& parentID);
|
||||||
|
void parseDataFromBuffer(const QUuid& parentID, const QByteArray& buffer);
|
||||||
|
void processAvatarIdentity(const QUuid& parentID, const QByteArray& identityData, bool& identityChanged, bool& displayNameChanged);
|
||||||
|
void removeReplicas(const QUuid& parentID);
|
||||||
|
void processTrait(const QUuid& parentID, AvatarTraits::TraitType traitType, QByteArray traitBinaryData);
|
||||||
|
void processDeletedTraitInstance(const QUuid& parentID, AvatarTraits::TraitType traitType, AvatarTraits::TraitInstanceID instanceID);
|
||||||
|
void processTraitInstance(const QUuid& parentID, AvatarTraits::TraitType traitType,
|
||||||
|
AvatarTraits::TraitInstanceID instanceID, QByteArray traitBinaryData);
|
||||||
|
void setReplicaCount(int count) { _replicaCount = count; }
|
||||||
|
int getReplicaCount() { return _replicaCount; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::map<QUuid, std::vector<AvatarSharedPointer>> _replicasMap;
|
||||||
|
int _replicaCount { 0 };
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
class AvatarHashMap : public QObject, public Dependency {
|
class AvatarHashMap : public QObject, public Dependency {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
SINGLETON_DEPENDENCY
|
SINGLETON_DEPENDENCY
|
||||||
|
@ -77,6 +98,9 @@ public:
|
||||||
virtual AvatarSharedPointer getAvatarBySessionID(const QUuid& sessionID) const { return findAvatar(sessionID); }
|
virtual AvatarSharedPointer getAvatarBySessionID(const QUuid& sessionID) const { return findAvatar(sessionID); }
|
||||||
int numberOfAvatarsInRange(const glm::vec3& position, float rangeMeters);
|
int numberOfAvatarsInRange(const glm::vec3& position, float rangeMeters);
|
||||||
|
|
||||||
|
void setReplicaCount(int count);
|
||||||
|
int getReplicaCount() { return _replicas.getReplicaCount(); };
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
|
|
||||||
/**jsdoc
|
/**jsdoc
|
||||||
|
@ -167,6 +191,8 @@ protected:
|
||||||
mutable QReadWriteLock _hashLock;
|
mutable QReadWriteLock _hashLock;
|
||||||
|
|
||||||
std::unordered_map<QUuid, AvatarTraits::TraitVersions> _processedTraitVersions;
|
std::unordered_map<QUuid, AvatarTraits::TraitVersions> _processedTraitVersions;
|
||||||
|
AvatarReplicas _replicas;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QUuid _lastOwnerSessionUUID;
|
QUuid _lastOwnerSessionUUID;
|
||||||
};
|
};
|
||||||
|
|
|
@ -571,7 +571,6 @@ bool RenderablePolyVoxEntityItem::findDetailedRayIntersection(const glm::vec3& o
|
||||||
}
|
}
|
||||||
|
|
||||||
glm::mat4 wtvMatrix = worldToVoxelMatrix();
|
glm::mat4 wtvMatrix = worldToVoxelMatrix();
|
||||||
glm::mat4 vtwMatrix = voxelToWorldMatrix();
|
|
||||||
glm::vec3 normDirection = glm::normalize(direction);
|
glm::vec3 normDirection = glm::normalize(direction);
|
||||||
|
|
||||||
// the PolyVox ray intersection code requires a near and far point.
|
// the PolyVox ray intersection code requires a near and far point.
|
||||||
|
@ -584,8 +583,6 @@ bool RenderablePolyVoxEntityItem::findDetailedRayIntersection(const glm::vec3& o
|
||||||
glm::vec4 originInVoxel = wtvMatrix * glm::vec4(origin, 1.0f);
|
glm::vec4 originInVoxel = wtvMatrix * glm::vec4(origin, 1.0f);
|
||||||
glm::vec4 farInVoxel = wtvMatrix * glm::vec4(farPoint, 1.0f);
|
glm::vec4 farInVoxel = wtvMatrix * glm::vec4(farPoint, 1.0f);
|
||||||
|
|
||||||
glm::vec4 directionInVoxel = glm::normalize(farInVoxel - originInVoxel);
|
|
||||||
|
|
||||||
glm::vec4 result = glm::vec4(0.0f, 0.0f, 0.0f, 0.0f);
|
glm::vec4 result = glm::vec4(0.0f, 0.0f, 0.0f, 0.0f);
|
||||||
PolyVox::RaycastResult raycastResult = doRayCast(originInVoxel, farInVoxel, result);
|
PolyVox::RaycastResult raycastResult = doRayCast(originInVoxel, farInVoxel, result);
|
||||||
if (raycastResult == PolyVox::RaycastResults::Completed) {
|
if (raycastResult == PolyVox::RaycastResults::Completed) {
|
||||||
|
@ -599,14 +596,9 @@ bool RenderablePolyVoxEntityItem::findDetailedRayIntersection(const glm::vec3& o
|
||||||
voxelBox += result3 - Vectors::HALF;
|
voxelBox += result3 - Vectors::HALF;
|
||||||
voxelBox += result3 + Vectors::HALF;
|
voxelBox += result3 + Vectors::HALF;
|
||||||
|
|
||||||
float voxelDistance;
|
glm::vec3 directionInVoxel = vec3(wtvMatrix * glm::vec4(direction, 0.0f));
|
||||||
bool hit = voxelBox.findRayIntersection(glm::vec3(originInVoxel), glm::vec3(directionInVoxel),
|
return voxelBox.findRayIntersection(glm::vec3(originInVoxel), directionInVoxel, 1.0f / directionInVoxel,
|
||||||
voxelDistance, face, surfaceNormal);
|
distance, face, surfaceNormal);
|
||||||
|
|
||||||
glm::vec4 voxelIntersectionPoint = glm::vec4(glm::vec3(originInVoxel) + glm::vec3(directionInVoxel) * voxelDistance, 1.0);
|
|
||||||
glm::vec4 intersectionPoint = vtwMatrix * voxelIntersectionPoint;
|
|
||||||
distance = glm::distance(origin, glm::vec3(intersectionPoint));
|
|
||||||
return hit;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RenderablePolyVoxEntityItem::findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity,
|
bool RenderablePolyVoxEntityItem::findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity,
|
||||||
|
|
|
@ -48,6 +48,7 @@ public:
|
||||||
// Inputs
|
// Inputs
|
||||||
glm::vec3 origin;
|
glm::vec3 origin;
|
||||||
glm::vec3 direction;
|
glm::vec3 direction;
|
||||||
|
glm::vec3 invDirection;
|
||||||
const QVector<EntityItemID>& entityIdsToInclude;
|
const QVector<EntityItemID>& entityIdsToInclude;
|
||||||
const QVector<EntityItemID>& entityIdsToDiscard;
|
const QVector<EntityItemID>& entityIdsToDiscard;
|
||||||
bool visibleOnly;
|
bool visibleOnly;
|
||||||
|
@ -825,28 +826,51 @@ bool findRayIntersectionOp(const OctreeElementPointer& element, void* extraData)
|
||||||
RayArgs* args = static_cast<RayArgs*>(extraData);
|
RayArgs* args = static_cast<RayArgs*>(extraData);
|
||||||
bool keepSearching = true;
|
bool keepSearching = true;
|
||||||
EntityTreeElementPointer entityTreeElementPointer = std::static_pointer_cast<EntityTreeElement>(element);
|
EntityTreeElementPointer entityTreeElementPointer = std::static_pointer_cast<EntityTreeElement>(element);
|
||||||
EntityItemID entityID = entityTreeElementPointer->findRayIntersection(args->origin, args->direction, keepSearching,
|
EntityItemID entityID = entityTreeElementPointer->findRayIntersection(args->origin, args->direction,
|
||||||
args->element, args->distance, args->face, args->surfaceNormal, args->entityIdsToInclude,
|
args->element, args->distance, args->face, args->surfaceNormal, args->entityIdsToInclude,
|
||||||
args->entityIdsToDiscard, args->visibleOnly, args->collidableOnly, args->extraInfo, args->precisionPicking);
|
args->entityIdsToDiscard, args->visibleOnly, args->collidableOnly, args->extraInfo, args->precisionPicking);
|
||||||
if (!entityID.isNull()) {
|
if (!entityID.isNull()) {
|
||||||
args->entityID = entityID;
|
args->entityID = entityID;
|
||||||
|
// We recurse OctreeElements in order, so if we hit something, we can stop immediately
|
||||||
|
keepSearching = false;
|
||||||
}
|
}
|
||||||
return keepSearching;
|
return keepSearching;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
float findRayIntersectionSortingOp(const OctreeElementPointer& element, void* extraData) {
|
||||||
|
RayArgs* args = static_cast<RayArgs*>(extraData);
|
||||||
|
EntityTreeElementPointer entityTreeElementPointer = std::static_pointer_cast<EntityTreeElement>(element);
|
||||||
|
float distance = FLT_MAX;
|
||||||
|
// If origin is inside the cube, always check this element first
|
||||||
|
if (entityTreeElementPointer->getAACube().contains(args->origin)) {
|
||||||
|
distance = 0.0f;
|
||||||
|
} else {
|
||||||
|
float boundDistance = FLT_MAX;
|
||||||
|
BoxFace face;
|
||||||
|
glm::vec3 surfaceNormal;
|
||||||
|
if (entityTreeElementPointer->getAACube().findRayIntersection(args->origin, args->direction, args->invDirection, boundDistance, face, surfaceNormal)) {
|
||||||
|
// Don't add this cell if it's already farther than our best distance so far
|
||||||
|
if (boundDistance < args->distance) {
|
||||||
|
distance = boundDistance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return distance;
|
||||||
|
}
|
||||||
|
|
||||||
EntityItemID EntityTree::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
EntityItemID EntityTree::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||||
QVector<EntityItemID> entityIdsToInclude, QVector<EntityItemID> entityIdsToDiscard,
|
QVector<EntityItemID> entityIdsToInclude, QVector<EntityItemID> entityIdsToDiscard,
|
||||||
bool visibleOnly, bool collidableOnly, bool precisionPicking,
|
bool visibleOnly, bool collidableOnly, bool precisionPicking,
|
||||||
OctreeElementPointer& element, float& distance,
|
OctreeElementPointer& element, float& distance,
|
||||||
BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo,
|
BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo,
|
||||||
Octree::lockType lockType, bool* accurateResult) {
|
Octree::lockType lockType, bool* accurateResult) {
|
||||||
RayArgs args = { origin, direction, entityIdsToInclude, entityIdsToDiscard,
|
RayArgs args = { origin, direction, 1.0f / direction, entityIdsToInclude, entityIdsToDiscard,
|
||||||
visibleOnly, collidableOnly, precisionPicking, element, distance, face, surfaceNormal, extraInfo, EntityItemID() };
|
visibleOnly, collidableOnly, precisionPicking, element, distance, face, surfaceNormal, extraInfo, EntityItemID() };
|
||||||
distance = FLT_MAX;
|
distance = FLT_MAX;
|
||||||
|
|
||||||
bool requireLock = lockType == Octree::Lock;
|
bool requireLock = lockType == Octree::Lock;
|
||||||
bool lockResult = withReadLock([&]{
|
bool lockResult = withReadLock([&]{
|
||||||
recurseTreeWithOperation(findRayIntersectionOp, &args);
|
recurseTreeWithOperationSorted(findRayIntersectionOp, findRayIntersectionSortingOp, &args);
|
||||||
}, requireLock);
|
}, requireLock);
|
||||||
|
|
||||||
if (accurateResult) {
|
if (accurateResult) {
|
||||||
|
@ -860,15 +884,38 @@ bool findParabolaIntersectionOp(const OctreeElementPointer& element, void* extra
|
||||||
ParabolaArgs* args = static_cast<ParabolaArgs*>(extraData);
|
ParabolaArgs* args = static_cast<ParabolaArgs*>(extraData);
|
||||||
bool keepSearching = true;
|
bool keepSearching = true;
|
||||||
EntityTreeElementPointer entityTreeElementPointer = std::static_pointer_cast<EntityTreeElement>(element);
|
EntityTreeElementPointer entityTreeElementPointer = std::static_pointer_cast<EntityTreeElement>(element);
|
||||||
EntityItemID entityID = entityTreeElementPointer->findParabolaIntersection(args->origin, args->velocity, args->acceleration, keepSearching,
|
EntityItemID entityID = entityTreeElementPointer->findParabolaIntersection(args->origin, args->velocity, args->acceleration,
|
||||||
args->element, args->parabolicDistance, args->face, args->surfaceNormal, args->entityIdsToInclude,
|
args->element, args->parabolicDistance, args->face, args->surfaceNormal, args->entityIdsToInclude,
|
||||||
args->entityIdsToDiscard, args->visibleOnly, args->collidableOnly, args->extraInfo, args->precisionPicking);
|
args->entityIdsToDiscard, args->visibleOnly, args->collidableOnly, args->extraInfo, args->precisionPicking);
|
||||||
if (!entityID.isNull()) {
|
if (!entityID.isNull()) {
|
||||||
args->entityID = entityID;
|
args->entityID = entityID;
|
||||||
|
// We recurse OctreeElements in order, so if we hit something, we can stop immediately
|
||||||
|
keepSearching = false;
|
||||||
}
|
}
|
||||||
return keepSearching;
|
return keepSearching;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
float findParabolaIntersectionSortingOp(const OctreeElementPointer& element, void* extraData) {
|
||||||
|
ParabolaArgs* args = static_cast<ParabolaArgs*>(extraData);
|
||||||
|
EntityTreeElementPointer entityTreeElementPointer = std::static_pointer_cast<EntityTreeElement>(element);
|
||||||
|
float distance = FLT_MAX;
|
||||||
|
// If origin is inside the cube, always check this element first
|
||||||
|
if (entityTreeElementPointer->getAACube().contains(args->origin)) {
|
||||||
|
distance = 0.0f;
|
||||||
|
} else {
|
||||||
|
float boundDistance = FLT_MAX;
|
||||||
|
BoxFace face;
|
||||||
|
glm::vec3 surfaceNormal;
|
||||||
|
if (entityTreeElementPointer->getAACube().findParabolaIntersection(args->origin, args->velocity, args->acceleration, boundDistance, face, surfaceNormal)) {
|
||||||
|
// Don't add this cell if it's already farther than our best distance so far
|
||||||
|
if (boundDistance < args->parabolicDistance) {
|
||||||
|
distance = boundDistance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return distance;
|
||||||
|
}
|
||||||
|
|
||||||
EntityItemID EntityTree::findParabolaIntersection(const PickParabola& parabola,
|
EntityItemID EntityTree::findParabolaIntersection(const PickParabola& parabola,
|
||||||
QVector<EntityItemID> entityIdsToInclude, QVector<EntityItemID> entityIdsToDiscard,
|
QVector<EntityItemID> entityIdsToInclude, QVector<EntityItemID> entityIdsToDiscard,
|
||||||
bool visibleOnly, bool collidableOnly, bool precisionPicking,
|
bool visibleOnly, bool collidableOnly, bool precisionPicking,
|
||||||
|
@ -882,7 +929,7 @@ EntityItemID EntityTree::findParabolaIntersection(const PickParabola& parabola,
|
||||||
|
|
||||||
bool requireLock = lockType == Octree::Lock;
|
bool requireLock = lockType == Octree::Lock;
|
||||||
bool lockResult = withReadLock([&] {
|
bool lockResult = withReadLock([&] {
|
||||||
recurseTreeWithOperation(findParabolaIntersectionOp, &args);
|
recurseTreeWithOperationSorted(findParabolaIntersectionOp, findParabolaIntersectionSortingOp, &args);
|
||||||
}, requireLock);
|
}, requireLock);
|
||||||
|
|
||||||
if (accurateResult) {
|
if (accurateResult) {
|
||||||
|
|
|
@ -140,31 +140,18 @@ bool EntityTreeElement::bestFitBounds(const glm::vec3& minPoint, const glm::vec3
|
||||||
}
|
}
|
||||||
|
|
||||||
EntityItemID EntityTreeElement::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
EntityItemID EntityTreeElement::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||||
bool& keepSearching, OctreeElementPointer& element, float& distance,
|
OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal,
|
||||||
BoxFace& face, glm::vec3& surfaceNormal, const QVector<EntityItemID>& entityIdsToInclude,
|
const QVector<EntityItemID>& entityIdsToInclude, const QVector<EntityItemID>& entityIdsToDiscard,
|
||||||
const QVector<EntityItemID>& entityIdsToDiscard, bool visibleOnly, bool collidableOnly,
|
bool visibleOnly, bool collidableOnly, QVariantMap& extraInfo, bool precisionPicking) {
|
||||||
QVariantMap& extraInfo, bool precisionPicking) {
|
|
||||||
|
|
||||||
EntityItemID result;
|
EntityItemID result;
|
||||||
float distanceToElementCube = std::numeric_limits<float>::max();
|
|
||||||
BoxFace localFace;
|
BoxFace localFace;
|
||||||
glm::vec3 localSurfaceNormal;
|
glm::vec3 localSurfaceNormal;
|
||||||
|
|
||||||
// if the ray doesn't intersect with our cube OR the distance to element is less than current best distance
|
|
||||||
// we can stop searching!
|
|
||||||
bool hit = _cube.findRayIntersection(origin, direction, distanceToElementCube, localFace, localSurfaceNormal);
|
|
||||||
if (!hit || (!_cube.contains(origin) && distanceToElementCube > distance)) {
|
|
||||||
keepSearching = false; // no point in continuing to search
|
|
||||||
return result; // we did not intersect
|
|
||||||
}
|
|
||||||
|
|
||||||
// by default, we only allow intersections with leaves with content
|
|
||||||
if (!canPickIntersect()) {
|
if (!canPickIntersect()) {
|
||||||
return result; // we don't intersect with non-leaves, and we keep searching
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
// if the distance to the element cube is not less than the current best distance, then it's not possible
|
|
||||||
// for any details inside the cube to be closer so we don't need to consider them.
|
|
||||||
QVariantMap localExtraInfo;
|
QVariantMap localExtraInfo;
|
||||||
float distanceToElementDetails = distance;
|
float distanceToElementDetails = distance;
|
||||||
EntityItemID entityID = findDetailedRayIntersection(origin, direction, element, distanceToElementDetails,
|
EntityItemID entityID = findDetailedRayIntersection(origin, direction, element, distanceToElementDetails,
|
||||||
|
@ -228,7 +215,7 @@ EntityItemID EntityTreeElement::findDetailedRayIntersection(const glm::vec3& ori
|
||||||
float localDistance;
|
float localDistance;
|
||||||
BoxFace localFace;
|
BoxFace localFace;
|
||||||
glm::vec3 localSurfaceNormal;
|
glm::vec3 localSurfaceNormal;
|
||||||
if (entityFrameBox.findRayIntersection(entityFrameOrigin, entityFrameDirection, localDistance,
|
if (entityFrameBox.findRayIntersection(entityFrameOrigin, entityFrameDirection, 1.0f / entityFrameDirection, localDistance,
|
||||||
localFace, localSurfaceNormal)) {
|
localFace, localSurfaceNormal)) {
|
||||||
if (entityFrameBox.contains(entityFrameOrigin) || localDistance < distance) {
|
if (entityFrameBox.contains(entityFrameOrigin) || localDistance < distance) {
|
||||||
// now ask the entity if we actually intersect
|
// now ask the entity if we actually intersect
|
||||||
|
@ -289,31 +276,19 @@ bool EntityTreeElement::findSpherePenetration(const glm::vec3& center, float rad
|
||||||
}
|
}
|
||||||
|
|
||||||
EntityItemID EntityTreeElement::findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity,
|
EntityItemID EntityTreeElement::findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity,
|
||||||
const glm::vec3& acceleration, bool& keepSearching, OctreeElementPointer& element, float& parabolicDistance,
|
const glm::vec3& acceleration, OctreeElementPointer& element, float& parabolicDistance,
|
||||||
BoxFace& face, glm::vec3& surfaceNormal, const QVector<EntityItemID>& entityIdsToInclude,
|
BoxFace& face, glm::vec3& surfaceNormal, const QVector<EntityItemID>& entityIdsToInclude,
|
||||||
const QVector<EntityItemID>& entityIdsToDiscard, bool visibleOnly, bool collidableOnly,
|
const QVector<EntityItemID>& entityIdsToDiscard, bool visibleOnly, bool collidableOnly,
|
||||||
QVariantMap& extraInfo, bool precisionPicking) {
|
QVariantMap& extraInfo, bool precisionPicking) {
|
||||||
|
|
||||||
EntityItemID result;
|
EntityItemID result;
|
||||||
float distanceToElementCube = std::numeric_limits<float>::max();
|
|
||||||
BoxFace localFace;
|
BoxFace localFace;
|
||||||
glm::vec3 localSurfaceNormal;
|
glm::vec3 localSurfaceNormal;
|
||||||
|
|
||||||
// if the parabola doesn't intersect with our cube OR the distance to element is less than current best distance
|
|
||||||
// we can stop searching!
|
|
||||||
bool hit = _cube.findParabolaIntersection(origin, velocity, acceleration, distanceToElementCube, localFace, localSurfaceNormal);
|
|
||||||
if (!hit || (!_cube.contains(origin) && distanceToElementCube > parabolicDistance)) {
|
|
||||||
keepSearching = false; // no point in continuing to search
|
|
||||||
return result; // we did not intersect
|
|
||||||
}
|
|
||||||
|
|
||||||
// by default, we only allow intersections with leaves with content
|
|
||||||
if (!canPickIntersect()) {
|
if (!canPickIntersect()) {
|
||||||
return result; // we don't intersect with non-leaves, and we keep searching
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
// if the distance to the element cube is not less than the current best distance, then it's not possible
|
|
||||||
// for any details inside the cube to be closer so we don't need to consider them.
|
|
||||||
QVariantMap localExtraInfo;
|
QVariantMap localExtraInfo;
|
||||||
float distanceToElementDetails = parabolicDistance;
|
float distanceToElementDetails = parabolicDistance;
|
||||||
// We can precompute the world-space parabola normal and reuse it for the parabola plane intersects AABox sphere check
|
// We can precompute the world-space parabola normal and reuse it for the parabola plane intersects AABox sphere check
|
||||||
|
|
|
@ -136,10 +136,9 @@ public:
|
||||||
|
|
||||||
virtual bool canPickIntersect() const override { return hasEntities(); }
|
virtual bool canPickIntersect() const override { return hasEntities(); }
|
||||||
virtual EntityItemID findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
virtual EntityItemID findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||||
bool& keepSearching, OctreeElementPointer& element, float& distance,
|
OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal,
|
||||||
BoxFace& face, glm::vec3& surfaceNormal, const QVector<EntityItemID>& entityIdsToInclude,
|
const QVector<EntityItemID>& entityIdsToInclude, const QVector<EntityItemID>& entityIdsToDiscard,
|
||||||
const QVector<EntityItemID>& entityIdsToDiscard, bool visibleOnly, bool collidableOnly,
|
bool visibleOnly, bool collidableOnly, QVariantMap& extraInfo, bool precisionPicking = false);
|
||||||
QVariantMap& extraInfo, bool precisionPicking = false);
|
|
||||||
virtual EntityItemID findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
virtual EntityItemID findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||||
OctreeElementPointer& element, float& distance,
|
OctreeElementPointer& element, float& distance,
|
||||||
BoxFace& face, glm::vec3& surfaceNormal, const QVector<EntityItemID>& entityIdsToInclude,
|
BoxFace& face, glm::vec3& surfaceNormal, const QVector<EntityItemID>& entityIdsToInclude,
|
||||||
|
@ -149,7 +148,7 @@ public:
|
||||||
glm::vec3& penetration, void** penetratedObject) const override;
|
glm::vec3& penetration, void** penetratedObject) const override;
|
||||||
|
|
||||||
virtual EntityItemID findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity,
|
virtual EntityItemID findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity,
|
||||||
const glm::vec3& acceleration, bool& keepSearching, OctreeElementPointer& element, float& parabolicDistance,
|
const glm::vec3& acceleration, OctreeElementPointer& element, float& parabolicDistance,
|
||||||
BoxFace& face, glm::vec3& surfaceNormal, const QVector<EntityItemID>& entityIdsToInclude,
|
BoxFace& face, glm::vec3& surfaceNormal, const QVector<EntityItemID>& entityIdsToInclude,
|
||||||
const QVector<EntityItemID>& entityIdsToDiscard, bool visibleOnly, bool collidableOnly,
|
const QVector<EntityItemID>& entityIdsToDiscard, bool visibleOnly, bool collidableOnly,
|
||||||
QVariantMap& extraInfo, bool precisionPicking = false);
|
QVariantMap& extraInfo, bool precisionPicking = false);
|
||||||
|
|
|
@ -262,20 +262,18 @@ bool ShapeEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const
|
||||||
glm::mat4 entityToWorldMatrix = getEntityToWorldMatrix();
|
glm::mat4 entityToWorldMatrix = getEntityToWorldMatrix();
|
||||||
glm::mat4 worldToEntityMatrix = glm::inverse(entityToWorldMatrix);
|
glm::mat4 worldToEntityMatrix = glm::inverse(entityToWorldMatrix);
|
||||||
glm::vec3 entityFrameOrigin = glm::vec3(worldToEntityMatrix * glm::vec4(origin, 1.0f));
|
glm::vec3 entityFrameOrigin = glm::vec3(worldToEntityMatrix * glm::vec4(origin, 1.0f));
|
||||||
glm::vec3 entityFrameDirection = glm::normalize(glm::vec3(worldToEntityMatrix * glm::vec4(direction, 0.0f)));
|
glm::vec3 entityFrameDirection = glm::vec3(worldToEntityMatrix * glm::vec4(direction, 0.0f));
|
||||||
|
|
||||||
float localDistance;
|
|
||||||
// NOTE: unit sphere has center of 0,0,0 and radius of 0.5
|
// NOTE: unit sphere has center of 0,0,0 and radius of 0.5
|
||||||
if (findRaySphereIntersection(entityFrameOrigin, entityFrameDirection, glm::vec3(0.0f), 0.5f, localDistance)) {
|
if (findRaySphereIntersection(entityFrameOrigin, entityFrameDirection, glm::vec3(0.0f), 0.5f, distance)) {
|
||||||
// determine where on the unit sphere the hit point occured
|
|
||||||
glm::vec3 entityFrameHitAt = entityFrameOrigin + (entityFrameDirection * localDistance);
|
|
||||||
// then translate back to work coordinates
|
|
||||||
glm::vec3 hitAt = glm::vec3(entityToWorldMatrix * glm::vec4(entityFrameHitAt, 1.0f));
|
|
||||||
distance = glm::distance(origin, hitAt);
|
|
||||||
bool success;
|
bool success;
|
||||||
// FIXME: this is only correct for uniformly scaled spheres
|
glm::vec3 center = getCenterPosition(success);
|
||||||
surfaceNormal = glm::normalize(hitAt - getCenterPosition(success));
|
if (success) {
|
||||||
if (!success) {
|
// FIXME: this is only correct for uniformly scaled spheres
|
||||||
|
// determine where on the unit sphere the hit point occured
|
||||||
|
glm::vec3 hitAt = origin + (direction * distance);
|
||||||
|
surfaceNormal = glm::normalize(hitAt - center);
|
||||||
|
} else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
@ -297,9 +295,11 @@ bool ShapeEntityItem::findDetailedParabolaIntersection(const glm::vec3& origin,
|
||||||
// NOTE: unit sphere has center of 0,0,0 and radius of 0.5
|
// NOTE: unit sphere has center of 0,0,0 and radius of 0.5
|
||||||
if (findParabolaSphereIntersection(entityFrameOrigin, entityFrameVelocity, entityFrameAcceleration, glm::vec3(0.0f), 0.5f, parabolicDistance)) {
|
if (findParabolaSphereIntersection(entityFrameOrigin, entityFrameVelocity, entityFrameAcceleration, glm::vec3(0.0f), 0.5f, parabolicDistance)) {
|
||||||
bool success;
|
bool success;
|
||||||
// FIXME: this is only correct for uniformly scaled spheres
|
glm::vec3 center = getCenterPosition(success);
|
||||||
surfaceNormal = glm::normalize((origin + velocity * parabolicDistance + 0.5f * acceleration * parabolicDistance * parabolicDistance) - getCenterPosition(success));
|
if (success) {
|
||||||
if (!success) {
|
// FIXME: this is only correct for uniformly scaled spheres
|
||||||
|
surfaceNormal = glm::normalize((origin + velocity * parabolicDistance + 0.5f * acceleration * parabolicDistance * parabolicDistance) - center);
|
||||||
|
} else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -28,6 +28,20 @@
|
||||||
#include <graphics/Geometry.h>
|
#include <graphics/Geometry.h>
|
||||||
#include <graphics/Material.h>
|
#include <graphics/Material.h>
|
||||||
|
|
||||||
|
#if defined(Q_OS_ANDROID)
|
||||||
|
#define FBX_PACK_NORMALS 0
|
||||||
|
#else
|
||||||
|
#define FBX_PACK_NORMALS 1
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if FBX_PACK_NORMALS
|
||||||
|
using NormalType = glm::uint32;
|
||||||
|
#define FBX_NORMAL_ELEMENT gpu::Element::VEC4F_NORMALIZED_XYZ10W2
|
||||||
|
#else
|
||||||
|
using NormalType = glm::vec3;
|
||||||
|
#define FBX_NORMAL_ELEMENT gpu::Element::VEC3F_XYZ
|
||||||
|
#endif
|
||||||
|
|
||||||
// See comment in FBXReader::parseFBX().
|
// See comment in FBXReader::parseFBX().
|
||||||
static const int FBX_HEADER_BYTES_BEFORE_VERSION = 23;
|
static const int FBX_HEADER_BYTES_BEFORE_VERSION = 23;
|
||||||
static const QByteArray FBX_BINARY_PROLOG("Kaydara FBX Binary ");
|
static const QByteArray FBX_BINARY_PROLOG("Kaydara FBX Binary ");
|
||||||
|
@ -226,6 +240,7 @@ public:
|
||||||
QVector<glm::vec3> vertices;
|
QVector<glm::vec3> vertices;
|
||||||
QVector<glm::vec3> normals;
|
QVector<glm::vec3> normals;
|
||||||
QVector<glm::vec3> tangents;
|
QVector<glm::vec3> tangents;
|
||||||
|
mutable QVector<NormalType> normalsAndTangents; // Populated later if needed for blendshapes
|
||||||
QVector<glm::vec3> colors;
|
QVector<glm::vec3> colors;
|
||||||
QVector<glm::vec2> texCoords;
|
QVector<glm::vec2> texCoords;
|
||||||
QVector<glm::vec2> texCoords1;
|
QVector<glm::vec2> texCoords1;
|
||||||
|
|
|
@ -34,20 +34,6 @@
|
||||||
class QIODevice;
|
class QIODevice;
|
||||||
class FBXNode;
|
class FBXNode;
|
||||||
|
|
||||||
#if defined(Q_OS_ANDROID)
|
|
||||||
#define FBX_PACK_NORMALS 0
|
|
||||||
#else
|
|
||||||
#define FBX_PACK_NORMALS 1
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if FBX_PACK_NORMALS
|
|
||||||
using NormalType = glm::uint32;
|
|
||||||
#define FBX_NORMAL_ELEMENT gpu::Element::VEC4F_NORMALIZED_XYZ10W2
|
|
||||||
#else
|
|
||||||
using NormalType = glm::vec3;
|
|
||||||
#define FBX_NORMAL_ELEMENT gpu::Element::VEC3F_XYZ
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/// Reads FBX geometry from the supplied model and mapping data.
|
/// Reads FBX geometry from the supplied model and mapping data.
|
||||||
/// \exception QString if an error occurs in parsing
|
/// \exception QString if an error occurs in parsing
|
||||||
FBXGeometry* readFBX(const QByteArray& model, const QVariantHash& mapping, const QString& url = "", bool loadLightmaps = true, float lightmapLevel = 1.0f);
|
FBXGeometry* readFBX(const QByteArray& model, const QVariantHash& mapping, const QString& url = "", bool loadLightmaps = true, float lightmapLevel = 1.0f);
|
||||||
|
|
|
@ -935,7 +935,7 @@ FBXGeometry* GLTFReader::readGLTF(QByteArray& model, const QVariantHash& mapping
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GLTFReader::readBinary(const QString& url, QByteArray& outdata) {
|
bool GLTFReader::readBinary(const QString& url, QByteArray& outdata) {
|
||||||
QUrl binaryUrl = _url.resolved(QUrl(url).fileName());
|
QUrl binaryUrl = _url.resolved(url);
|
||||||
|
|
||||||
qCDebug(modelformat) << "binaryUrl: " << binaryUrl << " OriginalUrl: " << _url;
|
qCDebug(modelformat) << "binaryUrl: " << binaryUrl << " OriginalUrl: " << _url;
|
||||||
bool success;
|
bool success;
|
||||||
|
@ -948,7 +948,7 @@ bool GLTFReader::doesResourceExist(const QString& url) {
|
||||||
if (_url.isEmpty()) {
|
if (_url.isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
QUrl candidateUrl = _url.resolved(QUrl(url).fileName());
|
QUrl candidateUrl = _url.resolved(url);
|
||||||
return DependencyManager::get<ResourceManager>()->resourceExists(candidateUrl);
|
return DependencyManager::get<ResourceManager>()->resourceExists(candidateUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1001,8 +1001,9 @@ FBXTexture GLTFReader::getFBXTexture(const GLTFTexture& texture) {
|
||||||
fbxtex.texcoordSet = 0;
|
fbxtex.texcoordSet = 0;
|
||||||
|
|
||||||
if (texture.defined["source"]) {
|
if (texture.defined["source"]) {
|
||||||
QString fname = QUrl(_file.images[texture.source].uri).fileName();
|
QString url = _file.images[texture.source].uri;
|
||||||
QUrl textureUrl = _url.resolved(fname);
|
QString fname = QUrl(url).fileName();
|
||||||
|
QUrl textureUrl = _url.resolved(url);
|
||||||
qCDebug(modelformat) << "fname: " << fname;
|
qCDebug(modelformat) << "fname: " << fname;
|
||||||
qCDebug(modelformat) << "textureUrl: " << textureUrl;
|
qCDebug(modelformat) << "textureUrl: " << textureUrl;
|
||||||
qCDebug(modelformat) << "Url: " << _url;
|
qCDebug(modelformat) << "Url: " << _url;
|
||||||
|
|
|
@ -765,7 +765,6 @@ void GLBackend::recycle() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
_textureManagement._transferEngine->manageMemory();
|
_textureManagement._transferEngine->manageMemory();
|
||||||
Texture::KtxStorage::releaseOpenKtxFiles();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GLBackend::setCameraCorrection(const Mat4& correction, const Mat4& prevRenderView, bool reset) {
|
void GLBackend::setCameraCorrection(const Mat4& correction, const Mat4& prevRenderView, bool reset) {
|
||||||
|
|
|
@ -405,6 +405,7 @@ bool GLTextureTransferEngineDefault::processActiveBufferQueue() {
|
||||||
_activeTransferQueue.splice(_activeTransferQueue.end(), activeBufferQueue);
|
_activeTransferQueue.splice(_activeTransferQueue.end(), activeBufferQueue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Texture::KtxStorage::releaseOpenKtxFiles();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,9 +20,6 @@
|
||||||
#include <AABox.h>
|
#include <AABox.h>
|
||||||
#include <Extents.h>
|
#include <Extents.h>
|
||||||
|
|
||||||
#include <glm/gtc/packing.hpp>
|
|
||||||
#include <glm/detail/type_vec.hpp>
|
|
||||||
|
|
||||||
namespace glm {
|
namespace glm {
|
||||||
using hvec2 = glm::tvec2<glm::detail::hdata>;
|
using hvec2 = glm::tvec2<glm::detail::hdata>;
|
||||||
using hvec4 = glm::tvec4<glm::detail::hdata>;
|
using hvec4 = glm::tvec4<glm::detail::hdata>;
|
||||||
|
@ -62,32 +59,6 @@ namespace {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void packNormalAndTangent(glm::vec3 normal, glm::vec3 tangent, glm::uint32& packedNormal, glm::uint32& packedTangent) {
|
|
||||||
auto absNormal = glm::abs(normal);
|
|
||||||
auto absTangent = glm::abs(tangent);
|
|
||||||
normal /= glm::max(1e-6f, glm::max(glm::max(absNormal.x, absNormal.y), absNormal.z));
|
|
||||||
tangent /= glm::max(1e-6f, glm::max(glm::max(absTangent.x, absTangent.y), absTangent.z));
|
|
||||||
normal = glm::clamp(normal, -1.0f, 1.0f);
|
|
||||||
tangent = glm::clamp(tangent, -1.0f, 1.0f);
|
|
||||||
normal *= 511.0f;
|
|
||||||
tangent *= 511.0f;
|
|
||||||
normal = glm::round(normal);
|
|
||||||
tangent = glm::round(tangent);
|
|
||||||
|
|
||||||
glm::detail::i10i10i10i2 normalStruct;
|
|
||||||
glm::detail::i10i10i10i2 tangentStruct;
|
|
||||||
normalStruct.data.x = int(normal.x);
|
|
||||||
normalStruct.data.y = int(normal.y);
|
|
||||||
normalStruct.data.z = int(normal.z);
|
|
||||||
normalStruct.data.w = 0;
|
|
||||||
tangentStruct.data.x = int(tangent.x);
|
|
||||||
tangentStruct.data.y = int(tangent.y);
|
|
||||||
tangentStruct.data.z = int(tangent.z);
|
|
||||||
tangentStruct.data.w = 0;
|
|
||||||
packedNormal = normalStruct.pack;
|
|
||||||
packedTangent = tangentStruct.pack;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
glm::uint32 forEachGlmVec(const gpu::BufferView& view, std::function<bool(glm::uint32 index, const T& value)> func) {
|
glm::uint32 forEachGlmVec(const gpu::BufferView& view, std::function<bool(glm::uint32 index, const T& value)> func) {
|
||||||
QVector<glm::uint32> result;
|
QVector<glm::uint32> result;
|
||||||
|
|
|
@ -9,6 +9,8 @@
|
||||||
#include <QtCore>
|
#include <QtCore>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <glm/glm.hpp>
|
#include <glm/glm.hpp>
|
||||||
|
#include <glm/gtc/packing.hpp>
|
||||||
|
#include <glm/detail/type_vec.hpp>
|
||||||
|
|
||||||
#include "GpuHelpers.h"
|
#include "GpuHelpers.h"
|
||||||
|
|
||||||
|
@ -44,7 +46,31 @@ namespace buffer_helpers {
|
||||||
gpu::BufferView clone(const gpu::BufferView& input);
|
gpu::BufferView clone(const gpu::BufferView& input);
|
||||||
gpu::BufferView resized(const gpu::BufferView& input, glm::uint32 numElements);
|
gpu::BufferView resized(const gpu::BufferView& input, glm::uint32 numElements);
|
||||||
|
|
||||||
void packNormalAndTangent(glm::vec3 normal, glm::vec3 tangent, glm::uint32& packedNormal, glm::uint32& packedTangent);
|
inline void packNormalAndTangent(glm::vec3 normal, glm::vec3 tangent, glm::uint32& packedNormal, glm::uint32& packedTangent) {
|
||||||
|
auto absNormal = glm::abs(normal);
|
||||||
|
auto absTangent = glm::abs(tangent);
|
||||||
|
normal /= glm::max(1e-6f, glm::max(glm::max(absNormal.x, absNormal.y), absNormal.z));
|
||||||
|
tangent /= glm::max(1e-6f, glm::max(glm::max(absTangent.x, absTangent.y), absTangent.z));
|
||||||
|
normal = glm::clamp(normal, -1.0f, 1.0f);
|
||||||
|
tangent = glm::clamp(tangent, -1.0f, 1.0f);
|
||||||
|
normal *= 511.0f;
|
||||||
|
tangent *= 511.0f;
|
||||||
|
normal = glm::round(normal);
|
||||||
|
tangent = glm::round(tangent);
|
||||||
|
|
||||||
|
glm::detail::i10i10i10i2 normalStruct;
|
||||||
|
glm::detail::i10i10i10i2 tangentStruct;
|
||||||
|
normalStruct.data.x = int(normal.x);
|
||||||
|
normalStruct.data.y = int(normal.y);
|
||||||
|
normalStruct.data.z = int(normal.z);
|
||||||
|
normalStruct.data.w = 0;
|
||||||
|
tangentStruct.data.x = int(tangent.x);
|
||||||
|
tangentStruct.data.y = int(tangent.y);
|
||||||
|
tangentStruct.data.z = int(tangent.z);
|
||||||
|
tangentStruct.data.w = 0;
|
||||||
|
packedNormal = normalStruct.pack;
|
||||||
|
packedTangent = tangentStruct.pack;
|
||||||
|
}
|
||||||
|
|
||||||
namespace mesh {
|
namespace mesh {
|
||||||
glm::uint32 forEachVertex(const graphics::MeshPointer& mesh, std::function<bool(glm::uint32 index, const QVariantMap& attributes)> func);
|
glm::uint32 forEachVertex(const graphics::MeshPointer& mesh, std::function<bool(glm::uint32 index, const QVariantMap& attributes)> func);
|
||||||
|
|
|
@ -302,6 +302,8 @@ controller::Input::NamedVector KeyboardMouseDevice::InputDevice::getAvailableInp
|
||||||
availableInputs.append(Input::NamedPair(makeInput(Qt::Key_PageDown), QKeySequence(Qt::Key_PageDown).toString()));
|
availableInputs.append(Input::NamedPair(makeInput(Qt::Key_PageDown), QKeySequence(Qt::Key_PageDown).toString()));
|
||||||
availableInputs.append(Input::NamedPair(makeInput(Qt::Key_Tab), QKeySequence(Qt::Key_Tab).toString()));
|
availableInputs.append(Input::NamedPair(makeInput(Qt::Key_Tab), QKeySequence(Qt::Key_Tab).toString()));
|
||||||
availableInputs.append(Input::NamedPair(makeInput(Qt::Key_Control), "Control"));
|
availableInputs.append(Input::NamedPair(makeInput(Qt::Key_Control), "Control"));
|
||||||
|
availableInputs.append(Input::NamedPair(makeInput(Qt::Key_Delete), "Delete"));
|
||||||
|
availableInputs.append(Input::NamedPair(makeInput(Qt::Key_Backspace), QKeySequence(Qt::Key_Backspace).toString()));
|
||||||
|
|
||||||
availableInputs.append(Input::NamedPair(makeInput(Qt::LeftButton), "LeftMouseButton"));
|
availableInputs.append(Input::NamedPair(makeInput(Qt::LeftButton), "LeftMouseButton"));
|
||||||
availableInputs.append(Input::NamedPair(makeInput(Qt::MiddleButton), "MiddleMouseButton"));
|
availableInputs.append(Input::NamedPair(makeInput(Qt::MiddleButton), "MiddleMouseButton"));
|
||||||
|
|
|
@ -96,6 +96,8 @@ public:
|
||||||
|
|
||||||
QUrl getMetaverseServerURL() { return NetworkingConstants::METAVERSE_SERVER_URL(); }
|
QUrl getMetaverseServerURL() { return NetworkingConstants::METAVERSE_SERVER_URL(); }
|
||||||
|
|
||||||
|
void removeAccountFromFile();
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void requestAccessToken(const QString& login, const QString& password);
|
void requestAccessToken(const QString& login, const QString& password);
|
||||||
void requestAccessTokenWithSteam(QByteArray authSessionTicket);
|
void requestAccessTokenWithSteam(QByteArray authSessionTicket);
|
||||||
|
@ -133,7 +135,6 @@ private:
|
||||||
void operator=(AccountManager const& other) = delete;
|
void operator=(AccountManager const& other) = delete;
|
||||||
|
|
||||||
void persistAccountToFile();
|
void persistAccountToFile();
|
||||||
void removeAccountFromFile();
|
|
||||||
|
|
||||||
void passSuccessToCallback(QNetworkReply* reply);
|
void passSuccessToCallback(QNetworkReply* reply);
|
||||||
void passErrorToCallback(QNetworkReply* reply);
|
void passErrorToCallback(QNetworkReply* reply);
|
||||||
|
|
|
@ -273,6 +273,7 @@ void NodeList::reset(bool skipDomainHandlerReset) {
|
||||||
|
|
||||||
// refresh the owner UUID to the NULL UUID
|
// refresh the owner UUID to the NULL UUID
|
||||||
setSessionUUID(QUuid());
|
setSessionUUID(QUuid());
|
||||||
|
setSessionLocalID(Node::NULL_LOCAL_ID);
|
||||||
|
|
||||||
// if we setup the DTLS socket, also disconnect from the DTLS socket readyRead() so it can handle handshaking
|
// if we setup the DTLS socket, also disconnect from the DTLS socket readyRead() so it can handle handshaking
|
||||||
if (_dtlsSocket) {
|
if (_dtlsSocket) {
|
||||||
|
@ -647,6 +648,23 @@ void NodeList::processDomainServerList(QSharedPointer<ReceivedMessage> message)
|
||||||
Node::LocalID newLocalID;
|
Node::LocalID newLocalID;
|
||||||
packetStream >> newUUID;
|
packetStream >> newUUID;
|
||||||
packetStream >> newLocalID;
|
packetStream >> newLocalID;
|
||||||
|
|
||||||
|
// when connected, if the session ID or local ID were not null and changed, we should reset
|
||||||
|
auto currentLocalID = getSessionLocalID();
|
||||||
|
auto currentSessionID = getSessionUUID();
|
||||||
|
if (_domainHandler.isConnected() &&
|
||||||
|
((currentLocalID != Node::NULL_LOCAL_ID && newLocalID != currentLocalID) ||
|
||||||
|
(!currentSessionID.isNull() && newUUID != currentSessionID))) {
|
||||||
|
qCDebug(networking) << "Local ID or Session ID changed while connected to domain - forcing NodeList reset";
|
||||||
|
|
||||||
|
// reset the nodelist, but don't do a domain handler reset since we're about to process a good domain list
|
||||||
|
reset(true);
|
||||||
|
|
||||||
|
// tell the domain handler that we're no longer connected so that below
|
||||||
|
// it can re-perform actions as if we just connected
|
||||||
|
_domainHandler.setIsConnected(false);
|
||||||
|
}
|
||||||
|
|
||||||
setSessionLocalID(newLocalID);
|
setSessionLocalID(newLocalID);
|
||||||
setSessionUUID(newUUID);
|
setSessionUUID(newUUID);
|
||||||
|
|
||||||
|
|
|
@ -68,55 +68,12 @@ Octree::~Octree() {
|
||||||
eraseAllOctreeElements(false);
|
eraseAllOctreeElements(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Inserts the value and key into three arrays sorted by the key array, the first array is the value,
|
|
||||||
// the second array is a sorted key for the value, the third array is the index for the value in it original
|
|
||||||
// non-sorted array
|
|
||||||
// returns -1 if size exceeded
|
|
||||||
// originalIndexArray is optional
|
|
||||||
int insertOctreeElementIntoSortedArrays(const OctreeElementPointer& value, float key, int originalIndex,
|
|
||||||
OctreeElementPointer* valueArray, float* keyArray, int* originalIndexArray,
|
|
||||||
int currentCount, int maxCount) {
|
|
||||||
|
|
||||||
if (currentCount < maxCount) {
|
|
||||||
int i = 0;
|
|
||||||
if (currentCount > 0) {
|
|
||||||
while (i < currentCount && key > keyArray[i]) {
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
// i is our desired location
|
|
||||||
// shift array elements to the right
|
|
||||||
if (i < currentCount && i+1 < maxCount) {
|
|
||||||
for (int j = currentCount - 1; j > i; j--) {
|
|
||||||
valueArray[j] = valueArray[j - 1];
|
|
||||||
keyArray[j] = keyArray[j - 1];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// place new element at i
|
|
||||||
valueArray[i] = value;
|
|
||||||
keyArray[i] = key;
|
|
||||||
if (originalIndexArray) {
|
|
||||||
originalIndexArray[i] = originalIndex;
|
|
||||||
}
|
|
||||||
return currentCount + 1;
|
|
||||||
}
|
|
||||||
return -1; // error case
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Recurses voxel tree calling the RecurseOctreeOperation function for each element.
|
// Recurses voxel tree calling the RecurseOctreeOperation function for each element.
|
||||||
// stops recursion if operation function returns false.
|
// stops recursion if operation function returns false.
|
||||||
void Octree::recurseTreeWithOperation(const RecurseOctreeOperation& operation, void* extraData) {
|
void Octree::recurseTreeWithOperation(const RecurseOctreeOperation& operation, void* extraData) {
|
||||||
recurseElementWithOperation(_rootElement, operation, extraData);
|
recurseElementWithOperation(_rootElement, operation, extraData);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Recurses voxel tree calling the RecurseOctreePostFixOperation function for each element in post-fix order.
|
|
||||||
void Octree::recurseTreeWithPostOperation(const RecurseOctreeOperation& operation, void* extraData) {
|
|
||||||
recurseElementWithPostOperation(_rootElement, operation, extraData);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Recurses voxel element with an operation function
|
// Recurses voxel element with an operation function
|
||||||
void Octree::recurseElementWithOperation(const OctreeElementPointer& element, const RecurseOctreeOperation& operation, void* extraData,
|
void Octree::recurseElementWithOperation(const OctreeElementPointer& element, const RecurseOctreeOperation& operation, void* extraData,
|
||||||
int recursionCount) {
|
int recursionCount) {
|
||||||
|
@ -129,71 +86,53 @@ void Octree::recurseElementWithOperation(const OctreeElementPointer& element, co
|
||||||
for (int i = 0; i < NUMBER_OF_CHILDREN; i++) {
|
for (int i = 0; i < NUMBER_OF_CHILDREN; i++) {
|
||||||
OctreeElementPointer child = element->getChildAtIndex(i);
|
OctreeElementPointer child = element->getChildAtIndex(i);
|
||||||
if (child) {
|
if (child) {
|
||||||
recurseElementWithOperation(child, operation, extraData, recursionCount+1);
|
recurseElementWithOperation(child, operation, extraData, recursionCount + 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Recurses voxel element with an operation function
|
void Octree::recurseTreeWithOperationSorted(const RecurseOctreeOperation& operation, const RecurseOctreeSortingOperation& sortingOperation, void* extraData) {
|
||||||
void Octree::recurseElementWithPostOperation(const OctreeElementPointer& element, const RecurseOctreeOperation& operation,
|
recurseElementWithOperationSorted(_rootElement, operation, sortingOperation, extraData);
|
||||||
void* extraData, int recursionCount) {
|
}
|
||||||
|
|
||||||
|
// Recurses voxel element with an operation function, calling operation on its children in a specific order
|
||||||
|
bool Octree::recurseElementWithOperationSorted(const OctreeElementPointer& element, const RecurseOctreeOperation& operation,
|
||||||
|
const RecurseOctreeSortingOperation& sortingOperation, void* extraData, int recursionCount) {
|
||||||
if (recursionCount > DANGEROUSLY_DEEP_RECURSION) {
|
if (recursionCount > DANGEROUSLY_DEEP_RECURSION) {
|
||||||
HIFI_FCDEBUG(octree(), "Octree::recurseElementWithPostOperation() reached DANGEROUSLY_DEEP_RECURSION, bailing!");
|
HIFI_FCDEBUG(octree(), "Octree::recurseElementWithOperationSorted() reached DANGEROUSLY_DEEP_RECURSION, bailing!");
|
||||||
return;
|
// If we go too deep, we want to keep searching other paths
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool keepSearching = operation(element, extraData);
|
||||||
|
|
||||||
|
std::vector<SortedChild> sortedChildren;
|
||||||
for (int i = 0; i < NUMBER_OF_CHILDREN; i++) {
|
for (int i = 0; i < NUMBER_OF_CHILDREN; i++) {
|
||||||
OctreeElementPointer child = element->getChildAtIndex(i);
|
OctreeElementPointer child = element->getChildAtIndex(i);
|
||||||
if (child) {
|
if (child) {
|
||||||
recurseElementWithPostOperation(child, operation, extraData, recursionCount+1);
|
float priority = sortingOperation(child, extraData);
|
||||||
}
|
if (priority < FLT_MAX) {
|
||||||
}
|
sortedChildren.emplace_back(priority, child);
|
||||||
operation(element, extraData);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Recurses voxel tree calling the RecurseOctreeOperation function for each element.
|
|
||||||
// stops recursion if operation function returns false.
|
|
||||||
void Octree::recurseTreeWithOperationDistanceSorted(const RecurseOctreeOperation& operation,
|
|
||||||
const glm::vec3& point, void* extraData) {
|
|
||||||
|
|
||||||
recurseElementWithOperationDistanceSorted(_rootElement, operation, point, extraData);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Recurses voxel element with an operation function
|
|
||||||
void Octree::recurseElementWithOperationDistanceSorted(const OctreeElementPointer& element, const RecurseOctreeOperation& operation,
|
|
||||||
const glm::vec3& point, void* extraData, int recursionCount) {
|
|
||||||
|
|
||||||
if (recursionCount > DANGEROUSLY_DEEP_RECURSION) {
|
|
||||||
HIFI_FCDEBUG(octree(), "Octree::recurseElementWithOperationDistanceSorted() reached DANGEROUSLY_DEEP_RECURSION, bailing!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (operation(element, extraData)) {
|
|
||||||
// determine the distance sorted order of our children
|
|
||||||
OctreeElementPointer sortedChildren[NUMBER_OF_CHILDREN] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL };
|
|
||||||
float distancesToChildren[NUMBER_OF_CHILDREN] = { 0, 0, 0, 0, 0, 0, 0, 0 };
|
|
||||||
int indexOfChildren[NUMBER_OF_CHILDREN] = { 0, 0, 0, 0, 0, 0, 0, 0 };
|
|
||||||
int currentCount = 0;
|
|
||||||
|
|
||||||
for (int i = 0; i < NUMBER_OF_CHILDREN; i++) {
|
|
||||||
OctreeElementPointer childElement = element->getChildAtIndex(i);
|
|
||||||
if (childElement) {
|
|
||||||
// chance to optimize, doesn't need to be actual distance!! Could be distance squared
|
|
||||||
float distanceSquared = childElement->distanceSquareToPoint(point);
|
|
||||||
currentCount = insertOctreeElementIntoSortedArrays(childElement, distanceSquared, i,
|
|
||||||
sortedChildren, (float*)&distancesToChildren,
|
|
||||||
(int*)&indexOfChildren, currentCount, NUMBER_OF_CHILDREN);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < currentCount; i++) {
|
|
||||||
OctreeElementPointer childElement = sortedChildren[i];
|
|
||||||
if (childElement) {
|
|
||||||
recurseElementWithOperationDistanceSorted(childElement, operation, point, extraData);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (sortedChildren.size() > 1) {
|
||||||
|
static auto comparator = [](const SortedChild& left, const SortedChild& right) { return left.first < right.first; };
|
||||||
|
std::sort(sortedChildren.begin(), sortedChildren.end(), comparator);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto it = sortedChildren.begin(); it != sortedChildren.end(); ++it) {
|
||||||
|
const SortedChild& sortedChild = *it;
|
||||||
|
// Our children were sorted, so if one hits something, we don't need to check the others
|
||||||
|
if (!recurseElementWithOperationSorted(sortedChild.second, operation, sortingOperation, extraData, recursionCount + 1)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// We checked all our children and didn't find anything.
|
||||||
|
// Stop if we hit something in this element. Continue if we didn't.
|
||||||
|
return keepSearching;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Octree::recurseTreeWithOperator(RecurseOctreeOperator* operatorObject) {
|
void Octree::recurseTreeWithOperator(RecurseOctreeOperator* operatorObject) {
|
||||||
|
|
|
@ -49,6 +49,9 @@ public:
|
||||||
|
|
||||||
// Callback function, for recuseTreeWithOperation
|
// Callback function, for recuseTreeWithOperation
|
||||||
using RecurseOctreeOperation = std::function<bool(const OctreeElementPointer&, void*)>;
|
using RecurseOctreeOperation = std::function<bool(const OctreeElementPointer&, void*)>;
|
||||||
|
// Function for sorting octree children during recursion. If return value == FLT_MAX, child is discarded
|
||||||
|
using RecurseOctreeSortingOperation = std::function<float(const OctreeElementPointer&, void*)>;
|
||||||
|
using SortedChild = std::pair<float, OctreeElementPointer>;
|
||||||
typedef QHash<uint, AACube> CubeList;
|
typedef QHash<uint, AACube> CubeList;
|
||||||
|
|
||||||
const bool NO_EXISTS_BITS = false;
|
const bool NO_EXISTS_BITS = false;
|
||||||
|
@ -163,17 +166,10 @@ public:
|
||||||
OctreeElementPointer getOrCreateChildElementContaining(const AACube& box);
|
OctreeElementPointer getOrCreateChildElementContaining(const AACube& box);
|
||||||
|
|
||||||
void recurseTreeWithOperation(const RecurseOctreeOperation& operation, void* extraData = NULL);
|
void recurseTreeWithOperation(const RecurseOctreeOperation& operation, void* extraData = NULL);
|
||||||
void recurseTreeWithPostOperation(const RecurseOctreeOperation& operation, void* extraData = NULL);
|
void recurseTreeWithOperationSorted(const RecurseOctreeOperation& operation, const RecurseOctreeSortingOperation& sortingOperation, void* extraData = NULL);
|
||||||
|
|
||||||
/// \param operation type of operation
|
|
||||||
/// \param point point in world-frame (meters)
|
|
||||||
/// \param extraData hook for user data to be interpreted by special context
|
|
||||||
void recurseTreeWithOperationDistanceSorted(const RecurseOctreeOperation& operation,
|
|
||||||
const glm::vec3& point, void* extraData = NULL);
|
|
||||||
|
|
||||||
void recurseTreeWithOperator(RecurseOctreeOperator* operatorObject);
|
void recurseTreeWithOperator(RecurseOctreeOperator* operatorObject);
|
||||||
|
|
||||||
|
|
||||||
bool isDirty() const { return _isDirty; }
|
bool isDirty() const { return _isDirty; }
|
||||||
void clearDirtyBit() { _isDirty = false; }
|
void clearDirtyBit() { _isDirty = false; }
|
||||||
void setDirtyBit() { _isDirty = true; }
|
void setDirtyBit() { _isDirty = true; }
|
||||||
|
@ -227,14 +223,8 @@ public:
|
||||||
|
|
||||||
void recurseElementWithOperation(const OctreeElementPointer& element, const RecurseOctreeOperation& operation,
|
void recurseElementWithOperation(const OctreeElementPointer& element, const RecurseOctreeOperation& operation,
|
||||||
void* extraData, int recursionCount = 0);
|
void* extraData, int recursionCount = 0);
|
||||||
|
bool recurseElementWithOperationSorted(const OctreeElementPointer& element, const RecurseOctreeOperation& operation,
|
||||||
/// Traverse child nodes of node applying operation in post-fix order
|
const RecurseOctreeSortingOperation& sortingOperation, void* extraData, int recursionCount = 0);
|
||||||
///
|
|
||||||
void recurseElementWithPostOperation(const OctreeElementPointer& element, const RecurseOctreeOperation& operation,
|
|
||||||
void* extraData, int recursionCount = 0);
|
|
||||||
|
|
||||||
void recurseElementWithOperationDistanceSorted(const OctreeElementPointer& element, const RecurseOctreeOperation& operation,
|
|
||||||
const glm::vec3& point, void* extraData, int recursionCount = 0);
|
|
||||||
|
|
||||||
bool recurseElementWithOperator(const OctreeElementPointer& element, RecurseOctreeOperator* operatorObject, int recursionCount = 0);
|
bool recurseElementWithOperator(const OctreeElementPointer& element, RecurseOctreeOperator* operatorObject, int recursionCount = 0);
|
||||||
|
|
||||||
|
|
|
@ -72,11 +72,15 @@ PickResultPointer PickQuery::getPrevPickResult() const {
|
||||||
void PickQuery::setIgnoreItems(const QVector<QUuid>& ignoreItems) {
|
void PickQuery::setIgnoreItems(const QVector<QUuid>& ignoreItems) {
|
||||||
withWriteLock([&] {
|
withWriteLock([&] {
|
||||||
_ignoreItems = ignoreItems;
|
_ignoreItems = ignoreItems;
|
||||||
|
// We sort these items here so the PickCacheOptimizer can catch cases where two picks have the same ignoreItems in a different order
|
||||||
|
std::sort(_ignoreItems.begin(), _ignoreItems.end(), std::less<QUuid>());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void PickQuery::setIncludeItems(const QVector<QUuid>& includeItems) {
|
void PickQuery::setIncludeItems(const QVector<QUuid>& includeItems) {
|
||||||
withWriteLock([&] {
|
withWriteLock([&] {
|
||||||
_includeItems = includeItems;
|
_includeItems = includeItems;
|
||||||
|
// We sort these items here so the PickCacheOptimizer can catch cases where two picks have the same includeItems in a different order
|
||||||
|
std::sort(_includeItems.begin(), _includeItems.end(), std::less<QUuid>());
|
||||||
});
|
});
|
||||||
}
|
}
|
|
@ -37,7 +37,7 @@ template<typename T>
|
||||||
class PickCacheOptimizer {
|
class PickCacheOptimizer {
|
||||||
|
|
||||||
public:
|
public:
|
||||||
void update(std::unordered_map<uint32_t, std::shared_ptr<PickQuery>>& picks, uint32_t& nextToUpdate, uint64_t expiry, bool shouldPickHUD);
|
QVector4D update(std::unordered_map<uint32_t, std::shared_ptr<PickQuery>>& picks, uint32_t& nextToUpdate, uint64_t expiry, bool shouldPickHUD);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
typedef std::unordered_map<T, std::unordered_map<PickCacheKey, PickResultPointer>> PickCache;
|
typedef std::unordered_map<T, std::unordered_map<PickCacheKey, PickResultPointer>> PickCache;
|
||||||
|
@ -67,8 +67,9 @@ void PickCacheOptimizer<T>::cacheResult(const bool intersects, const PickResultP
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
void PickCacheOptimizer<T>::update(std::unordered_map<uint32_t, std::shared_ptr<PickQuery>>& picks,
|
QVector4D PickCacheOptimizer<T>::update(std::unordered_map<uint32_t, std::shared_ptr<PickQuery>>& picks,
|
||||||
uint32_t& nextToUpdate, uint64_t expiry, bool shouldPickHUD) {
|
uint32_t& nextToUpdate, uint64_t expiry, bool shouldPickHUD) {
|
||||||
|
QVector4D numIntersectionsComputed;
|
||||||
PickCache results;
|
PickCache results;
|
||||||
const uint32_t INVALID_PICK_ID = 0;
|
const uint32_t INVALID_PICK_ID = 0;
|
||||||
auto itr = picks.begin();
|
auto itr = picks.begin();
|
||||||
|
@ -91,6 +92,7 @@ void PickCacheOptimizer<T>::update(std::unordered_map<uint32_t, std::shared_ptr<
|
||||||
PickCacheKey entityKey = { pick->getFilter().getEntityFlags(), pick->getIncludeItems(), pick->getIgnoreItems() };
|
PickCacheKey entityKey = { pick->getFilter().getEntityFlags(), pick->getIncludeItems(), pick->getIgnoreItems() };
|
||||||
if (!checkAndCompareCachedResults(mathematicalPick, results, res, entityKey)) {
|
if (!checkAndCompareCachedResults(mathematicalPick, results, res, entityKey)) {
|
||||||
PickResultPointer entityRes = pick->getEntityIntersection(mathematicalPick);
|
PickResultPointer entityRes = pick->getEntityIntersection(mathematicalPick);
|
||||||
|
numIntersectionsComputed[0]++;
|
||||||
if (entityRes) {
|
if (entityRes) {
|
||||||
cacheResult(entityRes->doesIntersect(), entityRes, entityKey, res, mathematicalPick, results, pick);
|
cacheResult(entityRes->doesIntersect(), entityRes, entityKey, res, mathematicalPick, results, pick);
|
||||||
}
|
}
|
||||||
|
@ -101,6 +103,7 @@ void PickCacheOptimizer<T>::update(std::unordered_map<uint32_t, std::shared_ptr<
|
||||||
PickCacheKey overlayKey = { pick->getFilter().getOverlayFlags(), pick->getIncludeItems(), pick->getIgnoreItems() };
|
PickCacheKey overlayKey = { pick->getFilter().getOverlayFlags(), pick->getIncludeItems(), pick->getIgnoreItems() };
|
||||||
if (!checkAndCompareCachedResults(mathematicalPick, results, res, overlayKey)) {
|
if (!checkAndCompareCachedResults(mathematicalPick, results, res, overlayKey)) {
|
||||||
PickResultPointer overlayRes = pick->getOverlayIntersection(mathematicalPick);
|
PickResultPointer overlayRes = pick->getOverlayIntersection(mathematicalPick);
|
||||||
|
numIntersectionsComputed[1]++;
|
||||||
if (overlayRes) {
|
if (overlayRes) {
|
||||||
cacheResult(overlayRes->doesIntersect(), overlayRes, overlayKey, res, mathematicalPick, results, pick);
|
cacheResult(overlayRes->doesIntersect(), overlayRes, overlayKey, res, mathematicalPick, results, pick);
|
||||||
}
|
}
|
||||||
|
@ -111,6 +114,7 @@ void PickCacheOptimizer<T>::update(std::unordered_map<uint32_t, std::shared_ptr<
|
||||||
PickCacheKey avatarKey = { pick->getFilter().getAvatarFlags(), pick->getIncludeItems(), pick->getIgnoreItems() };
|
PickCacheKey avatarKey = { pick->getFilter().getAvatarFlags(), pick->getIncludeItems(), pick->getIgnoreItems() };
|
||||||
if (!checkAndCompareCachedResults(mathematicalPick, results, res, avatarKey)) {
|
if (!checkAndCompareCachedResults(mathematicalPick, results, res, avatarKey)) {
|
||||||
PickResultPointer avatarRes = pick->getAvatarIntersection(mathematicalPick);
|
PickResultPointer avatarRes = pick->getAvatarIntersection(mathematicalPick);
|
||||||
|
numIntersectionsComputed[2]++;
|
||||||
if (avatarRes) {
|
if (avatarRes) {
|
||||||
cacheResult(avatarRes->doesIntersect(), avatarRes, avatarKey, res, mathematicalPick, results, pick);
|
cacheResult(avatarRes->doesIntersect(), avatarRes, avatarKey, res, mathematicalPick, results, pick);
|
||||||
}
|
}
|
||||||
|
@ -122,6 +126,7 @@ void PickCacheOptimizer<T>::update(std::unordered_map<uint32_t, std::shared_ptr<
|
||||||
PickCacheKey hudKey = { pick->getFilter().getHUDFlags(), QVector<QUuid>(), QVector<QUuid>() };
|
PickCacheKey hudKey = { pick->getFilter().getHUDFlags(), QVector<QUuid>(), QVector<QUuid>() };
|
||||||
if (!checkAndCompareCachedResults(mathematicalPick, results, res, hudKey)) {
|
if (!checkAndCompareCachedResults(mathematicalPick, results, res, hudKey)) {
|
||||||
PickResultPointer hudRes = pick->getHUDIntersection(mathematicalPick);
|
PickResultPointer hudRes = pick->getHUDIntersection(mathematicalPick);
|
||||||
|
numIntersectionsComputed[3]++;
|
||||||
if (hudRes) {
|
if (hudRes) {
|
||||||
cacheResult(true, hudRes, hudKey, res, mathematicalPick, results, pick);
|
cacheResult(true, hudRes, hudKey, res, mathematicalPick, results, pick);
|
||||||
}
|
}
|
||||||
|
@ -145,6 +150,7 @@ void PickCacheOptimizer<T>::update(std::unordered_map<uint32_t, std::shared_ptr<
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return numIntersectionsComputed;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif // hifi_PickCacheOptimizer_h
|
#endif // hifi_PickCacheOptimizer_h
|
||||||
|
|
|
@ -20,6 +20,7 @@ unsigned int PickManager::addPick(PickQuery::PickType type, const std::shared_pt
|
||||||
id = _nextPickID++;
|
id = _nextPickID++;
|
||||||
_picks[type][id] = pick;
|
_picks[type][id] = pick;
|
||||||
_typeMap[id] = type;
|
_typeMap[id] = type;
|
||||||
|
_totalPickCounts[type]++;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return id;
|
return id;
|
||||||
|
@ -41,6 +42,7 @@ void PickManager::removePick(unsigned int uid) {
|
||||||
if (type != _typeMap.end()) {
|
if (type != _typeMap.end()) {
|
||||||
_picks[type->second].erase(uid);
|
_picks[type->second].erase(uid);
|
||||||
_typeMap.erase(uid);
|
_typeMap.erase(uid);
|
||||||
|
_totalPickCounts[type->second]--;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -96,12 +98,12 @@ void PickManager::update() {
|
||||||
});
|
});
|
||||||
|
|
||||||
bool shouldPickHUD = _shouldPickHUDOperator();
|
bool shouldPickHUD = _shouldPickHUDOperator();
|
||||||
// we pass the same expiry to both updates, but the stylus updates are relatively cheap
|
// FIXME: give each type its own expiry
|
||||||
// and the rayPicks updae will ALWAYS update at least one ray even when there is no budget
|
// Each type will update at least one pick, regardless of the expiry
|
||||||
_stylusPickCacheOptimizer.update(cachedPicks[PickQuery::Stylus], _nextPickToUpdate[PickQuery::Stylus], expiry, false);
|
_updatedPickCounts[PickQuery::Stylus] = _stylusPickCacheOptimizer.update(cachedPicks[PickQuery::Stylus], _nextPickToUpdate[PickQuery::Stylus], expiry, false);
|
||||||
_rayPickCacheOptimizer.update(cachedPicks[PickQuery::Ray], _nextPickToUpdate[PickQuery::Ray], expiry, shouldPickHUD);
|
_updatedPickCounts[PickQuery::Ray] = _rayPickCacheOptimizer.update(cachedPicks[PickQuery::Ray], _nextPickToUpdate[PickQuery::Ray], expiry, shouldPickHUD);
|
||||||
_parabolaPickCacheOptimizer.update(cachedPicks[PickQuery::Parabola], _nextPickToUpdate[PickQuery::Parabola], expiry, shouldPickHUD);
|
_updatedPickCounts[PickQuery::Parabola] = _parabolaPickCacheOptimizer.update(cachedPicks[PickQuery::Parabola], _nextPickToUpdate[PickQuery::Parabola], expiry, shouldPickHUD);
|
||||||
_collisionPickCacheOptimizer.update(cachedPicks[PickQuery::Collision], _nextPickToUpdate[PickQuery::Collision], expiry, false);
|
_updatedPickCounts[PickQuery::Collision] = _collisionPickCacheOptimizer.update(cachedPicks[PickQuery::Collision], _nextPickToUpdate[PickQuery::Collision], expiry, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PickManager::isLeftHand(unsigned int uid) {
|
bool PickManager::isLeftHand(unsigned int uid) {
|
||||||
|
|
|
@ -16,7 +16,10 @@
|
||||||
|
|
||||||
#include <NumericalConstants.h>
|
#include <NumericalConstants.h>
|
||||||
|
|
||||||
class PickManager : public Dependency, protected ReadWriteLockable {
|
#include <QObject>
|
||||||
|
|
||||||
|
class PickManager : public QObject, public Dependency, protected ReadWriteLockable {
|
||||||
|
Q_OBJECT
|
||||||
SINGLETON_DEPENDENCY
|
SINGLETON_DEPENDENCY
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
@ -53,7 +56,19 @@ public:
|
||||||
unsigned int getPerFrameTimeBudget() const { return _perFrameTimeBudget; }
|
unsigned int getPerFrameTimeBudget() const { return _perFrameTimeBudget; }
|
||||||
void setPerFrameTimeBudget(unsigned int numUsecs) { _perFrameTimeBudget = numUsecs; }
|
void setPerFrameTimeBudget(unsigned int numUsecs) { _perFrameTimeBudget = numUsecs; }
|
||||||
|
|
||||||
|
bool getForceCoarsePicking() { return _forceCoarsePicking; }
|
||||||
|
|
||||||
|
const std::vector<QVector4D>& getUpdatedPickCounts() { return _updatedPickCounts; }
|
||||||
|
const std::vector<int>& getTotalPickCounts() { return _totalPickCounts; }
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void setForceCoarsePicking(bool forceCoarsePicking) { _forceCoarsePicking = forceCoarsePicking; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
std::vector<QVector4D> _updatedPickCounts { PickQuery::NUM_PICK_TYPES };
|
||||||
|
std::vector<int> _totalPickCounts { 0, 0, 0, 0 };
|
||||||
|
|
||||||
|
bool _forceCoarsePicking { false };
|
||||||
std::function<bool()> _shouldPickHUDOperator;
|
std::function<bool()> _shouldPickHUDOperator;
|
||||||
std::function<glm::vec2(const glm::vec3&)> _calculatePos2DFromHUDOperator;
|
std::function<glm::vec2(const glm::vec3&)> _calculatePos2DFromHUDOperator;
|
||||||
|
|
||||||
|
|
|
@ -76,6 +76,7 @@ void CauterizedModel::createRenderItemSet() {
|
||||||
// Run through all of the meshes, and place them into their segregated, but unsorted buckets
|
// Run through all of the meshes, and place them into their segregated, but unsorted buckets
|
||||||
int shapeID = 0;
|
int shapeID = 0;
|
||||||
uint32_t numMeshes = (uint32_t)meshes.size();
|
uint32_t numMeshes = (uint32_t)meshes.size();
|
||||||
|
const FBXGeometry& fbxGeometry = getFBXGeometry();
|
||||||
for (uint32_t i = 0; i < numMeshes; i++) {
|
for (uint32_t i = 0; i < numMeshes; i++) {
|
||||||
const auto& mesh = meshes.at(i);
|
const auto& mesh = meshes.at(i);
|
||||||
if (!mesh) {
|
if (!mesh) {
|
||||||
|
@ -85,6 +86,10 @@ void CauterizedModel::createRenderItemSet() {
|
||||||
// Create the render payloads
|
// Create the render payloads
|
||||||
int numParts = (int)mesh->getNumParts();
|
int numParts = (int)mesh->getNumParts();
|
||||||
for (int partIndex = 0; partIndex < numParts; partIndex++) {
|
for (int partIndex = 0; partIndex < numParts; partIndex++) {
|
||||||
|
if (!fbxGeometry.meshes[i].blendshapes.empty() && !_blendedVertexBuffers[i]) {
|
||||||
|
_blendedVertexBuffers[i] = std::make_shared<gpu::Buffer>();
|
||||||
|
_blendedVertexBuffers[i]->resize(fbxGeometry.meshes[i].vertices.size() * (sizeof(glm::vec3) + 2 * sizeof(NormalType)));
|
||||||
|
}
|
||||||
auto ptr = std::make_shared<CauterizedMeshPartPayload>(shared_from_this(), i, partIndex, shapeID, transform, offset);
|
auto ptr = std::make_shared<CauterizedMeshPartPayload>(shared_from_this(), i, partIndex, shapeID, transform, offset);
|
||||||
_modelMeshRenderItems << std::static_pointer_cast<ModelMeshPartPayload>(ptr);
|
_modelMeshRenderItems << std::static_pointer_cast<ModelMeshPartPayload>(ptr);
|
||||||
auto material = getGeometry()->getShapeMaterial(shapeID);
|
auto material = getGeometry()->getShapeMaterial(shapeID);
|
||||||
|
@ -170,9 +175,10 @@ void CauterizedModel::updateClusterMatrices() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// post the blender if we're not currently waiting for one to finish
|
// post the blender if we're not currently waiting for one to finish
|
||||||
if (geometry.hasBlendedMeshes() && _blendshapeCoefficients != _blendedBlendshapeCoefficients) {
|
auto modelBlender = DependencyManager::get<ModelBlender>();
|
||||||
|
if (modelBlender->shouldComputeBlendshapes() && geometry.hasBlendedMeshes() && _blendshapeCoefficients != _blendedBlendshapeCoefficients) {
|
||||||
_blendedBlendshapeCoefficients = _blendshapeCoefficients;
|
_blendedBlendshapeCoefficients = _blendshapeCoefficients;
|
||||||
DependencyManager::get<ModelBlender>()->noteRequiresBlend(getThisPointer());
|
modelBlender->noteRequiresBlend(getThisPointer());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -208,7 +208,9 @@ ModelMeshPartPayload::ModelMeshPartPayload(ModelPointer model, int meshIndex, in
|
||||||
|
|
||||||
bool useDualQuaternionSkinning = model->getUseDualQuaternionSkinning();
|
bool useDualQuaternionSkinning = model->getUseDualQuaternionSkinning();
|
||||||
|
|
||||||
_blendedVertexBuffer = model->_blendedVertexBuffers[_meshIndex];
|
if (!model->getFBXGeometry().meshes[meshIndex].blendshapes.isEmpty()) {
|
||||||
|
_blendedVertexBuffer = model->_blendedVertexBuffers[meshIndex];
|
||||||
|
}
|
||||||
auto& modelMesh = model->getGeometry()->getMeshes().at(_meshIndex);
|
auto& modelMesh = model->getGeometry()->getMeshes().at(_meshIndex);
|
||||||
const Model::MeshState& state = model->getMeshState(_meshIndex);
|
const Model::MeshState& state = model->getMeshState(_meshIndex);
|
||||||
|
|
||||||
|
|
|
@ -42,8 +42,9 @@
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
int nakedModelPointerTypeId = qRegisterMetaType<ModelPointer>();
|
int nakedModelPointerTypeId = qRegisterMetaType<ModelPointer>();
|
||||||
int weakGeometryResourceBridgePointerTypeId = qRegisterMetaType<Geometry::WeakPointer >();
|
int weakGeometryResourceBridgePointerTypeId = qRegisterMetaType<Geometry::WeakPointer>();
|
||||||
int vec3VectorTypeId = qRegisterMetaType<QVector<glm::vec3> >();
|
int vec3VectorTypeId = qRegisterMetaType<QVector<glm::vec3>>();
|
||||||
|
int normalTypeVecTypeId = qRegisterMetaType<QVector<NormalType>>("QVector<NormalType>");
|
||||||
float Model::FAKE_DIMENSION_PLACEHOLDER = -1.0f;
|
float Model::FAKE_DIMENSION_PLACEHOLDER = -1.0f;
|
||||||
#define HTTP_INVALID_COM "http://invalid.com"
|
#define HTTP_INVALID_COM "http://invalid.com"
|
||||||
|
|
||||||
|
@ -301,41 +302,52 @@ bool Model::updateGeometry() {
|
||||||
assert(_meshStates.empty());
|
assert(_meshStates.empty());
|
||||||
|
|
||||||
const FBXGeometry& fbxGeometry = getFBXGeometry();
|
const FBXGeometry& fbxGeometry = getFBXGeometry();
|
||||||
|
int i = 0;
|
||||||
foreach (const FBXMesh& mesh, fbxGeometry.meshes) {
|
foreach (const FBXMesh& mesh, fbxGeometry.meshes) {
|
||||||
MeshState state;
|
MeshState state;
|
||||||
state.clusterDualQuaternions.resize(mesh.clusters.size());
|
state.clusterDualQuaternions.resize(mesh.clusters.size());
|
||||||
state.clusterMatrices.resize(mesh.clusters.size());
|
state.clusterMatrices.resize(mesh.clusters.size());
|
||||||
_meshStates.push_back(state);
|
_meshStates.push_back(state);
|
||||||
|
|
||||||
// Note: we add empty buffers for meshes that lack blendshapes so we can access the buffers by index
|
|
||||||
// later in ModelMeshPayload, however the vast majority of meshes will not have them.
|
|
||||||
// TODO? make _blendedVertexBuffers a map instead of vector and only add for meshes with blendshapes?
|
|
||||||
auto buffer = std::make_shared<gpu::Buffer>();
|
|
||||||
if (!mesh.blendshapes.isEmpty()) {
|
if (!mesh.blendshapes.isEmpty()) {
|
||||||
std::vector<NormalType> normalsAndTangents;
|
if (!_blendedVertexBuffers[i]) {
|
||||||
normalsAndTangents.reserve(mesh.normals.size() + mesh.tangents.size());
|
_blendedVertexBuffers[i] = std::make_shared<gpu::Buffer>();
|
||||||
|
|
||||||
for (auto normalIt = mesh.normals.begin(), tangentIt = mesh.tangents.begin();
|
|
||||||
normalIt != mesh.normals.end();
|
|
||||||
++normalIt, ++tangentIt) {
|
|
||||||
#if FBX_PACK_NORMALS
|
|
||||||
glm::uint32 finalNormal;
|
|
||||||
glm::uint32 finalTangent;
|
|
||||||
buffer_helpers::packNormalAndTangent(*normalIt, *tangentIt, finalNormal, finalTangent);
|
|
||||||
#else
|
|
||||||
const auto finalNormal = *normalIt;
|
|
||||||
const auto finalTangent = *tangentIt;
|
|
||||||
#endif
|
|
||||||
normalsAndTangents.push_back(finalNormal);
|
|
||||||
normalsAndTangents.push_back(finalTangent);
|
|
||||||
}
|
}
|
||||||
|
const auto& buffer = _blendedVertexBuffers[i];
|
||||||
|
QVector<NormalType> normalsAndTangents;
|
||||||
|
normalsAndTangents.resize(2 * mesh.normals.size());
|
||||||
|
|
||||||
buffer->resize(mesh.vertices.size() * (sizeof(glm::vec3) + 2 * sizeof(NormalType)));
|
// Interleave normals and tangents
|
||||||
buffer->setSubData(0, mesh.vertices.size() * sizeof(glm::vec3), (const gpu::Byte*) mesh.vertices.constData());
|
// Parallel version for performance
|
||||||
buffer->setSubData(mesh.vertices.size() * sizeof(glm::vec3),
|
tbb::parallel_for(tbb::blocked_range<int>(0, mesh.normals.size()), [&](const tbb::blocked_range<int>& range) {
|
||||||
mesh.normals.size() * 2 * sizeof(NormalType), (const gpu::Byte*) normalsAndTangents.data());
|
auto normalsRange = std::make_pair(mesh.normals.begin() + range.begin(), mesh.normals.begin() + range.end());
|
||||||
|
auto tangentsRange = std::make_pair(mesh.tangents.begin() + range.begin(), mesh.tangents.begin() + range.end());
|
||||||
|
auto normalsAndTangentsIt = normalsAndTangents.begin() + 2 * range.begin();
|
||||||
|
|
||||||
|
for (auto normalIt = normalsRange.first, tangentIt = tangentsRange.first;
|
||||||
|
normalIt != normalsRange.second;
|
||||||
|
++normalIt, ++tangentIt) {
|
||||||
|
#if FBX_PACK_NORMALS
|
||||||
|
glm::uint32 finalNormal;
|
||||||
|
glm::uint32 finalTangent;
|
||||||
|
buffer_helpers::packNormalAndTangent(*normalIt, *tangentIt, finalNormal, finalTangent);
|
||||||
|
#else
|
||||||
|
const auto& finalNormal = *normalIt;
|
||||||
|
const auto& finalTangent = *tangentIt;
|
||||||
|
#endif
|
||||||
|
*normalsAndTangentsIt = finalNormal;
|
||||||
|
++normalsAndTangentsIt;
|
||||||
|
*normalsAndTangentsIt = finalTangent;
|
||||||
|
++normalsAndTangentsIt;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const auto verticesSize = mesh.vertices.size() * sizeof(glm::vec3);
|
||||||
|
buffer->resize(mesh.vertices.size() * sizeof(glm::vec3) + normalsAndTangents.size() * sizeof(NormalType));
|
||||||
|
buffer->setSubData(0, verticesSize, (const gpu::Byte*) mesh.vertices.constData());
|
||||||
|
buffer->setSubData(verticesSize, 2 * mesh.normals.size() * sizeof(NormalType), (const gpu::Byte*) normalsAndTangents.data());
|
||||||
|
mesh.normalsAndTangents = normalsAndTangents;
|
||||||
}
|
}
|
||||||
_blendedVertexBuffers.push_back(buffer);
|
i++;
|
||||||
}
|
}
|
||||||
needFullUpdate = true;
|
needFullUpdate = true;
|
||||||
emit rigReady();
|
emit rigReady();
|
||||||
|
@ -376,17 +388,20 @@ bool Model::findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const g
|
||||||
|
|
||||||
// we can use the AABox's intersection by mapping our origin and direction into the model frame
|
// we can use the AABox's intersection by mapping our origin and direction into the model frame
|
||||||
// and testing intersection there.
|
// and testing intersection there.
|
||||||
if (modelFrameBox.findRayIntersection(modelFrameOrigin, modelFrameDirection, distance, face, surfaceNormal)) {
|
if (modelFrameBox.findRayIntersection(modelFrameOrigin, modelFrameDirection, 1.0f / modelFrameDirection, distance, face, surfaceNormal)) {
|
||||||
QMutexLocker locker(&_mutex);
|
QMutexLocker locker(&_mutex);
|
||||||
|
|
||||||
float bestDistance = std::numeric_limits<float>::max();
|
float bestDistance = FLT_MAX;
|
||||||
|
BoxFace bestFace;
|
||||||
Triangle bestModelTriangle;
|
Triangle bestModelTriangle;
|
||||||
Triangle bestWorldTriangle;
|
Triangle bestWorldTriangle;
|
||||||
|
glm::vec3 bestWorldIntersectionPoint;
|
||||||
|
glm::vec3 bestMeshIntersectionPoint;
|
||||||
|
int bestPartIndex = 0;
|
||||||
|
int bestShapeID = 0;
|
||||||
int bestSubMeshIndex = 0;
|
int bestSubMeshIndex = 0;
|
||||||
|
|
||||||
int subMeshIndex = 0;
|
|
||||||
const FBXGeometry& geometry = getFBXGeometry();
|
const FBXGeometry& geometry = getFBXGeometry();
|
||||||
|
|
||||||
if (!_triangleSetsValid) {
|
if (!_triangleSetsValid) {
|
||||||
calculateTriangleSets(geometry);
|
calculateTriangleSets(geometry);
|
||||||
}
|
}
|
||||||
|
@ -397,41 +412,78 @@ bool Model::findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const g
|
||||||
|
|
||||||
glm::vec3 meshFrameOrigin = glm::vec3(worldToMeshMatrix * glm::vec4(origin, 1.0f));
|
glm::vec3 meshFrameOrigin = glm::vec3(worldToMeshMatrix * glm::vec4(origin, 1.0f));
|
||||||
glm::vec3 meshFrameDirection = glm::vec3(worldToMeshMatrix * glm::vec4(direction, 0.0f));
|
glm::vec3 meshFrameDirection = glm::vec3(worldToMeshMatrix * glm::vec4(direction, 0.0f));
|
||||||
|
glm::vec3 meshFrameInvDirection = 1.0f / meshFrameDirection;
|
||||||
|
|
||||||
int shapeID = 0;
|
int shapeID = 0;
|
||||||
|
int subMeshIndex = 0;
|
||||||
|
|
||||||
|
std::vector<SortedTriangleSet> sortedTriangleSets;
|
||||||
for (auto& meshTriangleSets : _modelSpaceMeshTriangleSets) {
|
for (auto& meshTriangleSets : _modelSpaceMeshTriangleSets) {
|
||||||
int partIndex = 0;
|
int partIndex = 0;
|
||||||
for (auto &partTriangleSet : meshTriangleSets) {
|
for (auto& partTriangleSet : meshTriangleSets) {
|
||||||
float triangleSetDistance;
|
float priority = FLT_MAX;
|
||||||
BoxFace triangleSetFace;
|
if (partTriangleSet.getBounds().contains(meshFrameOrigin)) {
|
||||||
Triangle triangleSetTriangle;
|
priority = 0.0f;
|
||||||
if (partTriangleSet.findRayIntersection(meshFrameOrigin, meshFrameDirection, triangleSetDistance, triangleSetFace, triangleSetTriangle, pickAgainstTriangles, allowBackface)) {
|
} else {
|
||||||
glm::vec3 meshIntersectionPoint = meshFrameOrigin + (meshFrameDirection * triangleSetDistance);
|
float partBoundDistance = FLT_MAX;
|
||||||
glm::vec3 worldIntersectionPoint = glm::vec3(meshToWorldMatrix * glm::vec4(meshIntersectionPoint, 1.0f));
|
BoxFace partBoundFace;
|
||||||
float worldDistance = glm::distance(origin, worldIntersectionPoint);
|
glm::vec3 partBoundNormal;
|
||||||
|
if (partTriangleSet.getBounds().findRayIntersection(meshFrameOrigin, meshFrameDirection, meshFrameInvDirection,
|
||||||
if (worldDistance < bestDistance) {
|
partBoundDistance, partBoundFace, partBoundNormal)) {
|
||||||
bestDistance = worldDistance;
|
priority = partBoundDistance;
|
||||||
intersectedSomething = true;
|
|
||||||
face = triangleSetFace;
|
|
||||||
bestModelTriangle = triangleSetTriangle;
|
|
||||||
bestWorldTriangle = triangleSetTriangle * meshToWorldMatrix;
|
|
||||||
extraInfo["worldIntersectionPoint"] = vec3toVariant(worldIntersectionPoint);
|
|
||||||
extraInfo["meshIntersectionPoint"] = vec3toVariant(meshIntersectionPoint);
|
|
||||||
extraInfo["partIndex"] = partIndex;
|
|
||||||
extraInfo["shapeID"] = shapeID;
|
|
||||||
bestSubMeshIndex = subMeshIndex;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (priority < FLT_MAX) {
|
||||||
|
sortedTriangleSets.emplace_back(priority, &partTriangleSet, partIndex, shapeID, subMeshIndex);
|
||||||
|
}
|
||||||
partIndex++;
|
partIndex++;
|
||||||
shapeID++;
|
shapeID++;
|
||||||
}
|
}
|
||||||
subMeshIndex++;
|
subMeshIndex++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (sortedTriangleSets.size() > 1) {
|
||||||
|
static auto comparator = [](const SortedTriangleSet& left, const SortedTriangleSet& right) { return left.distance < right.distance; };
|
||||||
|
std::sort(sortedTriangleSets.begin(), sortedTriangleSets.end(), comparator);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto it = sortedTriangleSets.begin(); it != sortedTriangleSets.end(); ++it) {
|
||||||
|
const SortedTriangleSet& sortedTriangleSet = *it;
|
||||||
|
// We can exit once triangleSetDistance > bestDistance
|
||||||
|
if (sortedTriangleSet.distance > bestDistance) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
float triangleSetDistance = FLT_MAX;
|
||||||
|
BoxFace triangleSetFace;
|
||||||
|
Triangle triangleSetTriangle;
|
||||||
|
if (sortedTriangleSet.triangleSet->findRayIntersection(meshFrameOrigin, meshFrameDirection, meshFrameInvDirection, triangleSetDistance, triangleSetFace,
|
||||||
|
triangleSetTriangle, pickAgainstTriangles, allowBackface)) {
|
||||||
|
if (triangleSetDistance < bestDistance) {
|
||||||
|
bestDistance = triangleSetDistance;
|
||||||
|
intersectedSomething = true;
|
||||||
|
bestFace = triangleSetFace;
|
||||||
|
bestModelTriangle = triangleSetTriangle;
|
||||||
|
bestWorldTriangle = triangleSetTriangle * meshToWorldMatrix;
|
||||||
|
glm::vec3 meshIntersectionPoint = meshFrameOrigin + (meshFrameDirection * triangleSetDistance);
|
||||||
|
glm::vec3 worldIntersectionPoint = glm::vec3(meshToWorldMatrix * glm::vec4(meshIntersectionPoint, 1.0f));
|
||||||
|
bestWorldIntersectionPoint = worldIntersectionPoint;
|
||||||
|
bestMeshIntersectionPoint = meshIntersectionPoint;
|
||||||
|
bestPartIndex = sortedTriangleSet.partIndex;
|
||||||
|
bestShapeID = sortedTriangleSet.shapeID;
|
||||||
|
bestSubMeshIndex = sortedTriangleSet.subMeshIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (intersectedSomething) {
|
if (intersectedSomething) {
|
||||||
distance = bestDistance;
|
distance = bestDistance;
|
||||||
|
face = bestFace;
|
||||||
surfaceNormal = bestWorldTriangle.getNormal();
|
surfaceNormal = bestWorldTriangle.getNormal();
|
||||||
|
extraInfo["worldIntersectionPoint"] = vec3toVariant(bestWorldIntersectionPoint);
|
||||||
|
extraInfo["meshIntersectionPoint"] = vec3toVariant(bestMeshIntersectionPoint);
|
||||||
|
extraInfo["partIndex"] = bestPartIndex;
|
||||||
|
extraInfo["shapeID"] = bestShapeID;
|
||||||
if (pickAgainstTriangles) {
|
if (pickAgainstTriangles) {
|
||||||
extraInfo["subMeshIndex"] = bestSubMeshIndex;
|
extraInfo["subMeshIndex"] = bestSubMeshIndex;
|
||||||
extraInfo["subMeshName"] = geometry.getModelNameOfMesh(bestSubMeshIndex);
|
extraInfo["subMeshName"] = geometry.getModelNameOfMesh(bestSubMeshIndex);
|
||||||
|
@ -483,13 +535,16 @@ bool Model::findParabolaIntersectionAgainstSubMeshes(const glm::vec3& origin, co
|
||||||
QMutexLocker locker(&_mutex);
|
QMutexLocker locker(&_mutex);
|
||||||
|
|
||||||
float bestDistance = FLT_MAX;
|
float bestDistance = FLT_MAX;
|
||||||
|
BoxFace bestFace;
|
||||||
Triangle bestModelTriangle;
|
Triangle bestModelTriangle;
|
||||||
Triangle bestWorldTriangle;
|
Triangle bestWorldTriangle;
|
||||||
|
glm::vec3 bestWorldIntersectionPoint;
|
||||||
|
glm::vec3 bestMeshIntersectionPoint;
|
||||||
|
int bestPartIndex = 0;
|
||||||
|
int bestShapeID = 0;
|
||||||
int bestSubMeshIndex = 0;
|
int bestSubMeshIndex = 0;
|
||||||
|
|
||||||
int subMeshIndex = 0;
|
|
||||||
const FBXGeometry& geometry = getFBXGeometry();
|
const FBXGeometry& geometry = getFBXGeometry();
|
||||||
|
|
||||||
if (!_triangleSetsValid) {
|
if (!_triangleSetsValid) {
|
||||||
calculateTriangleSets(geometry);
|
calculateTriangleSets(geometry);
|
||||||
}
|
}
|
||||||
|
@ -503,40 +558,79 @@ bool Model::findParabolaIntersectionAgainstSubMeshes(const glm::vec3& origin, co
|
||||||
glm::vec3 meshFrameAcceleration = glm::vec3(worldToMeshMatrix * glm::vec4(acceleration, 0.0f));
|
glm::vec3 meshFrameAcceleration = glm::vec3(worldToMeshMatrix * glm::vec4(acceleration, 0.0f));
|
||||||
|
|
||||||
int shapeID = 0;
|
int shapeID = 0;
|
||||||
|
int subMeshIndex = 0;
|
||||||
|
|
||||||
|
std::vector<SortedTriangleSet> sortedTriangleSets;
|
||||||
for (auto& meshTriangleSets : _modelSpaceMeshTriangleSets) {
|
for (auto& meshTriangleSets : _modelSpaceMeshTriangleSets) {
|
||||||
int partIndex = 0;
|
int partIndex = 0;
|
||||||
for (auto &partTriangleSet : meshTriangleSets) {
|
for (auto& partTriangleSet : meshTriangleSets) {
|
||||||
float triangleSetDistance;
|
float priority = FLT_MAX;
|
||||||
BoxFace triangleSetFace;
|
if (partTriangleSet.getBounds().contains(meshFrameOrigin)) {
|
||||||
Triangle triangleSetTriangle;
|
priority = 0.0f;
|
||||||
if (partTriangleSet.findParabolaIntersection(meshFrameOrigin, meshFrameVelocity, meshFrameAcceleration,
|
} else {
|
||||||
triangleSetDistance, triangleSetFace, triangleSetTriangle, pickAgainstTriangles, allowBackface)) {
|
float partBoundDistance = FLT_MAX;
|
||||||
if (triangleSetDistance < bestDistance) {
|
BoxFace partBoundFace;
|
||||||
bestDistance = triangleSetDistance;
|
glm::vec3 partBoundNormal;
|
||||||
intersectedSomething = true;
|
if (partTriangleSet.getBounds().findParabolaIntersection(meshFrameOrigin, meshFrameVelocity, meshFrameAcceleration,
|
||||||
face = triangleSetFace;
|
partBoundDistance, partBoundFace, partBoundNormal)) {
|
||||||
bestModelTriangle = triangleSetTriangle;
|
priority = partBoundDistance;
|
||||||
bestWorldTriangle = triangleSetTriangle * meshToWorldMatrix;
|
|
||||||
glm::vec3 meshIntersectionPoint = meshFrameOrigin + meshFrameVelocity * triangleSetDistance +
|
|
||||||
0.5f * meshFrameAcceleration * triangleSetDistance * triangleSetDistance;
|
|
||||||
glm::vec3 worldIntersectionPoint = origin + velocity * triangleSetDistance +
|
|
||||||
0.5f * acceleration * triangleSetDistance * triangleSetDistance;
|
|
||||||
extraInfo["worldIntersectionPoint"] = vec3toVariant(worldIntersectionPoint);
|
|
||||||
extraInfo["meshIntersectionPoint"] = vec3toVariant(meshIntersectionPoint);
|
|
||||||
extraInfo["partIndex"] = partIndex;
|
|
||||||
extraInfo["shapeID"] = shapeID;
|
|
||||||
bestSubMeshIndex = subMeshIndex;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (priority < FLT_MAX) {
|
||||||
|
sortedTriangleSets.emplace_back(priority, &partTriangleSet, partIndex, shapeID, subMeshIndex);
|
||||||
|
}
|
||||||
partIndex++;
|
partIndex++;
|
||||||
shapeID++;
|
shapeID++;
|
||||||
}
|
}
|
||||||
subMeshIndex++;
|
subMeshIndex++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (sortedTriangleSets.size() > 1) {
|
||||||
|
static auto comparator = [](const SortedTriangleSet& left, const SortedTriangleSet& right) { return left.distance < right.distance; };
|
||||||
|
std::sort(sortedTriangleSets.begin(), sortedTriangleSets.end(), comparator);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto it = sortedTriangleSets.begin(); it != sortedTriangleSets.end(); ++it) {
|
||||||
|
const SortedTriangleSet& sortedTriangleSet = *it;
|
||||||
|
// We can exit once triangleSetDistance > bestDistance
|
||||||
|
if (sortedTriangleSet.distance > bestDistance) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
float triangleSetDistance = FLT_MAX;
|
||||||
|
BoxFace triangleSetFace;
|
||||||
|
Triangle triangleSetTriangle;
|
||||||
|
if (sortedTriangleSet.triangleSet->findParabolaIntersection(meshFrameOrigin, meshFrameVelocity, meshFrameAcceleration,
|
||||||
|
triangleSetDistance, triangleSetFace, triangleSetTriangle,
|
||||||
|
pickAgainstTriangles, allowBackface)) {
|
||||||
|
if (triangleSetDistance < bestDistance) {
|
||||||
|
bestDistance = triangleSetDistance;
|
||||||
|
intersectedSomething = true;
|
||||||
|
bestFace = triangleSetFace;
|
||||||
|
bestModelTriangle = triangleSetTriangle;
|
||||||
|
bestWorldTriangle = triangleSetTriangle * meshToWorldMatrix;
|
||||||
|
glm::vec3 meshIntersectionPoint = meshFrameOrigin + meshFrameVelocity * triangleSetDistance +
|
||||||
|
0.5f * meshFrameAcceleration * triangleSetDistance * triangleSetDistance;
|
||||||
|
glm::vec3 worldIntersectionPoint = origin + velocity * triangleSetDistance +
|
||||||
|
0.5f * acceleration * triangleSetDistance * triangleSetDistance;
|
||||||
|
bestWorldIntersectionPoint = worldIntersectionPoint;
|
||||||
|
bestMeshIntersectionPoint = meshIntersectionPoint;
|
||||||
|
bestPartIndex = sortedTriangleSet.partIndex;
|
||||||
|
bestShapeID = sortedTriangleSet.shapeID;
|
||||||
|
bestSubMeshIndex = sortedTriangleSet.subMeshIndex;
|
||||||
|
// These sets can overlap, so we can't exit early if we find something
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (intersectedSomething) {
|
if (intersectedSomething) {
|
||||||
parabolicDistance = bestDistance;
|
parabolicDistance = bestDistance;
|
||||||
|
face = bestFace;
|
||||||
surfaceNormal = bestWorldTriangle.getNormal();
|
surfaceNormal = bestWorldTriangle.getNormal();
|
||||||
|
extraInfo["worldIntersectionPoint"] = vec3toVariant(bestWorldIntersectionPoint);
|
||||||
|
extraInfo["meshIntersectionPoint"] = vec3toVariant(bestMeshIntersectionPoint);
|
||||||
|
extraInfo["partIndex"] = bestPartIndex;
|
||||||
|
extraInfo["shapeID"] = bestShapeID;
|
||||||
if (pickAgainstTriangles) {
|
if (pickAgainstTriangles) {
|
||||||
extraInfo["subMeshIndex"] = bestSubMeshIndex;
|
extraInfo["subMeshIndex"] = bestSubMeshIndex;
|
||||||
extraInfo["subMeshName"] = geometry.getModelNameOfMesh(bestSubMeshIndex);
|
extraInfo["subMeshName"] = geometry.getModelNameOfMesh(bestSubMeshIndex);
|
||||||
|
@ -1245,20 +1339,21 @@ Blender::Blender(ModelPointer model, int blendNumber, const Geometry::WeakPointe
|
||||||
|
|
||||||
void Blender::run() {
|
void Blender::run() {
|
||||||
DETAILED_PROFILE_RANGE_EX(simulation_animation, __FUNCTION__, 0xFFFF0000, 0, { { "url", _model->getURL().toString() } });
|
DETAILED_PROFILE_RANGE_EX(simulation_animation, __FUNCTION__, 0xFFFF0000, 0, { { "url", _model->getURL().toString() } });
|
||||||
QVector<glm::vec3> vertices, normals, tangents;
|
QVector<glm::vec3> vertices;
|
||||||
|
QVector<NormalType> normalsAndTangents;
|
||||||
if (_model) {
|
if (_model) {
|
||||||
int offset = 0;
|
int offset = 0;
|
||||||
|
int normalsAndTangentsOffset = 0;
|
||||||
foreach (const FBXMesh& mesh, _meshes) {
|
foreach (const FBXMesh& mesh, _meshes) {
|
||||||
if (mesh.blendshapes.isEmpty()) {
|
if (mesh.blendshapes.isEmpty()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
vertices += mesh.vertices;
|
vertices += mesh.vertices;
|
||||||
normals += mesh.normals;
|
normalsAndTangents += mesh.normalsAndTangents;
|
||||||
tangents += mesh.tangents;
|
|
||||||
glm::vec3* meshVertices = vertices.data() + offset;
|
glm::vec3* meshVertices = vertices.data() + offset;
|
||||||
glm::vec3* meshNormals = normals.data() + offset;
|
NormalType* meshNormalsAndTangents = normalsAndTangents.data() + normalsAndTangentsOffset;
|
||||||
glm::vec3* meshTangents = tangents.data() + offset;
|
|
||||||
offset += mesh.vertices.size();
|
offset += mesh.vertices.size();
|
||||||
|
normalsAndTangentsOffset += mesh.normalsAndTangents.size();
|
||||||
const float NORMAL_COEFFICIENT_SCALE = 0.01f;
|
const float NORMAL_COEFFICIENT_SCALE = 0.01f;
|
||||||
for (int i = 0, n = qMin(_blendshapeCoefficients.size(), mesh.blendshapes.size()); i < n; i++) {
|
for (int i = 0, n = qMin(_blendshapeCoefficients.size(), mesh.blendshapes.size()); i < n; i++) {
|
||||||
float vertexCoefficient = _blendshapeCoefficients.at(i);
|
float vertexCoefficient = _blendshapeCoefficients.at(i);
|
||||||
|
@ -1268,22 +1363,39 @@ void Blender::run() {
|
||||||
}
|
}
|
||||||
float normalCoefficient = vertexCoefficient * NORMAL_COEFFICIENT_SCALE;
|
float normalCoefficient = vertexCoefficient * NORMAL_COEFFICIENT_SCALE;
|
||||||
const FBXBlendshape& blendshape = mesh.blendshapes.at(i);
|
const FBXBlendshape& blendshape = mesh.blendshapes.at(i);
|
||||||
for (int j = 0; j < blendshape.indices.size(); j++) {
|
tbb::parallel_for(tbb::blocked_range<int>(0, blendshape.indices.size()), [&](const tbb::blocked_range<int>& range) {
|
||||||
int index = blendshape.indices.at(j);
|
for (auto j = range.begin(); j < range.end(); j++) {
|
||||||
meshVertices[index] += blendshape.vertices.at(j) * vertexCoefficient;
|
int index = blendshape.indices.at(j);
|
||||||
meshNormals[index] += blendshape.normals.at(j) * normalCoefficient;
|
meshVertices[index] += blendshape.vertices.at(j) * vertexCoefficient;
|
||||||
if (blendshape.tangents.size() > j) {
|
|
||||||
meshTangents[index] += blendshape.tangents.at(j) * normalCoefficient;
|
glm::vec3 normal = mesh.normals.at(index) + blendshape.normals.at(j) * normalCoefficient;
|
||||||
|
glm::vec3 tangent;
|
||||||
|
if (index < mesh.tangents.size()) {
|
||||||
|
tangent = mesh.tangents.at(index);
|
||||||
|
if ((int)j < blendshape.tangents.size()) {
|
||||||
|
tangent += blendshape.tangents.at(j) * normalCoefficient;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#if FBX_PACK_NORMALS
|
||||||
|
glm::uint32 finalNormal;
|
||||||
|
glm::uint32 finalTangent;
|
||||||
|
buffer_helpers::packNormalAndTangent(normal, tangent, finalNormal, finalTangent);
|
||||||
|
#else
|
||||||
|
const auto& finalNormal = normal;
|
||||||
|
const auto& finalTangent = tangent;
|
||||||
|
#endif
|
||||||
|
meshNormalsAndTangents[2 * index] = finalNormal;
|
||||||
|
meshNormalsAndTangents[2 * index + 1] = finalTangent;
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// post the result to the geometry cache, which will dispatch to the model if still alive
|
// post the result to the ModelBlender, which will dispatch to the model if still alive
|
||||||
QMetaObject::invokeMethod(DependencyManager::get<ModelBlender>().data(), "setBlendedVertices",
|
QMetaObject::invokeMethod(DependencyManager::get<ModelBlender>().data(), "setBlendedVertices",
|
||||||
Q_ARG(ModelPointer, _model), Q_ARG(int, _blendNumber),
|
Q_ARG(ModelPointer, _model), Q_ARG(int, _blendNumber),
|
||||||
Q_ARG(const Geometry::WeakPointer&, _geometry), Q_ARG(const QVector<glm::vec3>&, vertices),
|
Q_ARG(const Geometry::WeakPointer&, _geometry), Q_ARG(const QVector<glm::vec3>&, vertices),
|
||||||
Q_ARG(const QVector<glm::vec3>&, normals), Q_ARG(const QVector<glm::vec3>&, tangents));
|
Q_ARG(const QVector<NormalType>&, normalsAndTangents));
|
||||||
}
|
}
|
||||||
|
|
||||||
void Model::setScaleToFit(bool scaleToFit, const glm::vec3& dimensions, bool forceRescale) {
|
void Model::setScaleToFit(bool scaleToFit, const glm::vec3& dimensions, bool forceRescale) {
|
||||||
|
@ -1443,9 +1555,10 @@ void Model::updateClusterMatrices() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// post the blender if we're not currently waiting for one to finish
|
// post the blender if we're not currently waiting for one to finish
|
||||||
if (geometry.hasBlendedMeshes() && _blendshapeCoefficients != _blendedBlendshapeCoefficients) {
|
auto modelBlender = DependencyManager::get<ModelBlender>();
|
||||||
|
if (modelBlender->shouldComputeBlendshapes() && geometry.hasBlendedMeshes() && _blendshapeCoefficients != _blendedBlendshapeCoefficients) {
|
||||||
_blendedBlendshapeCoefficients = _blendshapeCoefficients;
|
_blendedBlendshapeCoefficients = _blendshapeCoefficients;
|
||||||
DependencyManager::get<ModelBlender>()->noteRequiresBlend(getThisPointer());
|
modelBlender->noteRequiresBlend(getThisPointer());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1462,83 +1575,31 @@ bool Model::maybeStartBlender() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Model::setBlendedVertices(int blendNumber, const Geometry::WeakPointer& geometry,
|
void Model::setBlendedVertices(int blendNumber, const Geometry::WeakPointer& geometry,
|
||||||
const QVector<glm::vec3>& vertices, const QVector<glm::vec3>& normals, const QVector<glm::vec3>& tangents) {
|
const QVector<glm::vec3>& vertices, const QVector<NormalType>& normalsAndTangents) {
|
||||||
auto geometryRef = geometry.lock();
|
auto geometryRef = geometry.lock();
|
||||||
if (!geometryRef || _renderGeometry != geometryRef || _blendedVertexBuffers.empty() || blendNumber < _appliedBlendNumber) {
|
if (!geometryRef || _renderGeometry != geometryRef || blendNumber < _appliedBlendNumber) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_appliedBlendNumber = blendNumber;
|
_appliedBlendNumber = blendNumber;
|
||||||
const FBXGeometry& fbxGeometry = getFBXGeometry();
|
const FBXGeometry& fbxGeometry = getFBXGeometry();
|
||||||
int index = 0;
|
int index = 0;
|
||||||
std::vector<NormalType> normalsAndTangents;
|
int normalAndTangentIndex = 0;
|
||||||
for (int i = 0; i < fbxGeometry.meshes.size(); i++) {
|
for (int i = 0; i < fbxGeometry.meshes.size(); i++) {
|
||||||
const FBXMesh& mesh = fbxGeometry.meshes.at(i);
|
const FBXMesh& mesh = fbxGeometry.meshes.at(i);
|
||||||
if (mesh.blendshapes.isEmpty()) {
|
if (mesh.blendshapes.isEmpty()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
gpu::BufferPointer& buffer = _blendedVertexBuffers[i];
|
|
||||||
const auto vertexCount = mesh.vertices.size();
|
const auto vertexCount = mesh.vertices.size();
|
||||||
const auto verticesSize = vertexCount * sizeof(glm::vec3);
|
const auto verticesSize = vertexCount * sizeof(glm::vec3);
|
||||||
const auto offset = index * sizeof(glm::vec3);
|
const auto& buffer = _blendedVertexBuffers[i];
|
||||||
|
assert(buffer);
|
||||||
normalsAndTangents.clear();
|
buffer->resize(mesh.vertices.size() * sizeof(glm::vec3) + mesh.normalsAndTangents.size() * sizeof(NormalType));
|
||||||
normalsAndTangents.resize(normals.size()+tangents.size());
|
buffer->setSubData(0, verticesSize, (gpu::Byte*) vertices.constData() + index * sizeof(glm::vec3));
|
||||||
// assert(normalsAndTangents.size() == 2 * vertexCount);
|
buffer->setSubData(verticesSize, 2 * mesh.normals.size() * sizeof(NormalType), (const gpu::Byte*) normalsAndTangents.data() + normalAndTangentIndex * sizeof(NormalType));
|
||||||
|
|
||||||
// Interleave normals and tangents
|
|
||||||
#if 0
|
|
||||||
// Sequential version for debugging
|
|
||||||
auto normalsRange = std::make_pair(normals.begin() + index, normals.begin() + index + vertexCount);
|
|
||||||
auto tangentsRange = std::make_pair(tangents.begin() + index, tangents.begin() + index + vertexCount);
|
|
||||||
auto normalsAndTangentsIt = normalsAndTangents.begin();
|
|
||||||
|
|
||||||
for (auto normalIt = normalsRange.first, tangentIt = tangentsRange.first;
|
|
||||||
normalIt != normalsRange.second;
|
|
||||||
++normalIt, ++tangentIt) {
|
|
||||||
#if FBX_PACK_NORMALS
|
|
||||||
glm::uint32 finalNormal;
|
|
||||||
glm::uint32 finalTangent;
|
|
||||||
buffer_helpers::packNormalAndTangent(*normalIt, *tangentIt, finalNormal, finalTangent);
|
|
||||||
#else
|
|
||||||
const auto finalNormal = *normalIt;
|
|
||||||
const auto finalTangent = *tangentIt;
|
|
||||||
#endif
|
|
||||||
*normalsAndTangentsIt = finalNormal;
|
|
||||||
++normalsAndTangentsIt;
|
|
||||||
*normalsAndTangentsIt = finalTangent;
|
|
||||||
++normalsAndTangentsIt;
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
// Parallel version for performance
|
|
||||||
tbb::parallel_for(tbb::blocked_range<size_t>(index, index+vertexCount), [&](const tbb::blocked_range<size_t>& range) {
|
|
||||||
auto normalsRange = std::make_pair(normals.begin() + range.begin(), normals.begin() + range.end());
|
|
||||||
auto tangentsRange = std::make_pair(tangents.begin() + range.begin(), tangents.begin() + range.end());
|
|
||||||
auto normalsAndTangentsIt = normalsAndTangents.begin() + (range.begin()-index)*2;
|
|
||||||
|
|
||||||
for (auto normalIt = normalsRange.first, tangentIt = tangentsRange.first;
|
|
||||||
normalIt != normalsRange.second;
|
|
||||||
++normalIt, ++tangentIt) {
|
|
||||||
#if FBX_PACK_NORMALS
|
|
||||||
glm::uint32 finalNormal;
|
|
||||||
glm::uint32 finalTangent;
|
|
||||||
buffer_helpers::packNormalAndTangent(*normalIt, *tangentIt, finalNormal, finalTangent);
|
|
||||||
#else
|
|
||||||
const auto finalNormal = *normalIt;
|
|
||||||
const auto finalTangent = *tangentIt;
|
|
||||||
#endif
|
|
||||||
*normalsAndTangentsIt = finalNormal;
|
|
||||||
++normalsAndTangentsIt;
|
|
||||||
*normalsAndTangentsIt = finalTangent;
|
|
||||||
++normalsAndTangentsIt;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
#endif
|
|
||||||
|
|
||||||
buffer->setSubData(0, verticesSize, (gpu::Byte*) vertices.constData() + offset);
|
|
||||||
buffer->setSubData(verticesSize, 2 * vertexCount * sizeof(NormalType), (const gpu::Byte*) normalsAndTangents.data());
|
|
||||||
|
|
||||||
index += vertexCount;
|
index += vertexCount;
|
||||||
|
normalAndTangentIndex += 2 * mesh.normals.size();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1604,6 +1665,7 @@ void Model::createRenderItemSet() {
|
||||||
// Run through all of the meshes, and place them into their segregated, but unsorted buckets
|
// Run through all of the meshes, and place them into their segregated, but unsorted buckets
|
||||||
int shapeID = 0;
|
int shapeID = 0;
|
||||||
uint32_t numMeshes = (uint32_t)meshes.size();
|
uint32_t numMeshes = (uint32_t)meshes.size();
|
||||||
|
auto fbxGeometry = getFBXGeometry();
|
||||||
for (uint32_t i = 0; i < numMeshes; i++) {
|
for (uint32_t i = 0; i < numMeshes; i++) {
|
||||||
const auto& mesh = meshes.at(i);
|
const auto& mesh = meshes.at(i);
|
||||||
if (!mesh) {
|
if (!mesh) {
|
||||||
|
@ -1613,6 +1675,9 @@ void Model::createRenderItemSet() {
|
||||||
// Create the render payloads
|
// Create the render payloads
|
||||||
int numParts = (int)mesh->getNumParts();
|
int numParts = (int)mesh->getNumParts();
|
||||||
for (int partIndex = 0; partIndex < numParts; partIndex++) {
|
for (int partIndex = 0; partIndex < numParts; partIndex++) {
|
||||||
|
if (fbxGeometry.meshes[i].blendshapes.empty() && !_blendedVertexBuffers[i]) {
|
||||||
|
_blendedVertexBuffers[i] = std::make_shared<gpu::Buffer>();
|
||||||
|
}
|
||||||
_modelMeshRenderItems << std::make_shared<ModelMeshPartPayload>(shared_from_this(), i, partIndex, shapeID, transform, offset);
|
_modelMeshRenderItems << std::make_shared<ModelMeshPartPayload>(shared_from_this(), i, partIndex, shapeID, transform, offset);
|
||||||
auto material = getGeometry()->getShapeMaterial(shapeID);
|
auto material = getGeometry()->getShapeMaterial(shapeID);
|
||||||
_modelMeshMaterialNames.push_back(material ? material->getName() : "");
|
_modelMeshMaterialNames.push_back(material ? material->getName() : "");
|
||||||
|
@ -1725,11 +1790,10 @@ void ModelBlender::noteRequiresBlend(ModelPointer model) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ModelBlender::setBlendedVertices(ModelPointer model, int blendNumber, const Geometry::WeakPointer& geometry,
|
void ModelBlender::setBlendedVertices(ModelPointer model, int blendNumber, const Geometry::WeakPointer& geometry,
|
||||||
const QVector<glm::vec3>& vertices, const QVector<glm::vec3>& normals,
|
const QVector<glm::vec3>& vertices, const QVector<NormalType>& normalsAndTangents) {
|
||||||
const QVector<glm::vec3>& tangents) {
|
|
||||||
if (model) {
|
if (model) {
|
||||||
model->setBlendedVertices(blendNumber, geometry, vertices, normals, tangents);
|
model->setBlendedVertices(blendNumber, geometry, vertices, normalsAndTangents);
|
||||||
}
|
}
|
||||||
_pendingBlenders--;
|
_pendingBlenders--;
|
||||||
{
|
{
|
||||||
|
@ -1745,4 +1809,3 @@ void ModelBlender::setBlendedVertices(ModelPointer model, int blendNumber, const
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -64,6 +64,16 @@ class Model;
|
||||||
using ModelPointer = std::shared_ptr<Model>;
|
using ModelPointer = std::shared_ptr<Model>;
|
||||||
using ModelWeakPointer = std::weak_ptr<Model>;
|
using ModelWeakPointer = std::weak_ptr<Model>;
|
||||||
|
|
||||||
|
struct SortedTriangleSet {
|
||||||
|
SortedTriangleSet(float distance, TriangleSet* triangleSet, int partIndex, int shapeID, int subMeshIndex) :
|
||||||
|
distance(distance), triangleSet(triangleSet), partIndex(partIndex), shapeID(shapeID), subMeshIndex(subMeshIndex) {}
|
||||||
|
|
||||||
|
float distance;
|
||||||
|
TriangleSet* triangleSet;
|
||||||
|
int partIndex;
|
||||||
|
int shapeID;
|
||||||
|
int subMeshIndex;
|
||||||
|
};
|
||||||
|
|
||||||
/// A generic 3D model displaying geometry loaded from a URL.
|
/// A generic 3D model displaying geometry loaded from a URL.
|
||||||
class Model : public QObject, public std::enable_shared_from_this<Model>, public scriptable::ModelProvider {
|
class Model : public QObject, public std::enable_shared_from_this<Model>, public scriptable::ModelProvider {
|
||||||
|
@ -135,7 +145,7 @@ public:
|
||||||
|
|
||||||
/// Sets blended vertices computed in a separate thread.
|
/// Sets blended vertices computed in a separate thread.
|
||||||
void setBlendedVertices(int blendNumber, const Geometry::WeakPointer& geometry,
|
void setBlendedVertices(int blendNumber, const Geometry::WeakPointer& geometry,
|
||||||
const QVector<glm::vec3>& vertices, const QVector<glm::vec3>& normals, const QVector<glm::vec3>& tangents);
|
const QVector<glm::vec3>& vertices, const QVector<NormalType>& normalsAndTangents);
|
||||||
|
|
||||||
bool isLoaded() const { return (bool)_renderGeometry && _renderGeometry->isGeometryLoaded(); }
|
bool isLoaded() const { return (bool)_renderGeometry && _renderGeometry->isGeometryLoaded(); }
|
||||||
bool isAddedToScene() const { return _addedToScene; }
|
bool isAddedToScene() const { return _addedToScene; }
|
||||||
|
@ -413,7 +423,7 @@ protected:
|
||||||
|
|
||||||
QUrl _url;
|
QUrl _url;
|
||||||
|
|
||||||
gpu::Buffers _blendedVertexBuffers;
|
std::unordered_map<int, gpu::BufferPointer> _blendedVertexBuffers;
|
||||||
|
|
||||||
QVector<QVector<QSharedPointer<Texture> > > _dilatedTextures;
|
QVector<QVector<QSharedPointer<Texture> > > _dilatedTextures;
|
||||||
|
|
||||||
|
@ -503,9 +513,12 @@ public:
|
||||||
/// Adds the specified model to the list requiring vertex blends.
|
/// Adds the specified model to the list requiring vertex blends.
|
||||||
void noteRequiresBlend(ModelPointer model);
|
void noteRequiresBlend(ModelPointer model);
|
||||||
|
|
||||||
|
bool shouldComputeBlendshapes() { return _computeBlendshapes; }
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void setBlendedVertices(ModelPointer model, int blendNumber, const Geometry::WeakPointer& geometry,
|
void setBlendedVertices(ModelPointer model, int blendNumber, const Geometry::WeakPointer& geometry,
|
||||||
const QVector<glm::vec3>& vertices, const QVector<glm::vec3>& normals, const QVector<glm::vec3>& tangents);
|
const QVector<glm::vec3>& vertices, const QVector<NormalType>& normalsAndTangents);
|
||||||
|
void setComputeBlendshapes(bool computeBlendshapes) { _computeBlendshapes = computeBlendshapes; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
using Mutex = std::mutex;
|
using Mutex = std::mutex;
|
||||||
|
@ -517,6 +530,8 @@ private:
|
||||||
std::set<ModelWeakPointer, std::owner_less<ModelWeakPointer>> _modelsRequiringBlends;
|
std::set<ModelWeakPointer, std::owner_less<ModelWeakPointer>> _modelsRequiringBlends;
|
||||||
int _pendingBlenders;
|
int _pendingBlenders;
|
||||||
Mutex _mutex;
|
Mutex _mutex;
|
||||||
|
|
||||||
|
bool _computeBlendshapes { true };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -33,6 +33,7 @@ void PickItemsJob::run(const render::RenderContextPointer& renderContext, const
|
||||||
render::ItemBound PickItemsJob::findNearestItem(const render::RenderContextPointer& renderContext, const render::ItemBounds& inputs, float& minIsectDistance) const {
|
render::ItemBound PickItemsJob::findNearestItem(const render::RenderContextPointer& renderContext, const render::ItemBounds& inputs, float& minIsectDistance) const {
|
||||||
const glm::vec3 rayOrigin = renderContext->args->getViewFrustum().getPosition();
|
const glm::vec3 rayOrigin = renderContext->args->getViewFrustum().getPosition();
|
||||||
const glm::vec3 rayDirection = renderContext->args->getViewFrustum().getDirection();
|
const glm::vec3 rayDirection = renderContext->args->getViewFrustum().getDirection();
|
||||||
|
const glm::vec3 rayInvDirection = 1.0f / rayDirection;
|
||||||
BoxFace face;
|
BoxFace face;
|
||||||
glm::vec3 normal;
|
glm::vec3 normal;
|
||||||
float isectDistance;
|
float isectDistance;
|
||||||
|
@ -42,7 +43,7 @@ render::ItemBound PickItemsJob::findNearestItem(const render::RenderContextPoint
|
||||||
render::ItemKey itemKey;
|
render::ItemKey itemKey;
|
||||||
|
|
||||||
for (const auto& itemBound : inputs) {
|
for (const auto& itemBound : inputs) {
|
||||||
if (!itemBound.bound.contains(rayOrigin) && itemBound.bound.findRayIntersection(rayOrigin, rayDirection, isectDistance, face, normal)) {
|
if (!itemBound.bound.contains(rayOrigin) && itemBound.bound.findRayIntersection(rayOrigin, rayDirection, rayInvDirection, isectDistance, face, normal)) {
|
||||||
auto& item = renderContext->_scene->getItem(itemBound.id);
|
auto& item = renderContext->_scene->getItem(itemBound.id);
|
||||||
itemKey = item.getKey();
|
itemKey = item.getKey();
|
||||||
if (itemKey.isWorldSpace() && isectDistance>minDistance && isectDistance < minIsectDistance && isectDistance<maxDistance
|
if (itemKey.isWorldSpace() && isectDistance>minDistance && isectDistance < minIsectDistance && isectDistance<maxDistance
|
||||||
|
|
|
@ -77,8 +77,9 @@ void SoftAttachmentModel::updateClusterMatrices() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// post the blender if we're not currently waiting for one to finish
|
// post the blender if we're not currently waiting for one to finish
|
||||||
if (geometry.hasBlendedMeshes() && _blendshapeCoefficients != _blendedBlendshapeCoefficients) {
|
auto modelBlender = DependencyManager::get<ModelBlender>();
|
||||||
|
if (modelBlender->shouldComputeBlendshapes() && geometry.hasBlendedMeshes() && _blendshapeCoefficients != _blendedBlendshapeCoefficients) {
|
||||||
_blendedBlendshapeCoefficients = _blendshapeCoefficients;
|
_blendedBlendshapeCoefficients = _blendshapeCoefficients;
|
||||||
DependencyManager::get<ModelBlender>()->noteRequiresBlend(getThisPointer());
|
modelBlender->noteRequiresBlend(getThisPointer());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -192,9 +192,9 @@ bool AABox::expandedIntersectsSegment(const glm::vec3& start, const glm::vec3& e
|
||||||
isWithin(start.x + axisDistance*direction.x, expandedCorner.x, expandedSize.x));
|
isWithin(start.x + axisDistance*direction.x, expandedCorner.x, expandedSize.x));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AABox::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance,
|
bool AABox::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, const glm::vec3& invDirection,
|
||||||
BoxFace& face, glm::vec3& surfaceNormal) const {
|
float& distance, BoxFace& face, glm::vec3& surfaceNormal) const {
|
||||||
return findRayAABoxIntersection(origin, direction, _corner, _scale, distance, face, surfaceNormal);
|
return findRayAABoxIntersection(origin, direction, invDirection, _corner, _scale, distance, face, surfaceNormal);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AABox::findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration,
|
bool AABox::findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration,
|
||||||
|
|
|
@ -69,7 +69,7 @@ public:
|
||||||
|
|
||||||
bool expandedContains(const glm::vec3& point, float expansion) const;
|
bool expandedContains(const glm::vec3& point, float expansion) const;
|
||||||
bool expandedIntersectsSegment(const glm::vec3& start, const glm::vec3& end, float expansion) const;
|
bool expandedIntersectsSegment(const glm::vec3& start, const glm::vec3& end, float expansion) const;
|
||||||
bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance,
|
bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, const glm::vec3& invDirection, float& distance,
|
||||||
BoxFace& face, glm::vec3& surfaceNormal) const;
|
BoxFace& face, glm::vec3& surfaceNormal) const;
|
||||||
bool findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration,
|
bool findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration,
|
||||||
float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal) const;
|
float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal) const;
|
||||||
|
|
|
@ -187,9 +187,9 @@ bool AACube::expandedIntersectsSegment(const glm::vec3& start, const glm::vec3&
|
||||||
isWithin(start.x + axisDistance*direction.x, expandedCorner.x, expandedSize.x));
|
isWithin(start.x + axisDistance*direction.x, expandedCorner.x, expandedSize.x));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AACube::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance,
|
bool AACube::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, const glm::vec3& invDirection,
|
||||||
BoxFace& face, glm::vec3& surfaceNormal) const {
|
float& distance, BoxFace& face, glm::vec3& surfaceNormal) const {
|
||||||
return findRayAABoxIntersection(origin, direction, _corner, glm::vec3(_scale), distance, face, surfaceNormal);
|
return findRayAABoxIntersection(origin, direction, invDirection, _corner, glm::vec3(_scale), distance, face, surfaceNormal);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AACube::findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration,
|
bool AACube::findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration,
|
||||||
|
|
|
@ -56,10 +56,10 @@ public:
|
||||||
bool touches(const AABox& otherBox) const;
|
bool touches(const AABox& otherBox) const;
|
||||||
bool expandedContains(const glm::vec3& point, float expansion) const;
|
bool expandedContains(const glm::vec3& point, float expansion) const;
|
||||||
bool expandedIntersectsSegment(const glm::vec3& start, const glm::vec3& end, float expansion) const;
|
bool expandedIntersectsSegment(const glm::vec3& start, const glm::vec3& end, float expansion) const;
|
||||||
bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance,
|
bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, const glm::vec3& invDirection,
|
||||||
BoxFace& face, glm::vec3& surfaceNormal) const;
|
float& distance, BoxFace& face, glm::vec3& surfaceNormal) const;
|
||||||
bool findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration,
|
bool findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration,
|
||||||
float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal) const;
|
float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal) const;
|
||||||
bool touchesSphere(const glm::vec3& center, float radius) const;
|
bool touchesSphere(const glm::vec3& center, float radius) const;
|
||||||
bool findSpherePenetration(const glm::vec3& center, float radius, glm::vec3& penetration) const;
|
bool findSpherePenetration(const glm::vec3& center, float radius, glm::vec3& penetration) const;
|
||||||
bool findCapsulePenetration(const glm::vec3& start, const glm::vec3& end, float radius, glm::vec3& penetration) const;
|
bool findCapsulePenetration(const glm::vec3& start, const glm::vec3& end, float radius, glm::vec3& penetration) const;
|
||||||
|
|
|
@ -214,65 +214,39 @@ bool findInsideOutIntersection(float origin, float direction, float corner, floa
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool findRayAABoxIntersection(const glm::vec3& origin, const glm::vec3& direction, const glm::vec3& corner, const glm::vec3& scale, float& distance,
|
// https://tavianator.com/fast-branchless-raybounding-box-intersections/
|
||||||
BoxFace& face, glm::vec3& surfaceNormal) {
|
bool findRayAABoxIntersection(const glm::vec3& origin, const glm::vec3& direction, const glm::vec3& invDirection,
|
||||||
// handle the trivial case where the box contains the origin
|
const glm::vec3& corner, const glm::vec3& scale, float& distance, BoxFace& face, glm::vec3& surfaceNormal) {
|
||||||
if (aaBoxContains(origin, corner, scale)) {
|
float t1, t2, newTmin, newTmax, tmin = -INFINITY, tmax = INFINITY;
|
||||||
// We still want to calculate the distance from the origin to the inside out plane
|
int minAxis = -1, maxAxis = -1;
|
||||||
float axisDistance;
|
|
||||||
if ((findInsideOutIntersection(origin.x, direction.x, corner.x, scale.x, axisDistance) && axisDistance >= 0 &&
|
for (int i = 0; i < 3; ++i) {
|
||||||
isWithin(origin.y + axisDistance * direction.y, corner.y, scale.y) &&
|
t1 = (corner[i] - origin[i]) * invDirection[i];
|
||||||
isWithin(origin.z + axisDistance * direction.z, corner.z, scale.z))) {
|
t2 = (corner[i] + scale[i] - origin[i]) * invDirection[i];
|
||||||
distance = axisDistance;
|
|
||||||
face = direction.x > 0 ? MAX_X_FACE : MIN_X_FACE;
|
newTmin = glm::min(t1, t2);
|
||||||
surfaceNormal = glm::vec3(direction.x > 0 ? 1.0f : -1.0f, 0.0f, 0.0f);
|
newTmax = glm::max(t1, t2);
|
||||||
return true;
|
|
||||||
}
|
minAxis = newTmin > tmin ? i : minAxis;
|
||||||
if ((findInsideOutIntersection(origin.y, direction.y, corner.y, scale.y, axisDistance) && axisDistance >= 0 &&
|
tmin = glm::max(tmin, newTmin);
|
||||||
isWithin(origin.x + axisDistance * direction.x, corner.x, scale.x) &&
|
maxAxis = newTmax < tmax ? i : maxAxis;
|
||||||
isWithin(origin.z + axisDistance * direction.z, corner.z, scale.z))) {
|
tmax = glm::min(tmax, newTmax);
|
||||||
distance = axisDistance;
|
|
||||||
face = direction.y > 0 ? MAX_Y_FACE : MIN_Y_FACE;
|
|
||||||
surfaceNormal = glm::vec3(0.0f, direction.y > 0 ? 1.0f : -1.0f, 0.0f);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if ((findInsideOutIntersection(origin.z, direction.z, corner.z, scale.z, axisDistance) && axisDistance >= 0 &&
|
|
||||||
isWithin(origin.y + axisDistance * direction.y, corner.y, scale.y) &&
|
|
||||||
isWithin(origin.x + axisDistance * direction.x, corner.x, scale.x))) {
|
|
||||||
distance = axisDistance;
|
|
||||||
face = direction.z > 0 ? MAX_Z_FACE : MIN_Z_FACE;
|
|
||||||
surfaceNormal = glm::vec3(0.0f, 0.0f, direction.z > 0 ? 1.0f : -1.0f);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// This case is unexpected, but mimics the previous behavior for inside out intersections
|
|
||||||
distance = 0;
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// check each axis
|
if (tmax >= glm::max(tmin, 0.0f)) {
|
||||||
float axisDistance;
|
if (tmin < 0.0f) {
|
||||||
if ((findIntersection(origin.x, direction.x, corner.x, scale.x, axisDistance) && axisDistance >= 0 &&
|
distance = tmax;
|
||||||
isWithin(origin.y + axisDistance * direction.y, corner.y, scale.y) &&
|
bool positiveDirection = direction[maxAxis] > 0.0f;
|
||||||
isWithin(origin.z + axisDistance * direction.z, corner.z, scale.z))) {
|
surfaceNormal = glm::vec3(0.0f);
|
||||||
distance = axisDistance;
|
surfaceNormal[maxAxis] = positiveDirection ? -1.0f : 1.0f;
|
||||||
face = direction.x > 0 ? MIN_X_FACE : MAX_X_FACE;
|
face = positiveDirection ? BoxFace(2 * maxAxis + 1) : BoxFace(2 * maxAxis);
|
||||||
surfaceNormal = glm::vec3(direction.x > 0 ? -1.0f : 1.0f, 0.0f, 0.0f);
|
} else {
|
||||||
return true;
|
distance = tmin;
|
||||||
}
|
bool positiveDirection = direction[minAxis] > 0.0f;
|
||||||
if ((findIntersection(origin.y, direction.y, corner.y, scale.y, axisDistance) && axisDistance >= 0 &&
|
surfaceNormal = glm::vec3(0.0f);
|
||||||
isWithin(origin.x + axisDistance * direction.x, corner.x, scale.x) &&
|
surfaceNormal[minAxis] = positiveDirection ? -1.0f : 1.0f;
|
||||||
isWithin(origin.z + axisDistance * direction.z, corner.z, scale.z))) {
|
face = positiveDirection ? BoxFace(2 * minAxis) : BoxFace(2 * minAxis + 1);
|
||||||
distance = axisDistance;
|
}
|
||||||
face = direction.y > 0 ? MIN_Y_FACE : MAX_Y_FACE;
|
|
||||||
surfaceNormal = glm::vec3(0.0f, direction.y > 0 ? -1.0f : 1.0f, 0.0f);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if ((findIntersection(origin.z, direction.z, corner.z, scale.z, axisDistance) && axisDistance >= 0 &&
|
|
||||||
isWithin(origin.y + axisDistance * direction.y, corner.y, scale.y) &&
|
|
||||||
isWithin(origin.x + axisDistance * direction.x, corner.x, scale.x))) {
|
|
||||||
distance = axisDistance;
|
|
||||||
face = direction.z > 0 ? MIN_Z_FACE : MAX_Z_FACE;
|
|
||||||
surfaceNormal = glm::vec3(0.0f, 0.0f, direction.z > 0 ? -1.0f : 1.0f);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
@ -286,12 +260,13 @@ bool findRaySphereIntersection(const glm::vec3& origin, const glm::vec3& directi
|
||||||
distance = 0.0f;
|
distance = 0.0f;
|
||||||
return true; // starts inside the sphere
|
return true; // starts inside the sphere
|
||||||
}
|
}
|
||||||
float b = glm::dot(direction, relativeOrigin);
|
float b = 2.0f * glm::dot(direction, relativeOrigin);
|
||||||
float radicand = b * b - c;
|
float a = glm::dot(direction, direction);
|
||||||
|
float radicand = b * b - 4.0f * a * c;
|
||||||
if (radicand < 0.0f) {
|
if (radicand < 0.0f) {
|
||||||
return false; // doesn't hit the sphere
|
return false; // doesn't hit the sphere
|
||||||
}
|
}
|
||||||
float t = -b - sqrtf(radicand);
|
float t = 0.5f * (-b - sqrtf(radicand)) / a;
|
||||||
if (t < 0.0f) {
|
if (t < 0.0f) {
|
||||||
return false; // doesn't hit the sphere
|
return false; // doesn't hit the sphere
|
||||||
}
|
}
|
||||||
|
@ -391,24 +366,34 @@ Triangle Triangle::operator*(const glm::mat4& transform) const {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// https://en.wikipedia.org/wiki/M%C3%B6ller%E2%80%93Trumbore_intersection_algorithm
|
||||||
bool findRayTriangleIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
bool findRayTriangleIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||||
const glm::vec3& v0, const glm::vec3& v1, const glm::vec3& v2, float& distance, bool allowBackface) {
|
const glm::vec3& v0, const glm::vec3& v1, const glm::vec3& v2, float& distance, bool allowBackface) {
|
||||||
glm::vec3 firstSide = v0 - v1;
|
glm::vec3 firstSide = v1 - v0;
|
||||||
glm::vec3 secondSide = v2 - v1;
|
glm::vec3 secondSide = v2 - v0;
|
||||||
glm::vec3 normal = glm::cross(secondSide, firstSide);
|
glm::vec3 P = glm::cross(direction, secondSide);
|
||||||
float dividend = glm::dot(normal, v1) - glm::dot(origin, normal);
|
float det = glm::dot(firstSide, P);
|
||||||
if (!allowBackface && dividend > 0.0f) {
|
if (!allowBackface && det < EPSILON) {
|
||||||
return false; // origin below plane
|
return false;
|
||||||
}
|
} else if (fabsf(det) < EPSILON) {
|
||||||
float divisor = glm::dot(normal, direction);
|
|
||||||
if (divisor >= 0.0f) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
float t = dividend / divisor;
|
|
||||||
glm::vec3 point = origin + direction * t;
|
float invDet = 1.0f / det;
|
||||||
if (glm::dot(normal, glm::cross(point - v1, firstSide)) > 0.0f &&
|
glm::vec3 T = origin - v0;
|
||||||
glm::dot(normal, glm::cross(secondSide, point - v1)) > 0.0f &&
|
float u = glm::dot(T, P) * invDet;
|
||||||
glm::dot(normal, glm::cross(point - v0, v2 - v0)) > 0.0f) {
|
if (u < 0.0f || u > 1.0f) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
glm::vec3 Q = glm::cross(T, firstSide);
|
||||||
|
float v = glm::dot(direction, Q) * invDet;
|
||||||
|
if (v < 0.0f || u + v > 1.0f) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
float t = glm::dot(secondSide, Q) * invDet;
|
||||||
|
if (t > EPSILON) {
|
||||||
distance = t;
|
distance = t;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -76,8 +76,8 @@ glm::vec3 addPenetrations(const glm::vec3& currentPenetration, const glm::vec3&
|
||||||
|
|
||||||
bool findIntersection(float origin, float direction, float corner, float size, float& distance);
|
bool findIntersection(float origin, float direction, float corner, float size, float& distance);
|
||||||
bool findInsideOutIntersection(float origin, float direction, float corner, float size, float& distance);
|
bool findInsideOutIntersection(float origin, float direction, float corner, float size, float& distance);
|
||||||
bool findRayAABoxIntersection(const glm::vec3& origin, const glm::vec3& direction, const glm::vec3& corner, const glm::vec3& scale, float& distance,
|
bool findRayAABoxIntersection(const glm::vec3& origin, const glm::vec3& direction, const glm::vec3& invDirection,
|
||||||
BoxFace& face, glm::vec3& surfaceNormal);
|
const glm::vec3& corner, const glm::vec3& scale, float& distance, BoxFace& face, glm::vec3& surfaceNormal);
|
||||||
|
|
||||||
bool findRaySphereIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
bool findRaySphereIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||||
const glm::vec3& center, float radius, float& distance);
|
const glm::vec3& center, float radius, float& distance);
|
||||||
|
|
|
@ -13,6 +13,8 @@
|
||||||
|
|
||||||
#include "GLMHelpers.h"
|
#include "GLMHelpers.h"
|
||||||
|
|
||||||
|
#include <list>
|
||||||
|
|
||||||
void TriangleSet::insert(const Triangle& t) {
|
void TriangleSet::insert(const Triangle& t) {
|
||||||
_isBalanced = false;
|
_isBalanced = false;
|
||||||
|
|
||||||
|
@ -27,48 +29,7 @@ void TriangleSet::clear() {
|
||||||
_bounds.clear();
|
_bounds.clear();
|
||||||
_isBalanced = false;
|
_isBalanced = false;
|
||||||
|
|
||||||
_triangleOctree.clear();
|
_triangleTree.clear();
|
||||||
}
|
|
||||||
|
|
||||||
bool TriangleSet::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
|
||||||
float& distance, BoxFace& face, Triangle& triangle, bool precision, bool allowBackface) {
|
|
||||||
|
|
||||||
// reset our distance to be the max possible, lower level tests will store best distance here
|
|
||||||
distance = std::numeric_limits<float>::max();
|
|
||||||
|
|
||||||
if (!_isBalanced) {
|
|
||||||
balanceOctree();
|
|
||||||
}
|
|
||||||
|
|
||||||
int trianglesTouched = 0;
|
|
||||||
auto result = _triangleOctree.findRayIntersection(origin, direction, distance, face, triangle, precision, trianglesTouched, allowBackface);
|
|
||||||
|
|
||||||
#if WANT_DEBUGGING
|
|
||||||
if (precision) {
|
|
||||||
qDebug() << "trianglesTouched :" << trianglesTouched << "out of:" << _triangleOctree._population << "_triangles.size:" << _triangles.size();
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool TriangleSet::findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration,
|
|
||||||
float& parabolicDistance, BoxFace& face, Triangle& triangle, bool precision, bool allowBackface) {
|
|
||||||
// reset our distance to be the max possible, lower level tests will store best distance here
|
|
||||||
parabolicDistance = FLT_MAX;
|
|
||||||
|
|
||||||
if (!_isBalanced) {
|
|
||||||
balanceOctree();
|
|
||||||
}
|
|
||||||
|
|
||||||
int trianglesTouched = 0;
|
|
||||||
auto result = _triangleOctree.findParabolaIntersection(origin, velocity, acceleration, parabolicDistance, face, triangle, precision, trianglesTouched, allowBackface);
|
|
||||||
|
|
||||||
#if WANT_DEBUGGING
|
|
||||||
if (precision) {
|
|
||||||
qDebug() << "trianglesTouched :" << trianglesTouched << "out of:" << _triangleOctree._population << "_triangles.size:" << _triangles.size();
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TriangleSet::convexHullContains(const glm::vec3& point) const {
|
bool TriangleSet::convexHullContains(const glm::vec3& point) const {
|
||||||
|
@ -92,33 +53,158 @@ void TriangleSet::debugDump() {
|
||||||
qDebug() << __FUNCTION__;
|
qDebug() << __FUNCTION__;
|
||||||
qDebug() << "bounds:" << getBounds();
|
qDebug() << "bounds:" << getBounds();
|
||||||
qDebug() << "triangles:" << size() << "at top level....";
|
qDebug() << "triangles:" << size() << "at top level....";
|
||||||
qDebug() << "----- _triangleOctree -----";
|
qDebug() << "----- _triangleTree -----";
|
||||||
_triangleOctree.debugDump();
|
_triangleTree.debugDump();
|
||||||
}
|
}
|
||||||
|
|
||||||
void TriangleSet::balanceOctree() {
|
void TriangleSet::balanceTree() {
|
||||||
_triangleOctree.reset(_bounds, 0);
|
_triangleTree.reset(_bounds);
|
||||||
|
|
||||||
// insert all the triangles
|
// insert all the triangles
|
||||||
for (size_t i = 0; i < _triangles.size(); i++) {
|
for (size_t i = 0; i < _triangles.size(); i++) {
|
||||||
_triangleOctree.insert(i);
|
_triangleTree.insert(i);
|
||||||
}
|
}
|
||||||
|
|
||||||
_isBalanced = true;
|
_isBalanced = true;
|
||||||
|
|
||||||
#if WANT_DEBUGGING
|
#if WANT_DEBUGGING
|
||||||
debugDump();
|
debugDump();
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// With an octree: 8 ^ MAX_DEPTH = 4096 leaves
|
||||||
|
//static const int MAX_DEPTH = 4;
|
||||||
|
// With a k-d tree: 2 ^ MAX_DEPTH = 4096 leaves
|
||||||
|
static const int MAX_DEPTH = 12;
|
||||||
|
|
||||||
|
TriangleSet::TriangleTreeCell::TriangleTreeCell(std::vector<Triangle>& allTriangles, const AABox& bounds, int depth) :
|
||||||
|
_allTriangles(allTriangles)
|
||||||
|
{
|
||||||
|
reset(bounds, depth);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TriangleSet::TriangleTreeCell::clear() {
|
||||||
|
_population = 0;
|
||||||
|
_triangleIndices.clear();
|
||||||
|
_bounds.clear();
|
||||||
|
_children.first.reset();
|
||||||
|
_children.second.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TriangleSet::TriangleTreeCell::reset(const AABox& bounds, int depth) {
|
||||||
|
clear();
|
||||||
|
_bounds = bounds;
|
||||||
|
_depth = depth;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TriangleSet::TriangleTreeCell::debugDump() {
|
||||||
|
qDebug() << __FUNCTION__;
|
||||||
|
qDebug() << " bounds:" << getBounds();
|
||||||
|
qDebug() << " depth:" << _depth;
|
||||||
|
qDebug() << " population:" << _population << "this level or below"
|
||||||
|
<< " ---- triangleIndices:" << _triangleIndices.size() << "in this cell";
|
||||||
|
|
||||||
|
int numChildren = 0;
|
||||||
|
if (_children.first) {
|
||||||
|
numChildren++;
|
||||||
|
} else if (_children.second) {
|
||||||
|
numChildren++;
|
||||||
|
}
|
||||||
|
qDebug() << "child cells:" << numChildren;
|
||||||
|
if (_depth < MAX_DEPTH) {
|
||||||
|
if (_children.first) {
|
||||||
|
qDebug() << "child: 0";
|
||||||
|
_children.first->debugDump();
|
||||||
|
}
|
||||||
|
if (_children.second) {
|
||||||
|
qDebug() << "child: 1";
|
||||||
|
_children.second->debugDump();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::pair<AABox, AABox> TriangleSet::TriangleTreeCell::getTriangleTreeCellChildBounds() {
|
||||||
|
std::pair<AABox, AABox> toReturn;
|
||||||
|
int axis = 0;
|
||||||
|
// find biggest axis
|
||||||
|
glm::vec3 dimensions = _bounds.getDimensions();
|
||||||
|
for (int i = 0; i < 3; i++) {
|
||||||
|
if (dimensions[i] >= dimensions[(i + 1) % 3] && dimensions[i] >= dimensions[(i + 2) % 3]) {
|
||||||
|
axis = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The new boxes are half the size in the largest dimension
|
||||||
|
glm::vec3 newDimensions = dimensions;
|
||||||
|
newDimensions[axis] *= 0.5f;
|
||||||
|
toReturn.first.setBox(_bounds.getCorner(), newDimensions);
|
||||||
|
glm::vec3 offset = glm::vec3(0.0f);
|
||||||
|
offset[axis] = newDimensions[axis];
|
||||||
|
toReturn.second.setBox(_bounds.getCorner() + offset, newDimensions);
|
||||||
|
return toReturn;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TriangleSet::TriangleTreeCell::insert(size_t triangleIndex) {
|
||||||
|
_population++;
|
||||||
|
|
||||||
|
// if we're not yet at the max depth, then check which child the triangle fits in
|
||||||
|
if (_depth < MAX_DEPTH) {
|
||||||
|
const Triangle& triangle = _allTriangles[triangleIndex];
|
||||||
|
auto childBounds = getTriangleTreeCellChildBounds();
|
||||||
|
|
||||||
|
auto insertOperator = [&](const AABox& childBound, std::shared_ptr<TriangleTreeCell>& child) {
|
||||||
|
// if the child AABox would contain the triangle...
|
||||||
|
if (childBound.contains(triangle)) {
|
||||||
|
// if the child cell doesn't yet exist, create it...
|
||||||
|
if (!child) {
|
||||||
|
child = std::make_shared<TriangleTreeCell>(_allTriangles, childBound, _depth + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// insert the triangleIndex in the child cell
|
||||||
|
child->insert(triangleIndex);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
if (insertOperator(childBounds.first, _children.first) || insertOperator(childBounds.second, _children.second)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// either we're at max depth, or the triangle doesn't fit in one of our
|
||||||
|
// children and so we want to just record it here
|
||||||
|
_triangleIndices.push_back(triangleIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TriangleSet::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, const glm::vec3& invDirection, float& distance,
|
||||||
|
BoxFace& face, Triangle& triangle, bool precision, bool allowBackface) {
|
||||||
|
if (!_isBalanced) {
|
||||||
|
balanceTree();
|
||||||
|
}
|
||||||
|
|
||||||
|
float localDistance = distance;
|
||||||
|
int trianglesTouched = 0;
|
||||||
|
bool hit = _triangleTree.findRayIntersection(origin, direction, invDirection, localDistance, face, triangle, precision, trianglesTouched, allowBackface);
|
||||||
|
if (hit) {
|
||||||
|
distance = localDistance;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if WANT_DEBUGGING
|
||||||
|
if (precision) {
|
||||||
|
qDebug() << "trianglesTouched :" << trianglesTouched << "out of:" << _triangleTree._population << "_triangles.size:" << _triangles.size();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return hit;
|
||||||
|
}
|
||||||
|
|
||||||
// Determine of the given ray (origin/direction) in model space intersects with any triangles
|
// Determine of the given ray (origin/direction) in model space intersects with any triangles
|
||||||
// in the set. If an intersection occurs, the distance and surface normal will be provided.
|
// in the set. If an intersection occurs, the distance and surface normal will be provided.
|
||||||
bool TriangleSet::TriangleOctreeCell::findRayIntersectionInternal(const glm::vec3& origin, const glm::vec3& direction,
|
bool TriangleSet::TriangleTreeCell::findRayIntersectionInternal(const glm::vec3& origin, const glm::vec3& direction,
|
||||||
float& distance, BoxFace& face, Triangle& triangle, bool precision,
|
float& distance, BoxFace& face, Triangle& triangle, bool precision,
|
||||||
int& trianglesTouched, bool allowBackface) {
|
int& trianglesTouched, bool allowBackface) {
|
||||||
bool intersectedSomething = false;
|
bool intersectedSomething = false;
|
||||||
float bestDistance = FLT_MAX;
|
float bestDistance = FLT_MAX;
|
||||||
|
Triangle bestTriangle;
|
||||||
|
|
||||||
if (precision) {
|
if (precision) {
|
||||||
for (const auto& triangleIndex : _triangleIndices) {
|
for (const auto& triangleIndex : _triangleIndices) {
|
||||||
|
@ -128,8 +214,8 @@ bool TriangleSet::TriangleOctreeCell::findRayIntersectionInternal(const glm::vec
|
||||||
if (findRayTriangleIntersection(origin, direction, thisTriangle, thisTriangleDistance, allowBackface)) {
|
if (findRayTriangleIntersection(origin, direction, thisTriangle, thisTriangleDistance, allowBackface)) {
|
||||||
if (thisTriangleDistance < bestDistance) {
|
if (thisTriangleDistance < bestDistance) {
|
||||||
bestDistance = thisTriangleDistance;
|
bestDistance = thisTriangleDistance;
|
||||||
|
bestTriangle = thisTriangle;
|
||||||
intersectedSomething = true;
|
intersectedSomething = true;
|
||||||
triangle = thisTriangle;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -140,17 +226,133 @@ bool TriangleSet::TriangleOctreeCell::findRayIntersectionInternal(const glm::vec
|
||||||
|
|
||||||
if (intersectedSomething) {
|
if (intersectedSomething) {
|
||||||
distance = bestDistance;
|
distance = bestDistance;
|
||||||
|
triangle = bestTriangle;
|
||||||
}
|
}
|
||||||
|
|
||||||
return intersectedSomething;
|
return intersectedSomething;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TriangleSet::TriangleOctreeCell::findParabolaIntersectionInternal(const glm::vec3& origin, const glm::vec3& velocity,
|
bool TriangleSet::TriangleTreeCell::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, const glm::vec3& invDirection,
|
||||||
|
float& distance, BoxFace& face, Triangle& triangle, bool precision, int& trianglesTouched,
|
||||||
|
bool allowBackface) {
|
||||||
|
if (_population < 1) {
|
||||||
|
return false; // no triangles below here, so we can't intersect
|
||||||
|
}
|
||||||
|
|
||||||
|
float bestLocalDistance = FLT_MAX;
|
||||||
|
BoxFace bestLocalFace;
|
||||||
|
Triangle bestLocalTriangle;
|
||||||
|
bool intersects = false;
|
||||||
|
|
||||||
|
// Check our local triangle set first
|
||||||
|
// The distance passed in here is the distance to our bounding box. If !precision, that distance is used
|
||||||
|
{
|
||||||
|
float internalDistance = distance;
|
||||||
|
BoxFace internalFace;
|
||||||
|
Triangle internalTriangle;
|
||||||
|
if (findRayIntersectionInternal(origin, direction, internalDistance, internalFace, internalTriangle, precision, trianglesTouched, allowBackface)) {
|
||||||
|
bestLocalDistance = internalDistance;
|
||||||
|
bestLocalFace = internalFace;
|
||||||
|
bestLocalTriangle = internalTriangle;
|
||||||
|
intersects = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if we're not yet at the max depth, then check our children
|
||||||
|
if (_depth < MAX_DEPTH) {
|
||||||
|
std::list<SortedTriangleCell> sortedTriangleCells;
|
||||||
|
auto sortingOperator = [&](std::shared_ptr<TriangleTreeCell>& child) {
|
||||||
|
if (child) {
|
||||||
|
float priority = FLT_MAX;
|
||||||
|
if (child->getBounds().contains(origin)) {
|
||||||
|
priority = 0.0f;
|
||||||
|
} else {
|
||||||
|
float childBoundDistance = FLT_MAX;
|
||||||
|
BoxFace childBoundFace;
|
||||||
|
glm::vec3 childBoundNormal;
|
||||||
|
if (child->getBounds().findRayIntersection(origin, direction, invDirection, childBoundDistance, childBoundFace, childBoundNormal)) {
|
||||||
|
// We only need to add this cell if it's closer than the local triangle set intersection (if there was one)
|
||||||
|
if (childBoundDistance < bestLocalDistance) {
|
||||||
|
priority = childBoundDistance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (priority < FLT_MAX) {
|
||||||
|
if (sortedTriangleCells.size() > 0 && priority < sortedTriangleCells.front().first) {
|
||||||
|
sortedTriangleCells.emplace_front(priority, child);
|
||||||
|
} else {
|
||||||
|
sortedTriangleCells.emplace_back(priority, child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
sortingOperator(_children.first);
|
||||||
|
sortingOperator(_children.second);
|
||||||
|
|
||||||
|
for (auto it = sortedTriangleCells.begin(); it != sortedTriangleCells.end(); ++it) {
|
||||||
|
const SortedTriangleCell& sortedTriangleCell = *it;
|
||||||
|
float childDistance = sortedTriangleCell.first;
|
||||||
|
// We can exit once childDistance > bestLocalDistance
|
||||||
|
if (childDistance > bestLocalDistance) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// If we're inside the child cell and !precision, we need the actual distance to the cell bounds
|
||||||
|
if (!precision && childDistance < EPSILON) {
|
||||||
|
BoxFace childBoundFace;
|
||||||
|
glm::vec3 childBoundNormal;
|
||||||
|
sortedTriangleCell.second->getBounds().findRayIntersection(origin, direction, invDirection, childDistance, childBoundFace, childBoundNormal);
|
||||||
|
}
|
||||||
|
BoxFace childFace;
|
||||||
|
Triangle childTriangle;
|
||||||
|
if (sortedTriangleCell.second->findRayIntersection(origin, direction, invDirection, childDistance, childFace, childTriangle, precision, trianglesTouched)) {
|
||||||
|
if (childDistance < bestLocalDistance) {
|
||||||
|
bestLocalDistance = childDistance;
|
||||||
|
bestLocalFace = childFace;
|
||||||
|
bestLocalTriangle = childTriangle;
|
||||||
|
intersects = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (intersects) {
|
||||||
|
distance = bestLocalDistance;
|
||||||
|
face = bestLocalFace;
|
||||||
|
triangle = bestLocalTriangle;
|
||||||
|
}
|
||||||
|
return intersects;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TriangleSet::findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration,
|
||||||
|
float& parabolicDistance, BoxFace& face, Triangle& triangle, bool precision, bool allowBackface) {
|
||||||
|
if (!_isBalanced) {
|
||||||
|
balanceTree();
|
||||||
|
}
|
||||||
|
|
||||||
|
float localDistance = parabolicDistance;
|
||||||
|
int trianglesTouched = 0;
|
||||||
|
bool hit = _triangleTree.findParabolaIntersection(origin, velocity, acceleration, localDistance, face, triangle, precision, trianglesTouched, allowBackface);
|
||||||
|
if (hit) {
|
||||||
|
parabolicDistance = localDistance;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if WANT_DEBUGGING
|
||||||
|
if (precision) {
|
||||||
|
qDebug() << "trianglesTouched :" << trianglesTouched << "out of:" << _triangleTree._population << "_triangles.size:" << _triangles.size();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return hit;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TriangleSet::TriangleTreeCell::findParabolaIntersectionInternal(const glm::vec3& origin, const glm::vec3& velocity,
|
||||||
const glm::vec3& acceleration, float& parabolicDistance,
|
const glm::vec3& acceleration, float& parabolicDistance,
|
||||||
BoxFace& face, Triangle& triangle, bool precision,
|
BoxFace& face, Triangle& triangle, bool precision,
|
||||||
int& trianglesTouched, bool allowBackface) {
|
int& trianglesTouched, bool allowBackface) {
|
||||||
bool intersectedSomething = false;
|
bool intersectedSomething = false;
|
||||||
float bestDistance = FLT_MAX;
|
float bestDistance = FLT_MAX;
|
||||||
|
Triangle bestTriangle;
|
||||||
|
|
||||||
if (precision) {
|
if (precision) {
|
||||||
for (const auto& triangleIndex : _triangleIndices) {
|
for (const auto& triangleIndex : _triangleIndices) {
|
||||||
|
@ -160,8 +362,8 @@ bool TriangleSet::TriangleOctreeCell::findParabolaIntersectionInternal(const glm
|
||||||
if (findParabolaTriangleIntersection(origin, velocity, acceleration, thisTriangle, thisTriangleDistance, allowBackface)) {
|
if (findParabolaTriangleIntersection(origin, velocity, acceleration, thisTriangle, thisTriangleDistance, allowBackface)) {
|
||||||
if (thisTriangleDistance < bestDistance) {
|
if (thisTriangleDistance < bestDistance) {
|
||||||
bestDistance = thisTriangleDistance;
|
bestDistance = thisTriangleDistance;
|
||||||
|
bestTriangle = thisTriangle;
|
||||||
intersectedSomething = true;
|
intersectedSomething = true;
|
||||||
triangle = thisTriangle;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -172,146 +374,13 @@ bool TriangleSet::TriangleOctreeCell::findParabolaIntersectionInternal(const glm
|
||||||
|
|
||||||
if (intersectedSomething) {
|
if (intersectedSomething) {
|
||||||
parabolicDistance = bestDistance;
|
parabolicDistance = bestDistance;
|
||||||
|
triangle = bestTriangle;
|
||||||
}
|
}
|
||||||
|
|
||||||
return intersectedSomething;
|
return intersectedSomething;
|
||||||
}
|
}
|
||||||
|
|
||||||
static const int MAX_DEPTH = 4; // for now
|
bool TriangleSet::TriangleTreeCell::findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity,
|
||||||
static const int MAX_CHILDREN = 8;
|
|
||||||
|
|
||||||
TriangleSet::TriangleOctreeCell::TriangleOctreeCell(std::vector<Triangle>& allTriangles, const AABox& bounds, int depth) :
|
|
||||||
_allTriangles(allTriangles)
|
|
||||||
{
|
|
||||||
reset(bounds, depth);
|
|
||||||
}
|
|
||||||
|
|
||||||
void TriangleSet::TriangleOctreeCell::clear() {
|
|
||||||
_population = 0;
|
|
||||||
_triangleIndices.clear();
|
|
||||||
_bounds.clear();
|
|
||||||
_children.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
void TriangleSet::TriangleOctreeCell::reset(const AABox& bounds, int depth) {
|
|
||||||
clear();
|
|
||||||
_bounds = bounds;
|
|
||||||
_depth = depth;
|
|
||||||
}
|
|
||||||
|
|
||||||
void TriangleSet::TriangleOctreeCell::debugDump() {
|
|
||||||
qDebug() << __FUNCTION__;
|
|
||||||
qDebug() << "bounds:" << getBounds();
|
|
||||||
qDebug() << "depth:" << _depth;
|
|
||||||
qDebug() << "population:" << _population << "this level or below"
|
|
||||||
<< " ---- triangleIndices:" << _triangleIndices.size() << "in this cell";
|
|
||||||
|
|
||||||
qDebug() << "child cells:" << _children.size();
|
|
||||||
if (_depth < MAX_DEPTH) {
|
|
||||||
int childNum = 0;
|
|
||||||
for (auto& child : _children) {
|
|
||||||
qDebug() << "child:" << childNum;
|
|
||||||
child.second.debugDump();
|
|
||||||
childNum++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void TriangleSet::TriangleOctreeCell::insert(size_t triangleIndex) {
|
|
||||||
const Triangle& triangle = _allTriangles[triangleIndex];
|
|
||||||
_population++;
|
|
||||||
// if we're not yet at the max depth, then check which child the triangle fits in
|
|
||||||
if (_depth < MAX_DEPTH) {
|
|
||||||
|
|
||||||
for (int child = 0; child < MAX_CHILDREN; child++) {
|
|
||||||
AABox childBounds = getBounds().getOctreeChild((AABox::OctreeChild)child);
|
|
||||||
|
|
||||||
|
|
||||||
// if the child AABox would contain the triangle...
|
|
||||||
if (childBounds.contains(triangle)) {
|
|
||||||
// if the child cell doesn't yet exist, create it...
|
|
||||||
if (_children.find((AABox::OctreeChild)child) == _children.end()) {
|
|
||||||
_children.insert(
|
|
||||||
std::pair<AABox::OctreeChild, TriangleOctreeCell>
|
|
||||||
((AABox::OctreeChild)child, TriangleOctreeCell(_allTriangles, childBounds, _depth + 1)));
|
|
||||||
}
|
|
||||||
|
|
||||||
// insert the triangleIndex in the child cell
|
|
||||||
_children.at((AABox::OctreeChild)child).insert(triangleIndex);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// either we're at max depth, or the triangle doesn't fit in one of our
|
|
||||||
// children and so we want to just record it here
|
|
||||||
_triangleIndices.push_back(triangleIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool TriangleSet::TriangleOctreeCell::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance,
|
|
||||||
BoxFace& face, Triangle& triangle, bool precision, int& trianglesTouched,
|
|
||||||
bool allowBackface) {
|
|
||||||
if (_population < 1) {
|
|
||||||
return false; // no triangles below here, so we can't intersect
|
|
||||||
}
|
|
||||||
|
|
||||||
float bestLocalDistance = FLT_MAX;
|
|
||||||
BoxFace bestLocalFace;
|
|
||||||
Triangle bestLocalTriangle;
|
|
||||||
glm::vec3 bestLocalNormal;
|
|
||||||
bool intersects = false;
|
|
||||||
|
|
||||||
float boxDistance = FLT_MAX;
|
|
||||||
// if the pick intersects our bounding box, then continue
|
|
||||||
if (getBounds().findRayIntersection(origin, direction, boxDistance, bestLocalFace, bestLocalNormal)) {
|
|
||||||
// if the intersection with our bounding box, is greater than the current best distance (the distance passed in)
|
|
||||||
// then we know that none of our triangles can represent a better intersection and we can return
|
|
||||||
if (boxDistance > distance) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// if we're not yet at the max depth, then check which child the triangle fits in
|
|
||||||
if (_depth < MAX_DEPTH) {
|
|
||||||
float bestChildDistance = FLT_MAX;
|
|
||||||
for (auto& child : _children) {
|
|
||||||
// check each child, if there's an intersection, it will return some distance that we need
|
|
||||||
// to compare against the other results, because there might be multiple intersections and
|
|
||||||
// we will always choose the best (shortest) intersection
|
|
||||||
float childDistance = bestChildDistance;
|
|
||||||
BoxFace childFace;
|
|
||||||
Triangle childTriangle;
|
|
||||||
if (child.second.findRayIntersection(origin, direction, childDistance, childFace, childTriangle, precision, trianglesTouched)) {
|
|
||||||
if (childDistance < bestLocalDistance) {
|
|
||||||
bestLocalDistance = childDistance;
|
|
||||||
bestChildDistance = childDistance;
|
|
||||||
bestLocalFace = childFace;
|
|
||||||
bestLocalTriangle = childTriangle;
|
|
||||||
intersects = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// also check our local triangle set
|
|
||||||
float internalDistance = boxDistance;
|
|
||||||
BoxFace internalFace;
|
|
||||||
Triangle internalTriangle;
|
|
||||||
if (findRayIntersectionInternal(origin, direction, internalDistance, internalFace, internalTriangle, precision, trianglesTouched, allowBackface)) {
|
|
||||||
if (internalDistance < bestLocalDistance) {
|
|
||||||
bestLocalDistance = internalDistance;
|
|
||||||
bestLocalFace = internalFace;
|
|
||||||
bestLocalTriangle = internalTriangle;
|
|
||||||
intersects = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (intersects) {
|
|
||||||
distance = bestLocalDistance;
|
|
||||||
face = bestLocalFace;
|
|
||||||
triangle = bestLocalTriangle;
|
|
||||||
}
|
|
||||||
return intersects;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool TriangleSet::TriangleOctreeCell::findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity,
|
|
||||||
const glm::vec3& acceleration, float& parabolicDistance,
|
const glm::vec3& acceleration, float& parabolicDistance,
|
||||||
BoxFace& face, Triangle& triangle, bool precision,
|
BoxFace& face, Triangle& triangle, bool precision,
|
||||||
int& trianglesTouched, bool allowBackface) {
|
int& trianglesTouched, bool allowBackface) {
|
||||||
|
@ -322,52 +391,81 @@ bool TriangleSet::TriangleOctreeCell::findParabolaIntersection(const glm::vec3&
|
||||||
float bestLocalDistance = FLT_MAX;
|
float bestLocalDistance = FLT_MAX;
|
||||||
BoxFace bestLocalFace;
|
BoxFace bestLocalFace;
|
||||||
Triangle bestLocalTriangle;
|
Triangle bestLocalTriangle;
|
||||||
glm::vec3 bestLocalNormal;
|
|
||||||
bool intersects = false;
|
bool intersects = false;
|
||||||
|
|
||||||
float boxDistance = FLT_MAX;
|
// Check our local triangle set first
|
||||||
// if the pick intersects our bounding box, then continue
|
// The distance passed in here is the distance to our bounding box. If !precision, that distance is used
|
||||||
if (getBounds().findParabolaIntersection(origin, velocity, acceleration, boxDistance, bestLocalFace, bestLocalNormal)) {
|
{
|
||||||
// if the intersection with our bounding box, is greater than the current best distance (the distance passed in)
|
float internalDistance = parabolicDistance;
|
||||||
// then we know that none of our triangles can represent a better intersection and we can return
|
|
||||||
if (boxDistance > parabolicDistance) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// if we're not yet at the max depth, then check which child the triangle fits in
|
|
||||||
if (_depth < MAX_DEPTH) {
|
|
||||||
float bestChildDistance = FLT_MAX;
|
|
||||||
for (auto& child : _children) {
|
|
||||||
// check each child, if there's an intersection, it will return some distance that we need
|
|
||||||
// to compare against the other results, because there might be multiple intersections and
|
|
||||||
// we will always choose the best (shortest) intersection
|
|
||||||
float childDistance = bestChildDistance;
|
|
||||||
BoxFace childFace;
|
|
||||||
Triangle childTriangle;
|
|
||||||
if (child.second.findParabolaIntersection(origin, velocity, acceleration, childDistance, childFace, childTriangle, precision, trianglesTouched)) {
|
|
||||||
if (childDistance < bestLocalDistance) {
|
|
||||||
bestLocalDistance = childDistance;
|
|
||||||
bestChildDistance = childDistance;
|
|
||||||
bestLocalFace = childFace;
|
|
||||||
bestLocalTriangle = childTriangle;
|
|
||||||
intersects = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// also check our local triangle set
|
|
||||||
float internalDistance = boxDistance;
|
|
||||||
BoxFace internalFace;
|
BoxFace internalFace;
|
||||||
Triangle internalTriangle;
|
Triangle internalTriangle;
|
||||||
if (findParabolaIntersectionInternal(origin, velocity, acceleration, internalDistance, internalFace, internalTriangle, precision, trianglesTouched, allowBackface)) {
|
if (findParabolaIntersectionInternal(origin, velocity, acceleration, internalDistance, internalFace, internalTriangle, precision, trianglesTouched, allowBackface)) {
|
||||||
if (internalDistance < bestLocalDistance) {
|
bestLocalDistance = internalDistance;
|
||||||
bestLocalDistance = internalDistance;
|
bestLocalFace = internalFace;
|
||||||
bestLocalFace = internalFace;
|
bestLocalTriangle = internalTriangle;
|
||||||
bestLocalTriangle = internalTriangle;
|
intersects = true;
|
||||||
intersects = true;
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if we're not yet at the max depth, then check our children
|
||||||
|
if (_depth < MAX_DEPTH) {
|
||||||
|
std::list<SortedTriangleCell> sortedTriangleCells;
|
||||||
|
auto sortingOperator = [&](std::shared_ptr<TriangleTreeCell>& child) {
|
||||||
|
if (child) {
|
||||||
|
float priority = FLT_MAX;
|
||||||
|
if (child->getBounds().contains(origin)) {
|
||||||
|
priority = 0.0f;
|
||||||
|
} else {
|
||||||
|
float childBoundDistance = FLT_MAX;
|
||||||
|
BoxFace childBoundFace;
|
||||||
|
glm::vec3 childBoundNormal;
|
||||||
|
if (child->getBounds().findParabolaIntersection(origin, velocity, acceleration, childBoundDistance, childBoundFace, childBoundNormal)) {
|
||||||
|
// We only need to add this cell if it's closer than the local triangle set intersection (if there was one)
|
||||||
|
if (childBoundDistance < bestLocalDistance) {
|
||||||
|
priority = childBoundDistance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (priority < FLT_MAX) {
|
||||||
|
if (sortedTriangleCells.size() > 0 && priority < sortedTriangleCells.front().first) {
|
||||||
|
sortedTriangleCells.emplace_front(priority, child);
|
||||||
|
} else {
|
||||||
|
sortedTriangleCells.emplace_back(priority, child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
sortingOperator(_children.first);
|
||||||
|
sortingOperator(_children.second);
|
||||||
|
|
||||||
|
for (auto it = sortedTriangleCells.begin(); it != sortedTriangleCells.end(); ++it) {
|
||||||
|
const SortedTriangleCell& sortedTriangleCell = *it;
|
||||||
|
float childDistance = sortedTriangleCell.first;
|
||||||
|
// We can exit once childDistance > bestLocalDistance
|
||||||
|
if (childDistance > bestLocalDistance) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// If we're inside the child cell and !precision, we need the actual distance to the cell bounds
|
||||||
|
if (!precision && childDistance < EPSILON) {
|
||||||
|
BoxFace childBoundFace;
|
||||||
|
glm::vec3 childBoundNormal;
|
||||||
|
sortedTriangleCell.second->getBounds().findParabolaIntersection(origin, velocity, acceleration, childDistance, childBoundFace, childBoundNormal);
|
||||||
|
}
|
||||||
|
BoxFace childFace;
|
||||||
|
Triangle childTriangle;
|
||||||
|
if (sortedTriangleCell.second->findParabolaIntersection(origin, velocity, acceleration, childDistance, childFace, childTriangle, precision, trianglesTouched)) {
|
||||||
|
if (childDistance < bestLocalDistance) {
|
||||||
|
bestLocalDistance = childDistance;
|
||||||
|
bestLocalFace = childFace;
|
||||||
|
bestLocalTriangle = childTriangle;
|
||||||
|
intersects = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (intersects) {
|
if (intersects) {
|
||||||
parabolicDistance = bestLocalDistance;
|
parabolicDistance = bestLocalDistance;
|
||||||
face = bestLocalFace;
|
face = bestLocalFace;
|
||||||
|
|
|
@ -12,23 +12,23 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
#include "AABox.h"
|
#include "AABox.h"
|
||||||
#include "GeometryUtil.h"
|
#include "GeometryUtil.h"
|
||||||
|
|
||||||
class TriangleSet {
|
class TriangleSet {
|
||||||
|
|
||||||
class TriangleOctreeCell {
|
class TriangleTreeCell {
|
||||||
public:
|
public:
|
||||||
TriangleOctreeCell(std::vector<Triangle>& allTriangles) :
|
TriangleTreeCell(std::vector<Triangle>& allTriangles) : _allTriangles(allTriangles) {}
|
||||||
_allTriangles(allTriangles)
|
TriangleTreeCell(std::vector<Triangle>& allTriangles, const AABox& bounds, int depth);
|
||||||
{ }
|
|
||||||
|
|
||||||
void insert(size_t triangleIndex);
|
void insert(size_t triangleIndex);
|
||||||
void reset(const AABox& bounds, int depth = 0);
|
void reset(const AABox& bounds, int depth = 0);
|
||||||
void clear();
|
void clear();
|
||||||
|
|
||||||
bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, const glm::vec3& invDirection,
|
||||||
float& distance, BoxFace& face, Triangle& triangle, bool precision, int& trianglesTouched,
|
float& distance, BoxFace& face, Triangle& triangle, bool precision, int& trianglesTouched,
|
||||||
bool allowBackface = false);
|
bool allowBackface = false);
|
||||||
bool findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration,
|
bool findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration,
|
||||||
|
@ -40,8 +40,6 @@ class TriangleSet {
|
||||||
void debugDump();
|
void debugDump();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
TriangleOctreeCell(std::vector<Triangle>& allTriangles, const AABox& bounds, int depth);
|
|
||||||
|
|
||||||
// checks our internal list of triangles
|
// checks our internal list of triangles
|
||||||
bool findRayIntersectionInternal(const glm::vec3& origin, const glm::vec3& direction,
|
bool findRayIntersectionInternal(const glm::vec3& origin, const glm::vec3& direction,
|
||||||
float& distance, BoxFace& face, Triangle& triangle, bool precision, int& trianglesTouched,
|
float& distance, BoxFace& face, Triangle& triangle, bool precision, int& trianglesTouched,
|
||||||
|
@ -50,31 +48,33 @@ class TriangleSet {
|
||||||
float& parabolicDistance, BoxFace& face, Triangle& triangle, bool precision, int& trianglesTouched,
|
float& parabolicDistance, BoxFace& face, Triangle& triangle, bool precision, int& trianglesTouched,
|
||||||
bool allowBackface = false);
|
bool allowBackface = false);
|
||||||
|
|
||||||
|
std::pair<AABox, AABox> getTriangleTreeCellChildBounds();
|
||||||
|
|
||||||
std::vector<Triangle>& _allTriangles;
|
std::vector<Triangle>& _allTriangles;
|
||||||
std::map<AABox::OctreeChild, TriangleOctreeCell> _children;
|
std::pair<std::shared_ptr<TriangleTreeCell>, std::shared_ptr<TriangleTreeCell>> _children;
|
||||||
int _depth{ 0 };
|
int _depth { 0 };
|
||||||
int _population{ 0 };
|
int _population { 0 };
|
||||||
AABox _bounds;
|
AABox _bounds;
|
||||||
std::vector<size_t> _triangleIndices;
|
std::vector<size_t> _triangleIndices;
|
||||||
|
|
||||||
friend class TriangleSet;
|
friend class TriangleSet;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
using SortedTriangleCell = std::pair<float, std::shared_ptr<TriangleTreeCell>>;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
TriangleSet() :
|
TriangleSet() : _triangleTree(_triangles) {}
|
||||||
_triangleOctree(_triangles)
|
|
||||||
{}
|
|
||||||
|
|
||||||
void debugDump();
|
void debugDump();
|
||||||
|
|
||||||
void insert(const Triangle& t);
|
void insert(const Triangle& t);
|
||||||
|
|
||||||
bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, const glm::vec3& invDirection,
|
||||||
float& distance, BoxFace& face, Triangle& triangle, bool precision, bool allowBackface = false);
|
float& distance, BoxFace& face, Triangle& triangle, bool precision, bool allowBackface = false);
|
||||||
bool findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration,
|
bool findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration,
|
||||||
float& parabolicDistance, BoxFace& face, Triangle& triangle, bool precision, bool allowBackface = false);
|
float& parabolicDistance, BoxFace& face, Triangle& triangle, bool precision, bool allowBackface = false);
|
||||||
|
|
||||||
void balanceOctree();
|
void balanceTree();
|
||||||
|
|
||||||
void reserve(size_t size) { _triangles.reserve(size); } // reserve space in the datastructure for size number of triangles
|
void reserve(size_t size) { _triangles.reserve(size); } // reserve space in the datastructure for size number of triangles
|
||||||
size_t size() const { return _triangles.size(); }
|
size_t size() const { return _triangles.size(); }
|
||||||
|
@ -87,9 +87,8 @@ public:
|
||||||
const AABox& getBounds() const { return _bounds; }
|
const AABox& getBounds() const { return _bounds; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
bool _isBalanced { false };
|
||||||
bool _isBalanced{ false };
|
|
||||||
std::vector<Triangle> _triangles;
|
std::vector<Triangle> _triangles;
|
||||||
TriangleOctreeCell _triangleOctree;
|
TriangleTreeCell _triangleTree;
|
||||||
AABox _bounds;
|
AABox _bounds;
|
||||||
};
|
};
|
||||||
|
|
|
@ -140,8 +140,7 @@ int TabletButtonsProxyModel::buttonIndex(const QString &uuid) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
void TabletButtonsProxyModel::setPageIndex(int pageIndex)
|
void TabletButtonsProxyModel::setPageIndex(int pageIndex) {
|
||||||
{
|
|
||||||
if (_pageIndex == pageIndex)
|
if (_pageIndex == pageIndex)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
@ -465,6 +464,9 @@ void TabletProxy::onTabletShown() {
|
||||||
_showRunningScripts = false;
|
_showRunningScripts = false;
|
||||||
pushOntoStack("hifi/dialogs/TabletRunningScripts.qml");
|
pushOntoStack("hifi/dialogs/TabletRunningScripts.qml");
|
||||||
}
|
}
|
||||||
|
if (_currentPathLoaded == TABLET_HOME_SOURCE_URL) {
|
||||||
|
loadHomeScreen(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -407,8 +407,10 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
function onUsernameChanged() {
|
function onUsernameChanged() {
|
||||||
Settings.setValue("wallet/autoLogout", false);
|
if (Account.username !== Settings.getValue("wallet/savedUsername")) {
|
||||||
Settings.setValue("wallet/savedUsername", "");
|
Settings.setValue("wallet/autoLogout", false);
|
||||||
|
Settings.setValue("wallet/savedUsername", "");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Function Name: fromQml()
|
// Function Name: fromQml()
|
||||||
|
|
|
@ -14,7 +14,8 @@
|
||||||
PICK_MAX_DISTANCE, COLORS_GRAB_SEARCHING_HALF_SQUEEZE, COLORS_GRAB_SEARCHING_FULL_SQUEEZE, COLORS_GRAB_DISTANCE_HOLD,
|
PICK_MAX_DISTANCE, COLORS_GRAB_SEARCHING_HALF_SQUEEZE, COLORS_GRAB_SEARCHING_FULL_SQUEEZE, COLORS_GRAB_DISTANCE_HOLD,
|
||||||
DEFAULT_SEARCH_SPHERE_DISTANCE, TRIGGER_OFF_VALUE, TRIGGER_ON_VALUE, ZERO_VEC, ensureDynamic,
|
DEFAULT_SEARCH_SPHERE_DISTANCE, TRIGGER_OFF_VALUE, TRIGGER_ON_VALUE, ZERO_VEC, ensureDynamic,
|
||||||
getControllerWorldLocation, projectOntoEntityXYPlane, ContextOverlay, HMD, Reticle, Overlays, isPointingAtUI
|
getControllerWorldLocation, projectOntoEntityXYPlane, ContextOverlay, HMD, Reticle, Overlays, isPointingAtUI
|
||||||
Picks, makeLaserLockInfo Xform, makeLaserParams, AddressManager, getEntityParents, Selection, DISPATCHER_HOVERING_LIST
|
Picks, makeLaserLockInfo Xform, makeLaserParams, AddressManager, getEntityParents, Selection, DISPATCHER_HOVERING_LIST,
|
||||||
|
worldPositionToRegistrationFrameMatrix
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Script.include("/~/system/libraries/controllerDispatcherUtils.js");
|
Script.include("/~/system/libraries/controllerDispatcherUtils.js");
|
||||||
|
@ -593,18 +594,9 @@ Script.include("/~/system/libraries/Xform.js");
|
||||||
|
|
||||||
this.calculateOffset = function(controllerData) {
|
this.calculateOffset = function(controllerData) {
|
||||||
if (this.distanceHolding || this.distanceRotating) {
|
if (this.distanceHolding || this.distanceRotating) {
|
||||||
var targetProps = Entities.getEntityProperties(this.targetObject.entityID, [
|
var targetProps = Entities.getEntityProperties(this.targetObject.entityID,
|
||||||
"position",
|
[ "position", "rotation", "registrationPoint", "dimensions" ]);
|
||||||
"rotation"
|
return worldPositionToRegistrationFrameMatrix(targetProps, controllerData.rayPicks[this.hand].intersection);
|
||||||
]);
|
|
||||||
var zeroVector = { x: 0, y: 0, z:0, w: 0 };
|
|
||||||
var intersection = controllerData.rayPicks[this.hand].intersection;
|
|
||||||
var intersectionMat = new Xform(zeroVector, intersection);
|
|
||||||
var modelMat = new Xform(targetProps.rotation, targetProps.position);
|
|
||||||
var modelMatInv = modelMat.inv();
|
|
||||||
var xformMat = Xform.mul(modelMatInv, intersectionMat);
|
|
||||||
var offsetMat = Mat4.createFromRotAndTrans(xformMat.rot, xformMat.pos);
|
|
||||||
return offsetMat;
|
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
};
|
};
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
makeDispatcherModuleParameters, MSECS_PER_SEC, HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, TRIGGER_OFF_VALUE,
|
makeDispatcherModuleParameters, MSECS_PER_SEC, HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, TRIGGER_OFF_VALUE,
|
||||||
TRIGGER_ON_VALUE, ZERO_VEC, getControllerWorldLocation, projectOntoEntityXYPlane, ContextOverlay, HMD,
|
TRIGGER_ON_VALUE, ZERO_VEC, getControllerWorldLocation, projectOntoEntityXYPlane, ContextOverlay, HMD,
|
||||||
Picks, makeLaserLockInfo, Xform, makeLaserParams, AddressManager, getEntityParents, Selection, DISPATCHER_HOVERING_LIST,
|
Picks, makeLaserLockInfo, Xform, makeLaserParams, AddressManager, getEntityParents, Selection, DISPATCHER_HOVERING_LIST,
|
||||||
Uuid
|
Uuid, worldPositionToRegistrationFrameMatrix
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Script.include("/~/system/libraries/controllerDispatcherUtils.js");
|
Script.include("/~/system/libraries/controllerDispatcherUtils.js");
|
||||||
|
@ -572,18 +572,9 @@ Script.include("/~/system/libraries/Xform.js");
|
||||||
|
|
||||||
this.calculateOffset = function(controllerData) {
|
this.calculateOffset = function(controllerData) {
|
||||||
if (this.distanceHolding || this.distanceRotating) {
|
if (this.distanceHolding || this.distanceRotating) {
|
||||||
var targetProps = Entities.getEntityProperties(this.targetObject.entityID, [
|
var targetProps = Entities.getEntityProperties(this.targetObject.entityID,
|
||||||
"position",
|
[ "position", "rotation", "registrationPoint", "dimensions" ]);
|
||||||
"rotation"
|
return worldPositionToRegistrationFrameMatrix(targetProps, controllerData.rayPicks[this.hand].intersection);
|
||||||
]);
|
|
||||||
var zeroVector = { x: 0, y: 0, z:0, w: 0 };
|
|
||||||
var intersection = controllerData.rayPicks[this.hand].intersection;
|
|
||||||
var intersectionMat = new Xform(zeroVector, intersection);
|
|
||||||
var modelMat = new Xform(targetProps.rotation, targetProps.position);
|
|
||||||
var modelMatInv = modelMat.inv();
|
|
||||||
var xformMat = Xform.mul(modelMatInv, intersectionMat);
|
|
||||||
var offsetMat = Mat4.createFromRotAndTrans(xformMat.rot, xformMat.pos);
|
|
||||||
return offsetMat;
|
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
};
|
};
|
||||||
|
|
|
@ -11,7 +11,8 @@
|
||||||
Entities, enableDispatcherModule, disableDispatcherModule, entityIsGrabbable, makeDispatcherModuleParameters, MSECS_PER_SEC,
|
Entities, enableDispatcherModule, disableDispatcherModule, entityIsGrabbable, makeDispatcherModuleParameters, MSECS_PER_SEC,
|
||||||
HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, TRIGGER_OFF_VALUE, TRIGGER_ON_VALUE, ZERO_VEC, getControllerWorldLocation,
|
HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, TRIGGER_OFF_VALUE, TRIGGER_ON_VALUE, ZERO_VEC, getControllerWorldLocation,
|
||||||
projectOntoEntityXYPlane, ContextOverlay, HMD, Picks, makeLaserLockInfo, Xform, makeLaserParams, AddressManager,
|
projectOntoEntityXYPlane, ContextOverlay, HMD, Picks, makeLaserLockInfo, Xform, makeLaserParams, AddressManager,
|
||||||
getEntityParents, Selection, DISPATCHER_HOVERING_LIST, unhighlightTargetEntity, Messages, Uuid, findGroupParent
|
getEntityParents, Selection, DISPATCHER_HOVERING_LIST, unhighlightTargetEntity, Messages, Uuid, findGroupParent,
|
||||||
|
worldPositionToRegistrationFrameMatrix
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Script.include("/~/system/libraries/controllerDispatcherUtils.js");
|
Script.include("/~/system/libraries/controllerDispatcherUtils.js");
|
||||||
|
@ -615,18 +616,9 @@ Script.include("/~/system/libraries/Xform.js");
|
||||||
|
|
||||||
this.calculateOffset = function(controllerData) {
|
this.calculateOffset = function(controllerData) {
|
||||||
if (this.distanceHolding || this.distanceRotating) {
|
if (this.distanceHolding || this.distanceRotating) {
|
||||||
var targetProps = Entities.getEntityProperties(this.targetObject.entityID, [
|
var targetProps = Entities.getEntityProperties(this.targetObject.entityID,
|
||||||
"position",
|
[ "position", "rotation", "registrationPoint", "dimensions" ]);
|
||||||
"rotation"
|
return worldPositionToRegistrationFrameMatrix(targetProps, controllerData.rayPicks[this.hand].intersection);
|
||||||
]);
|
|
||||||
var zeroVector = { x: 0, y: 0, z:0, w: 0 };
|
|
||||||
var intersection = controllerData.rayPicks[this.hand].intersection;
|
|
||||||
var intersectionMat = new Xform(zeroVector, intersection);
|
|
||||||
var modelMat = new Xform(targetProps.rotation, targetProps.position);
|
|
||||||
var modelMatInv = modelMat.inv();
|
|
||||||
var xformMat = Xform.mul(modelMatInv, intersectionMat);
|
|
||||||
var offsetMat = Mat4.createFromRotAndTrans(xformMat.rot, xformMat.pos);
|
|
||||||
return offsetMat;
|
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
};
|
};
|
||||||
|
|
|
@ -20,6 +20,8 @@
|
||||||
|
|
||||||
var EDIT_TOGGLE_BUTTON = "com.highfidelity.interface.system.editButton";
|
var EDIT_TOGGLE_BUTTON = "com.highfidelity.interface.system.editButton";
|
||||||
|
|
||||||
|
var CONTROLLER_MAPPING_NAME = "com.highfidelity.editMode";
|
||||||
|
|
||||||
Script.include([
|
Script.include([
|
||||||
"libraries/stringHelpers.js",
|
"libraries/stringHelpers.js",
|
||||||
"libraries/dataViewHelpers.js",
|
"libraries/dataViewHelpers.js",
|
||||||
|
@ -860,6 +862,7 @@ var toolBar = (function () {
|
||||||
cameraManager.disable();
|
cameraManager.disable();
|
||||||
selectionDisplay.triggerMapping.disable();
|
selectionDisplay.triggerMapping.disable();
|
||||||
tablet.landscape = false;
|
tablet.landscape = false;
|
||||||
|
Controller.disableMapping(CONTROLLER_MAPPING_NAME);
|
||||||
} else {
|
} else {
|
||||||
if (shouldUseEditTabletApp()) {
|
if (shouldUseEditTabletApp()) {
|
||||||
tablet.loadQMLSource("hifi/tablet/Edit.qml", true);
|
tablet.loadQMLSource("hifi/tablet/Edit.qml", true);
|
||||||
|
@ -876,6 +879,7 @@ var toolBar = (function () {
|
||||||
selectionDisplay.triggerMapping.enable();
|
selectionDisplay.triggerMapping.enable();
|
||||||
print("starting tablet in landscape mode");
|
print("starting tablet in landscape mode");
|
||||||
tablet.landscape = true;
|
tablet.landscape = true;
|
||||||
|
Controller.enableMapping(CONTROLLER_MAPPING_NAME);
|
||||||
// Not sure what the following was meant to accomplish, but it currently causes
|
// Not sure what the following was meant to accomplish, but it currently causes
|
||||||
// everybody else to think that Interface has lost focus overall. fogbugzid:558
|
// everybody else to think that Interface has lost focus overall. fogbugzid:558
|
||||||
// Window.setFocus();
|
// Window.setFocus();
|
||||||
|
@ -1859,30 +1863,7 @@ var keyReleaseEvent = function (event) {
|
||||||
cameraManager.keyReleaseEvent(event);
|
cameraManager.keyReleaseEvent(event);
|
||||||
}
|
}
|
||||||
// since sometimes our menu shortcut keys don't work, trap our menu items here also and fire the appropriate menu items
|
// since sometimes our menu shortcut keys don't work, trap our menu items here also and fire the appropriate menu items
|
||||||
if (event.text === "DELETE") {
|
if (event.key === KEY_P && event.isControl && !event.isAutoRepeat) {
|
||||||
deleteSelectedEntities();
|
|
||||||
} else if (event.text === 'd' && event.isControl) {
|
|
||||||
selectionManager.clearSelections();
|
|
||||||
} else if (event.text === "t") {
|
|
||||||
selectionDisplay.toggleSpaceMode();
|
|
||||||
} else if (event.text === "f") {
|
|
||||||
if (isActive) {
|
|
||||||
if (selectionManager.hasSelection()) {
|
|
||||||
cameraManager.enable();
|
|
||||||
cameraManager.focus(selectionManager.worldPosition,
|
|
||||||
selectionManager.worldDimensions,
|
|
||||||
Menu.isOptionChecked(MENU_EASE_ON_FOCUS));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (event.text === '[') {
|
|
||||||
if (isActive) {
|
|
||||||
cameraManager.enable();
|
|
||||||
}
|
|
||||||
} else if (event.text === 'g') {
|
|
||||||
if (isActive && selectionManager.hasSelection()) {
|
|
||||||
grid.moveToSelection();
|
|
||||||
}
|
|
||||||
} else if (event.key === KEY_P && event.isControl && !event.isAutoRepeat ) {
|
|
||||||
if (event.isShifted) {
|
if (event.isShifted) {
|
||||||
unparentSelectedEntities();
|
unparentSelectedEntities();
|
||||||
} else {
|
} else {
|
||||||
|
@ -1893,6 +1874,45 @@ var keyReleaseEvent = function (event) {
|
||||||
Controller.keyReleaseEvent.connect(keyReleaseEvent);
|
Controller.keyReleaseEvent.connect(keyReleaseEvent);
|
||||||
Controller.keyPressEvent.connect(keyPressEvent);
|
Controller.keyPressEvent.connect(keyPressEvent);
|
||||||
|
|
||||||
|
function deleteKey(value) {
|
||||||
|
if (value === 0) { // on release
|
||||||
|
deleteSelectedEntities();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function deselectKey(value) {
|
||||||
|
if (value === 0) { // on release
|
||||||
|
selectionManager.clearSelections();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function toggleKey(value) {
|
||||||
|
if (value === 0) { // on release
|
||||||
|
selectionDisplay.toggleSpaceMode();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function focusKey(value) {
|
||||||
|
if (value === 0) { // on release
|
||||||
|
cameraManager.enable();
|
||||||
|
if (selectionManager.hasSelection()) {
|
||||||
|
cameraManager.focus(selectionManager.worldPosition, selectionManager.worldDimensions,
|
||||||
|
Menu.isOptionChecked(MENU_EASE_ON_FOCUS));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function gridKey(value) {
|
||||||
|
if (value === 0) { // on release
|
||||||
|
if (selectionManager.hasSelection()) {
|
||||||
|
grid.moveToSelection();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var mapping = Controller.newMapping(CONTROLLER_MAPPING_NAME);
|
||||||
|
mapping.from([Controller.Hardware.Keyboard.Delete]).when([!Controller.Hardware.Application.PlatformMac]).to(deleteKey);
|
||||||
|
mapping.from([Controller.Hardware.Keyboard.Backspace]).when([Controller.Hardware.Application.PlatformMac]).to(deleteKey);
|
||||||
|
mapping.from([Controller.Hardware.Keyboard.D]).when([Controller.Hardware.Keyboard.Control]).to(deselectKey);
|
||||||
|
mapping.from([Controller.Hardware.Keyboard.T]).to(toggleKey);
|
||||||
|
mapping.from([Controller.Hardware.Keyboard.F]).to(focusKey);
|
||||||
|
mapping.from([Controller.Hardware.Keyboard.G]).to(gridKey);
|
||||||
|
|
||||||
function recursiveAdd(newParentID, parentData) {
|
function recursiveAdd(newParentID, parentData) {
|
||||||
if (parentData.children !== undefined) {
|
if (parentData.children !== undefined) {
|
||||||
var children = parentData.children;
|
var children = parentData.children;
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
// Distributed under the Apache License, Version 2.0.
|
// Distributed under the Apache License, Version 2.0.
|
||||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
|
|
||||||
/* global module, Camera, HMD, MyAvatar, controllerDispatcherPlugins:true, Quat, Vec3, Overlays, Xform,
|
/* global module, Camera, HMD, MyAvatar, controllerDispatcherPlugins:true, Quat, Vec3, Overlays, Xform, Mat4,
|
||||||
Selection, Uuid,
|
Selection, Uuid,
|
||||||
MSECS_PER_SEC:true , LEFT_HAND:true, RIGHT_HAND:true, FORBIDDEN_GRAB_TYPES:true,
|
MSECS_PER_SEC:true , LEFT_HAND:true, RIGHT_HAND:true, FORBIDDEN_GRAB_TYPES:true,
|
||||||
HAPTIC_PULSE_STRENGTH:true, HAPTIC_PULSE_DURATION:true, ZERO_VEC:true, ONE_VEC:true,
|
HAPTIC_PULSE_STRENGTH:true, HAPTIC_PULSE_DURATION:true, ZERO_VEC:true, ONE_VEC:true,
|
||||||
|
@ -58,7 +58,8 @@
|
||||||
highlightTargetEntity:true,
|
highlightTargetEntity:true,
|
||||||
clearHighlightedEntities:true,
|
clearHighlightedEntities:true,
|
||||||
unhighlightTargetEntity:true,
|
unhighlightTargetEntity:true,
|
||||||
distanceBetweenEntityLocalPositionAndBoundingBox: true
|
distanceBetweenEntityLocalPositionAndBoundingBox: true,
|
||||||
|
worldPositionToRegistrationFrameMatrix: true
|
||||||
*/
|
*/
|
||||||
|
|
||||||
MSECS_PER_SEC = 1000.0;
|
MSECS_PER_SEC = 1000.0;
|
||||||
|
@ -487,6 +488,30 @@ entityIsFarGrabbedByOther = function(entityID) {
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
worldPositionToRegistrationFrameMatrix = function(wptrProps, pos) {
|
||||||
|
// get world matrix for intersection point
|
||||||
|
var intersectionMat = new Xform({ x: 0, y: 0, z:0, w: 1 }, pos);
|
||||||
|
|
||||||
|
// calculate world matrix for registrationPoint addjusted entity
|
||||||
|
var DEFAULT_REGISTRATION_POINT = { x: 0.5, y: 0.5, z: 0.5 };
|
||||||
|
var regRatio = Vec3.subtract(DEFAULT_REGISTRATION_POINT, wptrProps.registrationPoint);
|
||||||
|
var regOffset = Vec3.multiplyVbyV(regRatio, wptrProps.dimensions);
|
||||||
|
var regOffsetRot = Vec3.multiplyQbyV(wptrProps.rotation, regOffset);
|
||||||
|
var modelMat = new Xform(wptrProps.rotation, Vec3.sum(wptrProps.position, regOffsetRot));
|
||||||
|
|
||||||
|
// get inverse of model matrix
|
||||||
|
var modelMatInv = modelMat.inv();
|
||||||
|
|
||||||
|
// transform world intersection point into object's registrationPoint frame
|
||||||
|
var xformMat = Xform.mul(modelMatInv, intersectionMat);
|
||||||
|
|
||||||
|
// convert to Mat4
|
||||||
|
var offsetMat = Mat4.createFromRotAndTrans(xformMat.rot, xformMat.pos);
|
||||||
|
return offsetMat;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
if (typeof module !== 'undefined') {
|
if (typeof module !== 'undefined') {
|
||||||
module.exports = {
|
module.exports = {
|
||||||
makeDispatcherModuleParameters: makeDispatcherModuleParameters,
|
makeDispatcherModuleParameters: makeDispatcherModuleParameters,
|
||||||
|
@ -508,6 +533,7 @@ if (typeof module !== 'undefined') {
|
||||||
projectOntoEntityXYPlane: projectOntoEntityXYPlane,
|
projectOntoEntityXYPlane: projectOntoEntityXYPlane,
|
||||||
TRIGGER_OFF_VALUE: TRIGGER_OFF_VALUE,
|
TRIGGER_OFF_VALUE: TRIGGER_OFF_VALUE,
|
||||||
TRIGGER_ON_VALUE: TRIGGER_ON_VALUE,
|
TRIGGER_ON_VALUE: TRIGGER_ON_VALUE,
|
||||||
DISPATCHER_HOVERING_LIST: DISPATCHER_HOVERING_LIST
|
DISPATCHER_HOVERING_LIST: DISPATCHER_HOVERING_LIST,
|
||||||
|
worldPositionToRegistrationFrameMatrix: worldPositionToRegistrationFrameMatrix
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -306,7 +306,7 @@
|
||||||
}
|
}
|
||||||
wantsMenu = clicked;
|
wantsMenu = clicked;
|
||||||
});
|
});
|
||||||
|
|
||||||
clickMapping.from(Controller.Standard.Start).peek().to(function (clicked) {
|
clickMapping.from(Controller.Standard.Start).peek().to(function (clicked) {
|
||||||
if (clicked) {
|
if (clicked) {
|
||||||
//activeHudPoint2dGamePad();
|
//activeHudPoint2dGamePad();
|
||||||
|
|
Loading…
Reference in a new issue