2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

MayaAdvent Calendar 2023

Day 15

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

Posted at

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

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

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

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

こんな感じかなぁ

実装

まずはBBOXをcubeで満たそう

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

image.png

浮動小数点の罠

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

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

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

    numX = (bbox[3] - bbox[0]) / cubeSize
    #12.000001192092894
    
    numY = (bbox[4] - bbox[1]) / cubeSize
    #11.999999999999998

これを intで包んでしまったので

12.000001192092894 -> 12
11.999999999999998 -> 11

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

雑ならラウンドを追加しますか。
※こういう関数名は後で絶対後悔しますね!

def theTwoRound(input):
    return int((input * 2 + 1) // 2)    

改めて実行

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

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

確認

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

反省&改修案

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

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

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

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?