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