This document is about: QUANTUM 3
SWITCH TO

クエリ

はじめに

クエリは、動的エンティティと静的コライダーを考慮できます。各API(RaycastLinecastOverlapShape)は非常に似ていて、ヒットのコレクション(フィールドのデータの種類も同じ)を返します。
すべての物理クエリはオプションフラグでカスタマイズ可能で、非常に柔軟かつ最適化されています。例えば、ほとんどのクエリはデフォルトで衝突点や法線を返さないため、ユーザーによる明示的な定義が必要です(このドキュメントの後半で説明します)。

クエリ

LinecastとRaycast

C#

// For 2D
var hits = f.Physics2D.LinecastAll(FPVector2.Zero, FPVector2.One);
for (int i = 0; i < hits.Count; i++) {
    var hit = hits[i];
}

// For 3D
var hits = f.Physics3D.LinecastAll(FPVector3.Zero, FPVector3.One);
for (int i = 0; i < hits.Count; i++){
    var hit = hits[i];
}

結果として返されるHitCollectionオブジェクトには、以下のプロパティが含まれます。

  • HitCollectionの各要素は、EntityRefか静的コライダー情報を保持します。これらは排他的で、片方が有効なら、もう片方はnullになります。
  • HitCollectionの反復処理にはCountを常に使用してください。
  • 要素はソートされていません。FPVector2を渡してSort()を呼び出すことで、渡された基準点からの距離に基づいて要素をソートできます。

RaycastLinecastのシンタックスシュガーです。動作は同じですが、startendのかわりにstartdirectionmax-distanceを指定します。また、LinecastRaycastにオプションパラメーターを渡すことができます。

  • LayerMask:どの物理レイヤーに対してキャストするかを指定します。
  • QueryOptions:キャストで考慮するコライダー型を指定します。

シェイプクエリ

Quantumは、2種類のシェイプクエリをサポートしています。

  • ShapeOverlap
  • ShapeCasts

これらは、Quantumでサポートされているすべての動的シェイプに使用できます。

備考: シェイプクエリを実行するためにCoumpoundShapeを使用できます。詳細は「Shape Config」ページをご覧ください。

シェイプオーバーラップ

