Системы координат в DirectX

Примечание

Эта статья относится к устаревшим собственным API-интерфейсам WinRT. Для новых проектов собственных приложений рекомендуется использовать API OpenXR.

Системы координат формируют основу для пространственного понимания, предлагаемого Windows Mixed Reality API.

Современные устройства виртуальной реальности или однокомнатной виртуальной реальности устанавливают одну основную систему координат для отслеживаемого пространства. Смешанная реальность устройствах, таких как HoloLens, предназначены для больших неопределенных сред, при этом устройство обнаруживает и изучает окружающую среду, когда пользователь ходит вокруг. Устройство адаптируется к постоянному совершенствованию знаний о комнатах пользователя, но в результате системы координат изменяют их отношения друг с другом в течение жизненного цикла приложений. Windows Mixed Reality поддерживает широкий спектр устройств, начиная от иммерсивных гарнитур и заканчивая опорными рамками, подключенными к миру.

Примечание

Фрагменты кода в этой статье в настоящее время демонстрируют использование C++/CX вместо C++17-совместимого C++/WinRT, как используется в шаблоне голографического проекта C++. Эти понятия эквивалентны для проекта C++/WinRT, хотя вам потребуется перевести код.

Системы пространственных координат в Windows

Основным типом, используемым для рассуждения о реальных системах координат в Windows, является SpatialCoordinateSystem. Экземпляр этого типа представляет произвольную систему координат, предоставляя метод получения данных матрицы преобразования, который можно использовать для преобразования между двумя системами координат, не разбираясь в деталях каждой из них.

Методы, возвращающие пространственные сведения, принимают параметр SpatialCoordinateSystem, чтобы вы решили, какая система координат наиболее полезна для возвращаемых координат. Пространственные данные представляются в виде точек, лучей или объемов в окружающей среде пользователя, и единицы измерения этих координат всегда будут находиться в метрах.

SpatialCoordinateSystem имеет динамическую связь с другими системами координат, включая системы, представляющие положение устройства. В любой момент устройство может найти некоторые системы координат, а не другие. Для большинства систем координат ваше приложение должно быть готово к обработке периодов времени, в течение которых они не могут быть расположены.

Приложение не должно создавать SpatialCoordinateSystems напрямую, а использовать их через API perception. Существует три основных источника систем координат в API распознавания, каждый из которых соответствует концепции, описанной на странице Системы координат :

Все системы координат, возвращаемые этими объектами, являются правой рукой: +y up, +x вправо и +z назад. Вы можете запомнить, в каком направлении указывает положительная ось Z, указывая пальцы левой или правой руки в положительном направлении x и свернув их в направлении положительного y. Направление, в котором указывают ваши пальцы (к вам или от вас), — это направление, в котором указывает положительная ось z в этой системе координат. На следующем рисунке показаны эти две системы координат.

Левая и правая системы координат
Левая и правая системы координат

Используйте класс SpatialLocator для создания присоединенного или стационарного кадра ссылок для начальной загрузки в SpatialCoordinateSystem на основе позиции HoloLens. Перейдите к следующему разделу, чтобы узнать больше об этом процессе.

Размещение голограмм в мире с помощью пространственной стадии

Доступ к системе координат для непрозрачных Windows Mixed Reality иммерсивных гарнитур осуществляется с помощью статического свойства SpatialStageFrameOfReference::Current. Этот API предоставляет следующие возможности:

  • Система координат
  • Сведения о том, является ли игрок сидячим или мобильным устройством
  • Граница безопасной зоны для прогулок, если игрок мобильный
  • Указывает, является ли гарнитура направленной.
  • Обработчик событий для обновлений пространственного этапа.

Во-первых, мы получаем пространственный этап и подписываемся на обновления для него:

Код для инициализации пространственного этапа

SpatialStageManager::SpatialStageManager(
    const std::shared_ptr<DX::DeviceResources>& deviceResources, 
    const std::shared_ptr<SceneController>& sceneController)
    : m_deviceResources(deviceResources), m_sceneController(sceneController)
{
    // Get notified when the stage is updated.
    m_spatialStageChangedEventToken = SpatialStageFrameOfReference::CurrentChanged +=
        ref new EventHandler<Object^>(std::bind(&SpatialStageManager::OnCurrentChanged, this, _1));

    // Make sure to get the current spatial stage.
    OnCurrentChanged(nullptr);
}

В методе OnCurrentChanged приложение должно проверить пространственный этап и обновить взаимодействие с игроком. В этом примере мы предоставляем визуализацию границы этапа и начальной позиции, указанной пользователем, а также диапазона представления и диапазона свойств перемещения этапа. Мы также вернемся к нашей собственной стационарной системе координат, когда этап не может быть предоставлен.

Код для обновления пространственного этапа

