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 polygonUVWTag *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
-