모델 충돌 감지

업데이트: 2022/10/06

모델 자체에는 충돌 감지를 실시하는 기능은 없지만, 아트메쉬의 정점 정보를 취득할 수 있는 점을 이용해 충돌 감지 처리를 구현할 수 있습니다.
여기서 말하는 “충돌 감지”란, 화면상의 지정의 포인트에 대상 메쉬가 있는지 여부를 판정하는 테스트를 가리킵니다.

충돌 감지 준비

충돌 감지에는 아트메쉬를 이용합니다.
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 함수

제1인수로 대상 drawable의 ID, 제2, 제3인수로 검사하는 묘화상의 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 좌표의 최대, 최소치로 직사각형 범위를 만들고
검사 대상의 화면 좌표를 모델의 변환 행렬로부터 역변환해, 직사각형에 들어가 있는지 확인하는 구조입니다.
이 함수를 사용하는 경우 다음 사항에 유의해야 합니다.

  • 모델의 변환 좌표에 회전이 들어가면 올바르게 변환되지 않는다.
  • 사각형에 가까운 아트메쉬가 회전 디포머 등으로 회전하면 사이즈가 크게 바뀐다.
이 기사에 관한 의견 및 요청사항을 보내 주시기 바랍니다.