Raycasting (Cocos Creator)

업데이트: 2023/03/14

개요

Raycasting은 사용자가 지정한 Cubism의 메쉬와 임의의 좌표가 교차할지 어떨지를 판정하는 기능입니다.
클릭/탭된 좌표나 장면 중의 임의의 오브젝트와 지정된 메쉬와의 충돌 감지를 취득하는 것이 가능합니다.
Raycasting 사용 방법은 여기를 참조하십시오.

Cubism SDK for Cocos Creator의 Raycasting은 크게 두 가지 요소로 구성됩니다.

  1. 판정할 아트메쉬 지정용 컴포넌트
  2. 입력된 좌표와 지정된 아트메쉬의 충돌 감지를 실시하는 컴포넌트

1. 판정할 아트메쉬 지정용 컴포넌트

충돌 감지에 사용할 메쉬를 지정하려면 CubismRaycastable을 사용합니다.

CubismRaycastable은 [Prefab의 루트]/Drawables/ 아래에 배치된 Node에 연결하여 사용합니다.
이것이 연결된 Node와 같은 ID의 아트메쉬를 충돌 감지에 사용합니다.

CubismRaycastable는 메쉬 충돌 감지의 정확도를 설정하는 파라미터를 가지고 있습니다.

@ccclass('CubismRaycastable')
export default class CubismRaycastable extends Component {
  /** The precision. */
  @property({
    type: Enum(CubismRaycastablePrecision),
    serializable: true,
    visible: true,
    readonly: false,
  })
  public precision: CubismRaycastablePrecision = CubismRaycastablePrecision.boundingBox;
}

CubismRaycastable.Precision에는 설정할 수 있는 값이 2개 있어 각각 아래와 같이 되어 있습니다.

  • BoundingBox
    그 메쉬를 둘러싸는, 네 변이 수평 또는 수직인 직사각형을 충돌 감지로서 사용합니다.
    메쉬의 형상에 따라서는 메쉬 밖의 좌표에서도 충돌 감지를 취득하는데, Triangles에 비해 퍼포먼스가 뛰어납니다.
  • Triangles
    메쉬의 형상을 충돌 감지의 범위로서 사용합니다.
    BoundingBox와 비교하면 성능이 떨어지지만 정확한 범위에서 판정을 할 수 있습니다.
enum CubismRaycastablePrecision {
  /** Cast against bounding box. */
  boundingBox,

  /** Cast against triangles. */
  triangles,
}

2. 입력된 좌표와 지정된 아트메쉬의 충돌 감지를 실행하는 컴포넌트

실제 충돌 감지의 취득은 CubismRaycaster를 사용합니다.
CubismRaycaster를 사용할 때는 Cubism의 Prefab 루트에 연결합니다.

CubismRaycaster를 초기화할 때 Prefab에 연결된 모든 CubismRaycastable에 대한 참조를 가져옵니다.
실행 중에 충돌 감지용 메쉬를 추가/삭제했을 때에는 CubismRaycaster.refresh()를 호출해 참조를 다시 취득합니다.

  /** Refreshes the controller. Call this method after adding and/or removing {@link CubismRaycastable}. */
  private refresh(): void {
    const candidates = ComponentExtensionMethods.findCubismModel(this)?.drawables ?? null;
    if (candidates == null) {
      console.error('CubismRaycaster.refresh(): candidates is null.');
      return;
    }

    // Find raycastable drawables.
    const raycastables = new Array<CubismRenderer>();
    const raycastablePrecisions = new Array<CubismRaycastablePrecision>();

    for (var i = 0; i < candidates.length; i++) {
      // Skip non-raycastables.
      if (candidates[i].getComponent(CubismRaycastable) == null) {
        continue;
      }
      const renderer = candidates[i].getComponent(CubismRenderer);
      console.assert(renderer);
      raycastables.push(renderer!) ;

      const raycastable = candidates[i].getComponent(CubismRaycastable);
      console.assert(raycastable);
      console.assert(raycastable!.precision);
      raycastablePrecisions.push(raycastable!.precision!) ;
    }

    // Cache raycastables.
    this.raycastables = raycastables;
    this.raycastablePrecisions = raycastablePrecisions;
  }

  /** Called by Cocos Creator. Makes sure cache is initialized. */
  protected start(): void {
    // Initialize cache.
    this.refresh();
  }

