Convert Barycentric coords to UV coords?



  • 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



  • 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



  • 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



  • 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



  • 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


Log in to reply