モデルの当たり判定

最終更新: 2023年1月26日

モデル自体には当たり判定を行う機能はありませんが、アートメッシュの頂点情報を取得できることを利用して当たり判定処理を実装することができます。
ここで述べる”当たり判定”とは、画面上の指定のポイントに対象のメッシュがあるかどうかのテストのことを指します。

当たり判定の用意

当たり判定にはアートメッシュを利用します。
Editor上での当たり判定用のアートメッシュの用意は「Editor上での当たり判定の設定」を確認してください。
当たり判定を用意した後、Cubism Viewer (for OW)の機能で当たり判定を設定して.model3.jsonファイルに組み込む必要があります。

Cubism Viewer (for OW)上で当たり判定を設定して出力すると、.model3.jsonファイルには以下のように記述されます。

{
    ...
       "HitAreas": [
                {
                        "Id": "HitArea",
                        "Name": "Body"
                }
        ]
}

Cubism Viewer (for OW)ではHitArea~とIDが設定されたアートメッシュしか登録できませんが、
.model3.jsonファイルをテキストエディタで直接加工すればHitArea~以外のアートメッシュでも当たり判定に設定できます。

当たり判定を行う

当たり判定には以下のいずれかの関数が利用できます。

  • Native(C++)のCubismUserModel::IsHit関数
  • Web(TypeScript)のCubismUserModel.isHit関数
  • JavaのCubismUserModel.isHit関数

第一引数で対象のdrawableのID、第二、第三引数で検査する描画上のX、Yの値を指定します。

// C++
CubismUserModel model;
CubismIdHandle drawId = CubismFramework::GetIdManager()->GetId("HitArea1");
if(model.IsHit(drawId, x, y))
{
  //処理
}
// TypeScript
let model: CubismUserModel;
let drawId: CubismIdHandle = CubismFramework.getIdManager().getId("HitArea1");
if(mode.isHit(drawId, x, y))
{
    // 処理
}
// Java
CubismUserModel model;
CubismId drawId = CubismFramework.getIdManager().getId("HitArea1");
if(model.isHit(drawId, x, y)){
	// 処理
}

当たり判定の内容と注意点

// C++
csmBool CubismUserModel::IsHit(CubismIdHandle drawableId, csmFloat32 pointX, csmFloat32 pointY)
{
    csmInt32 drawIndex = _model->GetDrawableIndex(drawableId);

    if (drawIndex < 0)
    {
        return false; // 存在しない場合はfalse
    }

    csmInt32 count = _model->GetDrawableVertexCount(drawIndex);
    const csmFloat32* vertices = _model->GetDrawableVertices(drawIndex);

    csmFloat32 left = _model->GetCanvasWidth();
    csmFloat32 right = 0.0f;
    csmFloat32 top = _model->GetCanvasHeight();
    csmFloat32 bottom = 0.0f;

    for (csmInt32 j = 0; j < count; ++j)
    {
        csmFloat32 x = vertices[Constant::VertexOffset + j * Constant::VertexStep];
        csmFloat32 y = vertices[Constant::VertexOffset + j * Constant::VertexStep + 1];

        if (x < left)
        {
            left = x; // Min x
        }

        if (x > right)
        {
            right = x; // Max x
        }

        if (y < top)
        {
            top = y; // Min y
        }

        if (y > bottom)
        {
            bottom = y; // Max y
        }
    }

    csmFloat32 tx = _modelMatrix->InvertTransformX(pointX);
    csmFloat32 ty = _modelMatrix->InvertTransformY(pointY);

    return ((left <= tx) && (tx <= right) && (top <= ty) && (ty <= bottom));
}
// TypeScript
public isHit(drawableId: CubismIdHandle, pointX: number, pointY: number): boolean
{
    const drawIndex: number = this._model.getDrawableIndex(drawableId);

    if(drawIndex < 0)
    {
        return false; // 存在しない場合はfalse
    }
    
    const count: number = this._model.getDrawableVertexCount(drawIndex);
    const vertices: Float32Array = this._model.getDrawableVertices(drawIndex);

    let left: number = vertices[0];
    let right: number = vertices[0];
    let top: number = vertices[1];
    let bottom: number = vertices[1];

    for(let j: number = 1; j < count; ++j)
    {
        let x = vertices[Constant.vertexOffset + j * Constant.vertexStep];
        let y = vertices[Constant.vertexOffset + j * Constant.vertexStep + 1];

        if(x < left)
        {
            left = x; // Min x
        }
        
        if(x > right)
        {
            right = x; // Max x
        }

        if(y < top)
        {
            top = y; // Min y
        }

        if(y > bottom)
        {
            bottom = y; // Max y
        }
    }

    const tx: number = this._modelMatrix.invertTransformX(pointX);
    const ty: number = this._modelMatrix.invertTransformY(pointY);

    return ((left <= tx) && (tx <= right) && (top <= ty) && (ty <= bottom));
}
// Java
public boolean isHit(CubismId drawableId, float pointX, float pointY){
  final int drawIndex = model.getDrawableIndex(drawableId);
  
  if (drawIndex < 0) {
    return false;
  }
  
  final int count = model.getDrawableVertexCount(drawIndex);
  final float[] vertices = model.getDrawableVertices(drawIndex);
  float left = vertices[0];
  float right = vertices[0];
  float top = vertices[1];
  float bottom = vertices[1];
  
  for (int i = 1; i < count; ++i)
  {
    float x = vertices[VERTEX_OFFSET + i * VERTEX_STEP];
    float y = vertices[VERTEX_OFFSET + i * VERTEX_STEP + 1];
    
    if (x < left) {
      // Min x
      left = x;
    }

    if (x > right) {
      // Max x
      right = x;
    }
    
    if (y < top) {
      // Min y
      top = y;
    }
    
    if (y > bottom) {
      // Max y
      bottom = y;
    }
  }

  final float tx = modelMatrix.invertTransformX(pointX);
  final float ty = modelMatrix.invertTransformY(pointY);
  return (left <= tx) && (tx <= right) && (top <= ty) && (ty <= bottom);
}

当たり判定はアートメッシュの頂点をすべて走査し、頂点のXY座標の最大、最小値から矩形の範囲を作り、
検査対象の画面座標をモデルの変換行列から逆変換し、矩形に収まっているか確認する仕組みです。
この関数を使う場合、以下の点に注意が必要です。

  • モデルの変換座標に回転が入ると正しく変換されない。
  • 四角形に近いアートメッシュが回転デフォーマなどで回転すると大きくサイズが変わる。
この記事はお役に立ちましたか?
はいいいえ
この記事に関するご意見・
ご要望をお聞かせください。