Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MAYA-106477 selection and point snapping support for mtoh #776

Merged
merged 6 commits into from
Sep 30, 2020
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
184 changes: 183 additions & 1 deletion lib/mayaUsd/render/mayaToHydra/renderOverride.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
#include <exception>

#include <maya/M3dView.h>
#include <maya/MDagPath.h>
#include <maya/MDrawContext.h>
#include <maya/MEventMessage.h>
#include <maya/MGlobal.h>
Expand All @@ -36,6 +35,7 @@
#include <pxr/base/vt/value.h>
#include <pxr/imaging/glf/contextCaps.h>
#include <pxr/imaging/hd/rprim.h>
#include <pxr/imaging/hdx/pickTask.h>

#include <hdMaya/delegates/delegateRegistry.h>
#include <hdMaya/delegates/sceneDelegate.h>
Expand All @@ -51,6 +51,10 @@
#include <pxr/imaging/hdx/tokens.h>
#include <pxr/imaging/hdx/renderTask.h>

#include <boost/functional/hash.hpp>

#include <limits>

#if USD_VERSION_NUM > 2002
#include <pxr/imaging/hgi/hgi.h>
#include <pxr/imaging/hgi/tokens.h>
Expand Down Expand Up @@ -106,6 +110,80 @@ class UfeSelectionObserver : public UFE_NS::Observer {

#endif // WANT_UFE_BUILD

#if MAYA_API_VERSION >= 20210000

//! \brief Get the index of the hit nearest to a given cursor point.
int GetNearestHitIndex(
const MHWRender::MFrameContext& frameContext,
const HdxPickHitVector& hits,
int cursor_x,
int cursor_y)
{
int nearestHitIndex = -1;

double dist2_min = std::numeric_limits<double>::max();
float depth_min = std::numeric_limits<float>::max();

for (unsigned int i = 0; i < hits.size(); i++) {
const HdxPickHit& hit = hits[i];
const MPoint worldSpaceHitPoint(
hit.worldSpaceHitPoint[0], hit.worldSpaceHitPoint[1], hit.worldSpaceHitPoint[2]);

// Calculate the (x, y) coordinate relative to the lower left corner of the viewport.
double hit_x, hit_y;
frameContext.worldToViewport(worldSpaceHitPoint, hit_x, hit_y);

// Calculate the 2D distance between the hit and the cursor
double dist_x = hit_x - (double)cursor_x;
double dist_y = hit_y - (double)cursor_y;
double dist2 = dist_x * dist_x + dist_y * dist_y;

// Find the hit nearest to the cursor.
if ((dist2 < dist2_min) || (dist2 == dist2_min && hit.normalizedDepth < depth_min)) {
dist2_min = dist2;
depth_min = hit.normalizedDepth;
nearestHitIndex = (int)i;
}
}

return nearestHitIndex;
}

//! \brief workaround to remove duplicate hits and improve selection performance.
void ResolveUniqueHits_Workaround(const HdxPickHitVector& inHits, HdxPickHitVector& outHits)
{
outHits.clear();

// hash -> hitIndex
std::unordered_map<size_t, size_t> hitIndices;

size_t previousHash = 0;

for (size_t i = 0; i < inHits.size(); i++) {
const HdxPickHit& hit = inHits[i];

size_t hash = 0;
boost::hash_combine(hash, hit.delegateId.GetHash());
boost::hash_combine(hash, hit.objectId.GetHash());
boost::hash_combine(hash, hit.instancerId.GetHash());
boost::hash_combine(hash, hit.instanceIndex);

// As an optimization, keep track of the previous hash value and
// reject indices that match it without performing a map lookup.
// Adjacent indices are likely enough to have the same prim,
// instance and element ids that this can be a significant
// improvement.
if (hitIndices.empty() || hash != previousHash) {
if (hitIndices.insert(std::make_pair(hash, i)).second) {
outHits.push_back(inHits[i]);
}
previousHash = hash;
}
}
}

#endif

} // namespace

MtohRenderOverride::MtohRenderOverride(const MtohRendererDescription& desc)
Expand Down Expand Up @@ -770,6 +848,110 @@ bool MtohRenderOverride::nextRenderOperation() {
return ++_currentOperation < static_cast<int>(_operations.size());
}

