Maxon Developers Maxon Developers
    • Documentation
      • Cinema 4D Python API
      • Cinema 4D C++ API
      • Cineware API
      • ZBrush Python API
      • ZBrush GoZ API
      • Code Examples on Github
    • Forum
    • Downloads
    • Support
      • Support Procedures
      • Registered Developer Program
      • Plugin IDs
      • Contact Us
    • Categories
      • Overview
      • News & Information
      • Cinema 4D SDK Support
      • Cineware SDK Support
      • ZBrush 4D SDK Support
      • Bugs
      • General Talk
    • Unread
    • Recent
    • Tags
    • Users
    • Login

    Finding 3D point under the cursor

    SDK Help
    0
    11
    1.1k
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • H
      Helper
      last edited by

      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.

      1 Reply Last reply Reply Quote 0
      • H
        Helper
        last edited by

        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.)

        1 Reply Last reply Reply Quote 0
        • H
          Helper
          last edited by

          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;  
          }
          
          1 Reply Last reply Reply Quote 0
          • H
            Helper
            last edited by

            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

            1 Reply Last reply Reply Quote 0
            • H
              Helper
              last edited by

              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...

              1 Reply Last reply Reply Quote 0
              • H
                Helper
                last edited by

                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 ...

                1. 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?

                2. 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

                1 Reply Last reply Reply Quote 0
                • H
                  Helper
                  last edited by

                  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());  
                   }  
                  }
                  
                  1 Reply Last reply Reply Quote 0
                  • H
                    Helper
                    last edited by

                    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

                    1 Reply Last reply Reply Quote 0
                    • H
                      Helper
                      last edited by

                      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.

                      1 Reply Last reply Reply Quote 0
                      • H
                        Helper
                        last edited by

                        On 28/03/2013 at 09:01, xxxxxxxx wrote:

                        OK, I'm not getting quite what I expect here.

                        1. 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

                        1 Reply Last reply Reply Quote 0
                        • First post
                          Last post