void SpatialStageManager::OnCurrentChanged(Object^ /*o*/)
{
    // The event notifies us that a new stage is available.
    // Get the current stage.
    m_currentStage = SpatialStageFrameOfReference::Current;

    // Clear previous content.
    m_sceneController->ClearSceneObjects();

    if (m_currentStage != nullptr)
    {
        // Obtain stage geometry.
        auto stageCoordinateSystem = m_currentStage->CoordinateSystem;
        auto boundsVertexArray = m_currentStage->TryGetMovementBounds(stageCoordinateSystem);

        // Visualize the area where the user can move around.
        std::vector<float3> boundsVertices;
        boundsVertices.resize(boundsVertexArray->Length);
        memcpy(boundsVertices.data(), boundsVertexArray->Data, boundsVertexArray->Length * sizeof(float3));
        std::vector<unsigned short> indices = TriangulatePoints(boundsVertices);
        m_stageBoundsShape =
            std::make_shared<SceneObject>(
                    m_deviceResources,
                    reinterpret_cast<std::vector<XMFLOAT3>&>(boundsVertices),
                    indices,
                    XMFLOAT3(DirectX::Colors::SeaGreen),
                    stageCoordinateSystem);
        m_sceneController->AddSceneObject(m_stageBoundsShape);

        // In this sample, we draw a visual indicator for some spatial stage properties.
        // If the view is forward-only, the indicator is a half circle pointing forward - otherwise, it
        // is a full circle.
        // If the user can walk around, the indicator is blue. If the user is seated, it is red.

        // The indicator is rendered at the origin - which is where the user declared the center of the
        // stage to be during setup - above the plane of the stage bounds object.
        float3 visibleAreaCenter = float3(0.f, 0.001f, 0.f);

        // Its shape depends on the look direction range.
        std::vector<float3> visibleAreaIndicatorVertices;
        if (m_currentStage->LookDirectionRange == SpatialLookDirectionRange::ForwardOnly)
        {
            // Half circle for forward-only look direction range.
            visibleAreaIndicatorVertices = CreateCircle(visibleAreaCenter, 0.25f, 9, XM_PI);
        }
        else
        {
            // Full circle for omnidirectional look direction range.
            visibleAreaIndicatorVertices = CreateCircle(visibleAreaCenter, 0.25f, 16, XM_2PI);
        }

        // Its color depends on the movement range.
        XMFLOAT3 visibleAreaColor;
        if (m_currentStage->MovementRange == SpatialMovementRange::NoMovement)
        {
            visibleAreaColor = XMFLOAT3(DirectX::Colors::OrangeRed);
        }
        else
        {
            visibleAreaColor = XMFLOAT3(DirectX::Colors::Aqua);
        }

        std::vector<unsigned short> visibleAreaIndicatorIndices = TriangulatePoints(visibleAreaIndicatorVertices);

        // Visualize the look direction range.
        m_stageVisibleAreaIndicatorShape =
            std::make_shared<SceneObject>(
                    m_deviceResources,
                    reinterpret_cast<std::vector<XMFLOAT3>&>(visibleAreaIndicatorVertices),
                    visibleAreaIndicatorIndices,
                    visibleAreaColor,
                    stageCoordinateSystem);
        m_sceneController->AddSceneObject(m_stageVisibleAreaIndicatorShape);
    }
    else
    {
        // No spatial stage was found.
        // Fall back to a stationary coordinate system.
        auto locator = SpatialLocator::GetDefault();
        if (locator)
        {
            m_stationaryFrameOfReference = locator->CreateStationaryFrameOfReferenceAtCurrentLocation();

            // Render an indicator, so that we know we fell back to a mode without a stage.
            std::vector<float3> visibleAreaIndicatorVertices;
            float3 visibleAreaCenter = float3(0.f, -2.0f, 0.f);
            visibleAreaIndicatorVertices = CreateCircle(visibleAreaCenter, 0.125f, 16, XM_2PI);
            std::vector<unsigned short> visibleAreaIndicatorIndices = TriangulatePoints(visibleAreaIndicatorVertices);
            m_stageVisibleAreaIndicatorShape =
                std::make_shared<SceneObject>(
                    m_deviceResources,
                    reinterpret_cast<std::vector<XMFLOAT3>&>(visibleAreaIndicatorVertices),
                    visibleAreaIndicatorIndices,
                    XMFLOAT3(DirectX::Colors::LightSlateGray),
                    m_stationaryFrameOfReference->CoordinateSystem);
            m_sceneController->AddSceneObject(m_stageVisibleAreaIndicatorShape);
        }
    }
}

Набор вершин, определяющих границу этапа, предоставляется в порядке часовой стрелки. Оболочка Windows Mixed Reality рисует ограждение на границе, когда пользователь приближается к ней, но может потребоваться треугольная область для своих целей. Следующий алгоритм можно использовать для треугольной обработки этапа.

Код для треугольной пространственной стадии

