ポリゴンメッシュで、稜線の一直線上に複数の頂点がある場合に最適化したい (スクリプト)

線形状からポリゴンメッシュに変換した場合など、
面を構成する稜線の一直線上に複数の頂点が含まれる場合があります。
このときに、角を構成する頂点以外を削除して最適化します。
頂点のクリーン」機能では、削除する頂点のみを選択する必要があります。

以下のスクリプトを実行すると、
ブラウザで選択されたポリゴンメッシュにて、面の稜線の一直線上に複数の頂点がある場合に
頂点を除去し最適化します。


import numpy
import math

scene = xshade.scene()

fMin = 1e-5

# -----------------------------------------------.
# ポリゴンメッシュの面を構成しない頂点や稜線を削除.
# @param[in] shape  ポリゴンメッシュクラス.
# -----------------------------------------------.
def cleanupPolygonmesh (shape):
    # 面で参照される頂点を取得.
    versCou  = shape.total_number_of_control_points
    facesCou = shape.number_of_faces
    vUsedList = [0] * versCou
    for fLoop in range(facesCou):
        f = shape.face(fLoop)
        vCou = f.number_of_vertices
        for i in range(vCou):
            vUsedList[ f.vertex_indices[i] ] = 1

    # 参照されない頂点を削除.
    shape.begin_removing_control_points()

    for i in range(versCou):
        if vUsedList[i] == 0:
            shape.remove_control_point(i)

    shape.end_removing_control_points()

    shape.update()

# -----------------------------------------------.
# 面の頂点のうち、一番3点が作る角度が大きい頂点を取得.
# @param[in] faceD  ポリゴンメッシュクラス.
# -----------------------------------------------.
def getMaxAngleInFace (faceD):
    vCou = faceD.number_of_vertices
    if vCou <= 2:
        return -1
    
    maxIndex = -1
    maxAngle = 1.0
    for i in range(vCou):
        prevV = shape.vertex(faceD.vertex_indices[(i - 1 + vCou) % vCou]).position
        curV  = shape.vertex(faceD.vertex_indices[i]).position
        nextV = shape.vertex(faceD.vertex_indices[(i + 1) % vCou]).position

        prevV = numpy.array([prevV[0], prevV[1], prevV[2]])
        curV  = numpy.array([curV[0], curV[1], curV[2]])
        nextV = numpy.array([nextV[0], nextV[1], nextV[2]])

        lenV1 = numpy.linalg.norm(curV - prevV)
        if lenV1 < fMin:
            continue
        lenV2 = numpy.linalg.norm(nextV - curV)
        if lenV2 < fMin:
            continue

        dirV1 = (prevV - curV) / lenV1
        dirV2 = (nextV - curV) / lenV2
        cAngleV = math.fabs(numpy.dot(dirV1, dirV2))
        if maxIndex < 0 or (cAngleV < maxAngle and cAngleV < 0.999):
            maxIndex = i
            maxAngle = cAngleV

    if maxIndex < 0:
        maxIndex = 0
    
    return maxIndex

# -----------------------------------------------.
# 指定の面の頂点を指定して追加.
# UVやfaceGroup、頂点カラーについても考慮.
# @param[in] shape        ポリゴンメッシュクラス.
# @param[in] faceIndex    面番号.
# @param[in] indicesList  面の頂点インデックス。-1の場合は面の頂点を削除する.
# -----------------------------------------------.
def appendNewFace (shape, faceIndex, indicesList):
    vCou = len(indicesList)
    if vCou < 3:
        return False

    uvLayersCou = shape.get_number_of_uv_layers()
    vertexColorCou = shape.number_of_vertex_color_layers
    f = shape.face(faceIndex)

    faceGroupIndex = shape.get_face_group_index(faceIndex)

    # 新しい面の頂点インデックス.
    newFaceV = []
    for i in range(vCou):
        if indicesList[i] >= 0:
            newFaceV.append(indicesList[i])

    # 新しい面のUV.
    newFaceUVs = []
    for i in range(uvLayersCou):
        uvs = []
        for j in range(vCou):
            if indicesList[j] >= 0:
                uvs.append(f.get_face_uv(i, j))
        newFaceUVs.append(uvs)
    
    # 新しい面の頂点カラー.
    newFaceColors = []
    for i in range(vertexColorCou):
        colors = []
        for j in range(vCou):
            if indicesList[j] >= 0:
                colors.append(f.get_vertex_color(i, j))
        newFaceColors.append(colors)

    if len(newFaceV) < 2:
        return False

    # polygon_meshに面情報を追加.
    newFaceIndex = shape.number_of_faces
    shape.append_face(newFaceV)

    newF = shape.face(newFaceIndex)
    newVCou = newF.number_of_vertices
    for i in range(uvLayersCou):
        uvs = newFaceUVs[i]
        for j in range(newVCou):
            newF.set_face_uv(i, j, uvs[j])

    for i in range(vertexColorCou):
        colors = newFaceColors[i]
        for j in range(newVCou):
            newF.set_vertex_color(i, j, colors[j])

    shape.set_face_group_index(newFaceIndex, faceGroupIndex)

    return True

