猫とゲームエンジン

今日も猫に面倒みられながら生きてる

Maya上でRayを飛ばして衝突点を取得する

Houdiniでやれ

リファレンス

MayaPythonAPI2.0 MFnMesh
MayaPythonAPI2.0 MItMeshVertex

モデルの用意

サンプル用の適当なメッシュを用意している

# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from __future__ import print_function
from __future__ import absolute_import


from maya import cmds
from maya.api import OpenMaya as om2


def create_bridge():
    """
    それっぽいモデルの作成

    :return :[piers_names], plank_name
    :rtype: list[list[str],str]
    """
    piers_num = 20
    piers_radius = 0.2
    piers_height = 2
    plank_margin = 1
    y_offset = 3

    piers = []
    for i in [-1, 1]:
        for j in range(piers_num):
            z_pos = (float(j) / (piers_num - 1) - 0.5) * piers_num
            created_cylinder = cmds.polyCylinder(radius=piers_radius, height=piers_height)
            cmds.xform(created_cylinder, translation=(i, piers_height / 2 + y_offset, z_pos))
            piers.append(created_cylinder)

    plank = cmds.polyCube(height=0.5, depth=piers_num + plank_margin, width=2 + plank_margin)
    cmds.xform(plank, translation=(0, piers_height + y_offset, 0))

    return [piers, plank]


def create_terrain():
    """
    ぼこぼこした地面の作成

    :return: terrain_name
    :rtype: str
    """
    max_height = 3
    base_plane = cmds.polyPlane(width=30, height=30, subdivisionsX=10, subdivisionsY=10)
    cmds.polyMoveVertex(base_plane, translateY=max_height, random=1)
    cmds.xform(base_plane, translation=(0, -max_height/2, 0))
    cmds.polySmooth(base_plane, divisions=2)
    return base_plane


piers, plank = create_bridge()
terrain = create_terrain()

f:id:yosemite_soupcurry:20200517135700p:plain
生成されるメッシュ

衝突点に対して頂点を移動

橋の法線方向が-Y方向のメッシュの頂点を取得し、rayで判定した位置に対して移動している

# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from __future__ import print_function
from __future__ import absolute_import


from maya import cmds
from maya.api import OpenMaya as om2


def get_face_from_direction_y_minus(mesh_dag):
    """
    指定のメッシュからYマイナス方向のフェースを構成する頂点番号を取得する

    :param mesh_dag: dag path from mesh node
    :type mesh_dag: om2.MDagPath
    :return : vertex indices
    :rtype : list[int]
    """
    mit_mesh = om2.MItMeshPolygon(mesh_dag)
    got_indices = []
    while True:
        if mit_mesh.isDone():
            break
        current_normal = mit_mesh.getNormal()
        if current_normal[1] < 0:
            got_indices += mit_mesh.getVertices()
        # MItMeshPolygon の next は無意味引数を求められる(バグ)
        mit_mesh.next(0)

    return got_indices


def ray_transform(ray_src, ray_dest, vertex_indices):
    """
    rayを飛ばして接点に頂点を移動
    今回はレイの方向は特に指定せず下方向で。

    :type ray_src: om2.MDagPath
    :type ray_dest: om2.MDagPath
    :type vertex_indices: list[int]
    :return:
    """
    ray_direction = om2.MFloatVector(0, -1, 0)
    src_mit_vtx = om2.MItMeshVertex(ray_src)
    dest_mfn_mesh = om2.MFnMesh(ray_dest)

    for index in vertex_indices:
        src_mit_vtx.setIndex(index)
        src_position = src_mit_vtx.position(om2.MSpace.kWorld)
        src_position = om2.MFloatPoint(src_position)

        # 被衝突メッシュ側の関数として用意されている衝突判定関数
        # また、似たような関数であるallIntersectionを利用すると貫通先の複数の衝突の取得もできる
        hit_point, hit_ray_param, hit_face, hit_triangle, hit_bary1, hit_bary2 = dest_mfn_mesh.anyIntersection(
            src_position,
            ray_direction,
            om2.MSpace.kWorld,
            1000,
            True)

        # APIで移動した場合は戻すが出来ないので今回のようなスクリプトで実行するだけなら大概はxformで移動
        # 速度を重視する場合はcmdsプラグインとして移動前の状態を保管してctrl+z用の関数として作成する
        # src_mit_vtx.setPosition(om2.MPoint(hit_point), om2.MSpace.kWorld)     # SPI2.0での頂点移動
        cmds.xform("{}.vtx[{}]".format(str(ray_src), index), t=[v for v in hit_point][0:3], worldSpace=True)

        # 衝突判定の取得情報
        """
        print("source_point:{}".format(src_position))   # 衝突した座標
        print("hit_point:{}".format(hit_point))         # 衝突した座標
        print("hit_ray_param:{}".format(hit_ray_param)) # 衝突点までの距離
        print("hit_face:{}".format(hit_face))           # 衝突したフェースのindex
        print("hit_triangle:{}".format(hit_triangle))   # 衝突した三角形フェースの相対index(ポリゴンが保持しているindex)
        print("hit_bary1:{}".format(hit_bary1))         # 衝突点が三角フェースのどの辺に位置するかどうかの比率
        print("hit_bary2:{}".format(hit_bary2))         # 衝突点が三角フェースのどの辺に位置するかどうかの比率
        """


terrain_sel = om2.MGlobal.getSelectionListByName(terrain[0])
""":type transform_sel: om2.MSelectionList"""
terrain_dag = terrain_sel.getDagPath(0)
""":type transform_dag: om2.MDagPath"""

for pier in piers:
    pier_sel = om2.MGlobal.getSelectionListByName(pier[0])
    """:type transform_sel: om2.MSelectionList"""
    pier_dag = pier_sel.getDagPath(0)
    """:type transform_dag: om2.MDagPath"""
    y_direction_face_vertices = get_face_from_direction_y_minus(pier_dag)
    ray_transform(pier_dag, terrain_dag, y_direction_face_vertices)

f:id:yosemite_soupcurry:20200517135749p:plainf:id:yosemite_soupcurry:20200517135754p:plain
実行結果

補足

注意点
anyIntersectionの以下の引数はドキュメント通りの名前だと受け入れてくれない
(arg=~を書かないで指定する)
- raySource
- rayDirection
- space
- maxParam
- testBothDirections

補足

実務ではカメラからの衝突したメッシュの取得に利用したことはある
接地を行うなどの特殊なコンストレインプラグインなんかを作ると楽しいかもしれない

モデリングに使うならHoudiniでやったほうが他の取り回しも効いていいと思う