std::vector<unsigned short> SpatialStageManager::TriangulatePoints(std::vector<float3> const& vertices)
{
    size_t const& vertexCount = vertices.size();

    // Segments of the shape are removed as they are triangularized.
    std::vector<bool> vertexRemoved;
    vertexRemoved.resize(vertexCount, false);
    unsigned int vertexRemovedCount = 0;

    // Indices are used to define triangles.
    std::vector<unsigned short> indices;

    // Decompose into convex segments.
    unsigned short currentVertex = 0;
    while (vertexRemovedCount < (vertexCount - 2))
    {
        // Get next triangle:
        // Start with the current vertex.
        unsigned short index1 = currentVertex;

        // Get the next available vertex.
        unsigned short index2 = index1 + 1;

        // This cycles to the next available index.
        auto CycleIndex = [=](unsigned short indexToCycle, unsigned short stopIndex)
        {
            // Make sure the index does not exceed bounds.
            if (indexToCycle >= unsigned short(vertexCount))
            {
                indexToCycle -= unsigned short(vertexCount);
            }

            while (vertexRemoved[indexToCycle])
            {
                // If the vertex is removed, go to the next available one.
                ++indexToCycle;

                // Make sure the index does not exceed bounds.
                if (indexToCycle >= unsigned short(vertexCount))
                {
                    indexToCycle -= unsigned short(vertexCount);
                }

                // Prevent cycling all the way around.
                // Should not be needed, as we limit with the vertex count.
                if (indexToCycle == stopIndex)
                {
                    break;
                }
            }

            return indexToCycle;
        };
        index2 = CycleIndex(index2, index1);

        // Get the next available vertex after that.
        unsigned short index3 = index2 + 1;
        index3 = CycleIndex(index3, index1);

        // Vertices that may define a triangle inside the 2D shape.
        auto& v1 = vertices[index1];
        auto& v2 = vertices[index2];
        auto& v3 = vertices[index3];

        // If the projection of the first segment (in clockwise order) onto the second segment is 
        // positive, we know that the clockwise angle is less than 180 degrees, which tells us 
        // that the triangle formed by the two segments is contained within the bounding shape.
        auto v2ToV1 = v1 - v2;
        auto v2ToV3 = v3 - v2;
        float3 normalToV2ToV3 = { -v2ToV3.z, 0.f, v2ToV3.x };
        float projectionOntoNormal = dot(v2ToV1, normalToV2ToV3);
        if (projectionOntoNormal >= 0)
        {
            // Triangle is contained within the 2D shape.

            // Remove peak vertex from the list.
            vertexRemoved[index2] = true;
            ++vertexRemovedCount;

            // Create the triangle.
            indices.push_back(index1);
            indices.push_back(index2);
            indices.push_back(index3);

            // Continue on to the next outer triangle.
            currentVertex = index3;
        }
        else
        {
            // Triangle is a cavity in the 2D shape.
            // The next triangle starts at the inside corner.
            currentVertex = index2;
        }
    }

    indices.shrink_to_fit();
    return indices;
}

Размещение голограмм в мире с помощью стационарной системы отсчета

Класс SpatialStationaryFrameOfReference представляет систему отсчета, которая остается неподвижной относительно окружения пользователя при перемещении пользователя. Эта система отсчета отдает приоритет поддержанию стабильной координаты рядом с устройством. Одним из ключевых способов использования SpatialStationaryFrameOfReference является выполнение роли базовой системы мировых координат в подсистеме отрисовки при отрисовке голограмм.

Чтобы получить Объект SpatialStationaryFrameOfReference, используйте класс SpatialLocator и вызовите Метод CreateStationaryFrameOfReferenceAtCurrentLocation.

Из кода шаблона приложения Windows Holographic:

           // The simplest way to render world-locked holograms is to create a stationary reference frame
           // when the app is launched. This is roughly analogous to creating a "world" coordinate system
           // with the origin placed at the device's position as the app is launched.
           referenceFrame = locator.CreateStationaryFrameOfReferenceAtCurrentLocation();
  • Стационарные опорные кадры предназначены для обеспечения оптимального положения относительно общего пространства. Отдельные позиции в этой системе отсчета могут немного отклоняться. Это нормально, так как устройство узнает больше о среде.
  • Если требуется точное размещение отдельных голограмм, следует использовать SpatialAnchor для привязки отдельной голограммы к позиции в реальном мире, например к точке, которую пользователь указывает, что она представляет особый интерес. Позиции привязки не смещения, но могут быть исправлены; Привязка будет использовать исправленную позицию, начиная со следующего кадра после исправления.

Размещение голограмм в мире с помощью пространственных привязок

Пространственные привязки — это отличный способ размещения голограмм в определенном месте в реальном мире, при этом система гарантирует, что привязка остается на месте с течением времени. В этом разделе объясняется, как создать и использовать привязку, а также как работать с данными привязки.

Вы можете создать Объект SpatialAnchor в любой позиции и ориентации в spatialCoordinateSystem по вашему выбору. Устройство должно быть в состоянии найти систему координат в данный момент, и система не должна достичь предела пространственных привязок.