# -----------------------------------------------.
# 指定のポリゴンメッシュで、面ごとに直線上の頂点を取得.
# -----------------------------------------------.
def cleanupMeshVertices (shape):
    if shape.type != 7:   # ポリゴンメッシュでない場合.
        return None

    versCou  = shape.total_number_of_control_points
    facesCou = shape.number_of_faces

    removeFaceList = []

    # 各頂点が共有する面の数を保持.
    verFacesCouList = [0] * versCou
    for fLoop in range(facesCou):
        f = shape.face(fLoop)
        vCou = f.number_of_vertices
        for i in range(vCou):
            vIndex = f.vertex_indices[i]
            verFacesCouList[vIndex] += 1

    for fLoop in range(facesCou):
        f = shape.face(fLoop)

        # 角のある頂点を取得.
        maxVIndex = getMaxAngleInFace(f)
        if maxVIndex < 0:
            removeFaceList.append(fLoop)
            continue

        # 面で削除する頂点番号を保持.
        vCou = f.number_of_vertices

        chkF = False
        fVIndexList = [0] * vCou
        for i in range(vCou):
            fVIndexList[i] = f.vertex_indices[i]

        vIndex0 = maxVIndex
        for i in range(vCou):
            vIndex1 = (maxVIndex + i + 1 + vCou) % vCou

            vI1 = f.vertex_indices[vIndex1]
            p0 = shape.vertex(f.vertex_indices[vIndex0]).position
            p1 = shape.vertex(vI1).position
            p0 = numpy.array([p0[0], p0[1], p0[2]])
            p1 = numpy.array([p1[0], p1[1], p1[2]])
            lenV = numpy.linalg.norm(p1 - p0)
            if lenV < fMin:
                fVIndexList[vIndex1] = -1
                chkF = True
                continue

            vIndex2 = (maxVIndex + i + 2 + vCou) % vCou
            p2 = shape.vertex(f.vertex_indices[vIndex2]).position
            p2 = numpy.array([p2[0], p2[1], p2[2]])
            lenV2 = numpy.linalg.norm(p2 - p1)
            if lenV2 < fMin:
                fVIndexList[vIndex1] = -1
                chkF = True
                continue

            vDir1 = (p1 - p0) / lenV
            vDir2 = (p2 - p1) / lenV2
            cAngleV = math.fabs(numpy.dot(vDir1, vDir2))
            if cAngleV > 0.99 and verFacesCouList[vI1] <= 2:
                fVIndexList[vIndex1] = -1
                chkF = True
                continue

            vIndex0 = vIndex1
        
        # 新しく面を追加.
        if chkF:
            appendNewFace(shape, fLoop, fVIndexList)
            removeFaceList.append(fLoop)

    if len(removeFaceList) > 0:
        shape.make_edges()
        shape.update()

        # 古い面を削除.
        shape.begin_removing_faces()
        for fIndex in removeFaceList:
            shape.remove_face(fIndex)
        shape.end_removing_faces()
        shape.update()

        # 面から参照されない頂点を削除.
        cleanupPolygonmesh(shape)

    return shape

# -------------------------------------------------.
# 選択されたポリゴンメッシュの頂点を最適化.
# -------------------------------------------------.
newShapesList = []
for shape in scene.active_shapes:
    nShape = cleanupMeshVertices(shape)
    if nShape != None:
        newShapesList.append(nShape)

if len(newShapesList) == 0:
    print 'ポリゴンメッシュを選択してください。'
else:
    scene.active_shapes = newShapesList

使い方

以下のような、ポリゴンメッシュの面で頂点が直線上に複数並んでいる形状があるとします。

これをブラウザで選択し、上記のスクリプトを実行します。
頂点の選択は不要です。
複数ポリゴンメッシュを選択してまとめて対応できます。

以下のように、一直線上にある頂点が整理され頂点が削減されました。

このスクリプトでは、頂点/UV/フェイスグループ/頂点カラーが最適化で考慮されます。
なお、UNDO/REDOには対応していません。

また、形状によっては頂点が最適化されることにより、わずかに輪郭が変化する場合があります。
これは上記スクリプトのcleanupMeshVertices内の
「if cAngleV > 0.99」の数値を「0.999」など1.0に近づけることで精度を上げることができます。

この記事のURLとタイトルをコピーする
Translate »