좌표로부터 충돌 감지를 취득하려면, CubismRaycaster.raycast1()이나 CubismRaycaster.raycast2()를 이용합니다.

  public raycast1(
    origin: Vector3,
    direction: Vector3,
    result: CubismRaycastHit[],
    maximumDistance: number = Number.POSITIVE_INFINITY
  ): number {
    return this.raycast2(
      geometry.Ray.create(origin.x, origin.y, origin.z, direction.x, direction.y, direction.z),
      result,
      maximumDistance
    );
  }

  /**
   * Casts a ray.
   * @param ray
   * @param result  The result of the cast.
   * @param maximumDistance [Optional] The maximum distance of the ray.
   * @returns
   * true in case of a hit; false otherwise.
   *
   * The numbers of drawables had hit
   */
  public raycast2(
    ray: geometry.Ray,
    result: CubismRaycastHit[],
    maximumDistance: number = Number.POSITIVE_INFINITY
  ): number {
    // Cast ray against model plane.
    const origin = Vector3.from(ray.o);
    const direction = Vector3.from(ray.d);
    const intersectionInWorldSpace = origin.add(
      direction.multiplySingle(direction.z / origin.z)
    );
    let intersectionInLocalSpace = Vector3.from(
      this.node.inverseTransformPoint(new math.Vec3(), intersectionInWorldSpace.toBuiltinType())
    );
    intersectionInLocalSpace = intersectionInLocalSpace.copyWith({ z: 0 });
    const distance = intersectionInWorldSpace.magnitude();
    // Return non-hits.
    if (distance > maximumDistance) {
      return 0;
    }
    // Cast against each raycastable.
    let hitCount = 0;
    console.assert(this.raycastables);
    const raycastables = this.raycastables!;
    console.assert(this.raycastablePrecisions);
    const raycastablePrecisions = this.raycastablePrecisions!;
    for (let i = 0; i < raycastables.length; i++) {
      const raycastable = raycastables[i];
      const raycastablePrecision = raycastablePrecisions[i];
      // Skip inactive raycastables.
      console.assert(raycastable.meshRenderer);
      if (!raycastable.meshRenderer!.enabled) {
        continue;
      }
      const bounds = raycastable.mesh.calculateBounds();

      // Skip non hits (bounding box)
      if (!bounds.contains(intersectionInLocalSpace)) {
        continue;
      }

      // Do detailed hit-detection against mesh if requested.
      if (raycastablePrecision == CubismRaycastablePrecision.triangles) {
        if (!this.containsInTriangles(raycastable.mesh, intersectionInLocalSpace)) {
          continue;
        }
      }

      result[hitCount] = new CubismRaycastHit({
        drawable: raycastable.getComponent(CubismDrawable),
        distance: distance,
        localPosition: intersectionInLocalSpace,
        worldPosition: intersectionInWorldSpace,
      });

      ++hitCount;

      // Exit if result buffer is full.
      if (hitCount == result.length) {
        break;
      }
    }

    return hitCount;
  }

CubismRaycaster.raycast()는 반환값으로 충돌 감지를 취득한 메쉬의 수를 돌려줍니다.
또 인수에 전달한 CubismRaycastHit[] 형태의 인스턴스에 충돌 감지를 취득한 메쉬의 정보가 설정됩니다.

      result[hitCount] = new CubismRaycastHit({
        drawable: raycastable.getComponent(CubismDrawable),
        distance: distance,
        localPosition: intersectionInLocalSpace,
        worldPosition: intersectionInWorldSpace,
      });

      ++hitCount;

CubismRaycaster.raycast()는 같은 좌표상에 복수의 메쉬가 겹쳐져 있었을 경우 최대로 CubismRaycastHit[]형의 인스턴스 요소의 수까지 취득합니다.
요소 수 이상의 메쉬가 겹치면 초과한 메쉬는 결과가 취득되지 않습니다.

CubismRaycastHit는 충돌 감지를 취득한 메쉬의 정보를 가지는 클래스입니다.

  /** The hit {@link CubismDrawable} */
  public readonly drawable: CubismDrawable | null;

  /** The distance the ray traveled until it hit the {@link CubismDrawable}. */
  public readonly distance: number;

  /** The hit position local to the {@link CubismDrawable}. */
  public readonly localPosition: Vector3;

  /** The hit position in world coordinates. */
  public readonly worldPosition: Vector3;
  • drawable
    충돌 감지를 취득한 아트메쉬의 참조입니다.
  • distance
    지정한 좌표로부터의 거리입니다.
    CubismRaycaster.Raycast()의 인수에 전달한 origin 또는 ray.origin과 Drawable의 Transform.position의 직선 거리입니다.
  • localPosition
    충돌 감지를 취득한 아트메쉬의 로컬 좌표입니다.
  • worldPosition
    충돌 감지를 취득한 아트메쉬의 월드 좌표입니다.
이 기사가 도움이 되었나요?
아니요
이 기사에 관한 의견 및 요청사항을 보내 주시기 바랍니다.