OverlapShape()HitCollectionを返します。必要なパラメーターは次の通りです。

  • 中心位置(FPVector2/FPVector3
  • 回転(FP、3Dの場合はFPQuaternion
  • シェイプ(Shape2D/Shape3D - PhysicsColliderのシェイプまたは呼び出し時に作成する)

C#

// For 2D
var hits = f.Physics2D.OverlapShape(FPVector2.Zero, FP._0, Shape2D.CreateCircle(FP._1));
for (int i = 0; i < hits.Count; i++){
    var hit = hits[i];
}

// For 3D
var hits = f.Physics3D.OverlapShape(FPVector3.Zero, FPQuaternion.Identity, Shape3D.CreateSphere(1));
for (int i = 0; i < hits.Count; i++){
    var hit = hits[i];
}

シェイプキャスト

ShapeCastAll()HitCollectionを返します。必要なパラメーターは次の通りです。

  • 中心位置(FPVector2/FPVector3
  • シェイプの回転(FP、3Dの場合はFPQuaternion
  • シェイプのポインタ(Shape2D*/Shape3D* - PhysicsColliderのシェイプまたは呼び出し時に作成する)
  • ベクトルで表される距離と方向(FPVector2/FPVector3

C#

// For 2D
var shape = Shape2D.CreateCircle(FP._1);
var hits = f.Physics2D.ShapeCastAll(FPVector2.Zero, FP._0, &shape, FPVector2.One);
for (int i = 0; i < hits.Count; i++){
    var hit = hits[i];
}

// For 3D
var shape = Shape3D.CreateSphere(1);
var hits = f.Physics3D.ShapeCastAll(FPVector3.Zero, FPQuaternion.Identity, &shape, FPVector3.One);
for (int i = 0; i < hits.Count; i++){
    var hit = hits[i];
}

シェイプキャストは、独自のGJKアルゴリズムを使用します。初期のシェイプキャスト位置がコライダー内にある場合、QueryOptionsパラメーターのDetectOverlapsAtCastOriginフラグを有効にしないと、そのコライダーとの衝突を検知できません。このフラグを有効にすると、開始位置で追加のチェックが行われます。

GJKConfig設定は、SimulationConfigアセットのPhysics > GJKConfigセクションにあります。この設定により、精度とパフォーマンスのバランスを取ることができますが、両者はトレードオフの関係にあります。デフォルトの値は、標準的なサイズのシェイプに合わせて調整されています。

  • Simplex Min/Max Bit Shift:生データを徐々にシフトすることで、ボロノイ領域内の点の精度を向上させます。これによって、物理空間における位置の有効範囲を損なうことなく、縮退するケースを回避します。シェイプのスケールやシェイプ間の距離が非常に小さい場合は、値を増やすことを検討してください。
  • Shape Cast Max Iterations:ハード許容値以下の解を探す際に、アルゴリズムが実行される最大反復回数です。この値を増やすと、精度が向上する可能性がありますが、最悪ケースではパフォーマンスが低下します。
  • Shape Cast Hard Tolerance:このしきい値以下の反復結果(シェイプ間の最近接距離)を、終了条件として受け入れます。この値を減らすと、精度が向上する可能性がありますが、反復回数が増加します。
  • Shape Cast Soft Tolerance:最大反復回数以内にハード許容値以下の結果が見つからなかった場合でも、最良の結果がこのしきい値以下であれば、ヒット判定を返します。このしきい値を増やすと偽陽性率が増加し、減らすと偽陰性率が増加します。

ヒットのソート

HitCollectionを返すすべてのクエリはソートできます。

  • Sort():2DならFPVector2、3DならFPVector3を渡し、その点までの距離に基づいてコレクションをソートします。
  • SortCastDistance()ShapeCastクエリの結果をソートするために使用します。引数は不要で、キャスト距離に基づいてコレクションをソートします。

オプション

すべてのクエリは、ブロードフェーズ版を含め、QueryOptionsを使用して処理やその結果をカスタマイズできます。
QueryOptionsは、考慮するオブジェクト型や計算する情報をフィルタリングするマスクを作成します。これらは、バイナリ演算子|を使用して組み合わせることができます。

ヒットの法線

最良のパフォーマンスを提供するため、すべてのデフォルトクエリは、2つのシェイプが重なっているかどうかのみをチェックします。デフォルトでは、大半のクエリはヒットの点や法線などを取得しません。

追加情報を取得するには、より計算が必要となるため、追加のオーバーヘッドが発生します。そのため、QueryOptionsパラメーターにComputeDetailedInfoを明示的に指定する必要があります。これによって、以下の計算が実行されるようになります。

  • point(点)
  • normal(法線)
  • penetration(めり込み)

レイキャストとトライアングルのチェックにおいて、法線は常にトライアングルの法線になります。この法線はトライアングルのデータにキャッシュされているため、追加の計算は行われません。

ヒットのフィルタリング

以下のQueryOptionsによって、クエリで使用されるマスクを定義できます。パラメーターとして指定されたQueryOptionsに一致しないオブジェクトはスキップされ、QueryOptionsに一致するオブジェクトのみが評価されて結果として返されます。

  • HitStatics:静的コライダーのみにヒットします。

  • HitKinematics:以下のいずれかの条件を満たすエンティティにヒットします。

    • PhysicsColliderを持ち、PhysicsBodyを持たないエンティティ
    • PhysicsColliderを持ち、無効なPhysicsBodyを持つエンティティ
    • PhysicsColliderを持ち、キネマティックなPhysicsBodyを持つエンティティ
  • HitDynamics:「有効」かつ「非キネマティック」なPhysicsBodyを持つエンティティのみヒットします。

  • HitTriggers:トリガーコライダーにヒットするため、他のフラグと組み合わせて使用します。

  • HitAll:トリガーコライダーを含め、すべての静的コライダーとPhysicsColliderを持つエンティティにヒットします。

  • HitSolids :トリガーコライダーを除き、すべての静的コライダーとPhysicsColliderを持つエンティティにヒットします。

デフォルトでは、クエリはHitAllオプションを使用します。他のオプションを選択することで計算を節約できます。

ブロードフェーズクエリ

Quantumではオプションとして、物理システム中に物理クエリ(レイキャストやオーバーラップ)を解決できます。そのためには次の手順が必要です。

  1. システムを作成する
  2. SystemsConfigアセットのCore.PhysicsSystem前にシステムを追加する
  3. Core.PhysicsSystem後に実行する任意のシステムで情報を取得する

このセットアップにより、物理ステップの並行処理の恩恵を受けるため、通常のクエリよりも非常に高速です。

Broadphase Queries System

備考: ブロードフェーズクエリは、ソルバーが実行される前に物理エンジンにスケジュール/注入されることから、「注入されたクエリ(Injected Queries)」や「スケジュールクエリ(Scheduled Queries)」とも呼ばれます。

クエリの注入

物理演算前に実行される任意のメインスレッドシステムから、クエリを注入することが可能です。クエリの注入はPhysicsQueryRefを返し、これを保持して、物理システム実行後に結果を取得できます。ブロードフェーズクエリの結果は、クエリが注入されたフレームと同じフレームで取得されることを想定しているため、PhysicsQueryRefはロールバック可能なフレームデータ外にも保存できます。

C#

namespace Quantum
{
    public unsafe struct ProjectileFilter
    {
        public EntityRef EntityRef;
        public Transform3D* Transform;
        public Projectile* Component;
    }
    
    public unsafe class ProjectileHitQueryInjectionSystem : SystemMainThread
    {
        public override void Update(Frame frame)
        {
            var projectileFilter = frame.Unsafe.FilterStruct<ProjectileFilter>();
            var projectile = default(ProjectileFilter);
            
            while (projectileFilter.Next(&projectile))
            {
                projectile.Component->PathQueryRef = frame.Physics3D.AddRaycastQuery(
                    projectile.Transform->Position, 
                    projectile.Transform->Forward,
                    projectile.Component->Speed * frame.DeltaTime);

                var spec = frame.FindAsset<WeaponSpec>(projectile.Component->WeaponSpec.Id);
                
                projectile.Component->DamageZoneQueryRef = frame.Physics3D.AddOverlapShapeQuery(
                    projectile.Transform->Position, 
                    projectile.Transform->Rotation,
                    spec.AttackShape.CreateShape(frame),
                    spec.AttackLayers);
            }
        }
    }
}

重要: AddXXXQueryによって返されるPhysicsQueryRefは、後でクエリ結果を取得するために絶対に必要です。そのため、後でヒットを処理する必要があるエンティティのコンポーネントに保存することを推奨します。

クエリ結果の取得

クエリ結果は、コアの物理システム実行後の任意のシステムから取得できます。結果(HitCollection*)を取得するには、以前に保存したインデックスをFrame.Physics.GetQueryHits()/.TryGetQueryHits()に渡してください。

無効なPhysicsQueryRef(例:異なるフレームで注入されたクエリ)で結果を取得しようとすると、GetQueryHitsでは例外が投げられ、TryGetQueryHitsではfalseが返されます。

C#

using Photon.Deterministic;

namespace Quantum
{
    public unsafe class ProjectileHitRetrievalSystem : SystemMainThread
    {
        public override void Update(Frame frame)
        {
            var projectileFilter = frame.Unsafe.FilterStruct<ProjectileFilter>();
            var projectile = default(ProjectileFilter);

            while (projectileFilter.Next(&projectile))
            {
                if (frame.Physics3D.TryGetQueryHits(projectile.Component->PathQueryRef, out var hitsOnTrajectory) == false || hitsOnTrajectory.Count <= 0)
                {
                    projectile.Transform->Position = 
                        projectile.Transform->Rotation * 
                        projectile.Transform->Forward *
                        projectile.Component->Speed * frame.DeltaTime;
                    continue;
                }
                
                if (frame.Physics3D.TryGetQueryHits(projectile.Component->DamageZoneQueryRef, out var damageZoneHits))
                {
                    for (int i = 0; i < damageZoneHits.Count; i++)
                    {
                        // 被ダメージロジックの適用
                    }
                }                
            }
        }
    } 
}

これに加えて、Frame.Physicsから利用可能なpublic bool GetAllQueriesHits(out HitCollection* queriesHits, out int queriesCount)を呼び出すことで、すべてのブロードフェーズクエリ結果を取得可能です。

備考

ブロードフェーズクエリを使用する際には、留意すべき重要な点がいくつか存在します。

  • 大量のデータ(例:弾幕)に対しては、パフォーマンスが約20倍向上します。
  • クエリは、物理システムが実行される直前のフレームのステートに基づきます。
  • ブロードフェーズクエリは、フレーム間では引き継がれません。つまり、物理演算前のフレーム開始時に注入する必要があります。物理演算後に注入されたブロードフェーズクエリは、決して結果を返しません。これはQuantumの物理エンジンがステートレスであるためです。

CCDのエミュレート

Quantumの物理エンジンはステートレスであるため、連続的衝突判定(CCD)には過剰なコストがかかります。ステートレスな物理エンジンでCCD動作をエミュレートするための解決策は、レイキャストやシェイプオーバーラップを使用して、1フレーム分で予想される移動を拡張することです。

このトピックは、弾のような高速で動くエンティティを組み合わせて、通常取り上げられます。高速で動くオブジェクトのサイズに応じて、次のアプローチを取ることを推奨します。

  • velocity * deltaTimeの長さで、移動方向に短いレイを飛ばす
  • 単一のシェイプオーバーラップを実行する(複合的なシェイプも可能)

いずれの解決策でも、100%正確なCCDを再現しつつ、全体的なパフォーマンスを大幅に向上させます。さらにパフォーマンスを向上させるために、ブロードフェーズクエリとの併用が可能です。

Back to top