Maxon Developers Maxon Developers
    • Documentation
      • Cinema 4D Python API
      • Cinema 4D C++ API
      • Cineware 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

    Convert Barycentric coords to UV coords?

    SDK Help
    0
    5
    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 31/07/2016 at 13:45, xxxxxxxx wrote:

      User Information:
      Cinema 4D Version:   R13 
      Platform:   Windows  ;   
      Language(s) :     C++  ;

      ---------
      Hi,
      I'm trying to convert barycentric coords to UV coords. So that I can then use those UV coords to sample a shader in a material's color channel. But I keep hitting a dead end.
      There's tons of tutorials for this on the internet. But when it comes time to actually convert them to UV coords they either don't show it. Or the code they use doesn't work for me using the C4D SDK.

      Here is my DescriptionToolData plugin code that uses a GeRayCollider ray and gets the barycentric coords from the polygon when I LMB click on it.
      How can I properly convert them into UV coords (0, 1) with (0, 0) at the top left corner?

      TOOLDRAW MyTool::Draw(BaseDocument* doc, BaseContainer& data, BaseDraw* bd, BaseDrawHelp* bh, BaseThread* bt, TOOLDRAWFLAGS flags)  
      {  
        BaseObject *obj = doc->GetActiveObject();  
        if(!obj) return TOOLDRAW_0;      
        
        AutoAlloc<GeRayCollider> rc;                           //Create an instance of the ray collider class  
        if (!rc) return TOOLDRAW_0;  
        rc->Init(obj, TRUE);                                   //Initialize it to work with the active object  
        
        Vector wtail = bd->SW(Vector(mouseX,mouseY,0));        //Get the tail of the ray (a virtual line)  
        Vector whead = bd->SW(Vector(mouseX,mouseY,10000.0));  //Get the head of the ray (a virtual line)  
        Vector otail = (!obj->GetMg()) * wtail;  
        Vector oray = (whead - wtail) ^ (!obj->GetMg());  
        
        rc->Intersect(otail, !oray, 10000.0);                 //Checks to see if the ray(virtual line) running between the tail and the head intersects the object  
        
        GeRayColResult colliderResults;                       //Create a struct to hold the things the ray collides with  
        if (rc->GetNearestIntersection(&colliderResults))     //If an intersection (closest to the start of the ray) is true   
        {  
            faceID = colliderResults.face_id;                 //Gets the polygons Index#  
            hitPos = colliderResults.hitpos;                  //Position of the intersection  
            distance = colliderResults.distance;              //The distance from the camera to the mouse's position on the object(good for checking distances)  
            backface = colliderResults.backface;              //tests if the polygon is facing towards or away from the camera  
            faceNormal = colliderResults.f_normal;  
            LONG triId = colliderResults.tri_face_id;  
        
            bCoords.x = colliderResults.barrycoords.x;  
            bCoords.y = colliderResults.barrycoords.y;  
            bCoords.z = colliderResults.barrycoords.z;          
        
        
            //////// Now I get the shader that's in the materials color channel ///////////  
        
            //Get the shader in the color channel  
            BaseMaterial *mat = doc->GetFirstMaterial();  
            if (!mat) return TOOLDRAW_0;  
            BaseShader *shdr = mat->GetFirstShader();  
            if (!shdr) return TOOLDRAW_0;  
              
            //Render the shader  
            InitRenderStruct irs;  
            shdr->InitRender(irs);  
              
            ChannelData cd;  
            cd.p = Vector(0, 0, 0);  
            cd.n = Vector(0, 0, 1);  
            cd.d = Vector(0, 0, 0);  
            cd.t = doc->GetTime().Get();  
            cd.texflag = 0;  
            cd.vd = NULL;  
            cd.off = 0.0;  
            cd.scale = 0.0;  
        
            ////////// Now I put it all together and try to sample the UV's ///////////  
         
            PolygonObject *pObj = (PolygonObject* )obj;  
            if (!pObj) return TOOLDRAW_0;  
            CPolygon poly = pObj->GetPolygonW()[0];         
              
            Vector pnta = pObj->GetPointW()[poly.a];  
            Vector pntb = pObj->GetPointW()[poly.b];  
            Vector pntc = pObj->GetPointW()[poly.c];  
            Vector pntd = pObj->GetPointW()[poly.d];  
        
        
        
            //Here is where the problems start  
            //I'm trying to convert the barycentric values to UV values (0,1)  
            //With (0,0) being at the top left corner  
        
            //This does not work properly         
            //This will accurately sample the shader's colors if the mouse is clicked in the middle of the polygon  
            //But not if I click the mouse anywhere else on the polygon!!!  
            cd.p = bCoords;                    //Make the UVs match the barycentric coords?  
            Vector color = shdr->Sample(&cd);  //Sample the shader's colors using the barycentric coords  
            GePrint(RealToString(color.x) + "," + RealToString(color.y) + "," + RealToString(color.z));          
        
        
            //This also does not work properly  
            //if triId == (polyIndex+1) then the barrycentric coordinates correspond to a triangle or to the first triangle of a quad polygon  
            //Otherwise it corresponds to the second triangle of a quad polygon  
            Vector UV;  
            if (triId == (faceID + 1)) UV = pnta * (1.0 - bCoords.x - bCoords.y) + pntc*bCoords.x + pntb*bCoords.y;  
            else UV = pnta * (1.0 - bCoords.x - bCoords.y) + pntc*bCoords.x + pntb*bCoords.y;  
            Vector UVn = UV.GetNormalized();  
            GePrint(RealToString(UVn.x) + " , " + RealToString(UVn.y) + " , " + RealToString(UVn.z));  
        
        
            //How the heck do we convert barycentric coords to UV coords?  
         
            shdr->FreeRender();  
        }
      

      -ScottA

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

        On 31/07/2016 at 19:07, xxxxxxxx wrote:

        Hey Scott,

        the barycentric coordinates are in the space of the polygon. You can interpret them as weights for the
        currently rendered point in correlation to the 3 points of the polygon.

        realUv = bCoords.x * uvs[poly.a] + bCoords.y * uvs[poly.b] + bCoords.z * uvs[poly.c]
        

        Cheers,
        Niklas

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

          On 31/07/2016 at 19:54, xxxxxxxx wrote:

          Hi Niklas,
          I don't see any way to do uvz[poly.a] using the SDK. Is that a generic code example from the internet? Or actual C4D SDK syntax?

          Here is how I tried it using the C4D SDK. But it's still not working.
          I inserted this code under my pObj->GetPointW section.
          It is still returning jibberish values. Not (0,1) values starting from the top left corner of the polygon

                  UVWTag *uvTag = (UVWTag* )(obj->GetTag(Tuvw, 0)); //Gets the first UVW tag on the object  
                if (!uvTag)return TOOLDRAW_0;  
            
                UVWStruct myUVs;                                 //Gets the four verts a,b,c,d of the uvPolygon  
                UVWHandle UVpolys = uvTag->GetDataAddressW();    //Gets the vertice data for each uvPolygon          
                uvTag->Get(UVpolys, 0, myUVs);                   //Gets the first polygon in the UVW tag and stores it in the struct          
            
                Vector realUv = bCoords.x * myUVs.a + bCoords.y * myUVs.b + bCoords.z * myUVs.c; //<--- Is this right?  
                GePrint(RealToString(realUv.x) + "," + RealToString(realUv.y) + "," + RealToString(realUv.z)); //<---- Getting jibberish values
          

          BTW:
          I am using a single polygon object oriented in +Z with a gradient shader on it as the test subject.
          Converted it to a polygon, and set the UVW tag to UVW mode.

          -ScottA

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

            On 01/08/2016 at 04:26, xxxxxxxx wrote:

            Hey Scott,

            a formula in a form that you should be more or less familar with. 🙂 The last snippet you posted does look
            pretty good, but for quadrangles you have to check on which side of the polygon the ray hit.

            import c4d
              
            def main() :
                op = doc.SearchObject("op")
                uvwtag = op.GetTag(c4d.Tuvw)
                mg = doc.SearchObject("pos").GetMg()
                
                collider = c4d.utils.GeRayCollider()
                collider.Init(op)
                collider.Intersect(mg.off, mg.v3, 1000)
                count = collider.GetIntersectionCount()
                inter = collider.GetNearestIntersection()
                
                uvws = uvwtag.GetSlow(inter['face_id'])
                bcoords = inter['barrycoords']
                
                # Check if the hit was on the first half of the polygon 
                # (see GeRayCollider docs).
                if inter['tri_face_id'] == inter['face_id'] + 1:
                    realuv = bcoords.x * uvws['a'] + bcoords.y * uvws['b'] + bcoords.z * uvws['c']
                else:
                    realuv = bcoords.x * uvws['a'] + bcoords.y * uvws['c'] + bcoords.z * uvws['d']
              
                print(inter)
                print(uvws)
                print(realuv)
                print("-"*80)
              
            main()
            

            Cheers,
            Niklas

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

              On 01/08/2016 at 08:10, xxxxxxxx wrote:

              Thanks Niklas.
              The tri_face code was what I was having a hard time getting right.

              Here is the working code:

              TOOLDRAW MyTool::Draw(BaseDocument* doc, BaseContainer& data, BaseDraw* bd, BaseDrawHelp* bh, BaseThread* bt, TOOLDRAWFLAGS flags)  
              {  
                //if(lmbDown) GePrint("LMB down");  
                
                
                //To sample the colors on a polygon we need these 4 things  
                // - The polygon  
                // - The shader (applied to the polygon) that we want to sample inside of a material  
                // - The polygon's barycentric coordinates  
                // - The UV coordinates converted from the barycentric coordinates  
                
                
                //First gather the players(the polygon, the UVW tag, the material & the shader in it)  
                
                PolygonObject *pObj = static_cast<PolygonObject*>(doc->GetActiveObject());  
                if(!pObj || !pObj->IsInstanceOf(Opolygon)) return TOOLDRAW_0;  
                
                //Use the first polygon(change as desired)  
                CPolygon poly = pObj->GetPolygonW()[0];  
                Vector pnta = pObj->GetPointW()[poly.a];  
                Vector pntb = pObj->GetPointW()[poly.b];  
                Vector pntc = pObj->GetPointW()[poly.c];  
                Vector pntd = pObj->GetPointW()[poly.d];  
                
                //Gets the first UVW tag on the object  
                UVWTag *uvTag = (UVWTag* )(pObj->GetTag(Tuvw, 0));  
                if (!uvTag)return TOOLDRAW_0;  
                
                //Get the data in the UVW tag  
                UVWStruct myUVs;                                 //Gets the four verts a,b,c,d of the uvPolygon  
                UVWHandle UVpolys = uvTag->GetDataAddressW();    //Gets the vertice data for each uvPolygon          
                uvTag->Get(UVpolys, 0, myUVs);                   //Gets the first polygon in the UVW tag and stores it in the struct   
                
                //Get the shader in the color channel  
                BaseMaterial *mat = doc->GetFirstMaterial();  
                if (!mat) return TOOLDRAW_0;  
                BaseShader *shdr = mat->GetFirstShader();  
                if (!shdr) return TOOLDRAW_0;  
                  
                      
                //Build a ray collider here so we can use it to pick the surface of the polygon with the mouse  
                
                AutoAlloc<GeRayCollider> rc;                           //Create an instance of the ray collider class  
                if (!rc) return TOOLDRAW_0;  
                rc->Init(pObj, TRUE);                                  //Initialize it to work with the active object  
                
                Vector wtail = bd->SW(Vector(mouseX,mouseY,0));        //Get the tail of the ray (a virtual line)  
                Vector whead = bd->SW(Vector(mouseX,mouseY,10000.0));  //Get the head of the ray (a virtual line)  
                Vector otail = (!pObj->GetMg()) * wtail;  
                Vector oray = (whead - wtail) ^ (!pObj->GetMg());  
                
                rc->Intersect(otail, !oray, 10000.0);                 //Checks to see if the ray(virtual line) running between the tail and the head intersects the object  
                
                GeRayColResult colliderResults;                       //Create a struct to hold the things the ray collides with  
                if (rc->GetNearestIntersection(&colliderResults))     //If an intersection (closest to the start of the ray) is true   
                {  
                    //Get the values for the ray  
                    faceID = colliderResults.face_id;                 //Gets the polygons Index#  
                    hitPos = colliderResults.hitpos;                  //Position of the intersection  
                    distance = colliderResults.distance;              //The distance from the camera to the mouse's position on the object(good for checking distances)          
                    backface = colliderResults.backface;              //tests if the polygon is facing towards or away from the camera  
                    faceNormal = colliderResults.f_normal;            //Get the polygon's normal  
                    triId = colliderResults.tri_face_id;              //Get the specific triangle within the quadrangle  
                
                    //Get the barycentric coords where we LMB click on the polygon  
                    bCoords.x = colliderResults.barrycoords.x;  
                    bCoords.y = colliderResults.barrycoords.y;  
                    bCoords.z = colliderResults.barrycoords.z;  
                      
                    //Get the channel data so we use it to sample the shader  
                    ChannelData cd;  
                    cd.p = Vector(0, 0, 0);  
                    cd.n = Vector(0, 0, 1);  
                    cd.d = Vector(0, 0, 0);  
                    cd.t = doc->GetTime().Get();  
                    cd.texflag = 0;  
                    cd.vd = NULL;  
                    cd.off = 0.0;  
                    cd.scale = 0.0;  
                
                    //Render the shader so we can get it's color values  
                    InitRenderStruct irs;  
                    shdr->InitRender(irs);         
                
                    //Convert the barycentric coords to UV coords  
                    Vector baryToUVs = bCoords.x * myUVs.a + bCoords.y * myUVs.b + bCoords.z * myUVs.c;   
                
                    //Use the channel data to sample the shader's colors  
                    cd.p = baryToUVs;    //Make the UVs match the barycentric coords  
                
                    //Check if the coord was on the first half of the polygon or the second half  
                    if (triId == faceID + 1) baryToUVs = bCoords.x * myUVs.a + bCoords.y * myUVs.b + bCoords.z * myUVs.c;  
                    else baryToUVs = bCoords.x * myUVs.a + bCoords.y * myUVs.c + bCoords.z * myUVs.d;           
                
                    Vector color = shdr->Sample(&cd);   //Sample the shader's colors using the barycentric coords  
                    GePrint(RealToString(color.x) + "," + RealToString(color.y) + "," + RealToString(color.z));  //<---Print the surface color  
                 
                    shdr->FreeRender();  //Don't forget to free the memory!!  
                }  
                
                return TOOLDRAW_0;  
              }
              

              *Warning:
              The order of things seems to be very important.
              So if you shuffle this code around differently you could get inconsistent result from it.

              -ScottA

              1 Reply Last reply Reply Quote 0
              • M m_adam referenced this topic on
              • First post
                Last post