После определения система координат SpatialAnchor постоянно корректируется, чтобы сохранить точное положение и ориентацию исходного расположения. Затем вы можете использовать этот SpatialAnchor для отрисовки голограмм, которые будут отображаться фиксированными в окружении пользователя в точном расположении.

Эффекты корректировки, которые удерживают привязку на месте, увеличиваются по мере увеличения расстояния от привязки. Следует избегать отрисовки содержимого относительно привязки, которая находится более чем в 3 метрах от источника этой привязки.

Свойство CoordinateSystem получает систему координат, которая позволяет размещать содержимое относительно привязки, при этом применяется смягчение, когда устройство настраивает точное расположение привязки.

Используйте свойство RawCoordinateSystem и соответствующее событие RawCoordinateSystemAdjusted для самостоятельного управления этими корректировками.

Сохранение и совместное использование пространственных привязок

Вы можете сохранить SpatialAnchor локально с помощью класса SpatialAnchorStore , а затем получить его в следующем сеансе приложения на том же устройстве HoloLens.

С помощью Пространственных привязок Azure можно создать надежную облачную привязку из локального Объекта SpatialAnchor, которую приложение затем сможет найти на нескольких устройствах HoloLens, iOS и Android. Совместное использование общей пространственной привязки на нескольких устройствах позволяет каждому пользователю видеть содержимое, отображаемое относительно этой привязки, в одном физическом расположении в реальном времени.

Пространственные привязки Azure также можно использовать для асинхронной сохраняемости голограмм на устройствах HoloLens, iOS и Android. Благодаря совместному использованию устойчивой облачной пространственной привязки несколько устройств могут наблюдать одну и ту же сохраняемую голограмму с течением времени, даже если эти устройства не присутствуют вместе одновременно.

Чтобы приступить к созданию общих возможностей в приложении HoloLens, ознакомьтесь с 5-минутным кратким руководством по Использованию Пространственных привязок Azure Для HoloLens.

После запуска пространственных привязок Azure можно создавать и находить привязки в HoloLens. Пошаговые руководства также доступны для Android и iOS , что позволяет совместно использовать одни и те же привязки на всех устройствах.

Создание ПространственныхAnchor для голографического содержимого

В этом примере кода мы изменили шаблон приложения Windows Holographic, чтобы создавать привязки при обнаружении жеста Нажатие . Затем куб помещается в привязку во время прохода отрисовки.

Так как вспомогательный класс поддерживает несколько привязок, мы можем разместить столько кубов, сколько мы хотим использовать в этом примере кода.

Примечание

Идентификаторы привязок — это то, что вы контролируете в приложении. В этом примере мы создали схему именования, которая является последовательной на основе количества привязок, хранящихся в настоящее время в коллекции привязок приложения.

   // Check for new input state since the last frame.
   SpatialInteractionSourceState^ pointerState = m_spatialInputHandler->CheckForInput();
   if (pointerState != nullptr)
   {
       // Try to get the pointer pose relative to the SpatialStationaryReferenceFrame.
       SpatialPointerPose^ pointerPose = pointerState->TryGetPointerPose(currentCoordinateSystem);
       if (pointerPose != nullptr)
       {
           // When a Pressed gesture is detected, the anchor will be created two meters in front of the user.

           // Get the gaze direction relative to the given coordinate system.
           const float3 headPosition = pointerPose->Head->Position;
           const float3 headDirection = pointerPose->Head->ForwardDirection;

           // The anchor position in the StationaryReferenceFrame.
           static const float distanceFromUser = 2.0f; // meters
           const float3 gazeAtTwoMeters = headPosition + (distanceFromUser * headDirection);

           // Create the anchor at position.
           SpatialAnchor^ anchor = SpatialAnchor::TryCreateRelativeTo(currentCoordinateSystem, gazeAtTwoMeters);

           if ((anchor != nullptr) && (m_spatialAnchorHelper != nullptr))
           {
               // In this example, we store the anchor in an IMap.
               auto anchorMap = m_spatialAnchorHelper->GetAnchorMap();

               // Create an identifier for the anchor.
               String^ id = ref new String(L"HolographicSpatialAnchorStoreSample_Anchor") + anchorMap->Size;

               anchorMap->Insert(id->ToString(), anchor);
           }
       }
   }

Асинхронная загрузка и кэширование SpatialAnchorStore

Давайте посмотрим, как написать класс SampleSpatialAnchorHelper, который помогает справиться с этой сохраняемостью, в том числе:

  • Хранение коллекции привязок в памяти, индексированных ключом Platform::String.
  • Загрузка привязок из системного spatialAnchorStore, который хранится отдельно от локальной коллекции в памяти.
  • Сохранение локальной коллекции привязок в памяти в SpatialAnchorStore, когда приложение решит это сделать.

Вот как сохранить объекты SpatialAnchor в SpatialAnchorStore.

