[Closed] Challenge: Object under cursor
i recommend you to check this thread http://forums.cgsociety.org/showthread.php?f=98&t=693420&highlight=RayMeshGridIntersect
Thanks a lot, DenisT.
RayMeshGridIntersect seems to return 100% dependable results.
It’s terribly slow, however.
I’m including my code below, for anyone else who has similar issues.
Efforts have been made to speed things up. intersectRayScene is used to get a list of initial nodes (although results are broken, the correct node is always included among the others). Then I check rayMeshGridIntersect against the nodes that result from that. Finally I sort and get the closest node.
There’s a millisecond display in there to show performance. It’s spending more than 200ms on this end, but I don’t see what can be done about it, except:
There is one more optimization I should try… to sort by distance based on the initial intersectRayScene result. This would assume that the function always returns the correct position result for the nodes that are genuinely hit. This would typically reduce the number of nodes that need to be processed by rayMeshGridIntersect, since I can walk through the quicksorted result and use the first node encountered by rayMeshGridIntersect as my final result, instead of testing every single result of intersectRayScene.
(
fn theRayMeshGridIntersect pRay pO =
(
local myRMGI = rayMeshGridIntersect()
myRMGI.initialize 1 -- increasing this just slows things down
myRMGI.addnode pO -- have found no way of dealing with multiple nodes
myRMGI.buildgrid()
myRMGI.intersectray pRay.pos pRay.dir true -- bool = double sided
local hitIndex = myRMGI.getclosesthit()
local hitDist = myRMGI.getHitDist hitIndex -- returns -1 if invalid
myRMGI.free()
hitDist
)
fn theGetObjUnderCursorG =
(
local t = timeStamp()
local ray = mapScreenToWorldRay mouse.pos --[1055,500]
local os = for o in (intersectRayScene ray) where not o[1].isHidden and not o[1].isFrozen collect o[1]
local hits = for o in os where (hitDist = theRayMeshGridIntersect ray o) != -1.0 collect #(o, hitDist)
fn sortDistances n1 n2 = if n1[2] < n2[2] then -1 else if n1[2] > n2[2] then 1 else 0
qsort hits sortDistances -- graceful when hits is empty
for hit in hits do
format "% (%)
" hit[1].name hit[2]
format "%ms
" (timeStamp() - t)
)
theGetObjUnderCursorG()
)
first of all the mxs help doesn’t say true about
Properties
<RayMeshGridIntersect>.nodeList ArrayParameter default: #() – node array; Node_List
Get/set the array of nodes.
NoteDespite the implementation as an array parameter, the RayMeshGridIntersect does NOT actually operate on all objects in the list, but only on the FIRST object in the list. If the intersection against multiple objects is required, a separate RayMeshGridIntersect instance should be created for each one and the intersections have to be processed one after the other within a MAXScript loop.
this slows everything down.
(
delete objects
for k=1 to 1000 do geosphere pos:(random -[100,100,100] [100,100,100]) wirecolor:gray
completeredraw()
)
(
local rm
fn sortByDistance n1 n2 pos:[0,0,0] =
(
local d1 = distance n1[2].pos pos
local d2 = distance n2[2].pos pos
if d1 > d2 then 1 else if d1 < d2 then -1 else 0
)
fn pickingFace msg ir obj faceNum shift ctrl alt =
(
local node, face
case msg of
(
--#mousePoint:
#freeMove:
(
wray = mapScreenToWorldRay mouse.pos
inodes = intersectRayScene wray
inodes = for obj in inodes where not obj[1].ishiddenInVpt collect obj
if inodes.count > 0 do
(
qsort inodes sortByDistance pos:wray.pos
node = inodes[1][1]
rm.free()
rm.Initialize 10
rm.addNode node
rm.buildGrid()
local hits = rm.intersectRay wray.pos wray.dir off
faces = for i=1 to hits collect rm.getHitFace i
if (i = rm.getClosestHit()) != 0 do face = faces[i]
objects.wirecolor = gray
node.wirecolor = orange
--format "stack:% node:% face:%
" inodes.count node.name face
)
)
)
if msg == #mouseAbort then #abort else #continue
)
rm = RayMeshGridIntersect()
with undo off mouseTrack trackCallback:pickingFace
rm.free()
gc light:on
)
mousetrack ? I suggested mousetrack and got the following response !?
Mousetrack doesn’t play well with epoly. I can’t comment on the two others as you don’t mention them by name.
i think he meant that built-in mousetrack intersection feature doesn’t work with anything else than editable mesh
Klvnk: Yes, DenisT only uses mousetrack for demonstration purposes, not for its intersection functionality.
With the optimizations I mentioned above, plus hints from DenisT, speed has improved somewhat.
However, with a epoly modifier like Meshsmooth, polyop.getHiddenFaces returns nothing. This means the artist still isn’t getting expected results.
With the emesh modifier Turbosmooth, meshop.getHiddenFaces works just fine.
Has anyone faced this issue and come across a solution which won’t kill what little is left of performance? Ultimately the goal is to get the faceId of the polygon under the mouse pointer.
you can see the #mousePoint commented in my code. this piece of code was used in one of my tools designed exactly for picking a face id from screen.
The difficulty lies in filtering out hidden faces, though, with polyop.getHiddenFaces seemingly not working for anything other than the base object.
Just a note before I proceed down this path:
I’ve been avoiding mesh snapshot for performance reasons, but this is all so slow now that it seems that I might as well use snapshop and emesh functions. Checking this out currently.
There’s a triggered event (can come at any time and frequency, but will wait until the last op is done + a time delay) which tries to read the matId of whatever the user is pointing at when it happens. This involves ignoring hidden geo.
We do some different stuff logics-wise, but with very similar performance (end of post figures: my performance, yours, dot)
What I’m looking at doing now with regards to hidden faces is to check whether the baseobject has them, and then assume (not cool to assume, but it’s the best solution I’ve found so far) that the end result of the stack has hidden faces as well, make a snapshot of that mesh, use reliable emesh functions on the snapshot, ignore real hidden faces at the top level, delete the snapshot. This is terribly ugly… Ought to be able to make a robust “get whatever the mouse is hovering over” function without all this messiness…
309ms
310ms
"."
308ms
309ms
"."
303ms
309ms
"."
306ms
342ms
"."
305ms
303ms
"."
302ms
355ms
well. i see that you’re missing the point. try to understand what i’m doing in my code and why any snapmest is not critical.
It seems we have nearly identical RayMeshGridIntersect code, and nearly identical performance as well. I appreciate that you want me to figure this out, but as far as I can tell we’re doing the same stuff – how about a hint?
I didn’t get the thing about “screen or monitor”.
if you change point_on_obj function in MOUSTRAK.CPP in the mxsagni project (\maxsdk\samples\maxscript\mxsagni) to the following it works with editable poly just fine.
BOOL
TrackMouseCallBack::point_on_obj(ViewExp *vpt, IPoint2 m, Point3& pt, Point3 &norm)
{
// computes the normal ray at the point of intersection
Ray ray, world_ray;
float at, best_dist = 0.0f;
TimeValue t = MAXScript_time();
Object *obj = NULL;
Matrix3 obtm, iobtm;
Point3 testNorm;
BOOL found_hit = FALSE;
vl->face_num_val = vl->face_bary = &undefined;
hit_node = NULL;
// Calculate a ray from the mouse point
vpt->MapScreenToWorldRay(float(m.x), float(m.y), world_ray);
for( int i=(nodeTab.Count()-1); i>=0; i-- ) {
// Get the object from the node
INode* node = nodeTab[i];
ObjectState os = node->EvalWorldState(t);
obj = os.obj;
// Back transform the ray into object space.
obtm = node->GetObjectTM(t);
iobtm = Inverse(obtm);
ray.p = iobtm * world_ray.p;
ray.dir = VectorTransform(iobtm, world_ray.dir);
// See if we hit the object
if (obj->IsSubClassOf(triObjectClassID))
{
TriObject* tobj = (TriObject*)obj;
DWORD fi;
Point3 bary;
if (tobj->mesh.IntersectRay(ray, at, testNorm, fi, bary) &&
((!found_hit) || (at<=best_dist)) )
{
// Calculate the hit point and transform everything back into world space.
// record the face index and bary coord
best_dist = at;
pt = ray.p + ray.dir * at;
pt = pt * obtm;
norm = Normalize(VectorTransform(obtm, testNorm));
vl->face_num_val = Integer::intern(fi + 1);
vl->face_bary = new Point3Value(bary);
hit_node = node;
found_hit = TRUE;
}
}
else if (obj->IsSubClassOf(polyObjectClassID))
{
PolyObject* pobj = (PolyObject*)obj;
int fi;
//Point3 bary;
Tab<float> bary;
if (pobj->GetMesh().IntersectRay(ray, at, testNorm, fi, bary) && ((!found_hit) || (at<=best_dist)) )
{
// Calculate the hit point and transform everything back into world space.
// record the face index and bary coord
best_dist = at;
pt = ray.p + ray.dir * at;
pt = pt * obtm;
norm = Normalize(VectorTransform(obtm, testNorm));
vl->face_num_val = Integer::intern(fi + 1);
//vl->face_bary = new Point3Value(bary);
hit_node = node;
found_hit = TRUE;
}
}
else if (obj->IntersectRay(t, ray, at, testNorm) &&
((!found_hit) || (at<=best_dist)) )
{
// Calculate the hit point and transform everything back into world space.
best_dist = at;
pt = ray.p + ray.dir * at;
pt = pt * obtm;
norm = Normalize(VectorTransform(obtm, testNorm));
hit_node = node;
found_hit = TRUE;
}
}
if( found_hit ) return TRUE;
// Failed to find a hit on any node, look at the Normal Align Vector for the first node
if ((obj!=NULL) && obj->NormalAlignVector(t, pt, testNorm)) // See if a default NA vector is provided
{
pt = pt * obtm;
norm = Normalize(VectorTransform(obtm, testNorm));
return TRUE;
}
else
return FALSE;
}
obviously you then have to recompile the project and replace MXSAgni.dlx in stdplugs with the newer version.
Thanks a lot Klvnk! I’ll definitely look into that.
I’m using some hack-y workarounds now for performance, but will work at proper solutions.
Here’s something I’m using the code for: