Finding 3D point under the cursor
-
On 17/03/2013 at 05:48, xxxxxxxx wrote:
User Information:
Cinema 4D Version: 14
Platform: Windows ;
Language(s) : C++ ;---------
Hello;I am looking for the 3D point (vector) of the nearest object under the cursor in a viewport.
I could use a ViewportSelect, or maybe a GeRayCollider. However, both seem to have a horrible overhead with objects passed and initialized and copied... returning arrays of collision points, and so on, and so on. I am looking for something lightweight.
Now the C4D system is doing something similar when the navigation mode is "Cursor" (WPREF_CAMERAROTATION_CURSOR) : at the start of a movement, the POI is determined by checking the object under the cursor. This needs to be done very often and very fast, so I believe there is a lightweight check behind it, or at least a pre-cached something.
How is this done?
Also, if you are not directly responding to a plugin message that passes the mouse coordinates, how do you get the actual mouse position in the screen or preferably viewport coordinates? All methods that return the "cursor info" are part of a special plugin class; I don't see a method in the BaseView or general classes.
Thanks in advance...
-
On 18/03/2013 at 12:01, xxxxxxxx wrote:
Hi,
If you just need to select an object in a viewport you should consider using a PickSessionDataStruct.
And yes, it's only possible to get mouse coordinates from within a plugin mouse proc. -
On 19/03/2013 at 08:25, xxxxxxxx wrote:
Interesting, but PickSessionDataStruct returns an object list, not the actual point of impact.
What I am trying to do:
I have a plugin for the Space Navigator that replicates the mouse navigation behavior. Now if the user is in "Cursor mode" (C4D navigation mode) and starts to turn the knob, the plugin wants to look at the point the mouse is currently pointing at, and use that as the pivot of the rotation - exactly the same as the mouse navigation does under the same circumstances. (Since the Space Navigator has no pointer on the screen, I am recycling the mouse pointer, although it's not perfectly logical.)
So, the method I am looking for is "what is the first hit 3D point in the scene under the mouse pointer".
(If there is no such method, I can live without it. But since the mouse navigation does the very same thing, there should.)
-
On 19/03/2013 at 20:40, xxxxxxxx wrote:
Your best bet is to use a GeRayCollider after you find the 'nearest' object with the PickSessionDataStruct.
For instance, what I do for letting a user pick a point on an object as a starting for growing ivy (as a tool plugin) is:
// ToolData.MouseInput //*---------------------------------------------------------------------------* Bool IvyTool::MouseInput(BaseDocument* doc, BaseContainer& data, BaseDraw* bd, EditorWindow* win, const BaseContainer& msg) //*---------------------------------------------------------------------------* { obj = NULL; document = NULL; if (!doc) return FALSE; if (msg.GetLong(BFM_INPUT_CHANNEL) != BFM_INPUT_MOUSELEFT) return TRUE; if (!msg.GetBool(BFM_INPUT_DOUBLECLICK)) return TRUE; // Get Mouse coordinates if (!(bd && win)) return FALSE; mouseX = msg.GetReal(BFM_INPUT_X); mouseY = msg.GetReal(BFM_INPUT_Y); // - Mouse outside of Editor view if (mouseX < 0.0) return TRUE; // Get selected object (if any) ivy.seeded = FALSE; ivy.grown = FALSE; ivy.newborn = FALSE; obj = doc->GetActiveObject(); if (!obj) return TRUE; // - Polygonize and triangulate PolygonObject::Free(pobj); pobj = PolygonizeObject(doc); if (!pobj) { GePrint("IvyTool.MouseInput.pobj"); return TRUE; } // Get 3D coordinates on Object for grow point // - Is cursor over the visible portion of the object if (!rayC->Init(pobj, TRUE)) { GePrint("IvyTool.MouseInput.rayC->Init()"); DeletePolyObject(); return TRUE; } //Matrix m; Vector wtail = bd->SW(Vector(mouseX,mouseY,IVYCZ_START)); Vector otail = wtail; //m * wtail; Vector oray = (bd->SW(Vector(mouseX,mouseY,IVYCZ_END)) - wtail); // ^ m; if (!rayC->Intersect(otail, !oray, IVYCZ_LEN)) { GePrint("IvyTool.MouseInput.rayC->Intersect()"); DeletePolyObject(); return TRUE; } // - Compare the hits for visibility and nearness GeRayColResult res; if (!rayC->GetNearestIntersection(&res)) { GePrint("IvyTool.MouseInput.rayC->GetNearestIntersection()"); DeletePolyObject(); return TRUE; } // - Remove polygon object from document pobj->Remove(); // Enable Growing Process ivy.seed(res.hitpos); document = doc; return TRUE; }
-
On 20/03/2013 at 10:18, xxxxxxxx wrote:
Maybe I'm not understanding the desired result. But doesn't the highlighting effect do this kind of thing?
Where your mouse highlights a point when the cursor gets close enough to it?I have a C++ plugin called PolygonTool that highlights and selects polygons when the mouse cursor gets close to them using the GetNearestPolygon() method.
But the code can be changed to GetNearestPoint() to work with points instead.https://sites.google.com/site/scottayersmedia/plugins
If I change the code to this.
It prints the point id# the mouse is hovering over (based the the range option) on the selected object. Seems pretty fast too:if (mode == Mpoints) { highlight = TRUE; ViewportPixel *vpp = NULL; LONG mx = x; LONG my = y; vpp = vs->GetNearestPoint(op, mx, my, 10, FALSE, NULL, 0); if (vpp) { pointid = vpp->i; GePrint("You selected point# " + LongToString(pointid)); DrawViews(DRAWFLAGS_ONLY_ACTIVE_VIEW|DRAWFLAGS_NO_THREAD|DRAWFLAGS_NO_ANIMATION, bd); } else pointid = -1; }
-ScottA
-
On 26/03/2013 at 03:55, xxxxxxxx wrote:
Thanks a lot... I believe that for a single pick a GeRayCollider may be best indeed.
I don't know whether I could get the necessary response times out of the system though if I use the feature in a Space Navigator controller. I guess I will just drop this aspect; the Navigator doesn't have a mouse pointer anyway...
-
On 26/03/2013 at 11:33, xxxxxxxx wrote:
Regarding using GeRayCollider in exactly the context you are talking about.
I'm also looking at doing a ray collide on clicking in the view and I have it working in the same fashion as Robert has posted.
The ViewportSelect::PickObject call nicely gets the object under the cursor ... but ...
-
If that object is not polygonal, how to you change it to being in the correct format? I see you can convert an entire document (Polygonize) and walk the doc to locate the object by name, but that seems crazy! In Roberts example it's tucked away inside the PolygonizeObject call. What are you doing in there Robert?
-
The Init call for the ray collider can be quite slow. Is this the only way to do it?
If the user is clicking around a bit, I'd like to at least cache some of the data sets to speed things up. Maybe on starting the tool I just polygonize the entire document, init the ray collider, then just have the hit for the intersect test on clicking. This would give a massive hit at the start whilst we set up a "global" ray collider system, and a bit like taking a sledgehammer to an egg, but hopefully the users experience would be a lot smoother. Otherwise I fear that the repeated hit of polygonizing the document (or object if that's doable) and initialising the ray collider would make for a very sluggish experience
-Simon
-
-
On 26/03/2013 at 12:31, xxxxxxxx wrote:
const Matrix unitMatrix; // CollisionDeformerObj.GetPolygonObject //*---------------------------------------------------------------------------* PolygonObject* CollisionDeformerObj::GetPolygonObject(BaseObject* op) //*---------------------------------------------------------------------------* { // Clone object and put into temporary document BaseObject* clop = static_cast<BaseObject*>(op->GetClone(COPYFLAGS_NO_ANIMATION, NULL)); if (!clop) return (PolygonObject* )MessageSystem::NullThrow(GeLoadString(KDZERR_GENERAL), "CollisionDeformerObj.GetPolygonObject.cop"); fakeDoc->InsertObject(clop, NULL, NULL, FALSE); // Current State to Object mcd.op = clop; Bool smc = SendModelingCommand(MCOMMAND_CURRENTSTATETOOBJECT, mcd) && mcd.result; // - Remove and Delete clone clop->Remove(); BaseObject::Free(clop); if (!smc) return (PolygonObject* )MessageSystem::NullThrow(GeLoadString(KDZERR_GENERAL), "CollisionDeformerObj.GetPolygonObject.SendModelingCommand(CURRENTSTATETOOBJECT)"); // Get result BaseObject* cstoObj = static_cast<BaseObject*>(mcd.result->GetIndex(0L)); AtomArray::Free(mcd.result); if (!cstoObj) return (PolygonObject* )MessageSystem::NullThrow(GeLoadString(KDZERR_GENERAL), "CollisionDeformerObj.GetPolygonObject.cstoObj(CSTO)"); // Select Children cstoObj->SetBit(BIT_ACTIVE); SelectChildren(cstoObj); // Connect object (trick: result is always in global space) mcd.op = cstoObj; smc = SendModelingCommand(MCOMMAND_JOIN, mcd) && mcd.result; // - Free object created by CurrentStateToObject BaseObject::Free(cstoObj); if (!smc) return (PolygonObject* )MessageSystem::NullThrow(GeLoadString(KDZERR_GENERAL), "CollisionDeformerObj.GetPolygonObject.SendModelingCommand(JOIN)"); cstoObj = static_cast<BaseObject*>(mcd.result->GetIndex(0L)); AtomArray::Free(mcd.result); if (!cstoObj) return (PolygonObject* )MessageSystem::NullThrow(GeLoadString(KDZERR_GENERAL), "CollisionDeformerObj.GetPolygonObject.cstoObj(JOIN)"); // Triangulate mcd.op = cstoObj; if (!SendModelingCommand(MCOMMAND_TRIANGULATE, mcd)) { BaseObject::Free(cstoObj); return (PolygonObject* )MessageSystem::NullThrow(GeLoadString(KDZERR_GENERAL), "CollisionDeformerObj.GetPolygonObject.SendModelingCommand(TRIANGULATE)"); } // Finish up fakeDoc->InsertObject(cstoObj, NULL, NULL, FALSE); cstoObj->SetMg(unitMatrix); cstoObj->Remove(); return ToPoly(cstoObj); } // Select Children under op - recursive //*---------------------------------------------------------------------------* void CollisionDeformerObj::SelectChildren(BaseObject* op) //*---------------------------------------------------------------------------* { for (; op; op = op->GetNext()) { op->SetBit(BIT_ACTIVE); if (op->GetDown()) SelectChildren(op->GetDown()); } }
-
On 27/03/2013 at 13:48, xxxxxxxx wrote:
Thanks Robert! I'm not sure that I would have worked out the intricacies of that one by myself.
Oh, I assumed that the fake doc and ModelingCommandData structures were just members of your class so I just autoalloc-ed the fakedoc and had an instance of the mcd.Do you know under what circumstances you need to re-initialise the ray collider? I suspect that even if the object is just moved/rotated globally (ie, in its local space nothing has changed) then you would still have to re-initialise it.
I'm just wondering if I can "know" that I need to re-initialise the collider or not to speed things up for the end user if they are just clicking around without actually touching the model or camera, or if I have to just accept the lag and be done with it
Thanks again for your help Robert - I really appreciate it
-Simon
-
On 27/03/2013 at 14:23, xxxxxxxx wrote:
As far as I know, you should only need to re-initialize the GeRayCollider if data on the collider object changes - obj->IsDirty(DIRTYFLAGS_DATA). Transformations will not change the mesh relationships (vertices) locally.
-
On 28/03/2013 at 09:01, xxxxxxxx wrote:
OK, I'm not getting quite what I expect here.
- If I create an axis-aligned cube (great for testing normals) then is worked fine.
2a) If I copy/paste this cube, then move it along the X axis, your code (Robert) fails to find any intersections.
2b) If I use a brute force "Polygonize doc", then search for the object by name in this new doc, and push that in GeRayCollider Init, it does return an intersection. (I'm using 5000 units on my ray and ray distances if that matters)
But ... if I then rotate the cube around the X-axis, then use (2b) above, the normal is reported in what I assume is object space. ie, if I rotate by 45 degrees around the X-Axis, I still get a normal along the X-axis on the originally facing x-face.
It does not explicitly say the results are in object space so I'm wondering what is going on. (The hit point in the GeRayColResult also seems to be in object space too).
If it is in object space, I guess I need to work out how to transform that back into world space. Is there a simple call to do this that takes any hierarchy of transformation matrices into account etc?
-Simon
- If I create an axis-aligned cube (great for testing normals) then is worked fine.