Neighbor class and disconnected polygons



  • THE POST BELOW IS MORE THAN 5 YEARS OLD. RELATED SUPPORT INFORMATION MIGHT BE OUTDATED OR DEPRECATED

    On 06/07/2012 at 04:34, xxxxxxxx wrote:

    Hey Giblet,

    I'm not ignoring you but just had too many other things to do to try out the code.  I see that all of the polygon neighbors are in the Neighbor class but not checked by my criteria.  My critiera is looking at a 'seed' polygon and finding the direct neighbors level by level from there.  This fails to include disconnected groups though.  It may be that I want to use your grouping algorithm as a preprocess so that the indirect relation between groups (closest polygons and distance) can be used to make the continuum of levels that must jump these disconnected groups.



  • THE POST BELOW IS MORE THAN 5 YEARS OLD. RELATED SUPPORT INFORMATION MIGHT BE OUTDATED OR DEPRECATED

    On 06/07/2012 at 04:52, xxxxxxxx wrote:

    No problem - I figured you were busy with other things - I just tend to get hooked into some subject and get carried away with it at times :).  You're welcome to use any of the above that you find useful.

    Happy belated 4th!



  • THE POST BELOW IS MORE THAN 5 YEARS OLD. RELATED SUPPORT INFORMATION MIGHT BE OUTDATED OR DEPRECATED

    On 06/07/2012 at 08:23, xxxxxxxx wrote:

    I have a question about your examples Giblet.
    I see that you are using UCHAR instead of BaseSelect. Which is something I wanted to learn how to do. But I'm having trouble figuring out how to use the UCHAR this way.

    I'm not getting any return from my print function.
    Can you possibly look at this example, and the notes I've made in it. And tell me what I'm doing wrong?

    Example:

        BaseObject *obj = doc->GetActiveObject();  //The active object  
      PolygonObject *pobj = ToPoly(obj);         //Cast the active object to a polygon type  
      
      
      //The Number of polygons in the active object      
      LONG numPolys = pobj->GetPolygonCount();  
      
      
      //Creates a UCHAR array with it's size based on the number of polygons  
      //At this point all the UCHAR elements are empty(null values)  
      UCHAR *PolyProcessed = (UCHAR * )GeAlloc(numPolys * sizeof(UCHAR));   
      
      for(LONG i=0; i<numPolys; i++)  
      {  
          PolyProcessed[i] = i; //<--This is supposed to fill the UCHAR array elements with numbers starting from zero  
      
          String str;  
          str.SetCString(reinterpret_cast<const CHAR*>(PolyProcessed), -1, STRINGENCODING_8BIT); //<--This is probably wrong?  
          GePrint(str);    
      }
    

    Thanks,
    -ScottA



  • THE POST BELOW IS MORE THAN 5 YEARS OLD. RELATED SUPPORT INFORMATION MIGHT BE OUTDATED OR DEPRECATED

    On 06/07/2012 at 08:48, xxxxxxxx wrote:

    You are putting non-ASCII (non-printable) characters in the pseudo-string you are creating.  In other words, you _start_ by putting the value zero (null, effectively null-terminating the 'string') in the first position (not the ASCII value for '0'), then the value 1, then 2, then 3... these are all non-printable values.

    If you wanted to put actual printable characters in there, you'd still be hosed for any meshes with more than 10 polygons ('0' -> '9').... and keep in mind that the largest value a UCHAR can hold (printable or not) is 255.

    Basically, I think you may be thinking that 'reinterpret_cast' is doing something for you that it's not.

    To achieve what you're trying to achieve, I'd do something like:

      
        
        
            BaseObject *obj = doc->GetActiveObject();  //The active object  
          PolygonObject *pobj = ToPoly(obj);         //Cast the active object to a polygon type  
          
          
          //The Number of polygons in the active object      
          LONG numPolys = pobj->GetPolygonCount();  
          
          
          //Creates a LONG array with it's size based on the number of polygons  
          //At this point all the ***--> LONG <--*** elements are empty(null values)  
          LONG *PolyProcessed = (LONG * )GeAlloc(numPolys * sizeof(LONG));   
          
          String str;  
          for(LONG i=0; i<numPolys; i++)  
          {  
              PolyProcessed[i] = i; //<--This is supposed to fill the LONG array elements with numbers starting from zero  
          
                str += LongtoString(PolyProcessed[i]);
          
              GePrint(str);    
          }
    

    ...the above should give you the thing that your code was attempting (though I'm not sure how ultimately useful it would be - maybe just programing exercise?).

    EDIT: Note that I assumed that you wanted to GePrint() the entire PolyProcessed table - which could blow up GePrint() once it got large enough - if you just want to verify each element, replace the:

      
        
        
                str += LongtoString(PolyProcessed[i]);
        
    ...with...  
        
        
                str = LongtoString(PolyProcessed[i]);
        
    

    I'm using a UCHAR array basically as a 'boolean' table, with only 8bits = 1byte (sizeof(UCHAR)) per entry.  So 100,000 polygons would use 100,000 bytes of storage.  A BaseSelect is actually a lot more efficient than that, but does come with some processing overhead (it uses less memory, but is not as fast as a direct table look-up).  It's actually a bit of a moot point, since I doubt that you'd see any real-world difference in speed.



  • THE POST BELOW IS MORE THAN 5 YEARS OLD. RELATED SUPPORT INFORMATION MIGHT BE OUTDATED OR DEPRECATED

    On 06/07/2012 at 09:58, xxxxxxxx wrote:

    Thanks.
    I'm new to this whole ASCII, Unicode, etc.. stuff. And I'm trying to figure it all out.

    -I still don't see how you're getting a BOOL value from a UCHAR array.
    -I would also like to know how to convert a UCHAR value to a String value. If that's possible.

    -ScottA



  • THE POST BELOW IS MORE THAN 5 YEARS OLD. RELATED SUPPORT INFORMATION MIGHT BE OUTDATED OR DEPRECATED

    On 06/07/2012 at 10:25, xxxxxxxx wrote:

    Well... a Cinema 4D SDK "Bool" is actually typdef'd as an 'int', which would be a 32bit or 64bit value, but the typical use of such a variable is just to store true or false, 1 or 0... which only needs a single bit to represent, but the compiler/cpu can access a 32bit/64bit value just as fast.

    Since a Bool is either 32bits or 64bits (depending on the OS and Compiler options), I am using an 8bit 'unsigned character' (a UCHAR) instead, to save some memory.  I am just using it to store 1 or 0 - true or false.

    So, because of the way C/C++ works, I can test the byte for being 'non-zero'...

      
    UCHAR b = 0;  
      
    if( b ) ...  // would evaluate to false.  
      
    b = 1;  
      
    if( b ) ... // b is now 'non-zero', so it evaluates to true.  
      
    b = 255;  
      
    if( b ) ... // b is still non-zero, so it evaluates to true.  
      
    char *ptr = NULL;  
      
    if( ptr ) ... // since ptr is NULL ( zero ), it evaluates to false.  
      
    char mystring[] = "hello world";  
    ptr = mystring; // or ptr = &mystring[0];  
      
    if( ptr ) ... // since ptr now points to something, it has a non-zero/non-NULL value, so it evaluates to true.  
      
    String c4dStr = String(mystring);        // convert char C string to Cinema 4D String  
    String c4dStr = String("hello world"); // that also works  
    String c4dStr = "hello world";             // so does that  
      
    String c4dStr = "hello";  
    c4dStr += " world";                           // you can also concatenate...  
      
    String c4dStr = String("hello")+String(" world");  // but you might have to use the String() form to add/concatenate on the same statement.  
      
    

    ...basically any value except 0 (or NULL, for pointers) evaluates to true.

    For ASCII values, do a google search to find a chart like this one... where you can see (for example) the ASCII value for the character '0' is the value 48 decimal, so...

      
    char mystring[12];  
    mystring[0] =   72;  // H  
    mystring[1] =  101;  // e  
    mystring[2] =  108;  // l  
    mystring[3] =  108;  // l  
    mystring[4] =  111;  // o  
    mystring[5] =   32;  // <space>  
    mystring[6] =   87;  // W  
    mystring[7] =  111;  // o  
    mystring[8] =  114;  // r  
    mystring[9] =  108;  // l  
    mystring[10] =  100; // d  
    mystring[11] =    0; // null termination.  
      
    String c4dStr = String(mystring);  
    GePrint(c4dStr);   // prints...    "Hello World" (without the quotes)  
    


  • THE POST BELOW IS MORE THAN 5 YEARS OLD. RELATED SUPPORT INFORMATION MIGHT BE OUTDATED OR DEPRECATED

    On 06/07/2012 at 11:10, xxxxxxxx wrote:

    Thanks for the explanation.
    I understand how you're getting the BOOL now.👍

    I think I was misunderstanding what a UCHAR is. Please check my thinking on this.
    UCHAR is used to store values. Specifically positive values. Not characters as the name seems to imply.
    The same way a DWORD stores unsigned long values. Not strings as the name seems to imply.
    So there is no such thing as converting a UCHAR to a string.

    Is that correct?

    -ScottA



  • THE POST BELOW IS MORE THAN 5 YEARS OLD. RELATED SUPPORT INFORMATION MIGHT BE OUTDATED OR DEPRECATED

    On 06/07/2012 at 11:13, xxxxxxxx wrote:

    ...Just for completeness (I'm still not clear exactly what you're asking/trying to accomplish)...

    If you literally want to print the values of a UCHAR array, you can still do that fairly easily...

      
        GePrint(String("PolyProcessed[0] = ")+LongToString((LONG)PolyProcessed[0]));  
    

    ...you could even leave out the (LONG) cast (it would get cast anyway - I just added it for clarity).



  • THE POST BELOW IS MORE THAN 5 YEARS OLD. RELATED SUPPORT INFORMATION MIGHT BE OUTDATED OR DEPRECATED

    On 06/07/2012 at 11:15, xxxxxxxx wrote:

    Originally posted by xxxxxxxx

    Thanks for the explanation.
    I understand how you're getting the BOOL now.👍

    I think I was misunderstanding what a UCHAR is. Please check my thinking on this.
    UCHAR is used to store values. Specifically positive values. Not characters as the name seems to imply.
    The same way a DWORD stores unsigned long values. Not strings as the name seems to imply.
    So there is no such thing as converting a UCHAR to a string.

    Is that correct?

    -ScottA

    Yes - you are correct - I'm using it to just store some value between 0 - 255 (and in this case, just either 1 or 0).

    An array of UCHARs is actually a C string, by definition and can be used as such as long as it's null-terminated (erm... and as long as it contains printable characters :) - but not how I'm using it).

    Again, just for completeness/correctness... you can in fact store characters in a UCHAR array...

      
    mystring[0] =  'H';  // H  
    mystring[1] =  'e';  // e  
    mystring[2] =  'l';  // l  
    mystring[3] =  'l';  // l  
    mystring[4] =  'o';  // o  
    mystring[5] =  ' ';  // <space>  
    mystring[6] =  'W';  // W  
    mystring[7] =  'o';  // o  
    mystring[8] =  'r';  // r  
    mystring[9] =  'l';  // l  
    mystring[10] =  'd'; // d  
    mystring[11] = '\0'; // null termination.  
    

    ...the above is equivalent to my previous post, just using characters instead of the equivalent  ASCII values that represent those same characters.



  • THE POST BELOW IS MORE THAN 5 YEARS OLD. RELATED SUPPORT INFORMATION MIGHT BE OUTDATED OR DEPRECATED

    On 06/07/2012 at 12:05, xxxxxxxx wrote:

    Thanks a lot for explaining this stuff.
    I did some searching on WCHAR to see If it could help me learn more about UCHAR. But you've eplained it quite well.

    Thank you. 🍺
    -ScottA



  • THE POST BELOW IS MORE THAN 5 YEARS OLD. RELATED SUPPORT INFORMATION MIGHT BE OUTDATED OR DEPRECATED

    On 03/08/2012 at 04:43, xxxxxxxx wrote:

    Just a heads up: there is a slight bug in your ConnectedPolyGroup.Init() method.  I've moved ++m_GroupCount to below ProcessConnectedPolys() or your groupIDs are set too high in that method.  Now it appears to be working as expected.

        Bool    done =                FALSE;  
      for(LONG polyNdx = 0L; polyNdx != m_numPolys; ++polyNdx)  
      {  
          if (m_pDstPolyProcessed[polyNdx])    continue;  
      
          // set group index/ID  
          m_pGroupIDs[polyNdx] =                m_GroupCount;  
          // mark this one off...  
          m_pDstPolyProcessed[polyNdx] =        1;  
          ++m_numMappedPolys;  
      
          // set all neighbors to same group index/ID  
          done =                                this->ProcessConnectedPolys();  
          // bump group index/ID for next group  
    // THIS HAS BEEN MOVED TO BELOW ProcessConnectedPolys()!  
          ++m_GroupCount;  
          if (done)                            break;  
      }
    


  • THE POST BELOW IS MORE THAN 5 YEARS OLD. RELATED SUPPORT INFORMATION MIGHT BE OUTDATED OR DEPRECATED

    On 03/08/2012 at 10:03, xxxxxxxx wrote:

    Hmm... I don't see any difference in what you posted and the latest posted code above (?).  Maybe I found/fixed the post after you had already grabbed the code?  Or am I missing something?



  • THE POST BELOW IS MORE THAN 5 YEARS OLD. RELATED SUPPORT INFORMATION MIGHT BE OUTDATED OR DEPRECATED

    On 03/08/2012 at 10:11, xxxxxxxx wrote:

    Hehe...... ;)  Must have grabbed an earlier version of the code.  Good that you found it too! :)



  • THE POST BELOW IS MORE THAN 5 YEARS OLD. RELATED SUPPORT INFORMATION MIGHT BE OUTDATED OR DEPRECATED

    On 03/08/2012 at 10:22, xxxxxxxx wrote:

    I thought that might be the case :).  I don't recall what other changes I made (and whether or not any changes were bug-fixes), so you might want to compare the rest of that to what you're using as well.



  • THE POST BELOW IS MORE THAN 5 YEARS OLD. RELATED SUPPORT INFORMATION MIGHT BE OUTDATED OR DEPRECATED

    On 03/08/2012 at 10:33, xxxxxxxx wrote:

    Just for completeness, here is the latest in-use code...

      
    class ConnectedPolyGroup  
    {  
    private:  
      Bool        m_bInit;  
      LONG        m_numVerts;  
      LONG        m_numPolys;  
      LONG        m_numMappedPolys;  
      LONG        m_GroupCount;  
      CPolygon    *m_pDstPolys;  
      UCHAR        *m_pDstPolyProcessed;  
      UCHAR        *m_pNeighborsAdded;  
      LONG        *m_pGroupIDs;  
      Neighbor    m_dstNbr;  
      
      Bool ProccessConnectedPolys(void);  
    public:  
      void FreeData(void);  
      BaseSelect *SelectGroup(LONG GroupID);  
      LONG NumPolys(void)                        { return m_numPolys; }  
      LONG NumConnectedGroups(void)            { return m_GroupCount; }  
      LONG GroupID(LONG polyNdx)                { if(!m_bInit || polyNdx >= m_numPolys) return NOTOK;    return  m_pGroupIDs[polyNdx]; }  
      Bool Init(PolygonObject *pPolyObj);  
      
      ConnectedPolyGroup(void);  
      ~ConnectedPolyGroup(void);  
    };  
      
    ConnectedPolyGroup::ConnectedPolyGroup(void)  
    {  
      m_bInit = false;  
      m_GroupCount = 0;  
      m_pDstPolyProcessed = NULL;  
      m_pNeighborsAdded = NULL;  
      m_pGroupIDs = NULL;  
    }  
      
    ConnectedPolyGroup::~ConnectedPolyGroup(void)  
    {  
      this->FreeData();  
    }  
      
    void ConnectedPolyGroup::FreeData(void)  
    {  
      bDelete(m_pDstPolyProcessed);  
      bDelete(m_pNeighborsAdded);  
      bDelete(m_pGroupIDs);  
      m_numMappedPolys = 0;  
      m_GroupCount = 0;  
      m_numVerts = 0;  
      m_numPolys = 0;  
      m_bInit = false;  
    }  
      
    // generate a BaseSelect based on connected GroupID - caller owns returned BaseSelect pointer  
    BaseSelect *ConnectedPolyGroup::SelectGroup(LONG GroupID)  
    {  
      if(!m_bInit || GroupID >= m_GroupCount)    return NULL;  
      
      BaseSelect *pSel = BaseSelect::Alloc();  
      if( !pSel )                                return NULL;  
      pSel->DeselectAll();    // <-- shouldn't actually be needed  
      
      LONG polyNdx;  
      for(polyNdx=0; polyNdx<m_numPolys; polyNdx++)  
      {  
          if( m_pGroupIDs[polyNdx] == GroupID )  
              pSel->Select(polyNdx);  
      }  
      return pSel;  
    }  
      
    Bool ConnectedPolyGroup::Init(PolygonObject *pPolyObj)  
    {  
      this->FreeData();  
      
      m_numVerts = pPolyObj->GetPointCount();  
      m_numPolys = pPolyObj->GetPolygonCount();  
      m_pDstPolys = pPolyObj->GetPolygonW();  
      
      // track which polys have been processed.  
      m_pDstPolyProcessed = bNew UCHAR[m_numPolys];  
      if( !m_pDstPolyProcessed )        return false;  
      
      // track which polys have had their neighbors added.  
      m_pNeighborsAdded = bNew UCHAR[m_numPolys];  
      if( !m_pNeighborsAdded )        return false;  
      
      // track which 'connected group' each poly belongs to  
      m_pGroupIDs = bNew LONG[m_numPolys];  
      if( !m_pGroupIDs )                return false;  
      
      // initialize Neighbor class  
      if( !m_dstNbr.Init(m_numVerts, m_pDstPolys, m_numPolys, NULL) )    return false;  
      
      ClearMem(m_pDstPolyProcessed, m_numPolys * sizeof(UCHAR), 0);  
      ClearMem(m_pNeighborsAdded, m_numPolys * sizeof(UCHAR), 0);  
      ClearMem(m_pGroupIDs, m_numPolys * sizeof(LONG), 0);  
      
      LONG polyNdx;  
      Bool done = false;  
      for(polyNdx=0; polyNdx<m_numPolys; polyNdx++)  
      {  
          if( m_pDstPolyProcessed[polyNdx] )    continue;  
      
          m_pGroupIDs[polyNdx] = m_GroupCount;                        // set group index/ID  
          m_pDstPolyProcessed[polyNdx] = 1;                            // mark this one off...  
          m_numMappedPolys++;  
      
          done = this->ProccessConnectedPolys();                        // set all neighbors to same group index/ID  
      
          m_GroupCount++;                                                // bump group index/ID for next group  
          if( done )    break;  
      }  
      
      // done with these...  
      bDelete(m_pDstPolyProcessed);  
      bDelete(m_pNeighborsAdded);  
      m_bInit = true;  
      return true;  
    }  
      
    Bool ConnectedPolyGroup::ProccessConnectedPolys(void)  
    {  
      Bool morePolys = true;  
      while( morePolys )  
      {  
          morePolys = false;  
          LONG polyNdx;  
          for(polyNdx=0; polyNdx<m_numPolys; polyNdx++)  
          {  
              if( !m_pDstPolyProcessed[polyNdx] )    continue;    // only adding/processing neighbors of previously matched/valid polys  
              if( m_pNeighborsAdded[polyNdx] )    continue;    // don't add/process neighbors if already added  
      
    //            CPolygon *pDstPoly = &m_pDstPolys[polyNdx];  
              PolyInfo *pDstPolyInfo = m_dstNbr.GetPolyInfo(polyNdx);  
              LONG side;  
              for(side=0; side<4; side++)  
              {  
                      LONG dstNbrNdx = pDstPolyInfo->face[side];  
                  if( dstNbrNdx == NOTOK )                continue;    // skip any sides that don't exist  
                  if( m_pDstPolyProcessed[dstNbrNdx] )    continue;    // only adding neighbors not already added  
      
                  m_pGroupIDs[dstNbrNdx] = m_GroupCount;        // all connected polys get the same index/ID  
                  m_pDstPolyProcessed[dstNbrNdx] = 1;            // mark this one off...  
                  m_numMappedPolys++;  
                  morePolys = true;                            // since we added / processed another poly, make sure we loop again to get _it's_ neighbors  
              }  
              m_pNeighborsAdded[polyNdx] = 1;                    // we've processed this polys neighbors  
      
              if( m_numMappedPolys == m_numPolys )  
              {  
                  morePolys = false;  
                  break;    // return true?  
              }  
          }  
      }  
      if( m_numMappedPolys == m_numPolys )  
          return true;  
      return false;  
    }  
    

    ...I _think_ the only difference in that and what's posted above is that I go ahead and free a couple (no-longer-needed) arrays inside Init() instead of waiting for the Destructor.

    As an aside, based on some later use of this base code (in this thread), I think that I determined that I didn't really need the m_pNeighborsAdded array/checking/tracking at all.



  • THE POST BELOW IS MORE THAN 5 YEARS OLD. RELATED SUPPORT INFORMATION MIGHT BE OUTDATED OR DEPRECATED

    On 04/08/2012 at 07:55, xxxxxxxx wrote:

    Will compare code and update as needed.  Thanks!


Log in to reply