Raycasting (Cocos Creator)

最終更新: 2023年3月14日

概要

Raycastingは、ユーザが指定したCubismのメッシュと任意の座標とが交差するかどうかを判定する機能です。
クリック/タップされた座標や、シーン中の任意のオブジェクトと指定されたメッシュとの当たり判定を取得することが可能です。
Raycastingの使用方法については こちら をご覧ください。

Cubism SDK for Cocos Creator におけるRaycastingは大きく2種類の要素によって構成されています。

  1. 判定させるアートメッシュ指定用のコンポーネント
  2. 入力された座標と指定されたアートメッシュの当たり判定を行うコンポーネント

1. 判定させるアートメッシュ指定用のコンポーネント

当たり判定に使用するメッシュを指定するには、CubismRaycastableを使用します。

CubismRaycastableは[Prefabのルート]/Drawables/ 以下に配置されたNodeにアタッチして使用します。
これがアタッチされたNodeと同じIDのアートメッシュを当たり判定に使用します。

CubismRaycastableは、そのメッシュの当たり判定の精度を設定するパラメータを持っています。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
@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;
}
@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; }
@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と比較するとパフォーマンスに劣りますが、正確な範囲で判定を行うことができます。
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
enum CubismRaycastablePrecision {
/** Cast against bounding box. */
boundingBox,
/** Cast against triangles. */
triangles,
}
enum CubismRaycastablePrecision { /** Cast against bounding box. */ boundingBox, /** Cast against triangles. */ triangles, }
enum CubismRaycastablePrecision {
  /** Cast against bounding box. */
  boundingBox,

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

2. 入力された座標と指定されたアートメッシュの当たり判定を行うコンポーネント

実際の当たり判定の取得はCubismRaycasterを使用します。
CubismRaycasterを使用する際はCubismのPrefabのルートにアタッチします。

CubismRaycasterの初期化時に、PrefabにアタッチされたすべてのCubismRaycastableの参照を取得します。
実行中に当たり判定用のメッシュを追加/削除した際には、CubismRaycaster.refresh()を呼んで参照を取得し直します。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
/** 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();
}
/** 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(); }
  /** 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()を利用します。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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;
}
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; }
  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[]型のインスタンスに当たり判定を取得したメッシュの情報が設定されます。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
result[hitCount] = new CubismRaycastHit({
drawable: raycastable.getComponent(CubismDrawable),
distance: distance,
localPosition: intersectionInLocalSpace,
worldPosition: intersectionInWorldSpace,
});
++hitCount;
result[hitCount] = new CubismRaycastHit({ drawable: raycastable.getComponent(CubismDrawable), distance: distance, localPosition: intersectionInLocalSpace, worldPosition: intersectionInWorldSpace, }); ++hitCount;
      result[hitCount] = new CubismRaycastHit({
        drawable: raycastable.getComponent(CubismDrawable),
        distance: distance,
        localPosition: intersectionInLocalSpace,
        worldPosition: intersectionInWorldSpace,
      });

      ++hitCount;

CubismRaycaster.raycast()は、同座標上に複数のメッシュが重なっていた場合、最大でCubismRaycastHit[]型のインスタンスの要素の数まで取得します。
要素数以上のメッシュが重なっていた場合、超えた分のメッシュは結果が取得されません。

CubismRaycastHitは当たり判定を取得したメッシュの情報を持つクラスです。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
/** 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;
/** 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;
  /** 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
    当たり判定を取得したアートメッシュのワールド座標です。
この記事はお役に立ちましたか?
はいいいえ
この記事に関するご意見・
ご要望をお聞かせください。