### voxelize meshes v.3
### http://zoomy.net/2010/02/25/voxelize-meshes-script-v2/
### this script turns the selected meshes into cubes
### over the current timeline range.

import maya.OpenMaya as om
import maya.cmds as cmds
import maya.mel as mel

# shoot a ray from point in direction and return all hits with mesh
def rayIntersect(mesh, point, direction=(0.0, 0.0, -1.0)):
  # get dag path of mesh - so obnoxious
  om.MGlobal.clearSelectionList()
  om.MGlobal.selectByName(mesh)
  sList = om.MSelectionList()
  om.MGlobal.getActiveSelectionList(sList)
  item = om.MDagPath()
  sList.getDagPath(0, item)
  item.extendToShape()
  fnMesh = om.MFnMesh(item)

  raySource = om.MFloatPoint(point[0], point[1], point[2], 1.0)
  rayDir = om.MFloatVector(direction[0], direction[1], direction[2])
  faceIds            = None
  triIds             = None
  idsSorted          = False
  worldSpace         = om.MSpace.kWorld
  maxParam           = 99999999
  testBothDirections = False
  accelParams        = None
  sortHits           = True
  hitPoints          = om.MFloatPointArray()
  hitRayParams       = om.MFloatArray()
  hitFaces           = om.MIntArray()
  hitTris            = None
  hitBarys1          = None
  hitBarys2          = None
  tolerance          = 0.0001

  hit = fnMesh.allIntersections(raySource, rayDir, faceIds, triIds, idsSorted, worldSpace, maxParam, testBothDirections, accelParams, sortHits, hitPoints, hitRayParams, hitFaces, hitTris, hitBarys1, hitBarys2, tolerance)

  # clear selection as may cause problems if called repeatedly
  om.MGlobal.clearSelectionList()
  result = []
  for x in range(hitPoints.length()):
    result.append((hitPoints[x][0], hitPoints[x][1], hitPoints[x][2]))
  return result

# round to nearest fraction in decimal form: 1, .5, .25
def roundToFraction(input, fraction):
  factor = 1/fraction
  return round(input*factor)/factor

# progress bar, enabling "Esc"
def makeProgBar(length):
  global gMainProgressBar
  gMainProgressBar = mel.eval('$tmp = $gMainProgressBar');
  cmds.progressBar( gMainProgressBar,
        edit=True,
        beginProgress=True,
        isInterruptable=True,
        maxValue=length
        )

def promptNumber():
  result = cmds.promptDialog(
      title='Grow Shrub',
      message='Block size:',
      text="1",
      button=['OK', 'Cancel'],
      defaultButton='OK',
      cancelButton='Cancel',
      dismissString='Cancel')
  if result == 'OK':
    return float(cmds.promptDialog(query=True, text=True))
  else: return 0

def setLocs(mesh):
  global xmin, xmax, ymin, ymax, zmin, zmax, xLocs, yLocs, zLocs
  bb = cmds.exactWorldBoundingBox(mesh)
  xmin = bb[0]
  ymin = bb[1]
  zmin = bb[2]
  xmax = bb[3]
  ymax = bb[4]
  zmax = bb[5]
  
  # make 3 arrays of ray start points, one for each axis
  # this is necessary because rays aren't likely to catch surfaces
  # which are edge-on... so to make sure to catch all faces,
  # we shoot rays along each axis
  xLocs = []
  yLocs = []
  zLocs = []

  fac = 1/cubeSize
  for y in range(int(ymin*fac), int(ymax*fac+1)):
    for z in range(int(zmin*fac), int(zmax*fac+1)):
      loc = (xmax, y*cubeSize, z*cubeSize)
      xLocs.append(loc)
  for z in range(int(zmin*fac), int(zmax*fac+1)):
    for x in range(int(xmin*fac), int(xmax*fac+1)):
      loc = (x*cubeSize, ymax, z*cubeSize)
      yLocs.append(loc)
  for x in range(int(xmin*fac), int(xmax*fac+1)):
    for y in range(int(ymin*fac), int(ymax*fac+1)):
      loc = (x*cubeSize, y*cubeSize, zmax)
      zLocs.append(loc)
  
# start the action
if len(cmds.ls(sl=1)) == 0:
  result = cmds.confirmDialog( title='Mesh selection', message= 'Please select a mesh.', button=['OK'])
else:
  startTime= cmds.timerX()
  cubeSize = promptNumber()
  global cubeDict, xmin, xmax, ymin, ymax, zmin, zmax, xLocs, yLocs, zLocs
  cubeDict = {}

  # set selected objects to be the shape targets, aka controls
  ctrl = cmds.ls(sl=1)

  firstFrame = int(cmds.playbackOptions(query=1, min=1))
  lastFrame = int(cmds.playbackOptions(query=1, max=1))
  duration = int(lastFrame-firstFrame)

  makeProgBar(duration*len(ctrl))
  cmds.progressBar(gMainProgressBar, edit=1, beginProgress=1)

  print "Animating visibility over", duration, "frames..."
  print "Press ESC to cancel"

  # animate cube visibility
  resetList = []
  for f in range(firstFrame,lastFrame+1): # for each frame
    cmds.currentTime(f, edit=1, update=1)
    # if the cube was visible last frame, hide it
    for x in resetList:
      cmds.setKeyframe(x, at="scale", v=0, t=f)
    resetList = []
    directions = [(-1.0, 0.0, 0,0), (0.0, -1.0, 0,0), (0.0, 0.0, -1.0)]
    # for every target control object:
    for c in ctrl:
      if cmds.progressBar(gMainProgressBar, query=1, isCancelled=1 ):
        break
      cmds.progressBar(gMainProgressBar, edit=1, step=1)
      
      setLocs(c)
      locArrays = [xLocs, yLocs, zLocs]
     
      # for each axis:
      for i in range(3):
        cmds.flushUndo()
        # for every gridpoint:
        for loc in locArrays[i]:
          hits = []
          # zap a ray thrugh the object
          hits = rayIntersect(c, loc, directions[i])
          for x in hits:
            # snap hit locations to cubegrid
            x = (roundToFraction(x[0], cubeSize), roundToFraction(x[1], cubeSize), roundToFraction(x[2], cubeSize) )
            # if location isn't in cubeDict, add it and a new cube
            if x not in cubeDict:
              cubeDict[x] = cmds.polyCube(sz=1, sy=1, sx=1, cuv=4, d=cubeSize, h=cubeSize, w=cubeSize, ch=1)[0]
              # move cube to quantized location
              cmds.xform(cubeDict[x], t=x)
            # set a scale key
            cmds.setKeyframe(cubeDict[x], at="scale", v=1, t=f)
            # if previous frame didn't have a scale key, set it to 0
            tempCurTime = cmds.currentTime(q=1)-1
            lastKey = cmds.keyframe(cubeDict[x], at="scale", q=1, t=(tempCurTime,tempCurTime), valueChange=1)
            if lastKey == None or lastKey[0] != 1.0:
              cmds.setKeyframe(cubeDict[x], at="scale", v=0, t=(f-1))
            # add cube to resetList
            resetList.append(cubeDict[x])
    cmds.currentTime(f, edit=1, update=1)

  cmds.progressBar(gMainProgressBar, edit=1, endProgress=1)
  totalTime = cmds.timerX(startTime=startTime)
  print("Total Time: "+str(totalTime))

### end voxelize meshes v.3