#if MAYA_API_VERSION >= 20210000
bool MtohRenderOverride::select(
const MHWRender::MFrameContext& frameContext,
const MHWRender::MSelectionInfo& selectInfo,
bool /*useDepth*/,
MSelectionList& selectionList,
MPointArray& worldSpaceHitPts)
{
MStatus status = MStatus::kFailure;

MMatrix viewMatrix = frameContext.getMatrix(MHWRender::MFrameContext::kViewMtx, &status);
if (status != MStatus::kSuccess)
return false;

MMatrix projMatrix = frameContext.getMatrix(MHWRender::MFrameContext::kProjectionMtx, &status);
if (status != MStatus::kSuccess)
return false;

int view_x, view_y, view_w, view_h;
status = frameContext.getViewportDimensions(view_x, view_y, view_w, view_h);
if (status != MStatus::kSuccess)
return false;

unsigned int sel_x, sel_y, sel_w, sel_h;
status = selectInfo.selectRect(sel_x, sel_y, sel_w, sel_h);
if (status != MStatus::kSuccess)
return false;

// Compute a pick matrix that, when it is post-multiplied with the projection matrix, will
// cause the picking region to fill the entire/ viewport for OpenGL selection.
{
MMatrix pickMatrix;
pickMatrix[0][0] = view_w / double(sel_w);
pickMatrix[1][1] = view_h / double(sel_h);
pickMatrix[3][0] = (view_w - (double)(sel_x * 2 + sel_w)) / double(sel_w);
pickMatrix[3][1] = (view_h - (double)(sel_y * 2 + sel_h)) / double(sel_h);

projMatrix *= pickMatrix;
}

const bool pointSnappingActive = selectInfo.pointSnapping();

// Set up picking params.
HdxPickTaskContextParams pickParams;
pickParams.resolution.Set(view_w, view_h);
pickParams.viewMatrix.Set(viewMatrix.matrix);
pickParams.projectionMatrix.Set(projMatrix.matrix);
pickParams.resolveMode = HdxPickTokens->resolveUnique;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we probably want to vary this depending on what selectInfo.singleSelection and selectInfo.selectClosest return. resolveUnique makes sense for a marquee-selection scenario (within the limits under discussion here), but probably doesn't for single select.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe, but I haven't seen issue for single selection when testing the code. With the code merged, I am sure there are still some improvements in the plugin side as we iterate over time. So maybe something for future?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, like I said, this falls more under the class of an optimization. It's a pretty easy check to make, and you'd then be able to skip the ResolveUniqueHits_Workaround if you were doing resolveNearestToCenter or resolveNearestToCamera... but you're likely also right, in that it may not be noticeable for single select, since the pick region is generally going to be pretty small. So your call.


if (pointSnappingActive) {
pickParams.pickTarget = HdxPickTokens->pickPoints;

// Exclude selected Rprims to avoid self-snapping issue.
pickParams.collection = _pointSnappingCollection;
pickParams.collection.SetExcludePaths(_selectionCollection.GetRootPaths());
} else {
pickParams.collection = _renderCollection;
}

HdxPickHitVector outHits;
pickParams.outHits = &outHits;

// Execute picking tasks.
HdTaskSharedPtrVector pickingTasks = _taskController->GetPickingTasks();
_engine.SetTaskContextData(HdxPickTokens->pickParams, VtValue(pickParams));
_engine.Execute(_taskController->GetRenderIndex(), &pickingTasks);

if (pointSnappingActive) {
// Find the hit nearest to the cursor point and use it for point snapping.
int nearestHitIndex = -1;
int cursor_x, cursor_y;
if (selectInfo.cursorPoint(cursor_x, cursor_y)) {
nearestHitIndex = GetNearestHitIndex(frameContext, outHits, cursor_x, cursor_y);
}

if (nearestHitIndex >= 0) {
const HdxPickHit hit = outHits[nearestHitIndex];
outHits.clear();
outHits.push_back(hit);
} else {
outHits.clear();
}
} else {
// Multiple hits can be produced for a single object on marquee selection even pickTarget
// is the default "pickPrimsAndInstances" mode, and each hit is created for an "element"
// which I guess means a face id and should only be required when pickTarget is "pickFaces".
// I would expect only one hit to be created for object-level selection. Having duplicated
// hits for the same object would slow down selection performance, esp. for dense mesh.
// Some more details can be found: https://groups.google.com/g/usd-interest/c/Cgosy3r7Vv4
HdxPickHitVector uniqueHits;
ResolveUniqueHits_Workaround(outHits, uniqueHits);
outHits.swap(uniqueHits);
}

if (!outHits.empty()) {
for (auto& it : _delegates) {
it->PopulateSelectionList(outHits, selectInfo, selectionList, worldSpaceHitPts);
}
}

return true;
}
#endif