При запуске класса мы запрашиваем SpatialAnchorStore асинхронно. Это включает системный ввод-вывод, так как API загружает хранилище привязок, и этот API делается асинхронным, чтобы ввод-вывод не блокировался.

   // Request the spatial anchor store, which is the WinRT object that will accept the imported anchor data.
   return create_task(SpatialAnchorManager::RequestStoreAsync())
       .then([](task<SpatialAnchorStore^> previousTask)
   {
       std::shared_ptr<SampleSpatialAnchorHelper> newHelper = nullptr;

       try
       {
           SpatialAnchorStore^ anchorStore = previousTask.get();

           // Once the SpatialAnchorStore has been loaded by the system, we can create our helper class.

           // Using "new" to access private constructor
           newHelper = std::shared_ptr<SampleSpatialAnchorHelper>(new SampleSpatialAnchorHelper(anchorStore));

           // Now we can load anchors from the store.
           newHelper->LoadFromAnchorStore();
       }
       catch (Exception^ exception)
       {
           PrintWstringToDebugConsole(
               std::wstring(L"Exception while loading the anchor store: ") +
               exception->Message->Data() +
               L"\n"
               );
       }

       // Return the initialized class instance.
       return newHelper;
   });

Вам будет предоставлен объект SpatialAnchorStore, который можно использовать для сохранения привязок. Это IMapView, который связывает ключевые значения, которые являются Строками, со значениями данных SpatialAnchors. В нашем примере кода мы храним его в частной переменной-члене класса, доступной через общедоступную функцию нашего вспомогательного класса.

   SampleSpatialAnchorHelper::SampleSpatialAnchorHelper(SpatialAnchorStore^ anchorStore)
   {
       m_anchorStore = anchorStore;
       m_anchorMap = ref new Platform::Collections::Map<String^, SpatialAnchor^>();
   }

Примечание

Не забудьте подключить события приостановки и возобновления для сохранения и загрузки хранилища привязок.

   void HolographicSpatialAnchorStoreSampleMain::SaveAppState()
   {
       // For example, store information in the SpatialAnchorStore.
       if (m_spatialAnchorHelper != nullptr)
       {
           m_spatialAnchorHelper->TrySaveToAnchorStore();
       }
   }
   void HolographicSpatialAnchorStoreSampleMain::LoadAppState()
   {
       // For example, load information from the SpatialAnchorStore.
       LoadAnchorStore();
   }

Сохранение содержимого в хранилище привязок

Когда система приостанавливает работу приложения, необходимо сохранить пространственные привязки в хранилище привязок. Вы также можете сохранить привязки в хранилище привязок в другое время, так как это необходимо для реализации приложения.

Когда вы будете готовы сохранить привязки в памяти в SpatialAnchorStore, можно выполнить цикл по коллекции и попытаться сохранить каждую из них.

   // TrySaveToAnchorStore: Stores all anchors from memory into the app's anchor store.
   //
   // For each anchor in memory, this function tries to store it in the app's AnchorStore. The operation will fail if
   // the anchor store already has an anchor by that name.
   //
   bool SampleSpatialAnchorHelper::TrySaveToAnchorStore()
   {
       // This function returns true if all the anchors in the in-memory collection are saved to the anchor
       // store. If zero anchors are in the in-memory collection, we will still return true because the
       // condition has been met.
       bool success = true;

       // If access is denied, 'anchorStore' will not be obtained.
       if (m_anchorStore != nullptr)
       {
           for each (auto& pair in m_anchorMap)
           {
               auto const& id = pair->Key;
               auto const& anchor = pair->Value;

               // Try to save the anchors.
               if (!m_anchorStore->TrySave(id, anchor))
               {
                   // This may indicate the anchor ID is taken, or the anchor limit is reached for the app.
                   success=false;
               }
           }
       }

       return success;
   }

Загрузка содержимого из хранилища привязок при возобновлении работы приложения

Вы можете восстановить сохраненные привязки в AnchorStore, переведя их из IMapView хранилища привязок в собственную базу данных SpatialAnchors в памяти при возобновлении работы приложения или в любое время.

Чтобы восстановить привязки из SpatialAnchorStore, восстановите каждую из них в собственной коллекции в памяти.

Вам потребуется собственная база данных SpatialAnchors в памяти, чтобы связать строки с создаваемым spatialAnchors. В нашем примере кода мы решили использовать Windows::Foundation::Collections::IMap для хранения привязок, что упрощает использование одного и того же ключа и значения данных для SpatialAnchorStore.

   // This is an in-memory anchor list that is separate from the anchor store.
   // These anchors may be used, reasoned about, and so on before committing the collection to the store.
   Windows::Foundation::Collections::IMap<Platform::String^, Windows::Perception::Spatial::SpatialAnchor^>^ m_anchorMap;

Примечание

Восстановленная привязка может быть недоступна сразу. Например, это может быть якорь в отдельной комнате или в другом здании. Привязки, полученные из AnchorStore, должны быть проверены на возможность locatability перед их использованием.


