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