mayaでボクセルっぽいものを生成

まぁ(必要に迫られて)そんな気分になったんですよ。

ボクセルというかキューブの集合体ですね。
ドット絵というか、マイクラというか、そういう奴

MASHでできるんですけどもねぇ

参考
http://maya.indyzone.jp/2016/10/12/maya2017-mash%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%A6%E3%83%9D%E3%83%AA%E3%82%B4%E3%83%B3%E3%82%B5%E3%83%BC%E3%83%95%E3%82%A7%E3%83%BC%E3%82%B9%E3%82%92%E3%83%9C%E3%82%AF%E3%82%BB%E3%83%AB%E5%8C%96/


考える
-

これをボクセりたい
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1484234/99b12001-c13a-75b3-5018-50c07ddb70b3.png)

- BBOXを取得
- BBOXをcubeで埋め尽くす（cubeのサイズは指定できるように）
- cubeを仕分けする(完全にスフィア外、一部スフィア外、全てスフィア内)
- 完全にスフィア外は削除。　一部スフィア外を削除するかはオプション

こんな感じかなぁ

実装
-

まずはBBOXをcubeで満たそう

```python
def fillBBox(cubeSize,target):
    #getTargetBBox
    bbox = cmds.xform(target, q =True,boundingBox =True,ws=True)

    #bboxのサイズがcubeのサイズの倍数になるように加工
    extendX = ((bbox[3] - bbox[0]) - ((bbox[3] - bbox[0]) / cubeSize)*cubeSize )*0.5
    extendY = ((bbox[4] - bbox[1]) - ((bbox[4] - bbox[1]) / cubeSize)*cubeSize )*0.5
    extendZ = ((bbox[5] - bbox[2]) - ((bbox[5] - bbox[2]) / cubeSize)*cubeSize )*0.5

    ## minX Y Z  maxX Y Z
    ##ピッタリすぎないように念のためのりしろを追加
    bbox = [
            bbox[0] - extendX - cubeSize,
            bbox[1] - extendY - cubeSize,
            bbox[2] - extendZ - cubeSize,

            bbox[3] + extendX + cubeSize,
            bbox[4] + extendY + cubeSize,
            bbox[5] + extendZ + cubeSize,
        ]

    #cubeを配置する為に　数とスタート位置を取得
    numX = int((bbox[3] - bbox[0]) / cubeSize)
    numY = int((bbox[4] - bbox[1]) / cubeSize)
    numZ = int((bbox[5] - bbox[2]) / cubeSize)

    startX = bbox[0] + cubeSize*0.5
    startY = bbox[1] + cubeSize*0.5
    startZ = bbox[2] + cubeSize*0.5


    for i in range(0,numY):
        for ii in range(0,numX):
            for iii in range(0,numZ):
                cube = cmds.polyCube(ch =False,w = cubeSize,h = cubeSize, d = cubeSize)[0]

                cmds.setAttr(cube + ".tx",startX + (cubeSize*ii))
                cmds.setAttr(cube + ".ty",startY + (cubeSize*i))
                cmds.setAttr(cube + ".tz",startZ + (cubeSize*iii))
                
                
fillBBox(0.2,"pSphere1")
```

あれー？　なんか偏るなぁ・・・

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1484234/d9292ced-7a04-acf2-10a2-a4e5cfae0646.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1484234/e1116aa5-7f51-85e5-a429-7ebf722bb23c.png)

浮動小数点の罠
-

色々検証してみたところ、この辺が問題となってる。

```python
    numX = int((bbox[3] - bbox[0]) / cubeSize)
    numY = int((bbox[4] - bbox[1]) / cubeSize)
    numZ = int((bbox[5] - bbox[2]) / cubeSize)
```

https://docs.python.org/ja/3/tutorial/floatingpoint.html

うーん
ちゃんと考えないと駄目ですわね！

```python
    numX = (bbox[3] - bbox[0]) / cubeSize
    #12.000001192092894
    
    numY = (bbox[4] - bbox[1]) / cubeSize
    #11.999999999999998
```
これを intで包んでしまったので