Примечание

В этом примере кода мы извлекаем все привязки из AnchorStore. Это не является обязательным требованием; Приложение также может выбрать определенное подмножество привязок, используя значения ключа string, которые имеют смысл для вашей реализации.

   // LoadFromAnchorStore: Loads all anchors from the app's anchor store into memory.
   //
   // The anchors are stored in memory using an IMap, which stores anchors using a string identifier. Any string can be used as
   // the identifier; it can have meaning to the app, such as "Game_Leve1_CouchAnchor," or it can be a GUID that is generated
   // by the app.
   //
   void SampleSpatialAnchorHelper::LoadFromAnchorStore()
   {
       // If access is denied, 'anchorStore' will not be obtained.
       if (m_anchorStore != nullptr)
       {
           // Get all saved anchors.
           auto anchorMapView = m_anchorStore->GetAllSavedAnchors();
           for each (auto const& pair in anchorMapView)
           {
               auto const& id = pair->Key;
               auto const& anchor = pair->Value;
               m_anchorMap->Insert(id, anchor);
           }
       }
   }

При необходимости очистите хранилище привязок

Иногда необходимо очистить состояние приложения и записать новые данные. Вот как это сделать с помощью SpatialAnchorStore.

Используя наш вспомогательный класс, почти не нужно упаковывать функцию Clear. Мы решили сделать это в нашем примере реализации, так как наш вспомогательный класс отвечает за владение экземпляром SpatialAnchorStore.

   // ClearAnchorStore: Clears the AnchorStore for the app.
   //
   // This function clears the AnchorStore. It has no effect on the anchors stored in memory.
   //
   void SampleSpatialAnchorHelper::ClearAnchorStore()
   {
       // If access is denied, 'anchorStore' will not be obtained.
       if (m_anchorStore != nullptr)
       {
           // Clear all anchors from the store.
           m_anchorStore->Clear();
       }
   }

Пример. Связывание систем координат привязки с системами координат стационарных опорных систем

Предположим, у вас есть привязка и вы хотите связать что-то в системе координат привязки с элементом SpatialStationaryReferenceFrame, который вы уже используете для другого содержимого. С помощью TryGetTransformTo можно получить преобразование из системы координат привязки в систему стационарных опорных данных:

   // In this code snippet, someAnchor is a SpatialAnchor^ that has been initialized and is valid in the current environment.
   float4x4 anchorSpaceToCurrentCoordinateSystem;
   SpatialCoordinateSystem^ anchorSpace = someAnchor->CoordinateSystem;
   const auto tryTransform = anchorSpace->TryGetTransformTo(currentCoordinateSystem);
   if (tryTransform != nullptr)
   {
       anchorSpaceToCurrentCoordinateSystem = tryTransform->Value;
   }

Этот процесс полезен двумя способами:

  1. Он указывает, можно ли понять два опорных кадра относительно друг друга, и;
  2. Если это так, он предоставляет преобразование для перехода непосредственно из одной системы координат в другую.

Эти сведения позволяют понять пространственное отношение между объектами между двумя опорными кадрами.

Для отрисовки часто можно получить лучшие результаты, группируя объекты в соответствии с исходной опорной рамкой или привязкой. Выполните отдельный проход рисования для каждой группы. Матрицы представлений более точны для объектов с преобразованиями моделей, которые изначально создаются с использованием той же системы координат.

Создание голограмм с помощью прикрепленной к устройству системы отсчета

Иногда требуется отобразить голограмму, которая остается подключенной к расположению устройства, например панель с отлаживающими сведениями или информационное сообщение, когда устройство может определить только свою ориентацию, а не положение в пространстве. Для этого мы используем прикрепленную систему отсчета.

Класс SpatialLocatorAttachedFrameOfReference определяет системы координат, которые относятся к устройству, а не к реальному миру. Этот фрейм имеет фиксированный заголовок относительно окружения пользователя, который указывает на направление, с которым столкнулся пользователь при создании опорного кадра. С этого момента все ориентации в этой системе отсчета будут относительно этого фиксированного заголовка, даже когда пользователь поворачивает устройство.

Для HoloLens источник системы координат этого кадра находится в центре поворота головы пользователя, так что поворот головы пользователя не влияет на его положение. Приложение может указать смещение относительно этой точки, чтобы разместить голограммы перед пользователем.

Чтобы получить объект SpatialLocatorAttachedFrameOfReference, используйте класс SpatialLocator и вызовите метод CreateAttachedFrameOfReferenceAtCurrentHeading.

Это относится ко всему диапазону Windows Mixed Reality устройств.

Использование опорной рамки, подключенной к устройству

В этих разделах описывается, что мы изменили в шаблоне приложения Windows Holographic, чтобы включить прикрепленную к устройству систему ссылок с помощью этого API. Эта "подключенная" голограмма будет работать вместе со стационарными или привязанными голограммами, а также может использоваться, когда устройство временно не может найти свое положение в мире.

