CalcFaceNormal() Creates Strange Results
-
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