Monkey Voxelizer



  • On 13/05/2014 at 01:24, xxxxxxxx wrote:

    Hi all,

    I created a voxelizer with the old school thinking particles modul, that uses the ray collider as a polygon detector in Zaxis.
    Just wanted to share it.
    And thanks for helping me to learn all the stuff!

    One question is left: is it possible to read(and only read) the Max Particles value, to inform the user that
    his voxels may exceed the given limit? -script line 240-

      
    ######################################################################################  
    # Copyright (c) 2014 Martin Albertshauser                                            #  
    #                                                                                    #  
    # Permission is hereby granted, free of charge, to any person obtaining a copy       #  
    # of this software and associated documentation files (the "Software"), to deal      #  
    # in the Software without restriction, including without limitation the rights       #  
    # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell          #  
    # copies of the Software, and to permit persons to whom the Software is              #  
    # furnished to do so, subject to the following conditions:                           #  
    #                                                                                    #  
    # The above copyright notice and this permission notice shall be included in         #  
    # all copies or substantial portions of the Software.                                #  
    #                                                                                    #  
    # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR         #  
    # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,           #  
    # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE        #  
    # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER             #  
    # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,      #  
    # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN          #  
    # THE SOFTWARE.                                                                      #  
    ######################################################################################  
    ##_________________________________monkeyvoxel______________________________________##  
    #__________________This script voxelizes closed meshes_______________________________#  
    ######################################################################################  
       
    import c4d  
    import math  
      
    from c4d import utils,gui  
    from c4d.utils import SendModelingCommand  
      
    def axisreset(op) :  
      oldm= op.GetMg()  
      points= op.GetAllPoints()  
      pcount= op.GetPointCount()  
      bbox= op.GetMp()  
      doc.AddUndo(c4d.UNDOTYPE_CHANGE, op)  
      op.SetAbsRot(c4d.Vector(0,0,0))          
      newm= op.GetMg()  
      for p in xrange(pcount) :  
          op.SetPoint(p,~newm*oldm*points[p])  
            
      op.Message(c4d.MSG_UPDATE)    
          
      oldm2= op.GetMg()  
      points2= op.GetAllPoints()  
      bb2= op.GetMp()  
      glop= oldm*bbox  
      op.SetAbsPos(c4d.Vector(glop))      
      newm2= op.GetMg()  
      for p in xrange(pcount) :  
          op.SetPoint(p,~newm2*oldm2*points[p])  
            
      op.Message(c4d.MSG_UPDATE)  
      return  
      
      
    def voxel(xs,ys,zs) :  
        
      #Build a voxel object  
      vox= c4d.BaseObject(c4d.Opolygon)  
      vox.ResizeObject(8,6)  
        
      vox.SetPoint(0,c4d.Vector(-xs/2,-ys/2,-zs/2))  
      vox.SetPoint(1,c4d.Vector(-xs/2,ys/2,-zs/2))  
      vox.SetPoint(2,c4d.Vector(xs/2,ys/2,-zs/2))  
      vox.SetPoint(3,c4d.Vector(xs/2,-ys/2,-zs/2))  
      vox.SetPoint(4,c4d.Vector(-xs/2,-ys/2,zs/2))  
      vox.SetPoint(5,c4d.Vector(-xs/2,ys/2,zs/2))  
      vox.SetPoint(6,c4d.Vector(xs/2,ys/2,zs/2))  
      vox.SetPoint(7,c4d.Vector(xs/2,-ys/2,zs/2))  
        
      vox.SetPolygon(0,c4d.CPolygon(0,1,2,3))  
      vox.SetPolygon(1,c4d.CPolygon(4,5,1,0))  
      vox.SetPolygon(2,c4d.CPolygon(7,6,5,4))  
      vox.SetPolygon(3,c4d.CPolygon(3,2,6,7))  
      vox.SetPolygon(4,c4d.CPolygon(1,5,6,2))  
      vox.SetPolygon(5,c4d.CPolygon(0,4,7,3))  
      vox.Message (c4d.MSG_UPDATE)  
        
      return vox  
      
    def ray(op,rayposition,rayzdirection,raylength,xdim,ydim,zdim,Zcount,parSys,parGroup,parLife,pcounter) :  
        
      HitList= []  
      PosList= []  
      matr= op.GetMg()  
      precision= 6  
        
      #Initialize the collider  
      collider= c4d.utils.GeRayCollider()   
      collider.Init(op,True)   
        
      #Start the ray  
      inter= collider.Intersect(rayposition,rayzdirection,raylength)  
      if inter== True:  
          count= collider.GetIntersectionCount()  
          collision= 0  
            
          #Loop through all collisions   
          while collision< count:  
                
              hitposition= collider.GetIntersection(collision)["hitpos"]  
                
              #Limit the accuracy of Zpositions in the HitList because of the rounding errors with real type calculation at BitLimit with a precision value  
              hitposition.z= round(hitposition.z,precision)  
            
              #Fit the voxel´s Zposition in the grid  
              if hitposition.z>= 0:  
                  hitposition.z= (math.ceil(hitposition.z/zdim))*zdim  
                  if Zcount % 2== 0:  
                      hitposition.z= hitposition.z-zdim/2  
                  else:  
                      hitposition.z= hitposition.z-zdim  
              else:  
                  hitposition.z= (math.floor(hitposition.z/zdim))*zdim  
                  if Zcount % 2== 0:  
                      hitposition.z= hitposition.z+zdim/2  
                  else:  
                      hitposition.z= hitposition.z+zdim  
                    
                
              if hitposition.z not in HitList:  
                                    
                  backface= collider.GetIntersection(collision)["backface"]   
                                  
                  HitList.append(hitposition.z)  
                  PosList.append([hitposition,backface])  
                    
              collision+= 1  
                
                
      #Sort the PosList in Zdirection  
      PosList= sorted(PosList,key= lambda x: x[0][2])   
        
        
      #Place voxels on every unique collision position  
      #and fill the gaps between them with voxels    
      for i,position in enumerate(PosList) :  
          if position[1]== False and PosList[i-1][1]!= False :  
              x= position[0].x  
              y= position[0].y  
              z= position[0].z  
                
              z1= PosList[(i-1)][0].z  
                
              zdiv= ((z-z1)/zdim)-1  
              i= +1          
              for g in xrange(int(zdiv)) :  
                
                  z2= z-(g+1)*zdim  
                  vec2= c4d.Vector(x,y,z2)  
                    
                    
                  parSys.SetGroup(pcounter, parGroup)  
                  parSys.SetLife(pcounter, parLife)  
                  parSys.SetPosition(pcounter, (vec2*matr))   
                    
                  pcounter+= 1  
          parSys.SetGroup(pcounter, parGroup)  
          parSys.SetLife(pcounter, parLife)  
          parSys.SetPosition(pcounter, (position[0]*matr))           
          pcounter+= 1             
      return pcounter  
        
      
    def main() :  
      #Timer start  
      t= c4d.GeGetTimer()  
        
      #Make sure there is an active object  
      if op== None: return None  
        
      #Start an undo sequence  
      doc.StartUndo()  
        
      #Instantiate the object and reset the axis to boundingbox center:  
      #Ray collider works in local space, therefore axis reset is needed  
      testop= SendModelingCommand(command= c4d.MCOMMAND_CURRENTSTATETOOBJECT, list= [op.GetClone()], doc= doc)  
      if not testop : return  
      obj= testop[0]  
      axisreset(obj)  
        
      #Set voxel gaps:  
      xgap= 1  
      ygap= 1  
      zgap= 1  
        
      #Set voxel dimension:  
      xdim= 30  
      ydim= 30  
      zdim= 30  
        
      #Calculate the voxelgrid:  
      #If a mulitple of voxeldimension fits exactly into boundigboxsize, take it.  
      #Otherwise round up the count with a ceiling function, that the voxels covers the mesh  
      boundingbox= obj.GetRad()*2  
      
      CheckXcount= boundingbox.x/xdim  
      CeilXcount= math.ceil(CheckXcount)  
      if c4d.utils.CompareFloatTolerant(CheckXcount+1.0, CeilXcount)== True:  
          Xcount= CheckXcount  
      else:  
          Xcount= CeilXcount  
        
      CheckYcount= boundingbox.y/ydim  
      CeilYcount= math.ceil(CheckYcount)  
      if c4d.utils.CompareFloatTolerant(CheckYcount+1.0, CeilYcount)== True:  
          Ycount= CheckYcount  
      else:  
          Ycount= CeilYcount  
        
      CheckZcount= boundingbox.z/zdim   
      CeilZcount= math.ceil(CheckZcount)  
      if c4d.utils.CompareFloatTolerant(CheckZcount+1.0, CeilZcount)== True:  
          Zcount= CheckZcount  
      else:  
          Zcount= CeilZcount  
        
      #Calculate the origin of the VoxelGrid  
      Xorg= (Xcount*xdim)/2-xdim/2  
      Yorg= (Ycount*ydim)/2-ydim/2  
      Zorg= (Zcount*zdim)/2  
        
        
        
      #Basesettings of particles  
      if doc.GetParticleSystem()== None:  
          dialog=gui.MessageDialog("could not find 'Thinking Particles' module",c4d.GEMB_OK)  
          if dialog== 1:  
              return  
      else:  
          parSys= doc.GetParticleSystem()  
            
      parSys.FreeAllParticles()  
      doc.AddUndo(c4d.UNDOTYPE_CHANGE, parSys)  
      parMax= int((Xcount+1)*(Ycount+1)*(Zcount+1))  
        
      #Warn the user that the particles may be out of range  
      #don´t know how to access this Value:______________[IDC_TP_NUMLIMIT]____________________________________________  
      if parMax>100000:  
          dialog=gui.MessageDialog("Particle count exceeded! \nPlease raise the 'Max. Partikel' value.",c4d.GEMB_OK)  
            
      parGroup= parSys.AllocParticleGroup()  
      parGroup.SetTitle(op.GetName())  
      parGroup[c4d.PGROUP_NAME]= (str(op.GetName())+"_Voxels")  
      parGroup[c4d.PGROUP_VIEWTYPE]= 0  
      parGroupGlob= parSys.GetRootGroup()  
      parSys.SetPGroupHierarchy(parGroupGlob, parGroup, c4d.TP_INSERT_UNDERFIRST)  
      parLife= doc.GetMaxTime()  
      parSys.AllocParticles(parMax)  
      
      
      
      #Set the direction and length of the ray that the whole boundingbox is hit by the ray  
      rayzdirection= c4d.Vector(0,0,-1)  
      raylength= boundingbox.z+zdim  
         
      #Send rays  
      pcounter= 0   
      for py in xrange(int(Ycount)) :  
            
          y= float(ydim*py)  
          for px in xrange(int(Xcount)) :  
                
              x= float(xdim*px)      
              Zgrid= c4d.Vector((Xorg-x),(Yorg-y),(Zorg+zdim/2))  
              #recursivly raising the counter and call the rayfunction  
              pcounter= ray(obj,Zgrid,rayzdirection,raylength,xdim,ydim,zdim,Zcount,parSys,parGroup,parLife,pcounter)  
        
        
            
      #Objects,Tag: set and insert  
      doc.AddUndo(c4d.UNDOTYPE_CHANGE, op)  
      op.SetEditorMode(c4d.MODE_OFF)  
      op.SetRenderMode(c4d.MODE_OFF)      
        
      cloner= c4d.BaseObject(1018544)   
      cloner[c4d.ID_MG_MOTIONGENERATOR_MODE]= 0  
      cloner[c4d.MG_OBJECT_LINK]= parGroup  
      cloner[c4d.MGCLONER_FIX_CLONES]= False  
      cloner[c4d.MG_OBJECT_ALIGN]= True  
      cloner.SetName(str(op.GetName())+"_Voxels")  
      doc.InsertObject(cloner)  
        
      display= cloner.MakeTag(c4d.Tdisplay)  
      display[c4d.DISPLAYTAG_AFFECT_DISPLAYMODE]= True  
      display[c4d.DISPLAYTAG_SDISPLAYMODE]= 4  
      display[c4d.DISPLAYTAG_WDISPLAYMODE]= 2  
      doc.AddUndo(c4d.UNDOTYPE_NEW,cloner)  
        
      vox= voxel(xdim-xgap,ydim-ygap,zdim-zgap)  
      vox.SetName("Voxel")  
      vox.InsertUnder(cloner)   
      doc.AddUndo(c4d.UNDOTYPE_NEW,vox)    
          
      random= c4d.BaseObject(1018643)  
      random.SetName("Voxel_Color")  
      random.InsertBefore(cloner)   
      doc.AddUndo(c4d.UNDOTYPE_NEW,random)  
        
      random[c4d.ID_MG_BASEEFFECTOR_COLOR_MODE]= 3  
      random[c4d.ID_MG_BASEEFFECTOR_POSITION_ACTIVE]= False  
        
      inexcludeList= cloner[c4d.ID_MG_MOTIONGENERATOR_EFFECTORLIST]  
      inexcludeList.InsertObject(random, 1)  
      cloner[c4d.ID_MG_MOTIONGENERATOR_EFFECTORLIST]= inexcludeList  
        
      doc.EndUndo()  
      c4d.EventAdd(c4d.EVENT_FORCEREDRAW)  
        
        
      time1= c4d.GeGetTimer() - t  
      print str(parSys.GetGroupParticleCount(parGroup, subgroups= False))+" build in "+ str(time1) + "msec"  
        
        
    if __name__=='__main__':  
      main()  
      
    


  • On 13/05/2014 at 07:15, xxxxxxxx wrote:

    Incredible speed. 
    Thanks!



  • On 14/05/2014 at 01:42, xxxxxxxx wrote:

    I´m glad you tested it, Pim !
    1000000 voxels in 2531msec was my fastest approach, great so far!
    I jumped into programming a few months ago and this is my first a little bit more complex script, therefore if anyone has some criticism to pass or some improvements, I would be thankful.
    Cheers
    Martin



  • On 14/05/2014 at 06:29, xxxxxxxx wrote:

    It is looking very good. 
    So nothing major, just some observations:
    - give out a message when no object is selected
    - why not just create a Cube instead of using your voxel function?
    - the particle group has the same name as the cloner. For me that was a bit confusing.
    Also you seem to set the particle group name twice:
        parGroup.SetTitle(op.GetName())
        parGroup[c4d.PGROUP_NAME]= (str(op.GetName())+"_Voxels")

    To see the difference in the 2 approaches (algorithms) I took a standard cinema 4d figure object, transformed it to polygons and used the 2 ways.
    - using raytracing it took about 4 mseconds and 336 voxels
    - using "polygon vertex in voxel" it took about 4 seconds and 1013 voxels (displayed timing seems wrong)
    Note: the number of voxels depends on the given max surface. Here 200.



  • On 14/05/2014 at 12:40, xxxxxxxx wrote:

    Thanks for your comments!
    - dialog for  if op== None , useful idea.
    - I intended to write a remesher with this, therefore a self built cube object was   
      needed and if the user wanted to convert the cloner it would be one click less.
      On the other hand a fillet option would be nice, too.
    - And yes, it should be called Voxel_Group.

    I´m really curious finding out which method will be faster and more reliable at the end of the day;
    the ray or the brute force method.
    Especially if it comes to a denser voxelgrid or a denser mesh.
    Is it possible for you to test it with a dimension of maybe 5 units?
    Or  - if you are interested - I would write you an E-mail and we could compare the scripts during the development.

    Another improvement of the ray method will be setting the x axis to the shortest boundingbox expansion to minimize the grid resolution. I´ve missed to implement it to the axis reset function.
    This is most obvious with the c4d figure object.

    It seems that your voxelgrid is not centered right, cause the figure object is symmetrical but your voxelobject isn't.

    Cheers!
    Martin



  • On 15/05/2014 at 00:00, xxxxxxxx wrote:

    I already saw that increasing the nr of voxels or polygons has a dramatic impact on the brute force and less on the ray method.
    I will send you an email and start some developing together.
    -Pim



  • On 15/05/2014 at 03:36, xxxxxxxx wrote:

    The ray method has also some deficiencies right now.
    But let´s figure this out and develop it together.
    I´ll mail you.
    Cheers
    Martin


Log in to reply