void MtohRenderOverride::_ClearHydraCallback(void* data) {
auto* instance = reinterpret_cast<MtohRenderOverride*>(data);
if (!TF_VERIFY(instance)) { return; }
Expand Down
14 changes: 14 additions & 0 deletions lib/mayaUsd/render/mayaToHydra/renderOverride.h
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,15 @@ class MtohRenderOverride : public MHWRender::MRenderOverride {
MHWRender::MRenderOperation* renderOperation() override;
bool nextRenderOperation() override;

#if MAYA_API_VERSION >= 20210000
bool select(
const MHWRender::MFrameContext& frameContext,
const MHWRender::MSelectionInfo& selectInfo,
bool useDepth,
MSelectionList& selectionList,
MPointArray& worldSpaceHitPts) override;
#endif

private:
typedef std::pair<MString, MCallbackIdArray> PanelCallbacks;
typedef std::vector<PanelCallbacks> PanelCallbacksList;
Expand Down Expand Up @@ -175,6 +184,11 @@ class MtohRenderOverride : public MHWRender::MRenderOverride {
HdRprimCollection _selectionCollection{
HdReprTokens->wire, HdReprSelector(HdReprTokens->wire)
};
HdRprimCollection _pointSnappingCollection{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be worth also hiding this one behind a 20210000 version guard, since it's only used there.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point.

HdTokens->geometry,
HdReprSelector(HdReprTokens->refined, TfToken(), HdReprTokens->points),
SdfPath::AbsoluteRootPath()
};
GlfSimpleLight _defaultLight;

std::vector<HdMayaDelegatePtr> _delegates;
Expand Down
10 changes: 10 additions & 0 deletions lib/usd/hdMaya/adapters/proxyAdapter.h
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,16 @@ class HdMayaProxyAdapter : public HdMayaShapeAdapter, public TfWeakBase {
return _usdDelegate->ConvertCachePathToIndexPath(cachePath);
}

bool PopulateSelection(
HdSelection::HighlightMode const& highlightMode,
const SdfPath& usdPath,
int instanceIndex,
HdSelectionSharedPtr const& result) {
return _usdDelegate->PopulateSelection(highlightMode, usdPath, instanceIndex, result);
}

const SdfPath& GetUsdDelegateID() const { return _usdDelegate->GetDelegateID(); }

private:
/// Notice listener method for proxy stage set
void _OnStageSet(const MayaUsdProxyStageSetNotice& notice);
Expand Down
14 changes: 13 additions & 1 deletion lib/usd/hdMaya/delegates/delegate.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,17 @@

#include <memory>

#include <maya/MDagPath.h>
#include <maya/MDrawContext.h>
#include <maya/MPointArray.h>
#include <maya/MSelectionContext.h>
#include <maya/MSelectionList.h>

#include <pxr/pxr.h>
#include <pxr/imaging/glf/glew.h>
#include <pxr/imaging/hd/engine.h>
#include <pxr/imaging/hd/renderIndex.h>
#include <pxr/imaging/hd/selection.h>
#include <pxr/imaging/hdx/pickTask.h>
#include <pxr/imaging/hdx/taskController.h>
#include <pxr/usd/sdf/path.h>

Expand Down Expand Up @@ -95,6 +97,16 @@ class HdMayaDelegate {
virtual bool SupportsUfeSelection() { return false; }
#endif // WANT_UFE_BUILD

#if MAYA_API_VERSION >= 20210000
virtual void PopulateSelectionList(
const HdxPickHitVector& hits,
const MHWRender::MSelectionInfo& selectInfo,
MSelectionList& mayaSelection,
MPointArray& worldSpaceHitPts)
{
}
#endif

void SetLightsEnabled(const bool enabled) { _lightsEnabled = enabled; }
bool GetLightsEnabled() { return _lightsEnabled; }

Expand Down
Loading