Сначала мы изменили шаблон для хранения SpatialLocatorAttachedFrameOfReference вместо SpatialStationaryFrameOfReference:

Из HolographicTagAlongSampleMain.h:

   // A reference frame attached to the holographic camera.
   Windows::Perception::Spatial::SpatialLocatorAttachedFrameOfReference^   m_referenceFrame;

Из HolographicTagAlongSampleMain.cpp:

   // In this example, we create a reference frame attached to the device.
   m_referenceFrame = m_locator->CreateAttachedFrameOfReferenceAtCurrentHeading();

Теперь во время обновления мы получаем систему координат по метке времени, полученной из с помощью прогноза кадра.

   // Next, we get a coordinate system from the attached frame of reference that is
   // associated with the current frame. Later, this coordinate system is used for
   // for creating the stereo view matrices when rendering the sample content.
   SpatialCoordinateSystem^ currentCoordinateSystem =
       m_referenceFrame->GetStationaryCoordinateSystemAtTimestamp(prediction->Timestamp);

Получение пространственного положения указателя и отслеживание взгляда пользователя

Мы хотим, чтобы наш пример голограммы следовало за взглядом пользователя, аналогично тому, как голографическая оболочка может следовать за взглядом пользователя. Для этого нам нужно получить SpatialPointerPose из той же метки времени.

SpatialPointerPose^ pose = SpatialPointerPose::TryGetAtTimestamp(currentCoordinateSystem, prediction->Timestamp);

Этот объект SpatialPointerPose содержит сведения, необходимые для размещения голограммы в соответствии с текущим заголовком пользователя.

Для удобства пользователей мы используем линейную интерполяцию ("lerp"), чтобы сгладить изменение положения в течение определенного периода времени. Это удобнее для пользователя, чем блокировка голограммы для его взгляда. Удаление положения голограммы с тегами также позволяет стабилизировать голограмму, за счет подавления движения. Если бы мы не сделали это затухание, пользователь увидит дрожание голограммы из-за того, что обычно считается незаметным движением головы пользователя.

Из раздела StationaryQuadRenderer::P ositionHologram:

   const float& dtime = static_cast<float>(timer.GetElapsedSeconds());

   if (pointerPose != nullptr)
   {
       // Get the gaze direction relative to the given coordinate system.
       const float3 headPosition  = pointerPose->Head->Position;
       const float3 headDirection = pointerPose->Head->ForwardDirection;

       // The tag-along hologram follows a point 2.0m in front of the user's gaze direction.
       static const float distanceFromUser = 2.0f; // meters
       const float3 gazeAtTwoMeters = headPosition + (distanceFromUser * headDirection);

       // Lerp the position, to keep the hologram comfortably stable.
       auto lerpedPosition = lerp(m_position, gazeAtTwoMeters, dtime * c_lerpRate);

       // This will be used as the translation component of the hologram's
       // model transform.
       SetPosition(lerpedPosition);
   }

Примечание

В случае с панелью отладки вы можете немного переместить голограмму в сторону, чтобы она не мешала вашему представлению. Вот пример того, как это можно сделать.

Для StationaryQuadRenderer::P ositionHologram:

       // If you're making a debug view, you might not want the tag-along to be directly in the
       // center of your field of view. Use this code to position the hologram to the right of
       // the user's gaze direction.
       /*
       const float3 offset = float3(0.13f, 0.0f, 0.f);
       static const float distanceFromUser = 2.2f; // meters
       const float3 gazeAtTwoMeters = headPosition + (distanceFromUser * (headDirection + offset));
       */

Поворот голограммы в сторону камеры

Недостаточно расположить голограмму, которая в данном случае является четырехугольниками; Необходимо также повернуть объект, чтобы он был лицом к пользователю. Такое вращение происходит в мировом пространстве, так как этот тип рекламных щитов позволяет голограмме оставаться частью среды пользователя. Рекламные щиты в пространстве просмотра не так удобны, так как голограмма становится заблокированной для ориентации дисплея; в этом случае вам также придется интерполировать между левой и правой матрицами представления, чтобы получить преобразование рекламного щита пространства просмотра, которое не нарушает стерео отрисовку. Здесь мы поворачиваем оси X и Z, чтобы столкнуться с пользователем.