12.000001192092894　-> 12
11.999999999999998  -> 11

となったわけですわね。
んー　小数点第1位で四捨五入・・・かなぁ

雑ならラウンドを追加しますか。
※こういう関数名は後で絶対後悔しますね！
```python
def theTwoRound(input):
    return int((input * 2 + 1) // 2)    
```

改めて実行
```python
from decimal import *

def theTwoRound(input):
    return int((input * 2 + 1) // 2) 
    
def fillBBox(cubeSize,target):
    #getTargetBBox
    bbox = cmds.xform(target, q =True,boundingBox =True,ws=True)

    #bboxのサイズがcubeのサイズの倍数になるように加工
    extendX = ((bbox[3] - bbox[0]) - ((bbox[3] - bbox[0]) / cubeSize)*cubeSize )*0.5
    extendY = ((bbox[4] - bbox[1]) - ((bbox[4] - bbox[1]) / cubeSize)*cubeSize )*0.5
    extendZ = ((bbox[5] - bbox[2]) - ((bbox[5] - bbox[2]) / cubeSize)*cubeSize )*0.5

    ## minX Y Z  maxX Y Z
    bbox = [
            bbox[0] - extendX - cubeSize,
            bbox[1] - extendY - cubeSize,
            bbox[2] - extendZ - cubeSize,

            bbox[3] + extendX + cubeSize,
            bbox[4] + extendY + cubeSize,
            bbox[5] + extendZ + cubeSize,
        ]

    #cubeを配置する為に　数とスタート位置を取得
    numX = theTwoRound((bbox[3] - bbox[0]) / cubeSize)
    numY = theTwoRound((bbox[4] - bbox[1]) / cubeSize)
    numZ = theTwoRound((bbox[5] - bbox[2]) / cubeSize)

    startX = bbox[0] + cubeSize*0.5
    startY = bbox[1] + cubeSize*0.5
    startZ = bbox[2] + cubeSize*0.5


    for i in range(0,numY):
        for ii in range(0,numX): 
            for iii in range(0,numZ):
                cube = cmds.polyCube(ch =False,w = cubeSize,h = cubeSize, d = cubeSize)[0]

                cmds.setAttr(cube + ".tx",startX + (cubeSize*ii))
                cmds.setAttr(cube + ".ty",startY + (cubeSize*i))
                cmds.setAttr(cube + ".tz",startZ + (cubeSize*iii))
                
                
fillBBox(0.2,"pSphere1")
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1484234/da4c2287-4a7b-ef93-d8d9-eb878045e9db.png)


おうけい
あとはこっちの内容を元に各cubeの内外仕分けをすればいいかな

https://qiita.com/9boz/items/69c9a77de4a2e95ea620

確認
-
```python
import maya.cmds as cmds
import maya.api.OpenMaya as om

def getDagNode(target):    
    try:
        sellist = om.MGlobal.getSelectionListByName(target)
        return sellist.getDagPath(0)
    except:
        return None
        
def stringToVector(axis):
    if axis == "x":
        return om.MVector.kXaxisVector 
    elif axis == "y":
        return om.MVector.kYaxisVector 
    elif axis == "z":
        return om.MVector.kZaxisVector 

    elif axis == "-x":
        return om.MVector.kXnegAxisVector
    elif axis == "-y":
        return om.MVector.kYnegAxisVector
    elif axis == "-z":
        return om.MVector.kZnegAxisVector

def checkVtxInside(vtx,mesh):
    meshDagPath = getDagNode(mesh)
    shapeFn = om.MFnMesh(meshDagPath)
    position = cmds.xform(vtx , q =True , ws = True, t =True)
    raySource = om.MFloatPoint(*position)
    space =  om.MSpace.kWorld
                
    id_list = []
    point_list = []
    isInside = True
    for axis in ["x","y","z","-x","-y","-z"]:
        #vtxの位置から全軸方向へrayをとばす
        axisVector = stringToVector(axis)    
        maxParam = 9999
        testBothDirections = False
        accelParams=om.MMeshIsectAccelParams()
        result = shapeFn.closestIntersection(
                                                raySource,
                                                om.MFloatVector(axisVector),
                                                space,
                                                maxParam,
                                                testBothDirections, idsSorted=False, accelParams=accelParams, tolerance=0.0001)    
        
        if result == None:
            isInside = False
            break

    return isInside

