CalcFaceNormal() Creates Strange Results
-
THE POST BELOW IS MORE THAN 5 YEARS OLD. RELATED SUPPORT INFORMATION MIGHT BE OUTDATED OR DEPRECATED
On 09/07/2012 at 12:53, xxxxxxxx wrote:
User Information:
Cinema 4D Version: 12
Platform: Windows ;
Language(s) : C++ ;---------
Can someone please explain what the CalcFaceNormal() function is returning?
I thought that it was for returning the direction a polygon's normal is facing. But I'm getting strange results from it.
Maybe I'm using it wrong?The code
BaseDocument *doc = GetActiveDocument(); BaseObject *obj = doc->GetActiveObject(); PolygonObject *pobj = ToPoly(obj); LONG polycount = pobj->GetPolygonCount(); LONG pointcount = pobj->GetPointCount(); const CPolygon *vadr = pobj->GetPolygonW(); Vector *points = pobj->GetPointW(); for(LONG i=0; i<polycount; i++) { Vector face = CalcFaceNormal(&points[i], vadr[i]); GePrint(LongToString(face.x) + LongToString(face.y) + LongToString(face.z)); }
On a single polygon I get the expected results of:
0,1,0 <------- for a forward facing polygon face
0,-1,0 <--------When that same face is reversedBut then things go wacky when I use this same code on a cube object:
00-1
000
000
000
000
000When I reverse any of the polygons in the cube. I still get this same result of numbers.
This is not what I expected to see from this function.
I thought I could use it to tell if there is any reversed normals in a mesh. But the results from this function are very strange to me.-ScottA
-
THE POST BELOW IS MORE THAN 5 YEARS OLD. RELATED SUPPORT INFORMATION MIGHT BE OUTDATED OR DEPRECATED
On 09/07/2012 at 13:47, xxxxxxxx wrote:
Doh!
Never mind. I think I found my mistake.I just had to change the code.
From this:
Vector face = CalcFaceNormal(&points _, vadr _);to this:
Vector face = CalcFaceNormal(points, vadr _);-ScottA
-
THE POST BELOW IS MORE THAN 5 YEARS OLD. RELATED SUPPORT INFORMATION MIGHT BE OUTDATED OR DEPRECATED
On 10/07/2012 at 06:43, xxxxxxxx wrote:
In the spirit of calling things what they are, I'd probably write it a little clearer (for any future reference)...
for(LONG i=0; i<polycount; i++) { CPolygon face = vadr[i]; Vector faceNorm = CalcFaceNormal(points, face); GePrint(LongToString(faceNorm.x) + LongToString(faceNorm.y) + LongToString(faceNorm.z)); // or, to see the actual values... // GePrint(RealToString(faceNorm.x)+" "+RealToString(faceNorm.y)+" "+RealToString(faceNorm.z)); }
-
THE POST BELOW IS MORE THAN 5 YEARS OLD. RELATED SUPPORT INFORMATION MIGHT BE OUTDATED OR DEPRECATED
On 10/07/2012 at 08:04, xxxxxxxx wrote:
Thanks for the advice Giblet.
Now that it's working. I see that this function does something different than I thought.
I was trying to use it to check the mesh for inverted normals. But now I'm thinking that this might not be the best thing to use for that.CalcFaceNormal() seems to provide the direction(not just 0, 1, or -1) a polygon normal is pointing. Relative to the axis of the object.
In other words, It tells what direction the polygon is pointing in local coords. Not it's forward or reversed state.So I think I have to look elsewhere for checking for inverted polygons.
Possibly the cross product?-ScottA
-
THE POST BELOW IS MORE THAN 5 YEARS OLD. RELATED SUPPORT INFORMATION MIGHT BE OUTDATED OR DEPRECATED
On 10/07/2012 at 08:48, xxxxxxxx wrote:
Correct - the face normal (computed by CalcFaceNormal()) will indeed point in the direction that the polygon is facing, relative to the axis of the object.
Normals are computed using a cross product, so that alone won't give you what you're after... I'm not sure what you're trying to (ultimately) do, so it's hard to say what the best approach might be, but you might have to compare 'winding-orders' of faces with the winding order of the neighboring faces, and so on.
In other words (numbers in parens are the point-indices)...
a(0)----b(1)
| |
d(3)----c(2)...if you have a polygon as above with a = 0, b = 1, c = 2, d = 3 and then the neighboring polygon on the b->c (to the right) side was...
a(1)----b(4)
| |
d(2)----c(5)...then those polygons would face the same (or at least not inverted) directions because they share a common (clockwise) winding-order, but if you found a a polygon like so:
a(1)----d(4)
| |
b(2)----c(5)...then the winding-order is reversed (counter-clockwise), so it's inverted from the first one.
The code for the above can get a little tricky, because the first point (a) of any given polygon could be on any corner. Also, for each edge... a->b, b->c, c->d, d->a ...you need to reverse the sequence when looking for that sequence on neighboring polys. Using our example above, when looking for the b->c edge point indices ( 1 followed by 2 ), you'd need to reverse that and look for an edge of 2 followed by 1 (and you'd find the d->a edge in the second image, above).
So...
- start with a face.
- using the Neighbor class, call GetPolyInfo() and loop through the 4 sides to get a neighbor poly for each side.
- for side 0 (a->b), set up 2 variable that reverse that sequence (b->a), then loop through each side of the neighbor poly looking for that (b->a indices) sequence - if you don't see it, then the winding-orders are not the same, so that neighbor poly's normal is inverted from the first poly.
- repeat the above step for the other 3 sides (b->c, c->d, d->a).
- repeat GetPolyInfo() step for the rest of the polygons.
...I'm basically doing something like this with my vertex-reordering plugin mentioned in that other thread a few days ago (as far as identifying/validating the neighbor polygons - I'm not looking at winding-orders).
If you find an inverted polygon, you could go ahead and reverse the winding order right then, if that's your intention (a, b, c, d effectively becomes d, c, b, a, or even a, d, c, b).
-
THE POST BELOW IS MORE THAN 5 YEARS OLD. RELATED SUPPORT INFORMATION MIGHT BE OUTDATED OR DEPRECATED
On 10/07/2012 at 09:02, xxxxxxxx wrote:
That sounds like what I'll need to do.
I'm just trying to iterate through all the polygons and fix(point forwards) any polygons that are reversed.This is probably above my skill level. I'm really bad at handling polygons. But I'll give it try.
You can't learn if you don't try. Right?-ScottA
-
THE POST BELOW IS MORE THAN 5 YEARS OLD. RELATED SUPPORT INFORMATION MIGHT BE OUTDATED OR DEPRECATED
On 10/07/2012 at 09:09, xxxxxxxx wrote:
Right :). Or.. you could just call the C4D command that does it all for you ("Align Normals"- you'd have to look up how to call that command - EDIT: look into doing a SendModelingCommand(MCOMMAND_ALIGNNORMALS, cd) ).
-
THE POST BELOW IS MORE THAN 5 YEARS OLD. RELATED SUPPORT INFORMATION MIGHT BE OUTDATED OR DEPRECATED
On 10/07/2012 at 09:24, xxxxxxxx wrote:
...btw, something that I implied but didn't really clarify above is...
The winding-order (clockwise or counter-clockwise) will determine what the cross-product results will be (and thus the direction of the face normal).
To get a feel for the above, just enter polygon creation mode in Cinema 4D and create some polygons in a clockwise direction and some others in a counter-clockwise direction - then enable Backface Culling and you'll see that the counter-clockwise polygons disappear.
-
THE POST BELOW IS MORE THAN 5 YEARS OLD. RELATED SUPPORT INFORMATION MIGHT BE OUTDATED OR DEPRECATED
On 10/07/2012 at 11:53, xxxxxxxx wrote:
OK. Thanks.
I'm trying to implement your instructions. But I don't understand how to "loop through the 4 sides to get a neighbor poly".This is what I've got so far.
But I don't think I'm doing that part correctly:BaseDocument *doc = GetActiveDocument(); BaseObject *obj = doc->GetActiveObject(); PolygonObject *pobj = ToPoly(obj); LONG polycount = pobj->GetPolygonCount(); LONG pointcount = pobj->GetPointCount(); CPolygon *vadr = pobj->GetPolygonW(); Vector *points = pobj->GetPointW(); Neighbor n; //Create an instance of the neighbor class if( !n.Init(pointcount, vadr, polycount, NULL) ) return FALSE; //initialize it to use the active object's points and polys for (int i=0; i<polycount; i++) //Loop once for each polygon { //Get the polygon's point index#'s and assign them to variables LONG a,b,c,d; a=vadr[i].a; b=vadr[i].b; c=vadr[i].c; d=vadr[i].d; PolyInfo *pli = n.GetPolyInfo(i); //Get each polygon's information LONG side; for (side=0; side<4; side++) //Loop 4 times to test all 4 sides of a polygon <----This is probably Wrong? { LONG connected = pli->face[side]; //Get the neighboring polygon's index# if(connected != -1) //If there is actually a connected polygon...do this stuff { GePrint(LongToString(connected)); CPolygon poly = vadr[connected]; //Get the polygon from the index# we got earlier //Get the polygon's point index#'s and assign them to variables LONG polya=poly.a, polyb=poly.b, polyc=poly.c, polyd=poly.d; GePrint("Point A= " + LongToString(polya) + " " + "Point B= " + LongToString(polyb) + " " \+ "Point C= " + LongToString(polyc) + " " + "Point D= " + LongToString(polyd)); } } }
-ScottA
-
THE POST BELOW IS MORE THAN 5 YEARS OLD. RELATED SUPPORT INFORMATION MIGHT BE OUTDATED OR DEPRECATED
On 10/07/2012 at 13:45, xxxxxxxx wrote:
No, you're pretty much right on track...
BaseDocument *doc = GetActiveDocument(); BaseObject *obj = doc->GetActiveObject(); PolygonObject *pobj = ToPoly(obj); LONG polycount = pobj->GetPolygonCount(); LONG pointcount = pobj->GetPointCount(); CPolygon *vadr = pobj->GetPolygonW(); Vector *points = pobj->GetPointW(); Neighbor n; //Create an instance of the neighbor class Bool reInit = TRUE; for (int i=0; i<polycount; i++) //Loop once for each polygon { // if needed, (re)initialize the Neighbor class if( reInit ) { if( !n.Init(pointcount, vadr, polycount, NULL) ) //initialize it to use the active object's points and polys return FALSE; reInit = FALSE; } PolyInfo *pli = n.GetPolyInfo(i); //Get each polygon's information LONG side; for (side=0; side<4; side++) //Loop 4 times to test all 4 sides of a polygon { LONG nbrNdx = pli->face[side]; //Get the neighboring polygon's index# if(nbrNdx == NOTOK) continue; // if there's no neighbor, skip it LONG pt1, pt2; switch(side) { case 0: // a->b pt1 = vadr[i].b; // set pt1 to b pt2 = vadr[i].a; // set pt2 to a (reversed sequence) break; case 1: // b->c pt1 = vadr[i].c; // set pt1 to c pt2 = vadr[i].b; // set pt2 to b (reversed sequence) break; case 2: // c->d pt1 = vadr[i].d; // set pt1 to d pt2 = vadr[i].c; // set pt2 to c (reversed sequence) break; case 3: // d->a pt1 = vadr[i].a; // set pt1 to a pt2 = vadr[i].d; // set pt2 to d (reversed sequence) break; } CPolygon nbrPoly = vadr[nbrNdx ]; //Get the neighbor poly if( (nbrPoly.a == pt1 && nbrPoly.b == pt2) || (nbrPoly.b == pt1 && nbrPoly.c == pt2) || (nbrPoly.c == pt1 && nbrPoly.d == pt2) || (nbrPoly.d == pt1 && nbrPoly.a == pt2) ) { GePrint("Poly: "+LongToString(nbrNdx )+" is NOT inverted (moving on to next side...)"); } else { GePrint("Poly: "+LongToString(nbrNdx )+" is inverted and needs to be flipped"); // flip_poly(nbrNdx , vadr); // fix up the poly indices, accounting for (potential) triangles // note that if you adjust/flip it here, you likely need to call the neighbor // Init() function again before calling GetPolyInfo() again, because the poly // indices would be altered, so set the reInit flag. reInit = TRUE; } } }
...you'll have to create the flip_poly() routine (or just do it right there)... just note that you have to look for and handle triangle indices correctly.
EDIT: fixed multiple typos in the above :).
-
THE POST BELOW IS MORE THAN 5 YEARS OLD. RELATED SUPPORT INFORMATION MIGHT BE OUTDATED OR DEPRECATED
On 10/07/2012 at 13:57, xxxxxxxx wrote:
..now, the issue you'll have with the above code is...
You're basically looking at each polygon, and checking to see if it's neighbors need to be flipped. The problem is, you really need to start with a single polygon (or at least one per connected-group of polygons) and make sure every other polygon in the mesh matches it's orientation.
In other words, with just a single for(i=0; i<polycount; i++) loop, the first poly (i=0) might face one direction and the second (or 300th) one might face the opposite direction, so you might end up flipping various polys multiple times (if they happened to be neighbors of opposite-facing polys).
To resolve this issue, you'd need to keep track of which polys were tested (whether flipped or not) and which ones neighbors have already been tested and skip them when appropriate (reference my ConnectedPolyGroup() class example code in that other thread ... you could basically strip out the GroupID stuff from that code and insert the above (as appropriate) into the ProcessConnectedPolys() routine and mostly be done).
-
THE POST BELOW IS MORE THAN 5 YEARS OLD. RELATED SUPPORT INFORMATION MIGHT BE OUTDATED OR DEPRECATED
On 10/07/2012 at 16:15, xxxxxxxx wrote:
Thanks a lot Giblet.
But it's reporting polygons as being reversed when they aren't.
I'm just using it on a cube with one polygon reversed to test it out. And it reports more than one polygon reversed.At this point I don't really care about efficiency yet. Just accuracy.
So it's ok if it checks the same polygons multiple times(unless that's what's producing the errors).
Are you saying that I'd have to use your class to fix that problem?-ScottA
-
THE POST BELOW IS MORE THAN 5 YEARS OLD. RELATED SUPPORT INFORMATION MIGHT BE OUTDATED OR DEPRECATED
On 10/07/2012 at 20:08, xxxxxxxx wrote:
Yes and... yes.
If you're just using that one loop, you will get errors - depending on which of the 6 polygons (of the cube) is flipped. Basically, if the flipped polygon gets tested - before it's found/fixed as a neighbor - it will cause it's 4 neighbors to be flipped (the wrong way) and then as others are tested, they'll end up flipping some of those back.
So yes... you'd need to do something like that example class does to track which ones have been processed and which have had their neighbors processed. As stated, that example code would be one solution - exactly as it's already set up.
In other words, my cautionary note above was NOT about efficiency, it was about the accuracy (or lack thereof) of just doing your one loop code.
-
THE POST BELOW IS MORE THAN 5 YEARS OLD. RELATED SUPPORT INFORMATION MIGHT BE OUTDATED OR DEPRECATED
On 10/07/2012 at 20:37, xxxxxxxx wrote:
...ok, I got bored again :). Here you go:
class NormAlign { private: LONG m_numVerts; LONG m_numPolys; LONG m_numMappedPolys; UVWTag *m_uvTag; CPolygon *m_pPolys; UCHAR *m_pPolyProcessed; BaseSelect *m_pSeedPolys; BaseSelect *m_pCheckPolys; void FlipPoly(CPolygon *pPoly, LONG polyNdx); Bool OrientPolyNeighbors(CPolygon *pPoly, PolyInfo *pPolyInfo); Bool ProccessConnectedPolys(void); public: Bool AlignNormals(PolygonObject *pPolyObj); NormAlign(void); ~NormAlign(void); }; NormAlign::NormAlign(void) { m_uvTag = NULL; m_pSeedPolys = NULL; m_pCheckPolys = NULL; m_pPolyProcessed = NULL; } NormAlign::~NormAlign(void) { BaseSelect::Free(m_pSeedPolys); BaseSelect::Free(m_pCheckPolys); bDelete(m_pPolyProcessed); } Bool NormAlign::AlignNormals(PolygonObject *pPolyObj) { m_numVerts = pPolyObj->GetPointCount(); m_numPolys = pPolyObj->GetPolygonCount(); m_pPolys = pPolyObj->GetPolygonW(); m_uvTag = (UVWTag * )pPolyObj->GetTag(Tuvw, 0); m_pSeedPolys = BaseSelect::Alloc(); m_pCheckPolys = BaseSelect::Alloc(); if( !m_pSeedPolys || !m_pCheckPolys ) return false; // track which polys have been processed. m_pPolyProcessed = bNew UCHAR[m_numPolys]; if( !m_pPolyProcessed ) return false; ClearMem(m_pPolyProcessed, m_numPolys * sizeof(UCHAR), 0); LONG polyNdx; Bool done = false; for(polyNdx=0; polyNdx<m_numPolys; polyNdx++) { if( m_pPolyProcessed[polyNdx] ) continue; m_pSeedPolys->Select(polyNdx); // add this one to the list as a new seed poly m_pPolyProcessed[polyNdx] = 1; // mark this one off... m_numMappedPolys++; done = this->ProccessConnectedPolys(); // check orientation of all neighbors if( done ) break; } // done with these... BaseSelect::Free(m_pSeedPolys); BaseSelect::Free(m_pCheckPolys); bDelete(m_pPolyProcessed); return true; } void NormAlign::FlipPoly(CPolygon *pPoly, LONG polyNdx) { CPolygon flippedpoly = *pPoly; // flip the poly orientation (face normal) by re-ordering the point indices if( pPoly->c != pPoly->d ) { // Quad pPoly->b = flippedpoly.d; pPoly->d = flippedpoly.b; } else { // Triangle pPoly->b = flippedpoly.c; pPoly->c = flippedpoly.b; pPoly->d = flippedpoly.b; } // also adjust the uv-mapping, if any if( m_uvTag ) { UVWHandle pUVHndl = m_uvTag->GetDataAddressW(); if( pUVHndl ) { UVWStruct olduvw, newuvw; m_uvTag->Get(pUVHndl, polyNdx, olduvw); newuvw.a = olduvw.a; if( pPoly->c != pPoly->d ) { // Quad newuvw.b = olduvw.d; newuvw.c = olduvw.c; newuvw.d = olduvw.b; } else { // Triangle newuvw.b = olduvw.c; newuvw.c = olduvw.b; newuvw.d = olduvw.b; } m_uvTag->Set(pUVHndl, polyNdx, newuvw); } } } Bool NormAlign::OrientPolyNeighbors(CPolygon *pPoly, PolyInfo *pPolyInfo) { Bool bFlipped = false; LONG side; for(side=0; side<4; side++) { LONG nbrNdx = pPolyInfo->face[side]; if( nbrNdx == NOTOK ) continue; // skip any sides that don't exist if( m_pPolyProcessed[nbrNdx] ) continue; // only checking neighbors not already checked LONG pt1, pt2; switch(side) { case 0: // a->b pt1 = pPoly->b; // set pt1 to b pt2 = pPoly->a; // set pt2 to a (reversed sequence) break; case 1: // b->c pt1 = pPoly->c; // set pt1 to c pt2 = pPoly->b; // set pt2 to b (reversed sequence) break; case 2: // c->d pt1 = pPoly->d; // set pt1 to d pt2 = pPoly->c; // set pt2 to c (reversed sequence) break; case 3: // d->a pt1 = pPoly->a; // set pt1 to a pt2 = pPoly->d; // set pt2 to d (reversed sequence) break; } CPolygon *pNbrPoly = &m_pPolys[nbrNdx]; //Get the neighbor poly if( (pNbrPoly->a == pt1 && pNbrPoly->b == pt2) || (pNbrPoly->b == pt1 && pNbrPoly->c == pt2) || (pNbrPoly->c == pt1 && pNbrPoly->d == pt2) || (pNbrPoly->d == pt1 && pNbrPoly->a == pt2) ) { //GePrint("Poly: "+LongToString(nbrNdx )+" is NOT inverted (moving on to next side...)"); } else { // GePrint("Poly: "+LongToString(nbrNdx)+" is inverted and needs to be flipped..."); // fix up the poly indices, accounting for (potential) triangles this->FlipPoly(pNbrPoly, nbrNdx); // note that if you adjust/flip it here, you likely need to call the neighbor // Init() function again before calling GetPolyInfo() again, because the poly // indices would be altered, so set the reInit flag. bFlipped = true; } m_pSeedPolys->Select(nbrNdx); // add this one to the list as a new seed poly (to check it's neighbors in the next pass) m_pPolyProcessed[nbrNdx] = 1; // mark this one off... m_numMappedPolys++; } return bFlipped; } Bool NormAlign::ProccessConnectedPolys(void) { Neighbor nbr; Bool reInit = true; // NOTE: the way this code is set up, GetCount() will return a value of 1, when called from AlignNormals()... // once inside here, it's copied to the CheckPolys selection and cleared. Then OrientPolyNeighbors() // repopulates the list by adding each (any) neighbor poly that was checked to the SeedPolys list, to // serve as a new 'seed' polys to be checked in the next pass. // // Even though this new method (using a BaseSelect to list the seed polys to be checked) requires 2 // loops to decode, it's faster than the old way of looping through every poly every pass. while( m_pSeedPolys->GetCount() ) { // GePrint("m_pSeedPolys->GetCount() : "+LongToString(m_pSeedPolys->GetCount())); m_pSeedPolys->CopyTo(m_pCheckPolys); // set up the CheckPolys selection to loop through m_pSeedPolys->DeselectAll(); // then clear out the SeedPolys selection for the next pass... LONG polyNdx, seg = 0, smin, smax; #if API_VERSION < 13000 while( m_pCheckPolys->GetRange(seg++,&smin,&smax) ) #else while( m_pCheckPolys->GetRange(seg++,MAXLONGl,&smin,&smax) ) #endif { for(polyNdx=smin; polyNdx<=smax; polyNdx++) { // if needed, (re)initialize the Neighbor class if( reInit ) { nbr.Flush(); if( !nbr.Init(m_numVerts, m_pPolys, m_numPolys, NULL) ) return true; // failure state, but return that we're "done" reInit = false; } // (re)Orient this polygon's neighbors (as needed)... reInit = this->OrientPolyNeighbors(&m_pPolys[polyNdx], nbr.GetPolyInfo(polyNdx)); if( m_numMappedPolys == m_numPolys ) { return true; } } } } if( m_numMappedPolys == m_numPolys ) return true; return false; }
I haven't compiled or tested that, but I think it should work.
EDIT: it's now been tested and I added the poly flipping code...
Notes:
1. You can call it like so...
NormAlign na; na.AlignNormals(op); op->Message(MSG_UPDATE); EventAdd();
2. also note that if the first polygon (at index 0) is the one that's facing the wrong directio, the above code will flip ALL the polygons to match it (ie. the wrong direction).
3. finally, while this was an interesting programming exercise, the Cinema 4D "Align Normals" command is apparently doing something more efficient (faster), so I'd still recommend not re-inventing the wheel.
EDIT #2:
Since it sounds like you want to use this, I broke it into more digestible chunks and re-worked the code a bit (for example, it turns out the the tracking array m_pNeighborsAdded[] was not actually needed, etc.)
-
THE POST BELOW IS MORE THAN 5 YEARS OLD. RELATED SUPPORT INFORMATION MIGHT BE OUTDATED OR DEPRECATED
On 10/07/2012 at 22:33, xxxxxxxx wrote:
This is insanely useful stuff Giblet.
The built-in command may be faster. But it doesn't tell you which polygons were reversed. It just changes them without telling you which ones it changed.
So this is VERY useful.I'm using it in a command data plugin. And it seems to be working well so far.
This is also the very first time I've used a custom class within a C++ C4D plugin project too.
Is there any benefit to calling to the class like you posted. Opposed to calling it like this?:NormAlign *myalign = gNew NormAlign; myalign->AlignNormals(pobj);
The SDK says that we should use gNew when possible.
And I don't know which way I should do it. Your way... or the way I posted.
Or is it just a matter of whatever style we prefer to use?-ScottA
-
THE POST BELOW IS MORE THAN 5 YEARS OLD. RELATED SUPPORT INFORMATION MIGHT BE OUTDATED OR DEPRECATED
On 10/07/2012 at 22:45, xxxxxxxx wrote:
re: using an instance of a class vs. allocating a pointer to one
If you just use an instance, when that instance goes out of scope (at the end of the routine that created it), the instance cleans itself up and goes away (it's Destructer is called).
If you allocate a pointer to one, memory is allocated for the class, so you have to be sure to call gDelete() when you're done with it to free up that memory.
If you need to pass it to some other function, it's generally more convenient to allocate a pointer to one, so you can just pass the pointer around, but if you're going to just use it (and be done with it) in the same function, it's typically more convenient to just declare an instance within that routine.
-
THE POST BELOW IS MORE THAN 5 YEARS OLD. RELATED SUPPORT INFORMATION MIGHT BE OUTDATED OR DEPRECATED
On 11/07/2012 at 00:46, xxxxxxxx wrote:
>Originally posted by xxxxxxxx
This is also the very first time I've used a custom class within a C++ C4D plugin project too.
Really? But it's so useful, not only in C++, but also in Python. For example, this is a class I use to compute Face and Vertex normals and store information while doing certain operations in a tool-plugin.
// Copyright (C) 2012 Niklas Rosenstein // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. /** This class computes the normals of a PolygonObject's faces and vertices. It also contains buffers for information that was calculated at the time the RockgenNormalCache object was passed to RockifyObject(). **/ class RockgenNormalCache { public: LONG facecount; LONG vertexcount; Matrix* facenormals; Matrix* vertexnormals; // Information filled by RockifyObject() Bool arith_ok; LONG* arith_count; Vector* arith_vectorsum; Real* arith_influence; Vector* final_vertices; RockgenNormalCache() : facecount(0), vertexcount(0), facenormals(NULL), vertexnormals(NULL), arith_vectorsum(NULL), arith_count(NULL), arith_influence(NULL), arith_ok(FALSE) { } RockgenNormalCache(PolygonObject* op) { Init(op); } ~RockgenNormalCache() { FreeCache(); } Bool IsInit() const { return facenormals && vertexnormals && arith_vectorsum && arith_count && arith_influence; } Bool Init(PolygonObject* op) { #ifdef DEBUG char buffer[200]; GePrint("RockgenNormalCache::Init()"); #endif facenormals = NULL; vertexnormals = NULL; arith_ok = FALSE; arith_vectorsum = NULL; arith_count = NULL; arith_influence = NULL; final_vertices = NULL; facecount = op->GetPolygonCount(); vertexcount = op->GetPointCount(); Vector p1, p2, p3, p4, e13, e24, mid; const Vector* op_vertices = op->GetPointR(); const CPolygon* op_faces = op->GetPolygonR(); Neighbor nbinfo; if (!nbinfo.Init(vertexcount, op_faces, facecount, NULL)) { #ifdef DEBUG GePrint("Neighbor information could not be built."); #endif return FALSE; } // Allocate memory for face- and vertex-normals facenormals = (Matrix* ) malloc(sizeof(Matrix) * facecount); if (!facenormals) { #ifdef DEBUG GePrint(" Could not allocate facenormals."); #endif goto allocfail; } vertexnormals = (Matrix* ) malloc(sizeof(Matrix) * vertexcount); if (!vertexnormals) { #ifdef DEBUG GePrint(" Could not allocate ppoints vertices."); #endif goto allocfail; } arith_vectorsum = (Vector* ) malloc(sizeof(Vector) * vertexcount); if (!arith_vectorsum) { #ifdef DEBUG GePrint(" Could not allocate arith_vectorsum."); #endif goto allocfail; } arith_count = (LONG* ) malloc(sizeof(LONG) * vertexcount); if (!arith_count) { #ifdef DEBUG GePrint(" Could not allocate arith_count."); #endif goto allocfail; } arith_influence = (Real* ) malloc(sizeof(Real) * vertexcount); if (!arith_influence) { #ifdef DEBUG GePrint(" Could not allocate arith_influence."); #endif goto allocfail; } final_vertices = (Vector* ) malloc(sizeof(Vector) * vertexcount); if (!final_vertices) { #ifdef DEBUG GePrint(" Could not allocate final_vertices."); #endif goto allocfail; } goto continue_; allocfail: nbinfo.Flush(); FreeCache(); return FALSE; continue_: Matrix* c_facenormal = facenormals; const CPolygon* c_op_face = op_faces; #ifdef DEBUG GePrint(" Computing face normals."); #endif // Compute face Normals for (int i=0; i < facecount; i++, c_op_face++, c_facenormal++) { p1 = op_vertices[c_op_face->a]; p2 = op_vertices[c_op_face->b]; p3 = op_vertices[c_op_face->c]; p4 = op_vertices[c_op_face->d]; // Compute normal-adjacents c_facenormal->v1 = p3 - p1; c_facenormal->v2 = p4 - p2; c_facenormal->v1.Normalize(); c_facenormal->v2.Normalize(); // Compute face midpoint c_facenormal->off = p1 + p2 + p3; if (c_op_face->c != c_op_face->d) { c_facenormal->off = (c_facenormal->off + p4) / 4.0; } else { c_facenormal->off /= 3.0; } // Compute face normal c_facenormal->v3 = c_facenormal->v1.Cross(c_facenormal->v2); c_facenormal->v3.Normalize(); } LONG nbinfo_polyc, *nbinfo_polyv; const Vector* c_op_vertex = op_vertices; Matrix* c_vertexnormal = vertexnormals; Matrix* t_facenormal; Vector v1, v2, v3; #ifdef DEBUG GePrint(" Computing vertex normals."); #endif // Compute vertex normals for (int i=0; i < vertexcount; i++, c_vertexnormal++, c_op_vertex++) { nbinfo.GetPointPolys(i, &nbinfo_polyv, &nbinfo_polyc); v1 = v2 = v3 = 0; for (int j=0; j < nbinfo_polyc; j++) { t_facenormal = &facenormals[nbinfo_polyv[j]]; v1 += t_facenormal->v1; v2 += t_facenormal->v2; v3 += t_facenormal->v3; } c_vertexnormal->off = *c_op_vertex; c_vertexnormal->v1 = v1; c_vertexnormal->v2 = v2; c_vertexnormal->v3 = v3; c_vertexnormal->Normalize(); } #ifdef DEBUG GePrint("RockgenNormalCache::Init() ended"); #endif nbinfo.Flush(); return TRUE; } void FreeCache() { #ifdef DEBUG GePrint("RockgenNormalCache::FreeCache()"); #endif if (facenormals) { free(facenormals); facenormals = NULL; } if (vertexnormals) { free(vertexnormals); vertexnormals = NULL; } if (arith_vectorsum) { free(arith_vectorsum); arith_vectorsum = NULL; } if (arith_count) { free(arith_count); arith_count = NULL; } if (arith_influence) { free(arith_influence); arith_influence = NULL; } if (final_vertices) { free(final_vertices); final_vertices = NULL; } } };
-
THE POST BELOW IS MORE THAN 5 YEARS OLD. RELATED SUPPORT INFORMATION MIGHT BE OUTDATED OR DEPRECATED
On 11/07/2012 at 04:59, xxxxxxxx wrote:
Scott, I updated my latest code listing above, if you're interested.
-
THE POST BELOW IS MORE THAN 5 YEARS OLD. RELATED SUPPORT INFORMATION MIGHT BE OUTDATED OR DEPRECATED
On 11/07/2012 at 07:32, xxxxxxxx wrote:
Thanks a ton Giblet.
Very helpful stuff.@Nik,
I haven't needed to use a custom class in the C++ SDK up to this point. But I have used them in Python and Coffee before.-ScottA