공간 쿼리

공간 쿼리는 영역에 있는 개체가 무엇인지 원격 렌더링 서비스에 질문할 수 있는 작업입니다. 공간 쿼리는 사용자가 가리키는 개체를 파악하는 등의 상호 작용을 구현하는 데 자주 사용됩니다.

모든 공간 쿼리는 서버에서 평가됩니다. 따라서, 해당 쿼리는 비동기 작업이며 결과는 네트워크 대기 시간에 따라 지연되어 도착합니다.

광선 캐스트

광선 캐스트는 런타임이 지정된 위치에서 시작하여 특정 방향을 가리키는 광선과 교차하는 개체를 확인하는 공간 쿼리입니다. 최적화를 위해 너무 멀리 떨어져 있는 개체를 검색하지 않도록 최대 광선 거리도 제공됩니다. 각 프레임마다 수백 개의 광선 캐스트를 수행하는 것은 서버 쪽에서 계산할 수 있지만 각 쿼리는 네트워크 트래픽도 생성하므로 프레임당 쿼리 수를 최대한 낮게 유지해야 합니다.

async void CastRay(RenderingSession session)
{
    // trace a line from the origin into the +z direction, over 10 units of distance.
    RayCast rayCast = new RayCast(new Double3(0, 0, 0), new Double3(0, 0, 1), 10);

    // only return the closest hit
    rayCast.HitCollection = HitCollectionPolicy.ClosestHit;

    RayCastQueryResult result = await session.Connection.RayCastQueryAsync(rayCast);
    RayCastHit[] hits = result.Hits;
    if (hits.Length > 0)
    {
        var hitObject = hits[0].HitObject;
        var hitPosition = hits[0].HitPosition;
        var hitNormal = hits[0].HitNormal;
        var hitType = hits[0].HitType;
        // do something with the hit information
    }
}
void CastRay(ApiHandle<RenderingSession> session)
{
    // trace a line from the origin into the +z direction, over 10 units of distance.
    RayCast rayCast;
    rayCast.StartPos = {0, 0, 0};
    rayCast.EndPos = {0, 0, 10};

    // only return the closest hit
    rayCast.HitCollection = HitCollectionPolicy::ClosestHit;

    session->Connection()->RayCastQueryAsync(rayCast, [](Status status, ApiHandle<RayCastQueryResult> result)
    {
        if (status == Status::OK)
        {
            std::vector<RayCastHit> hits;
            result->GetHits(hits);

            if (hits.size() > 0)
            {
                auto hitObject = hits[0].HitObject;
                auto hitPosition = hits[0].HitPosition;
                auto hitNormal = hits[0].HitNormal;
                auto hitType = hits[0].HitType;

                // do something with the hit information
            }
        }
    });
}

다음과 같은 세 가지 적중 수집 모드가 있습니다.

  • Closest: 이 모드에서는 가장 가까운 적중만 보고됩니다.
  • Any: 광선이 어떤 것을 적중하지 여부만 알려고 하지만 정확히 무엇을 적중하는지 중요하지 않는 경우 이 모드를 사용하는 것이 좋습니다. 이 쿼리는 평가 비용이 매우 저렴하지만 몇 개의 애플리케이션만 포함할 수 있습니다.
  • All: 이 모드에서는 광선에 따른 적중이 보고되고 거리별로 정렬됩니다. 실제로 첫 번째 적중보다 많이 필요한 경우가 아니면 이 모드를 사용하지 마세요. MaxHits 옵션을 사용하여 보고된 적중 횟수를 제한합니다.

필요한 경우 광선 캐스트에 고려되지 않도록 개체를 제외하기 위해 HierarchicalStateOverrideComponent 구성 요소를 사용할 수 있습니다.

적중 결과

광선 캐스트 쿼리의 결과는 적중의 배열입니다. 개체가 적중되지 않은 경우 배열이 비어 있습니다.

적중의 속성은 다음과 같습니다.

  • HitEntity: 적중된 엔터티입니다.
  • SubPartId: MeshComponent에서 적중된 하위 메시입니다. MeshComponent.UsedMaterials로 인덱싱하고 해당 시점에 재질을 조회하는 데 사용될 수 있습니다.
  • HitPosition: 광선이 개체와 교차한 월드 공간 위치입니다.
  • HitNormal: 교차 위치에 있는 메시에 일반적인 월드 공간 표면입니다.
  • DistanceToHit: 광선 시작 위치에서 적중까지 거리입니다.
  • HitType: 광선에 맞은 것: TriangleFrontFaceTriangleBackFace 또는 Point입니다. 기본적으로 ARR은 양면으로 렌더링하므로 사용자가 볼 수 있는 삼각형이 반드시 전면을 향하지는 않습니다. 코드에서 TriangleFrontFaceTriangleBackFace하려면 모델을 올바른 얼굴 방향으로 먼저 작성해야 합니다.

공간 쿼리

공간 쿼리를 사용하면 런타임에서 사용자 정의 볼륨과 교차하는 MeshComponent를 확인할 수 있습니다. 개별 검사가 개별 삼각형 기반이 아닌 장면에서 각 메시 부분의 경계를 기반으로 수행되므로 이 검사는 성능이 좋습니다. 최적화로 최대 적중 메시 구성 요소 수를 제공할 수 있습니다.
이러한 쿼리는 클라이언트 쪽에서 수동으로 실행할 수 있지만, 큰 장면의 경우 서버에서 이를 계산하는 것이 훨씬 더 빠를 수 있습니다.

다음 예제 코드에서는 AABB(축 정렬 경계 상자)에 대해 쿼리를 수행하는 방법을 보여 줍니다. 또한 쿼리의 변형은 방향이 지정된 경계 상자 볼륨(SpatialQueryObbAsync) 및 구 볼륨(SpatialQuerySphereAsync)을 허용합니다.

async void QueryAABB(RenderingSession session)
{
    // Query all mesh components in a 2x2x2m cube.
    SpatialQueryAabb query = new SpatialQueryAabb();
    query.Bounds = new Microsoft.Azure.RemoteRendering.Bounds(new Double3(-1, -1, -1), new Double3(1, 1, 1));
    query.MaxResults = 100;

    SpatialQueryResult result = await session.Connection.SpatialQueryAabbAsync(query);
    foreach (MeshComponent meshComponent in result.Overlaps)
    {
        Entity owner = meshComponent.Owner;
        // do something with the hit MeshComponent / Entity
    }
}
void QueryAABB(ApiHandle<RenderingSession> session)
{
    // Query all mesh components in a 2x2x2m cube.
    SpatialQueryAabb query;
    query.Bounds.Min = {-1, -1, -1};
    query.Bounds.Max = {1, 1, 1};
    query.MaxResults = 100;

    session->Connection()->SpatialQueryAabbAsync(query, [](Status status, ApiHandle<SpatialQueryResult> result)
        {
            if (status == Status::OK)
            {
                std::vector<ApiHandle<MeshComponent>> overlaps;
                result->GetOverlaps(overlaps);

                for (ApiHandle<MeshComponent> meshComponent : overlaps)
                {
                    ApiHandle<Entity> owner = meshComponent->GetOwner();
                    // do something with the hit MeshComponent / Entity
                }
            }
        });
}

API 설명서

다음 단계