Из раздела StationaryQuadRenderer::Update:

   // Seconds elapsed since previous frame.
   const float& dTime = static_cast<float>(timer.GetElapsedSeconds());

   // Create a direction normal from the hologram's position to the origin of person space.
   // This is the z-axis rotation.
   XMVECTOR facingNormal = XMVector3Normalize(-XMLoadFloat3(&m_position));

   // Rotate the x-axis around the y-axis.
   // This is a 90-degree angle from the normal, in the xz-plane.
   // This is the x-axis rotation.
   XMVECTOR xAxisRotation = XMVector3Normalize(XMVectorSet(XMVectorGetZ(facingNormal), 0.f, -XMVectorGetX(facingNormal), 0.f));

   // Create a third normal to satisfy the conditions of a rotation matrix.
   // The cross product  of the other two normals is at a 90-degree angle to
   // both normals. (Normalize the cross product to avoid floating-point math
   // errors.)
   // Note how the cross product will never be a zero-matrix because the two normals
   // are always at a 90-degree angle from one another.
   XMVECTOR yAxisRotation = XMVector3Normalize(XMVector3Cross(facingNormal, xAxisRotation));

   // Construct the 4x4 rotation matrix.

   // Rotate the quad to face the user.
   XMMATRIX rotationMatrix = XMMATRIX(
       xAxisRotation,
       yAxisRotation,
       facingNormal,
       XMVectorSet(0.f, 0.f, 0.f, 1.f)
       );

   // Position the quad.
   const XMMATRIX modelTranslation = XMMatrixTranslationFromVector(XMLoadFloat3(&m_position));

   // The view and projection matrices are provided by the system; they are associated
   // with holographic cameras, and updated on a per-camera basis.
   // Here, we provide the model transform for the sample hologram. The model transform
   // matrix is transposed to prepare it for the shader.
   XMStoreFloat4x4(&m_modelConstantBufferData.model, XMMatrixTranspose(rotationMatrix * modelTranslation));

Отрисовка подключенной голограммы

В этом примере мы также выбираем отрисовку голограммы в системе координат Объекта SpatialLocatorAttachedReferenceFrame, на котором размещена голограмма. (Если бы мы решили выполнить отрисовку с помощью другой системы координат, нам нужно было бы получить преобразование из системы координат, присоединенной к устройству системы координат, в ту же систему координат.)

Из HolographicTagAlongSampleMain::Render:

   // The view and projection matrices for each holographic camera will change
   // every frame. This function refreshes the data in the constant buffer for
   // the holographic camera indicated by cameraPose.
   pCameraResources->UpdateViewProjectionBuffer(
       m_deviceResources,
       cameraPose,
       m_referenceFrame->GetStationaryCoordinateSystemAtTimestamp(prediction->Timestamp)
       );

Вот и все! Голограмма теперь будет "преследовать" положение, которое находится на 2 метра перед направлением взгляда пользователя.

Примечание

В этом примере также загружается дополнительное содержимое. См. статью StationaryQuadRenderer.cpp.

Обработка потерь отслеживания

Если устройство не может найти себя в мире, приложение испытывает "отслеживание потери". Windows Mixed Reality приложения должны иметь возможность обрабатывать такие перебои в работе системы отслеживания позиций. Эти сбои можно наблюдать и создавать ответы с помощью события LocatabilityChanged в пространственном указателе по умолчанию.

Из AppMain::SetHolographicSpace:

   // Be able to respond to changes in the positional tracking state.
   m_locatabilityChangedToken =
       m_locator->LocatabilityChanged +=
           ref new Windows::Foundation::TypedEventHandler<SpatialLocator^, Object^>(
               std::bind(&HolographicApp1Main::OnLocatabilityChanged, this, _1, _2)
               );

Когда приложение получает событие LocatabilityChanged, оно может изменить поведение при необходимости. Например, в состоянии PositionalTrackingInhibited приложение может приостановить нормальную работу и отобразить голограмму с тегом , отображающую предупреждающее сообщение.

Шаблон приложения Windows Holographic поставляется с уже созданным обработчиком LocatabilityChanged. По умолчанию в консоли отладки отображается предупреждение о недоступности отслеживания позиций. Вы можете добавить код в этот обработчик, чтобы предоставить ответ от приложения по мере необходимости.

Из AppMain.cpp:

   void HolographicApp1Main::OnLocatabilityChanged(SpatialLocator^ sender, Object^ args)
   {
       switch (sender->Locatability)
       {
       case SpatialLocatability::Unavailable:
           // Holograms cannot be rendered.
           {
               String^ message = L"Warning! Positional tracking is " +
                                           sender->Locatability.ToString() + L".\n";
               OutputDebugStringW(message->Data());
           }
           break;

       // In the following three cases, it is still possible to place holograms using a
       // SpatialLocatorAttachedFrameOfReference.
       case SpatialLocatability::PositionalTrackingActivating:
           // The system is preparing to use positional tracking.

       case SpatialLocatability::OrientationOnly:
           // Positional tracking has not been activated.

       case SpatialLocatability::PositionalTrackingInhibited:
           // Positional tracking is temporarily inhibited. User action may be required
           // in order to restore positional tracking.
           break;

       case SpatialLocatability::PositionalTrackingActive:
           // Positional tracking is active. World-locked content can be rendered.
           break;
       }
   }

пространственное сопоставление

API пространственного сопоставления используют системы координат для получения преобразований моделей для сеток поверхности.

См. также раздел