Notifications
Clear all

[Closed] Challenge: Object under cursor

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.

1 Reply
(@denist)
Joined: 11 months ago

Posts: 0

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.

3 Replies
(@denist)
Joined: 11 months ago

Posts: 0

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.

(@per128)
Joined: 11 months ago

Posts: 0

The difficulty lies in filtering out hidden faces, though, with polyop.getHiddenFaces seemingly not working for anything other than the base object.

(@denist)
Joined: 11 months ago

Posts: 0

BTW… do you see a big difference of your and my code performance?

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.

2 Replies
(@denist)
Joined: 11 months ago

Posts: 0

what do you really want? pick a screen or monitor?

(@per128)
Joined: 11 months ago

Posts: 0

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.

1 Reply
(@per128)
Joined: 11 months ago

Posts: 0

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:

//youtu.be/bniCvySbPS0

very cool !

Page 2 / 2