上周部门内部进行分享知识讲座时讲到一个词Voxelization,也就是体素化,也就是用某些东西充满一个物体的内部,如下面这两张图,用小方块充满圆环和锥体。
其主要算法也有多种,我这里采用的是用boundingbox来切割物体的方法,如下图:
就是先求出一个模型的boundingbox,然后以一定的step对boundingbox的长宽高进行切割,然后从这些黄色的交点,按照某一方向,向模型打点,如果是奇数个点就证明是在模型内部,然后在这个坐标位置处创建个box或其他的什么模型都行,偶数个点则证明在模型外部,不予处理就行。
下面这个是Voxel的生成器,需要输入三个参数: mesh, typeIndex, density
mesh要体素化的模型
typeIndex是world还是local模式
density是创建盒子的密度,默认是2,不能低于2
import maya.OpenMaya as om
import maya.OpenMayaMPx as omp
class VoxelGenerator(object):
World = 0
Local = 1
def __init__(self, inputMesh, typeIndex, density):
self.density = density
self.typeIndex = typeIndex
self.meshDagPath = om.MDagPath.getAPathTo(inputMesh)
self.mexclusive = self.meshDagPath.exclusiveMatrix()
self.borderOffset = 0.01
self.voxelObjects = om.MObjectArray()
self.bbox = self.getBoundingBox()
self.xlen = self.bbox.max().x - self.bbox.min().x
self.ylen = self.bbox.max().y - self.bbox.min().y
self.zlen = self.bbox.max().z - self.bbox.min().z
minEdge = self.xlen
if minEdge > self.ylen:
minEdge = self.ylen
if minEdge > self.zlen:
minEdge = self.zlen
self.step = (minEdge - 2 * minEdge * self.borderOffset) / (self.density - 1.0)
def decimalRange(self, start, end, step):
while start < end:
yield start
start += step
def pointToMObject(self, name):
selectionList= om.MSelectionList()
_mobject = om.MObject()
selectionList.add(name)
selectionList.getDependNode(0, _mobject)
return _mobject
def generateVoxel(self):
outerPoint = om.MFloatPoint(self.bbox.max().x * 2, self.bbox.max().y * 2, self.bbox.max().z * 2)
mfnMesh = om.MFnMesh(self.meshDagPath)
transformationMatrix = om.MTransformationMatrix(self.mexclusive)
rotation = transformationMatrix.rotation()
rayDirection = om.MFloatVector(outerPoint)
faceIds = None
triIds = None
idsSorted = False
space = om.MSpace.kWorld
maxParam = 999999
testBothDirections = False
accelParams = None
sortHits = True
hitPoints = om.MFloatPointArray()
hitRayParams = om.MFloatArray()
hitFaces = om.MIntArray()
voxelElement_mfnTransform = om.MFnTransform()
dagModifier = om.MDagModifier()
for x in self.decimalRange((self.bbox.min().x + self.xlen * self.borderOffset), self.bbox.max().x, self.step):
for y in self.decimalRange((self.bbox.min().y + self.ylen * self.borderOffset), self.bbox.max().y, self.step):
for z in self.decimalRange((self.bbox.min().z + self.zlen * self.borderOffset), self.bbox.max().z, self.step):
point = om.MPoint(x, y, z)
if self.typeIndex == VoxelGenerator.Local:
point = self.localToWorldSpace(point)
fPoint = om.MFloatPoint(point.x, point.y, point.z)
hitPoints.clear()
mfnMesh.allIntersections(fPoint,
rayDirection,
faceIds,
triIds,
idsSorted,
space,
maxParam,
testBothDirections,
accelParams,
sortHits,
hitPoints,
hitRayParams,
hitFaces,
None, None, None,
0.000001)
if hitPoints.length() % 2 == 0:
continue
# voxelElement = dagModifier.createNode('locator')
pcube = cmds.polyCube(w=1, h=1, d=1, sx=1, sy=1, sz=1)
voxelElement = self.pointToMObject(pcube[0])
voxelElementMDagPath = om.MDagPath.getAPathTo(voxelElement)
self.voxelObjects.append(voxelElement)
voxelElement_mfnTransform.setObject(voxelElementMDagPath)
voxelElement_mfnTransform.setTranslation(om.MVector(fPoint.x, fPoint.y, fPoint.z), om.MSpace.kWorld)
if self.typeIndex == VoxelGenerator.Local:
voxelElement_mfnTransform.setRotation(rotation)
dagModifier.doIt()
voxelCount = self.voxelObjects.length()
return voxelCount
def localToWorldSpace(self, point):
mInclusive = self.meshDagPath.inclusiveMatrix()
point = point * mInclusive
return point
def deleteVoxel(self):
try:
for i in range(self.voxelObjects.length()):
om.MGlobal.deleteNode(self.voxelObjects[i])
except Exception as e:
print e
self.voxelObjects.clear()
def getBoundingBox(self):
meshDagNode = om.MFnDagNode(self.meshDagPath)
bbox = meshDagNode.boundingBox()
if self.typeIndex == VoxelGenerator.World:
bbox.transformUsing(self.mexclusive)
return bbox
测试代码:
if __name__ == '__main__':
selectionList= om.MSelectionList()
_cone= om.MObject()
selectionList.add('pCone1')
selectionList.getDependNode(0, _cone)
vg = VoxelGenerator(_torus, 0, 8)
count = vg.generateVoxel()
print count
# vg.deleteVoxel()
这里说明下,加了borderOffset这个参数的目的主要是让其起始位置不要定在boundingbox的边界上,因为那个肯定不在模型的内部,所以向里略微偏移一点点,然后在对模型进行切割。
从某个点沿着某个方向向模型打点,自己就不多解释了,很常用的算法。
有了这个生成器之后,我们就可以写成一个节点了,当我们调节节点的density的时候,生成的小盒子会实时更新,
import sys
import time
import maya.OpenMaya as om
import maya.OpenMayaMPx as omp
kPluginNodeTypeName = 'voxelizer'
voxelizerNodeId = om.MTypeId(0x9500)
class Voxelizer(omp.MPxNode):
voxelType = om.MObject()
inputMesh = om.MObject()
output = om.MObject()
voxelDensity = om.MObject()
def __init__(self):
omp.MPxNode.__init__(self)
self.grid = None
def compute(self, plug, dataBlock):
meshDataHandle = dataBlock.inputValue(Voxelizer.inputMesh)
connectedMesh = meshDataHandle.asMesh()
meshApiType = connectedMesh.apiType()
if meshApiType == om.MFn.kInvalid:
om.MGlobal.displayInfo('No mesh connected')
dataBlock.setClean(plug)
return False
elif meshApiType == om.MFn.kMeshData:
start = time.time()
# get connected mesh
mesh_plug = om.MPlug(self.thisMObject(), Voxelizer.inputMesh)
plugs = om.MPlugArray()
mesh_plug.connectedTo(plugs, True, True)
real_connectedMesh = plugs[0].node()
voxelTypeDataHandle = dataBlock.inputValue(Voxelizer.voxelType)
voxelType = voxelTypeDataHandle.asShort()
densityDataHandle = dataBlock.inputValue(Voxelizer.voxelDensity)
density = densityDataHandle.asShort()
if self.grid:
self.grid.deleteVoxel()
self.grid = VoxelGenerator(real_connectedMesh, voxelType, density)
voxelCount = self.grid.generateVoxel()
outputHandle = dataBlock.outputValue(Voxelizer.output)
outputHandle.setInt(voxelCount)
end = time.time()
om.MGlobal.displayInfo('calculation time: %s' % (end - start))
dataBlock.setClean(plug)
return True
class VoxelGenerator(object):
World = 0
Local = 1
def __init__(self, inputMesh, typeIndex, density):
self.density = density
self.typeIndex = typeIndex
self.meshDagPath = om.MDagPath.getAPathTo(inputMesh)
self.mexclusive = self.meshDagPath.exclusiveMatrix()
self.borderOffset = 0.01
self.voxelObjects = om.MObjectArray()
self.bbox = self.getBoundingBox()
self.xlen = self.bbox.max().x - self.bbox.min().x
self.ylen = self.bbox.max().y - self.bbox.min().y
self.zlen = self.bbox.max().z - self.bbox.min().z
minEdge = self.xlen
if minEdge > self.ylen:
minEdge = self.ylen
if minEdge > self.zlen:
minEdge = self.zlen
self.step = (minEdge - 2 * minEdge * self.borderOffset) / (self.density - 1.0)
def decimalRange(self, start, end, step):
while start < end:
yield start
start += step
def pointToMObject(self, name):
selectionList= om.MSelectionList()
_mobject = om.MObject()
selectionList.add(name)
selectionList.getDependNode(0, _mobject)
return _mobject
def generateVoxel(self):
outerPoint = om.MFloatPoint(self.bbox.max().x * 2, self.bbox.max().y * 2, self.bbox.max().z * 2)
mfnMesh = om.MFnMesh(self.meshDagPath)
transformationMatrix = om.MTransformationMatrix(self.mexclusive)
rotation = transformationMatrix.rotation()
rayDirection = om.MFloatVector(outerPoint)
faceIds = None
triIds = None
idsSorted = False
space = om.MSpace.kWorld
maxParam = 999999
testBothDirections = False
accelParams = None
sortHits = True
hitPoints = om.MFloatPointArray()
hitRayParams = om.MFloatArray()
hitFaces = om.MIntArray()
voxelElement_mfnTransform = om.MFnTransform()
dagModifier = om.MDagModifier()
for x in self.decimalRange((self.bbox.min().x + self.xlen * self.borderOffset), self.bbox.max().x, self.step):
for y in self.decimalRange((self.bbox.min().y + self.ylen * self.borderOffset), self.bbox.max().y, self.step):
for z in self.decimalRange((self.bbox.min().z + self.zlen * self.borderOffset), self.bbox.max().z, self.step):
point = om.MPoint(x, y, z)
if self.typeIndex == VoxelGenerator.Local:
point = self.localToWorldSpace(point)
fPoint = om.MFloatPoint(point.x, point.y, point.z)
hitPoints.clear()
mfnMesh.allIntersections(fPoint,
rayDirection,
faceIds,
triIds,
idsSorted,
space,
maxParam,
testBothDirections,
accelParams,
sortHits,
hitPoints,
hitRayParams,
hitFaces,
None, None, None,
0.000001)
if hitPoints.length() % 2 == 0:
continue
voxelElement = dagModifier.createNode('locator')
# pcube = cmds.polyCube(w=1, h=1, d=1, sx=1, sy=1, sz=1)
# voxelElement = self.pointToMObject(pcube[0])
voxelElementMDagPath = om.MDagPath.getAPathTo(voxelElement)
self.voxelObjects.append(voxelElement)
voxelElement_mfnTransform.setObject(voxelElementMDagPath)
voxelElement_mfnTransform.setTranslation(om.MVector(fPoint.x, fPoint.y, fPoint.z), om.MSpace.kWorld)
if self.typeIndex == VoxelGenerator.Local:
voxelElement_mfnTransform.setRotation(rotation)
dagModifier.doIt()
voxelCount = self.voxelObjects.length()
return voxelCount
def localToWorldSpace(self, point):
mInclusive = self.meshDagPath.inclusiveMatrix()
point = point * mInclusive
return point
def deleteVoxel(self):
try:
for i in range(self.voxelObjects.length()):
om.MGlobal.deleteNode(self.voxelObjects[i])
except Exception as e:
print e
self.voxelObjects.clear()
def getBoundingBox(self):
meshDagNode = om.MFnDagNode(self.meshDagPath)
bbox = meshDagNode.boundingBox()
if self.typeIndex == VoxelGenerator.World:
bbox.transformUsing(self.mexclusive)
return bbox
def voxelizerCreator():
return omp.asMPxPtr(Voxelizer())
def voxelizerInitializer():
# enum
enumAttr = om.MFnEnumAttribute()
Voxelizer.voxelType = enumAttr.create('voxelType', 'type')
enumAttr.addField('World', 0)
enumAttr.addField('Local', 1)
# typeattr
typedAttr = om.MFnTypedAttribute()
Voxelizer.inputMesh = typedAttr.create('inputMesh', 'iMesh', om.MFnData.kMesh)
typedAttr.disconnectBehavior = om.MFnAttribute.kReset
# output
numAttr = om.MFnNumericAttribute()
Voxelizer.output = numAttr.create('output', 'out', om.MFnNumericData.kInt)
numAttr.isReadable = True
numAttr.isStorable = False
# density
density = om.MFnNumericAttribute()
Voxelizer.voxelDensity = density.create('density', 'den', om.MFnNumericData.kShort)
density.setDefault(5)
density.setMin(2)
Voxelizer.addAttribute(Voxelizer.voxelType)
Voxelizer.addAttribute(Voxelizer.inputMesh)
Voxelizer.addAttribute(Voxelizer.output)
Voxelizer.addAttribute(Voxelizer.voxelDensity)
Voxelizer.attributeAffects(Voxelizer.voxelType, Voxelizer.output)
Voxelizer.attributeAffects(Voxelizer.voxelDensity, Voxelizer.output)
Voxelizer.attributeAffects(Voxelizer.inputMesh, Voxelizer.output)
def initializePlugin(mobject):
mplugin = omp.MFnPlugin(mobject, 'lulongfei', '1.0', 'Any')
try:
mplugin.registerNode(kPluginNodeTypeName, voxelizerNodeId, voxelizerCreator, voxelizerInitializer)
except:
sys.stderr.write('Failed to register node: %s' % kPluginNodeTypeName)
raise
def uninitializePlugin(mobject):
mplugin = omp.MFnPlugin(mobject)
try:
mplugin.deregister(voxelizerNodeId)
except:
sys.stderr.write('Failed to register node: %s' % kPluginNodeTypeName)
raise
测试代码:
import maya.cmds as cmds
voxel = cmds.createNode('voxelizer')
cmds.connectAttr('%s.worldMesh' % 'pTorusShape1', '%s.inputMesh' % voxel)