def getOutsideVtx(checkTarget,clipGuide):
    meshDagPath = getDagNode(checkTarget)
    shapeFn = om.MFnMesh(meshDagPath) 
    
    outsideVtx = []
    for i in range(0,shapeFn.numVertices):
        #ターゲットのvtxを順番に処理
        isInside = checkVtxInside(checkTarget +".vtx["+str(i)+"]",clipGuide)

        if isInside == False:
            outsideVtx.append(checkTarget +".vtx["+str(i)+"]")

    return outsideVtx

def theTwoRound(input):
    return int((input * 2 + 1) // 2) 
    
def fillBBox(cubeSize,target):
    #getTargetBBox
    bbox = cmds.xform(target, q =True,boundingBox =True,ws=True)

    #bboxのサイズがcubeのサイズの倍数になるように加工
    extendX = ((bbox[3] - bbox[0]) - ((bbox[3] - bbox[0]) / cubeSize)*cubeSize )*0.5
    extendY = ((bbox[4] - bbox[1]) - ((bbox[4] - bbox[1]) / cubeSize)*cubeSize )*0.5
    extendZ = ((bbox[5] - bbox[2]) - ((bbox[5] - bbox[2]) / cubeSize)*cubeSize )*0.5

    ## minX Y Z  maxX Y Z
    bbox = [
            bbox[0] - extendX - cubeSize,
            bbox[1] - extendY - cubeSize,
            bbox[2] - extendZ - cubeSize,

            bbox[3] + extendX + cubeSize,
            bbox[4] + extendY + cubeSize,
            bbox[5] + extendZ + cubeSize,
        ]

    #cubeを配置する為に　数とスタート位置を取得
    numX = theTwoRound((bbox[3] - bbox[0]) / cubeSize)
    numY = theTwoRound((bbox[4] - bbox[1]) / cubeSize)
    numZ = theTwoRound((bbox[5] - bbox[2]) / cubeSize)

    startX = bbox[0] + cubeSize*0.5
    startY = bbox[1] + cubeSize*0.5
    startZ = bbox[2] + cubeSize*0.5

    cubes = []
    for i in range(0,numY):
        for ii in range(0,numX): 
            for iii in range(0,numZ):
                cube = cmds.polyCube(ch =False,w = cubeSize,h = cubeSize, d = cubeSize)[0]

                cmds.setAttr(cube + ".tx",startX + (cubeSize*ii))
                cmds.setAttr(cube + ".ty",startY + (cubeSize*i))
                cmds.setAttr(cube + ".tz",startZ + (cubeSize*iii))
                
                cubes.append(cube)
                
    return cubes
    
clipGuide = "pSphere1"    
cubes = fillBBox(0.2,clipGuide)

cubeDict = {
            "inside":[],
            "outside":[],
            "border":[]
            }

for cube in cubes:
    outsideVtx = getOutsideVtx(cube,clipGuide)
    
    if len(outsideVtx) == 0:
        cubeDict["inside"].append(cube)

    elif len(outsideVtx) == 4:
        cubeDict["outside"].append(cube)        
    else:
        cubeDict["border"].append(cube)
        
cmds.delete(cubeDict["outside"])
cmds.delete(cubeDict["border"])
```

わーい詰まった。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1484234/f5aa5a68-d899-fec3-d6ac-a8a98a94be56.png)

反省&改修案
-
処理が重い。
たかがsphereでも結構時間かかるので、cubeのサイズを小さくし過ぎると恐らくmayaが耐えられません。

であれば、あらかじめvtxの位置かcubeの位置をシミュレートして
別の方法でメッシュを生成すればもう少し軽くなるかしら？

まぁ
MASHで良いと思うんですけどね！